@dynamic-labs/react-native-extension 4.90.0 → 4.91.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs CHANGED
@@ -35,7 +35,7 @@ function _interopNamespace(e) {
35
35
  return Object.freeze(n);
36
36
  }
37
37
 
38
- var version = "4.90.0";
38
+ var version = "4.91.0";
39
39
 
40
40
  function _extends() {
41
41
  return _extends = Object.assign ? Object.assign.bind() : function (n) {
@@ -877,6 +877,26 @@ https://docs.dynamic.xyz/react-native/react-native-extension
877
877
 
878
878
  // eslint-disable-next-line import/no-extraneous-dependencies
879
879
  const NATIVE_MODULE_NAME = 'EmbeddedWebView';
880
+ /**
881
+ * Reports whether the EmbeddedWebView native module is linked into the
882
+ * currently running native binary.
883
+ *
884
+ * The embedded webview was introduced mid-version, so an over-the-air JS
885
+ * update can land on an older binary that predates the native module. Callers
886
+ * use this to decide between the native overlay path and the
887
+ * react-native-webview fallback before wiring any side effects.
888
+ *
889
+ * `requireOptionalNativeModule` returns `null` (instead of throwing) when the
890
+ * module is absent. Any unexpected throw is treated as "unavailable" so a
891
+ * runtime quirk can never break the calling flow.
892
+ */
893
+ const isEmbeddedWebViewAvailable = () => {
894
+ try {
895
+ return Boolean(expoModulesCore.requireOptionalNativeModule(NATIVE_MODULE_NAME));
896
+ } catch (_a) {
897
+ return false;
898
+ }
899
+ };
880
900
  const getEmbeddedWebView = () => {
881
901
  try {
882
902
  return expoModulesCore.requireNativeModule(NATIVE_MODULE_NAME);
@@ -945,6 +965,11 @@ const createEmbeddedWebViewPhaseTimers = ({
945
965
  const EMBEDDED_SUCCESS_LOG_COOLDOWN_STORAGE_KEY = 'dynamic.embeddedWebView.successLogLastEmittedAt';
946
966
  const DEFAULT_LOADING_TIMEOUT_MS = 20000;
947
967
  const DEFAULT_RECOVERY_TIMEOUT_MS = 20000;
968
+ // Debug log labels matching the react-native-webview bridge
969
+ // (`useMessageTransportWebViewBridge`) so both paths surface the same
970
+ // HOST/WEBVIEW message trace under `core.debug.messageTransport`.
971
+ const HOST_TO_WEBVIEW_LOG = 'HOST >>>> WEBVIEW';
972
+ const WEBVIEW_TO_HOST_LOG = 'WEBVIEW >>>> HOST';
948
973
  // Wires the JS-side bridge to the native WKWebView singleton owned by Swift.
949
974
  // This is intentionally not a React component: the embedded webview lives
950
975
  // outside the React tree (in a dedicated UIWindow on iOS), so its setup runs
@@ -1010,7 +1035,11 @@ const setupEmbeddedWebView = ({
1010
1035
  });
1011
1036
  });
1012
1037
  const handleHostMessage = message => {
1038
+ var _a;
1013
1039
  if (message.origin !== 'host') return;
1040
+ if ((_a = core.debug) === null || _a === void 0 ? void 0 : _a.messageTransport) {
1041
+ logger.debug(HOST_TO_WEBVIEW_LOG, JSON.stringify(message, null, 2));
1042
+ }
1014
1043
  native.postMessage(JSON.stringify(message, messageTransport.messageTransportDataJsonReplacer)).catch(err => {
1015
1044
  logger.warn('EmbeddedWebView.postMessage failed', err);
1016
1045
  });
@@ -1041,15 +1070,19 @@ const setupEmbeddedWebView = ({
1041
1070
  };
1042
1071
  core.messageTransport.on(handleSdkReady);
1043
1072
  const onMessageSub = native.addListener('onMessage', event => {
1073
+ var _a;
1044
1074
  let parsed = null;
1045
1075
  try {
1046
1076
  parsed = JSON.parse(event.message, messageTransport.messageTransportDataJsonReviver);
1047
- } catch (_a) {
1077
+ } catch (_b) {
1048
1078
  return;
1049
1079
  }
1050
1080
  const message = messageTransport.parseMessageTransportData(parsed);
1051
1081
  if (!message) return;
1052
1082
  if (message.origin === 'webview') {
1083
+ if ((_a = core.debug) === null || _a === void 0 ? void 0 : _a.messageTransport) {
1084
+ logger.debug(WEBVIEW_TO_HOST_LOG, JSON.stringify(message, null, 2));
1085
+ }
1053
1086
  core.messageTransport.emit(message);
1054
1087
  }
1055
1088
  });
@@ -1131,6 +1164,71 @@ const setupEmbeddedWebView = ({
1131
1164
  };
1132
1165
  };
1133
1166
 
1167
+ // eslint-disable-next-line import/no-extraneous-dependencies
1168
+ const KEYCHAIN_MODULE_NAME = 'Keychain';
1169
+ /**
1170
+ * Reports whether the Keychain native module is linked into the currently
1171
+ * running native binary. Mirrors `isEmbeddedWebViewAvailable`: an over-the-air
1172
+ * JS update can run on an older binary that predates the module, so any
1173
+ * unexpected throw is treated as "unavailable".
1174
+ */
1175
+ const isKeychainAvailable = () => {
1176
+ try {
1177
+ return Boolean(expoModulesCore.requireOptionalNativeModule(KEYCHAIN_MODULE_NAME));
1178
+ } catch (_a) {
1179
+ return false;
1180
+ }
1181
+ };
1182
+ const getKeychain = () => {
1183
+ try {
1184
+ const keychain = expoModulesCore.requireNativeModule(KEYCHAIN_MODULE_NAME);
1185
+ return keychain;
1186
+ } catch (error) {
1187
+ logger.warn('Could not get dynamic keychain, using unavailable keychain');
1188
+ const keychainUnavailableError = new Error('Keychain is not available');
1189
+ const unavailableKeychain = {
1190
+ deleteKey: () => Promise.reject(keychainUnavailableError),
1191
+ generateKeyPair: () => Promise.reject(keychainUnavailableError),
1192
+ getPublicKey: () => Promise.reject(keychainUnavailableError),
1193
+ hasKey: () => Promise.reject(keychainUnavailableError),
1194
+ isAvailable: () => Promise.resolve(false),
1195
+ sign: () => Promise.reject(keychainUnavailableError)
1196
+ };
1197
+ return unavailableKeychain;
1198
+ }
1199
+ };
1200
+
1201
+ const NATIVE_MODULES_INSTRUMENT_KEY = 'react_native_extension.native_modules';
1202
+ const describeAvailability = available => available ? 'available' : 'unavailable';
1203
+ /**
1204
+ * Reports the running package version and which native modules are linked into
1205
+ * the current binary. The embedded webview / keychain modules were introduced
1206
+ * mid-version, so an over-the-air JS update can run ahead of the native binary;
1207
+ * pairing the version with the availability matrix surfaces that gap (and lets
1208
+ * `embeddedWebViewRequested && !embeddedWebViewAvailable` quantify the
1209
+ * react-native-webview fallback rate).
1210
+ */
1211
+ const logNativeModuleAvailability = ({
1212
+ core,
1213
+ embeddedWebView,
1214
+ embeddedWebViewAvailable,
1215
+ keychainAvailable
1216
+ }) => {
1217
+ const platform = reactNative.Platform.OS;
1218
+ logger.instrument('React Native extension native module availability', {
1219
+ embeddedWebViewAvailable,
1220
+ embeddedWebViewRequested: embeddedWebView,
1221
+ environmentId: core.environmentId,
1222
+ hostSdkSessionId: core.hostSdkSessionId,
1223
+ key: NATIVE_MODULES_INSTRUMENT_KEY,
1224
+ keychainAvailable,
1225
+ platform,
1226
+ time: 0,
1227
+ version
1228
+ });
1229
+ logger.info(`@dynamic-labs/react-native-extension v${version} (${platform}) — native modules: Keychain=${describeAvailability(keychainAvailable)}, EmbeddedWebView=${describeAvailability(embeddedWebViewAvailable)}`);
1230
+ };
1231
+
1134
1232
  const setupPasskeyHandler = core => {
1135
1233
  const passkeyRequestChannel = messageTransport.createRequestChannel(core.messageTransport);
1136
1234
  passkeyRequestChannel.handle('passkey_register', _a => __awaiter(void 0, [_a], void 0, function* ({
@@ -1347,26 +1445,6 @@ const setupTurnkeyPasskeyHandler = core => {
1347
1445
  }));
1348
1446
  };
1349
1447
 
1350
- // eslint-disable-next-line import/no-extraneous-dependencies
1351
- const getKeychain = () => {
1352
- try {
1353
- const keychain = expoModulesCore.requireNativeModule('Keychain');
1354
- return keychain;
1355
- } catch (error) {
1356
- logger.warn('Could not get dynamic keychain, using unavailable keychain');
1357
- const keychainUnavailableError = new Error('Keychain is not available');
1358
- const unavailableKeychain = {
1359
- deleteKey: () => Promise.reject(keychainUnavailableError),
1360
- generateKeyPair: () => Promise.reject(keychainUnavailableError),
1361
- getPublicKey: () => Promise.reject(keychainUnavailableError),
1362
- hasKey: () => Promise.reject(keychainUnavailableError),
1363
- isAvailable: () => Promise.resolve(false),
1364
- sign: () => Promise.reject(keychainUnavailableError)
1365
- };
1366
- return unavailableKeychain;
1367
- }
1368
- };
1369
-
1370
1448
  const setupKeychainHandler = core => {
1371
1449
  const keychainRequestChannel = messageTransport.createRequestChannel(core.messageTransport);
1372
1450
  const keychain = getKeychain();
@@ -1426,15 +1504,31 @@ const ReactNativeExtension = ({
1426
1504
  }
1427
1505
  };
1428
1506
  }
1507
+ // The embedded webview / keychain modules were introduced mid-version, so
1508
+ // an over-the-air JS update can run on an older binary that predates them.
1509
+ // Resolve availability once, here, and reuse it for both telemetry and the
1510
+ // path-selection gate below. (We are past the web early-return, so the
1511
+ // native resolvers only ever run on iOS / Android.)
1512
+ const embeddedWebViewAvailable = isEmbeddedWebViewAvailable();
1513
+ const keychainAvailable = isKeychainAvailable();
1514
+ logNativeModuleAvailability({
1515
+ core,
1516
+ embeddedWebView: _embeddedWebView,
1517
+ embeddedWebViewAvailable,
1518
+ keychainAvailable
1519
+ });
1429
1520
  if (appOrigin) core.manifest.setAppOrigin(appOrigin);
1430
1521
  setupPasskeyHandler(core);
1431
1522
  setupTurnkeyPasskeyHandler(core);
1432
1523
  setupPlatformHandler(core);
1433
1524
  setupStorageHandler(core);
1434
1525
  setupKeychainHandler(core);
1435
- // Native overlay-webview path supported on iOS (WKWebView) and Android
1436
- // (android.webkit.WebView). Other platforms fall back to react-native-webview.
1437
- const useNativeEmbeddedWebView = _embeddedWebView && (reactNative.Platform.OS === 'ios' || reactNative.Platform.OS === 'android');
1526
+ // Native overlay-webview path (iOS WKWebView / Android android.webkit
1527
+ // .WebView). When the native module isn't linked — e.g. an OTA JS update on
1528
+ // an older binary that predates it fall through to the
1529
+ // react-native-webview path instead of wiring a bridge to a missing module
1530
+ // (which would hang silently).
1531
+ const useNativeEmbeddedWebView = _embeddedWebView && embeddedWebViewAvailable;
1438
1532
  if (useNativeEmbeddedWebView) {
1439
1533
  // The native WKWebView lives in its own UIWindow, outside the React
1440
1534
  // tree. Wire the JS bridge once at extension construction and hand the
package/index.js CHANGED
@@ -7,13 +7,13 @@ import { messageTransportDataJsonReviver, parseMessageTransportData, messageTran
7
7
  import { sdkHasLoadedEventName } from '@dynamic-labs/webview-messages';
8
8
  import { setItemAsync, getItemAsync, deleteItemAsync } from 'expo-secure-store';
9
9
  import { jsx } from 'react/jsx-runtime';
10
- import { requireNativeModule } from 'expo-modules-core';
10
+ import { requireOptionalNativeModule, requireNativeModule } from 'expo-modules-core';
11
11
  import { Passkey } from 'react-native-passkey';
12
12
  import { createURL, getInitialURL, addEventListener, openURL } from 'expo-linking';
13
13
  import { openAuthSessionAsync } from 'expo-web-browser';
14
14
  import { createPasskey, PasskeyStamper } from '@turnkey/react-native-passkey-stamper';
15
15
 
16
- var version = "4.90.0";
16
+ var version = "4.91.0";
17
17
 
18
18
  function _extends() {
19
19
  return _extends = Object.assign ? Object.assign.bind() : function (n) {
@@ -855,6 +855,26 @@ https://docs.dynamic.xyz/react-native/react-native-extension
855
855
 
856
856
  // eslint-disable-next-line import/no-extraneous-dependencies
857
857
  const NATIVE_MODULE_NAME = 'EmbeddedWebView';
858
+ /**
859
+ * Reports whether the EmbeddedWebView native module is linked into the
860
+ * currently running native binary.
861
+ *
862
+ * The embedded webview was introduced mid-version, so an over-the-air JS
863
+ * update can land on an older binary that predates the native module. Callers
864
+ * use this to decide between the native overlay path and the
865
+ * react-native-webview fallback before wiring any side effects.
866
+ *
867
+ * `requireOptionalNativeModule` returns `null` (instead of throwing) when the
868
+ * module is absent. Any unexpected throw is treated as "unavailable" so a
869
+ * runtime quirk can never break the calling flow.
870
+ */
871
+ const isEmbeddedWebViewAvailable = () => {
872
+ try {
873
+ return Boolean(requireOptionalNativeModule(NATIVE_MODULE_NAME));
874
+ } catch (_a) {
875
+ return false;
876
+ }
877
+ };
858
878
  const getEmbeddedWebView = () => {
859
879
  try {
860
880
  return requireNativeModule(NATIVE_MODULE_NAME);
@@ -923,6 +943,11 @@ const createEmbeddedWebViewPhaseTimers = ({
923
943
  const EMBEDDED_SUCCESS_LOG_COOLDOWN_STORAGE_KEY = 'dynamic.embeddedWebView.successLogLastEmittedAt';
924
944
  const DEFAULT_LOADING_TIMEOUT_MS = 20000;
925
945
  const DEFAULT_RECOVERY_TIMEOUT_MS = 20000;
946
+ // Debug log labels matching the react-native-webview bridge
947
+ // (`useMessageTransportWebViewBridge`) so both paths surface the same
948
+ // HOST/WEBVIEW message trace under `core.debug.messageTransport`.
949
+ const HOST_TO_WEBVIEW_LOG = 'HOST >>>> WEBVIEW';
950
+ const WEBVIEW_TO_HOST_LOG = 'WEBVIEW >>>> HOST';
926
951
  // Wires the JS-side bridge to the native WKWebView singleton owned by Swift.
927
952
  // This is intentionally not a React component: the embedded webview lives
928
953
  // outside the React tree (in a dedicated UIWindow on iOS), so its setup runs
@@ -988,7 +1013,11 @@ const setupEmbeddedWebView = ({
988
1013
  });
989
1014
  });
990
1015
  const handleHostMessage = message => {
1016
+ var _a;
991
1017
  if (message.origin !== 'host') return;
1018
+ if ((_a = core.debug) === null || _a === void 0 ? void 0 : _a.messageTransport) {
1019
+ logger.debug(HOST_TO_WEBVIEW_LOG, JSON.stringify(message, null, 2));
1020
+ }
992
1021
  native.postMessage(JSON.stringify(message, messageTransportDataJsonReplacer)).catch(err => {
993
1022
  logger.warn('EmbeddedWebView.postMessage failed', err);
994
1023
  });
@@ -1019,15 +1048,19 @@ const setupEmbeddedWebView = ({
1019
1048
  };
1020
1049
  core.messageTransport.on(handleSdkReady);
1021
1050
  const onMessageSub = native.addListener('onMessage', event => {
1051
+ var _a;
1022
1052
  let parsed = null;
1023
1053
  try {
1024
1054
  parsed = JSON.parse(event.message, messageTransportDataJsonReviver);
1025
- } catch (_a) {
1055
+ } catch (_b) {
1026
1056
  return;
1027
1057
  }
1028
1058
  const message = parseMessageTransportData(parsed);
1029
1059
  if (!message) return;
1030
1060
  if (message.origin === 'webview') {
1061
+ if ((_a = core.debug) === null || _a === void 0 ? void 0 : _a.messageTransport) {
1062
+ logger.debug(WEBVIEW_TO_HOST_LOG, JSON.stringify(message, null, 2));
1063
+ }
1031
1064
  core.messageTransport.emit(message);
1032
1065
  }
1033
1066
  });
@@ -1109,6 +1142,71 @@ const setupEmbeddedWebView = ({
1109
1142
  };
1110
1143
  };
1111
1144
 
1145
+ // eslint-disable-next-line import/no-extraneous-dependencies
1146
+ const KEYCHAIN_MODULE_NAME = 'Keychain';
1147
+ /**
1148
+ * Reports whether the Keychain native module is linked into the currently
1149
+ * running native binary. Mirrors `isEmbeddedWebViewAvailable`: an over-the-air
1150
+ * JS update can run on an older binary that predates the module, so any
1151
+ * unexpected throw is treated as "unavailable".
1152
+ */
1153
+ const isKeychainAvailable = () => {
1154
+ try {
1155
+ return Boolean(requireOptionalNativeModule(KEYCHAIN_MODULE_NAME));
1156
+ } catch (_a) {
1157
+ return false;
1158
+ }
1159
+ };
1160
+ const getKeychain = () => {
1161
+ try {
1162
+ const keychain = requireNativeModule(KEYCHAIN_MODULE_NAME);
1163
+ return keychain;
1164
+ } catch (error) {
1165
+ logger.warn('Could not get dynamic keychain, using unavailable keychain');
1166
+ const keychainUnavailableError = new Error('Keychain is not available');
1167
+ const unavailableKeychain = {
1168
+ deleteKey: () => Promise.reject(keychainUnavailableError),
1169
+ generateKeyPair: () => Promise.reject(keychainUnavailableError),
1170
+ getPublicKey: () => Promise.reject(keychainUnavailableError),
1171
+ hasKey: () => Promise.reject(keychainUnavailableError),
1172
+ isAvailable: () => Promise.resolve(false),
1173
+ sign: () => Promise.reject(keychainUnavailableError)
1174
+ };
1175
+ return unavailableKeychain;
1176
+ }
1177
+ };
1178
+
1179
+ const NATIVE_MODULES_INSTRUMENT_KEY = 'react_native_extension.native_modules';
1180
+ const describeAvailability = available => available ? 'available' : 'unavailable';
1181
+ /**
1182
+ * Reports the running package version and which native modules are linked into
1183
+ * the current binary. The embedded webview / keychain modules were introduced
1184
+ * mid-version, so an over-the-air JS update can run ahead of the native binary;
1185
+ * pairing the version with the availability matrix surfaces that gap (and lets
1186
+ * `embeddedWebViewRequested && !embeddedWebViewAvailable` quantify the
1187
+ * react-native-webview fallback rate).
1188
+ */
1189
+ const logNativeModuleAvailability = ({
1190
+ core,
1191
+ embeddedWebView,
1192
+ embeddedWebViewAvailable,
1193
+ keychainAvailable
1194
+ }) => {
1195
+ const platform = Platform.OS;
1196
+ logger.instrument('React Native extension native module availability', {
1197
+ embeddedWebViewAvailable,
1198
+ embeddedWebViewRequested: embeddedWebView,
1199
+ environmentId: core.environmentId,
1200
+ hostSdkSessionId: core.hostSdkSessionId,
1201
+ key: NATIVE_MODULES_INSTRUMENT_KEY,
1202
+ keychainAvailable,
1203
+ platform,
1204
+ time: 0,
1205
+ version
1206
+ });
1207
+ logger.info(`@dynamic-labs/react-native-extension v${version} (${platform}) — native modules: Keychain=${describeAvailability(keychainAvailable)}, EmbeddedWebView=${describeAvailability(embeddedWebViewAvailable)}`);
1208
+ };
1209
+
1112
1210
  const setupPasskeyHandler = core => {
1113
1211
  const passkeyRequestChannel = createRequestChannel(core.messageTransport);
1114
1212
  passkeyRequestChannel.handle('passkey_register', _a => __awaiter(void 0, [_a], void 0, function* ({
@@ -1325,26 +1423,6 @@ const setupTurnkeyPasskeyHandler = core => {
1325
1423
  }));
1326
1424
  };
1327
1425
 
1328
- // eslint-disable-next-line import/no-extraneous-dependencies
1329
- const getKeychain = () => {
1330
- try {
1331
- const keychain = requireNativeModule('Keychain');
1332
- return keychain;
1333
- } catch (error) {
1334
- logger.warn('Could not get dynamic keychain, using unavailable keychain');
1335
- const keychainUnavailableError = new Error('Keychain is not available');
1336
- const unavailableKeychain = {
1337
- deleteKey: () => Promise.reject(keychainUnavailableError),
1338
- generateKeyPair: () => Promise.reject(keychainUnavailableError),
1339
- getPublicKey: () => Promise.reject(keychainUnavailableError),
1340
- hasKey: () => Promise.reject(keychainUnavailableError),
1341
- isAvailable: () => Promise.resolve(false),
1342
- sign: () => Promise.reject(keychainUnavailableError)
1343
- };
1344
- return unavailableKeychain;
1345
- }
1346
- };
1347
-
1348
1426
  const setupKeychainHandler = core => {
1349
1427
  const keychainRequestChannel = createRequestChannel(core.messageTransport);
1350
1428
  const keychain = getKeychain();
@@ -1404,15 +1482,31 @@ const ReactNativeExtension = ({
1404
1482
  }
1405
1483
  };
1406
1484
  }
1485
+ // The embedded webview / keychain modules were introduced mid-version, so
1486
+ // an over-the-air JS update can run on an older binary that predates them.
1487
+ // Resolve availability once, here, and reuse it for both telemetry and the
1488
+ // path-selection gate below. (We are past the web early-return, so the
1489
+ // native resolvers only ever run on iOS / Android.)
1490
+ const embeddedWebViewAvailable = isEmbeddedWebViewAvailable();
1491
+ const keychainAvailable = isKeychainAvailable();
1492
+ logNativeModuleAvailability({
1493
+ core,
1494
+ embeddedWebView: _embeddedWebView,
1495
+ embeddedWebViewAvailable,
1496
+ keychainAvailable
1497
+ });
1407
1498
  if (appOrigin) core.manifest.setAppOrigin(appOrigin);
1408
1499
  setupPasskeyHandler(core);
1409
1500
  setupTurnkeyPasskeyHandler(core);
1410
1501
  setupPlatformHandler(core);
1411
1502
  setupStorageHandler(core);
1412
1503
  setupKeychainHandler(core);
1413
- // Native overlay-webview path supported on iOS (WKWebView) and Android
1414
- // (android.webkit.WebView). Other platforms fall back to react-native-webview.
1415
- const useNativeEmbeddedWebView = _embeddedWebView && (Platform.OS === 'ios' || Platform.OS === 'android');
1504
+ // Native overlay-webview path (iOS WKWebView / Android android.webkit
1505
+ // .WebView). When the native module isn't linked — e.g. an OTA JS update on
1506
+ // an older binary that predates it fall through to the
1507
+ // react-native-webview path instead of wiring a bridge to a missing module
1508
+ // (which would hang silently).
1509
+ const useNativeEmbeddedWebView = _embeddedWebView && embeddedWebViewAvailable;
1416
1510
  if (useNativeEmbeddedWebView) {
1417
1511
  // The native WKWebView lives in its own UIWindow, outside the React
1418
1512
  // tree. Wire the JS bridge once at extension construction and hand the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-labs/react-native-extension",
3
- "version": "4.90.0",
3
+ "version": "4.91.0",
4
4
  "main": "./index.cjs",
5
5
  "module": "./index.js",
6
6
  "types": "./src/index.d.ts",
@@ -18,11 +18,11 @@
18
18
  "@turnkey/react-native-passkey-stamper": "1.2.7",
19
19
  "@react-native-documents/picker": "^11.0.0",
20
20
  "react-native-fs": ">=2.20.0",
21
- "@dynamic-labs/assert-package-version": "4.90.0",
22
- "@dynamic-labs/client": "4.90.0",
23
- "@dynamic-labs/logger": "4.90.0",
24
- "@dynamic-labs/message-transport": "4.90.0",
25
- "@dynamic-labs/webview-messages": "4.90.0"
21
+ "@dynamic-labs/assert-package-version": "4.91.0",
22
+ "@dynamic-labs/client": "4.91.0",
23
+ "@dynamic-labs/logger": "4.91.0",
24
+ "@dynamic-labs/message-transport": "4.91.0",
25
+ "@dynamic-labs/webview-messages": "4.91.0"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": ">=18.0.0 <20.0.0",
@@ -23,17 +23,24 @@ export type ReactNativeExtensionProps = {
23
23
  * isolates the webview from RN re-renders, navigation transitions, and
24
24
  * other lifecycle events.
25
25
  *
26
- * When this flag is on, the `WebView` returned under
27
- * `extension.reactNative.WebView` is a no-op component do not render it.
28
- * The native overlay is created lazily and retained for the process
29
- * lifetime; subsequent extension factory invocations re-bind the JS bridge
30
- * to the same native overlay (e.g. when the consumer recreates the client
31
- * with a new `environmentId`).
26
+ * Always render the `WebView` returned under
27
+ * `extension.reactNative.WebView`. When the native overlay is active it is a
28
+ * no-op component that renders nothing the native overlay is created
29
+ * lazily and retained for the process lifetime, and subsequent extension
30
+ * factory invocations re-bind the JS bridge to the same native overlay (e.g.
31
+ * when the consumer recreates the client with a new `environmentId`).
32
32
  *
33
- * There is no automatic load recovery on this path. Any HTTP error,
34
- * network failure, blocked navigation, SSL error, or process termination
35
- * surfaces as `core.initialization.error` with `WebViewFailedToLoadError`
36
- * consumers should treat the SDK as un-initialised and react accordingly.
33
+ * The native module was introduced mid-version, so an over-the-air JS update
34
+ * can land on an older native binary that predates it. When the module is not
35
+ * linked, this flag is transparently ignored and the returned `WebView`
36
+ * becomes the regular react-native-webview component, which must be mounted
37
+ * for the SDK to initialise — hence always render it.
38
+ *
39
+ * On the native overlay path there is no automatic load recovery. Any HTTP
40
+ * error, network failure, blocked navigation, SSL error, or process
41
+ * termination surfaces as `core.initialization.error` with
42
+ * `WebViewFailedToLoadError` — consumers should treat the SDK as
43
+ * un-initialised and react accordingly.
37
44
  *
38
45
  * On platforms other than iOS / Android (e.g. web), this flag is ignored.
39
46
  * Defaults to false.
@@ -0,0 +1,19 @@
1
+ import { Core } from '@dynamic-labs/client';
2
+ export type LogNativeModuleAvailabilityArgs = {
3
+ core: Core;
4
+ /** The `embeddedWebView` flag the consumer requested. */
5
+ embeddedWebView: boolean;
6
+ /** Whether the EmbeddedWebView native module is linked in this binary. */
7
+ embeddedWebViewAvailable: boolean;
8
+ /** Whether the Keychain native module is linked in this binary. */
9
+ keychainAvailable: boolean;
10
+ };
11
+ /**
12
+ * Reports the running package version and which native modules are linked into
13
+ * the current binary. The embedded webview / keychain modules were introduced
14
+ * mid-version, so an over-the-air JS update can run ahead of the native binary;
15
+ * pairing the version with the availability matrix surfaces that gap (and lets
16
+ * `embeddedWebViewRequested && !embeddedWebViewAvailable` quantify the
17
+ * react-native-webview fallback rate).
18
+ */
19
+ export declare const logNativeModuleAvailability: ({ core, embeddedWebView, embeddedWebViewAvailable, keychainAvailable, }: LogNativeModuleAvailabilityArgs) => void;
@@ -51,4 +51,18 @@ export type EmbeddedWebViewNativeModule = {
51
51
  respondToShouldStartLoad: (id: string, allow: boolean) => Promise<void>;
52
52
  addListener: <T extends EmbeddedWebViewOnMessageEvent | EmbeddedWebViewOnShouldStartLoadEvent | EmbeddedWebViewOnLoadErrorEvent | EmbeddedWebViewOnLoadStartEvent | EmbeddedWebViewOnLoadEvent | EmbeddedWebViewOnLoadEndEvent>(eventName: EmbeddedWebViewEventName, listener: (event: T) => void) => EmbeddedWebViewSubscription;
53
53
  };
54
+ /**
55
+ * Reports whether the EmbeddedWebView native module is linked into the
56
+ * currently running native binary.
57
+ *
58
+ * The embedded webview was introduced mid-version, so an over-the-air JS
59
+ * update can land on an older binary that predates the native module. Callers
60
+ * use this to decide between the native overlay path and the
61
+ * react-native-webview fallback before wiring any side effects.
62
+ *
63
+ * `requireOptionalNativeModule` returns `null` (instead of throwing) when the
64
+ * module is absent. Any unexpected throw is treated as "unavailable" so a
65
+ * runtime quirk can never break the calling flow.
66
+ */
67
+ export declare const isEmbeddedWebViewAvailable: () => boolean;
54
68
  export declare const getEmbeddedWebView: () => EmbeddedWebViewNativeModule;
@@ -12,5 +12,12 @@ type KeychainNativeModule = {
12
12
  }>;
13
13
  deleteKey: (key: string) => Promise<void>;
14
14
  };
15
+ /**
16
+ * Reports whether the Keychain native module is linked into the currently
17
+ * running native binary. Mirrors `isEmbeddedWebViewAvailable`: an over-the-air
18
+ * JS update can run on an older binary that predates the module, so any
19
+ * unexpected throw is treated as "unavailable".
20
+ */
21
+ export declare const isKeychainAvailable: () => boolean;
15
22
  export declare const getKeychain: () => KeychainNativeModule;
16
23
  export {};