3.4.11 - JSI Proxy Fix & Receipt Refresh
Expo IAP 3.4.11 fixes a critical crash on New Architecture / Hermes caused by using a Proxy as the EventEmitter, and ensures requestReceiptRefreshIOS actually refreshes the receipt from Apple's servers before returning it.
Bug Fixes
JSI Proxy Crash Fix (#326)
The emitter used for purchaseUpdatedListener, purchaseErrorListener, and other event listeners was backed by a JavaScript Proxy. JSI HostObjects require the real native module as this when calling addListener — using a Proxy triggers the following crash:
Error: Exception in HostFunction: native state unsupported on Proxy
This affected both New Architecture and Old Architecture with Hermes, on both iOS and Android.
What changed:
- Added
getNativeModule()toExpoIapModule.tsthat returns the raw native module without Proxy wrapping - The
emitternow delegatesaddListener/removeListenerlazily throughgetNativeModule(), so:- The real native module is used as
this(no more JSI crash) - The native module is resolved lazily (importing the module won't throw on unsupported platforms)
- The real native module is used as
// Before (crashed on JSI)
export const emitter = ExpoIapModule; // Proxy as this
// After (works on all architectures)
export const emitter = {
addListener(eventName, listener) {
return getNativeModule().addListener(eventName, listener);
},
removeListener(eventName, listener) {
return getNativeModule().removeListener(eventName, listener);
},
};
iOS Receipt Refresh Fix (#325)
requestReceiptRefreshIOS was identical to getReceiptDataIOS — it just read the cached receipt without actually refreshing from Apple's servers. On first purchase (especially from TestFlight with no prior sandbox account), the receipt file may not yet exist on disk, causing an empty string to be returned.
What changed:
requestReceiptRefreshIOSnow callsAppStore.sync()before reading the receipt, ensuring the latest receipt data is available
import {requestReceiptRefreshIOS} from 'expo-iap';
// After first purchase, use this instead of getReceiptDataIOS
// to ensure the receipt is written to disk
const receipt = await requestReceiptRefreshIOS();
Workaround for current versions (< 3.4.11):
import {syncIOS, getReceiptDataIOS} from 'expo-iap';
await syncIOS();
const receipt = await getReceiptDataIOS();
Swift Deprecation Warnings
- Suppressed
requestPurchaseOnPromotedProductIOSdeprecation warning via a deprecated helper wrapper - Replaced deprecated
Constants {}block with individualConstant()declarations inOnsideIapModule
Installation
# bun
bun add expo-iap@3.4.11
# npm
npm install expo-iap@3.4.11
# yarn
yarn add expo-iap@3.4.11
