@dynamic-labs/react-native-extension 4.89.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 +119 -25
- package/index.js +120 -26
- package/package.json +14 -6
- package/src/ReactNativeExtension/ReactNativeExtension.d.ts +17 -10
- package/src/ReactNativeExtension/logNativeModuleAvailability.d.ts +19 -0
- package/src/nativeModules/EmbeddedWebView.d.ts +14 -0
- package/src/nativeModules/Keychain.d.ts +7 -0
- package/turboModules/NativeDynamicWebView.ts +47 -0
- package/turboModules/NativeKeychain.ts +22 -0
- package/turboModules/NativeReactNativeManifest.ts +24 -0
- package/turboModules/NativeScreenshotProtection.ts +29 -0
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.
|
|
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 (
|
|
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
|
|
1436
|
-
//
|
|
1437
|
-
|
|
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.
|
|
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 (
|
|
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
|
|
1414
|
-
//
|
|
1415
|
-
|
|
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.
|
|
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.
|
|
22
|
-
"@dynamic-labs/client": "4.
|
|
23
|
-
"@dynamic-labs/logger": "4.
|
|
24
|
-
"@dynamic-labs/message-transport": "4.
|
|
25
|
-
"@dynamic-labs/webview-messages": "4.
|
|
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",
|
|
@@ -32,5 +32,13 @@
|
|
|
32
32
|
"expo-web-browser": ">=12.0.0",
|
|
33
33
|
"expo-secure-store": ">=12.0.0",
|
|
34
34
|
"expo-modules-core": ">=2.0.0"
|
|
35
|
+
},
|
|
36
|
+
"codegenConfig": {
|
|
37
|
+
"name": "DynamicClientSpec",
|
|
38
|
+
"type": "modules",
|
|
39
|
+
"jsSrcsDir": "turboModules",
|
|
40
|
+
"android": {
|
|
41
|
+
"javaPackageName": "xyz.dynamic.client"
|
|
42
|
+
}
|
|
35
43
|
}
|
|
36
44
|
}
|
|
@@ -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
|
-
*
|
|
27
|
-
* `extension.reactNative.WebView
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* to the same native overlay (e.g.
|
|
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
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
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 {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// VENDORED from @dynamic-labs-sdk/client (src/turboModules/NativeDynamicWebView.ts).
|
|
2
|
+
// The OLD SDK pulls @dynamic-labs-sdk/client in transitively, and iOS React
|
|
3
|
+
// Native codegen only generates TurboModule specs for DIRECT app dependencies.
|
|
4
|
+
// react-native-extension is a direct dependency of old-SDK apps, so carrying the
|
|
5
|
+
// codegen spec here makes the consuming app generate DynamicClientSpec/*.h for
|
|
6
|
+
// the transitively-linked native module. Keep byte-identical to the source spec
|
|
7
|
+
// in @dynamic-labs-sdk/client — update there and re-copy, do not edit here.
|
|
8
|
+
//
|
|
9
|
+
// The `interface`, `default export`, and the `react-native` import are hard
|
|
10
|
+
// requirements of the codegen parser and intentionally deviate from the
|
|
11
|
+
// project's conventions. Codegen discovers specs by the `Native*` filename
|
|
12
|
+
// prefix, so this file cannot be renamed to `.native.ts`.
|
|
13
|
+
|
|
14
|
+
import { type TurboModule, TurboModuleRegistry } from 'react-native';
|
|
15
|
+
import type { UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes';
|
|
16
|
+
|
|
17
|
+
export interface Spec extends TurboModule {
|
|
18
|
+
addListener(eventName: string): void;
|
|
19
|
+
close(instanceId: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Creates a WKWebView for the given instanceId and begins loading `url`.
|
|
22
|
+
*
|
|
23
|
+
* Resolves on `didCommit` — i.e. the first byte of a 2xx/3xx HTTP
|
|
24
|
+
* response has been received and content parsing has started. Page-ready
|
|
25
|
+
* (e.g. the hosted iframe's `iframe-ready-*` handshake) is a separate
|
|
26
|
+
* concern and must be observed by the caller via the message bus; this
|
|
27
|
+
* Promise does not wait for it.
|
|
28
|
+
*
|
|
29
|
+
* Rejects with a `code` of `webview_<reason>` where `<reason>` is one of:
|
|
30
|
+
* - `invalid_url` — URL string was not parseable
|
|
31
|
+
* - `no_host_view` — no foreground window to attach the WebView
|
|
32
|
+
* - `http_error` — response status was >= 400 (userInfo contains `DynamicWebViewStatusCode`)
|
|
33
|
+
* - `navigation_failed` — DNS, TLS, connection failure, or post-commit abort
|
|
34
|
+
* - `cancelled` — `close(instanceId)` was called while this open was pending
|
|
35
|
+
* - `instance_busy` — `open` was called for an instanceId that already has an active WebView
|
|
36
|
+
* - `content_process_terminated` — the WKWebView WebContent process died during load
|
|
37
|
+
* - `unsupported` — the device's Android System WebView is too old to support the secure bridge (Android only)
|
|
38
|
+
*
|
|
39
|
+
* rejection codes into typed `WebViewLoadFailedError` instances.
|
|
40
|
+
*/
|
|
41
|
+
open(instanceId: string, url: string): Promise<void>;
|
|
42
|
+
removeListeners(count: number): void;
|
|
43
|
+
sendMessage(instanceId: string, data: UnsafeObject): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// eslint-disable-next-line import/no-default-export
|
|
47
|
+
export default TurboModuleRegistry.get<Spec>('DynamicClientWebView');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// VENDORED from @dynamic-labs-sdk/client (src/turboModules/NativeKeychain.ts).
|
|
2
|
+
// See NativeDynamicWebView.ts in this directory for why the spec is carried here.
|
|
3
|
+
// Keep byte-identical to the source spec in @dynamic-labs-sdk/client.
|
|
4
|
+
//
|
|
5
|
+
// The `interface`, `default export`, and `react-native` import are hard
|
|
6
|
+
// requirements of the codegen parser and intentionally deviate from the
|
|
7
|
+
// project's conventions. Codegen discovers specs by the `Native*` filename
|
|
8
|
+
// prefix, so this file cannot be renamed to `.native.ts`.
|
|
9
|
+
|
|
10
|
+
import { type TurboModule, TurboModuleRegistry } from 'react-native';
|
|
11
|
+
|
|
12
|
+
export interface Spec extends TurboModule {
|
|
13
|
+
deleteKey(key: string): Promise<void>;
|
|
14
|
+
generateKeyPair(key: string): Promise<{ publicKey: string }>;
|
|
15
|
+
getPublicKey(key: string): Promise<{ publicKey: string } | null>;
|
|
16
|
+
hasKey(key: string): Promise<boolean>;
|
|
17
|
+
isAvailable(): Promise<boolean>;
|
|
18
|
+
sign(key: string, payload: string): Promise<{ signature: string }>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line import/no-default-export
|
|
22
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('DynamicClientKeychain');
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// VENDORED from @dynamic-labs-sdk/client (src/turboModules/NativeReactNativeManifest.ts).
|
|
2
|
+
// See NativeDynamicWebView.ts in this directory for why the spec is carried here.
|
|
3
|
+
// Keep byte-identical to the source spec in @dynamic-labs-sdk/client.
|
|
4
|
+
//
|
|
5
|
+
// Consumed by React Native codegen to emit the Android/iOS native interfaces.
|
|
6
|
+
// The filename must stay prefixed with `Native` — codegen's parser discovers
|
|
7
|
+
// spec files by that convention and will silently skip anything else. The
|
|
8
|
+
// `interface` keyword, the `default export`, and the locally-declared
|
|
9
|
+
// `ReactNativeManifest` type are hard requirements of the codegen parser:
|
|
10
|
+
// codegen parses this file statically and does NOT follow imported type
|
|
11
|
+
// aliases, so the return shape must be defined inline here.
|
|
12
|
+
|
|
13
|
+
import { type TurboModule, TurboModuleRegistry } from 'react-native';
|
|
14
|
+
|
|
15
|
+
export type ReactNativeManifest = {
|
|
16
|
+
sdkVersion: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export interface Spec extends TurboModule {
|
|
20
|
+
getManifest(): ReactNativeManifest;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// eslint-disable-next-line import/no-default-export -- codegen requires a default export of the TurboModule spec
|
|
24
|
+
export default TurboModuleRegistry.get<Spec>('DynamicClientManifest');
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// VENDORED from @dynamic-labs-sdk/client (src/turboModules/NativeScreenshotProtection.ts).
|
|
2
|
+
// See NativeDynamicWebView.ts in this directory for why the spec is carried here.
|
|
3
|
+
// Keep byte-identical to the source spec in @dynamic-labs-sdk/client.
|
|
4
|
+
//
|
|
5
|
+
// The `interface`, `default export`, and the `react-native` import are hard
|
|
6
|
+
// requirements of the codegen parser and intentionally deviate from the
|
|
7
|
+
// project's conventions. Codegen discovers specs by the `Native*` filename
|
|
8
|
+
// prefix, so this file cannot be renamed to `.native.ts`.
|
|
9
|
+
|
|
10
|
+
import { type TurboModule, TurboModuleRegistry } from 'react-native';
|
|
11
|
+
|
|
12
|
+
export interface Spec extends TurboModule {
|
|
13
|
+
/**
|
|
14
|
+
* Toggles window-level screenshot/screen-recording protection on the
|
|
15
|
+
* foreground window while a sensitive surface (e.g. the private-key export
|
|
16
|
+
* display) is visible.
|
|
17
|
+
*
|
|
18
|
+
* Android applies `FLAG_SECURE` on the current Activity window when `enabled`
|
|
19
|
+
* is `true` and clears it when `false` (only if it set the flag itself, so a
|
|
20
|
+
* host app's own `FLAG_SECURE` is never cleared). iOS reparents the key
|
|
21
|
+
* window under a `secureTextEntry` field's canvas so captures render blank.
|
|
22
|
+
* The protection is window-level and independent of any WebView, so it works
|
|
23
|
+
* regardless of how the protected UI is rendered.
|
|
24
|
+
*/
|
|
25
|
+
setEnabled(enabled: boolean): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// eslint-disable-next-line import/no-default-export
|
|
29
|
+
export default TurboModuleRegistry.get<Spec>('DynamicClientScreenshotProtection');
|