Skip to content
This repository was archived by the owner on Apr 26, 2026. It is now read-only.

Commit 82c925e

Browse files
hyochanclaude
andcommitted
docs(blog): add 14.7.13 release notes
Thread safety for listener operations and Android JNI exception handling during initConnection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f7ff308 commit 82c925e

1 file changed

Lines changed: 94 additions & 0 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
---
2+
slug: 14.7.13
3+
title: 14.7.13 - Thread Safety & Error Handling
4+
authors: [hyochan]
5+
tags: [release, bugfix, android, ios]
6+
date: 2026-03-04
7+
---
8+
9+
# 14.7.13 Release Notes
10+
11+
This release fixes two critical reliability issues: **thread safety for purchase event listeners** across both platforms, and **unhandled JNI exceptions during Android initialization**.
12+
13+
<!-- truncate -->
14+
15+
## Thread Safety for Listener Operations ([#3152](https://github.com/hyochan/react-native-iap/pull/3152), [#3157](https://github.com/hyochan/react-native-iap/pull/3157))
16+
17+
### Problem
18+
19+
When purchase events fired concurrently (e.g., during high-volume purchases or rapid listener add/remove), listener arrays could be modified while being iterated — causing `ConcurrentModificationException` on Android and potential crashes on iOS. This resulted in **purchase events being silently lost**.
20+
21+
### Fix
22+
23+
Both platforms now use a **synchronized + snapshot pattern**: listener arrays are protected by a lock, and a snapshot copy is taken before iteration. This ensures safe concurrent access without blocking event delivery.
24+
25+
#### iOS
26+
27+
- Added `NSLock` to protect all listener arrays (`purchaseUpdatedListeners`, `purchaseErrorListeners`, `promotedProductListeners`)
28+
- `sendPurchaseUpdate` and `sendPurchaseError` iterate over snapshot copies
29+
- Error dedup state and delivery tracking collections are also protected
30+
- `cleanupExistingState` clears all state atomically within the lock
31+
32+
#### Android
33+
34+
- Applied snapshot pattern to `userChoiceBilling` and `developerProvidedBilling` listeners for consistency
35+
- Added `synchronized` for clearing these listener arrays in `endConnection`
36+
37+
### Before & After
38+
39+
```
40+
// Before: Concurrent modification could crash or silently drop events
41+
listeners.forEach { it(event) } // ❌ Another thread modifies the list
42+
43+
// After: Snapshot ensures safe iteration
44+
val snapshot = lock.withLock { ArrayList(listeners) }
45+
snapshot.forEach { it(event) } // ✅ Isolated from modifications
46+
```
47+
48+
## Android JNI Exception Handling ([#3158](https://github.com/hyochan/react-native-iap/pull/3158))
49+
50+
### Problem
51+
52+
Some users reported a cryptic error during `initConnection`:
53+
54+
```
55+
Unknown N8facebook3jni12JniExceptionE error.
56+
```
57+
58+
This occurred because the `openIap` object is lazy-initialized, and its first access happens during `setActivity()` or listener registration — both of which were **outside any try-catch block**. On devices without Google Play Services or with billing client issues, the raw JNI exception propagated unhandled, producing the mangled C++ class name as the error message.
59+
60+
### Fix
61+
62+
- Wrapped `setActivity` and listener registration in try-catch blocks
63+
- Converts exceptions to structured `OpenIapException` with error code `init-connection` and descriptive messages
64+
- Added `CancellationException` passthrough to preserve coroutine cancellation semantics
65+
- On listener registration failure, `initDeferred` is completed exceptionally to prevent concurrent callers from deadlocking
66+
67+
### Before & After
68+
69+
```typescript
70+
// Before: Cryptic error with no actionable information
71+
catch (error) {
72+
// error.message = "Unknown N8facebook3jni12JniExceptionE error."
73+
// error.code = undefined ❌
74+
}
75+
76+
// After: Structured error with clear context
77+
catch (error) {
78+
// error.code = "init-connection" ✅
79+
// error.message = "Failed to register billing listeners: <details>" ✅
80+
}
81+
```
82+
83+
## Installation
84+
85+
```bash
86+
yarn add react-native-iap@14.7.13
87+
```
88+
89+
## Related
90+
91+
- [#3150 - Purchase events silently lost](https://github.com/hyochan/react-native-iap/issues/3150)
92+
- [#3144 - JNI Exception in initConnection](https://github.com/hyochan/react-native-iap/issues/3144)
93+
- [#3157 - Thread safety PR](https://github.com/hyochan/react-native-iap/pull/3157)
94+
- [#3158 - JNI error handling PR](https://github.com/hyochan/react-native-iap/pull/3158)

0 commit comments

Comments
 (0)