Skip to main content

3.4.11 - JSI Proxy Fix & Receipt Refresh

· 3 min read
Hyo
Expo IAP Maintainer

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() to ExpoIapModule.ts that returns the raw native module without Proxy wrapping
  • The emitter now delegates addListener / removeListener lazily through getNativeModule(), 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)
// 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:

  • requestReceiptRefreshIOS now calls AppStore.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 requestPurchaseOnPromotedProductIOS deprecation warning via a deprecated helper wrapper
  • Replaced deprecated Constants {} block with individual Constant() declarations in OnsideIapModule

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

References