@dynamic-labs/react-native-extension 4.91.5 → 4.92.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.
@@ -24,7 +24,7 @@ import org.json.JSONObject
24
24
  import java.util.UUID
25
25
 
26
26
  private const val SCRIPT_HANDLER_NAME = "DynamicEmbeddedWebView"
27
- private const val NAVIGATION_DECISION_TIMEOUT_MS = 2000L
27
+ private const val NAVIGATION_DECISION_TIMEOUT_MS = 5000L
28
28
 
29
29
  // Singleton owning the in-app overlay view that hosts the WebView.
30
30
  // Mirrors EmbeddedWebViewController on iOS.
@@ -24,7 +24,7 @@ import org.json.JSONObject
24
24
  import java.util.UUID
25
25
 
26
26
  private const val SCRIPT_HANDLER_NAME = "DynamicEmbeddedWebView"
27
- private const val NAVIGATION_DECISION_TIMEOUT_MS = 2000L
27
+ private const val NAVIGATION_DECISION_TIMEOUT_MS = 5000L
28
28
 
29
29
  // Singleton owning the in-app overlay view that hosts the WebView.
30
30
  // Mirrors EmbeddedWebViewController on iOS.
@@ -24,7 +24,7 @@ import org.json.JSONObject
24
24
  import java.util.UUID
25
25
 
26
26
  private const val SCRIPT_HANDLER_NAME = "DynamicEmbeddedWebView"
27
- private const val NAVIGATION_DECISION_TIMEOUT_MS = 2000L
27
+ private const val NAVIGATION_DECISION_TIMEOUT_MS = 5000L
28
28
 
29
29
  // Singleton owning the in-app overlay view that hosts the WebView.
30
30
  // Mirrors EmbeddedWebViewController on iOS.
@@ -24,7 +24,7 @@ import org.json.JSONObject
24
24
  import java.util.UUID
25
25
 
26
26
  private const val SCRIPT_HANDLER_NAME = "DynamicEmbeddedWebView"
27
- private const val NAVIGATION_DECISION_TIMEOUT_MS = 2000L
27
+ private const val NAVIGATION_DECISION_TIMEOUT_MS = 5000L
28
28
 
29
29
  // Singleton owning the in-app overlay view that hosts the WebView.
30
30
  // Mirrors EmbeddedWebViewController on iOS.
@@ -24,7 +24,7 @@ import org.json.JSONObject
24
24
  import java.util.UUID
25
25
 
26
26
  private const val SCRIPT_HANDLER_NAME = "DynamicEmbeddedWebView"
27
- private const val NAVIGATION_DECISION_TIMEOUT_MS = 2000L
27
+ private const val NAVIGATION_DECISION_TIMEOUT_MS = 5000L
28
28
 
29
29
  // Singleton owning the in-app overlay view that hosts the WebView.
30
30
  // Mirrors EmbeddedWebViewController on iOS.
@@ -24,7 +24,7 @@ import org.json.JSONObject
24
24
  import java.util.UUID
25
25
 
26
26
  private const val SCRIPT_HANDLER_NAME = "DynamicEmbeddedWebView"
27
- private const val NAVIGATION_DECISION_TIMEOUT_MS = 2000L
27
+ private const val NAVIGATION_DECISION_TIMEOUT_MS = 5000L
28
28
 
29
29
  // Singleton owning the in-app overlay view that hosts the WebView.
30
30
  // Mirrors EmbeddedWebViewController on iOS.
@@ -24,7 +24,7 @@ import org.json.JSONObject
24
24
  import java.util.UUID
25
25
 
26
26
  private const val SCRIPT_HANDLER_NAME = "DynamicEmbeddedWebView"
27
- private const val NAVIGATION_DECISION_TIMEOUT_MS = 2000L
27
+ private const val NAVIGATION_DECISION_TIMEOUT_MS = 5000L
28
28
 
29
29
  // Singleton owning the in-app overlay view that hosts the WebView.
30
30
  // Mirrors EmbeddedWebViewController on iOS.
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.91.5";
38
+ var version = "4.92.0";
39
39
 
40
40
  function _extends() {
41
41
  return _extends = Object.assign ? Object.assign.bind() : function (n) {
@@ -1002,6 +1002,7 @@ const setupEmbeddedWebView = ({
1002
1002
  let recoveryTimer = null;
1003
1003
  let hasFailed = false;
1004
1004
  let hasEmittedSuccessLog = false;
1005
+ let tornDown = false;
1005
1006
  const clearLoadingTimer = () => {
1006
1007
  if (loadingTimer !== null) {
1007
1008
  clearTimeout(loadingTimer);
@@ -1023,6 +1024,7 @@ const setupEmbeddedWebView = ({
1023
1024
  hasFailed = true;
1024
1025
  clearLoadingTimer();
1025
1026
  clearRecoveryTimer();
1027
+ logger.debug(`[EmbeddedWebView] raiseFailure phase=${phase}`);
1026
1028
  const meta = phaseTimers.getMeta({
1027
1029
  phase
1028
1030
  });
@@ -1064,6 +1066,7 @@ const setupEmbeddedWebView = ({
1064
1066
  const handleSdkReady = message => {
1065
1067
  if (message.origin !== 'webview') return;
1066
1068
  if (message.type !== webviewMessages.sdkHasLoadedEventName) return;
1069
+ logger.debug('[EmbeddedWebView] SDK ready signal received');
1067
1070
  clearRecoveryTimer();
1068
1071
  if (hasEmittedSuccessLog || hasFailed) return;
1069
1072
  hasEmittedSuccessLog = true;
@@ -1099,11 +1102,13 @@ const setupEmbeddedWebView = ({
1099
1102
  isTopFrame: event.isTopFrame,
1100
1103
  url: event.url
1101
1104
  }, builtUrl);
1105
+ logger.debug(`[EmbeddedWebView] onShouldStartLoad id=${event.id} url=${event.url} isTopFrame=${event.isTopFrame} allow=${allow}`);
1102
1106
  native.respondToShouldStartLoad(event.id, allow).catch(err => {
1103
1107
  logger.warn('EmbeddedWebView.respondToShouldStartLoad failed', err);
1104
1108
  });
1105
1109
  });
1106
1110
  const onLoadStartSub = native.addListener('onLoadStart', () => {
1111
+ logger.debug('[EmbeddedWebView] onLoadStart');
1107
1112
  phaseTimers.recordEvent('load_start');
1108
1113
  // Re-arm both timers on every load_start. A retry from the native
1109
1114
  // side (e.g. a redirect, or a reload after a transient failure)
@@ -1116,10 +1121,12 @@ const setupEmbeddedWebView = ({
1116
1121
  }, _loadingTimeoutMs);
1117
1122
  });
1118
1123
  const onLoadSub = native.addListener('onLoad', () => {
1124
+ logger.debug('[EmbeddedWebView] onLoad (response committed)');
1119
1125
  phaseTimers.recordEvent('load');
1120
1126
  clearLoadingTimer();
1121
1127
  });
1122
1128
  const onLoadEndSub = native.addListener('onLoadEnd', () => {
1129
+ logger.debug('[EmbeddedWebView] onLoadEnd (page finished)');
1123
1130
  phaseTimers.recordEvent('load_end');
1124
1131
  clearLoadingTimer();
1125
1132
  clearRecoveryTimer();
@@ -1128,6 +1135,7 @@ const setupEmbeddedWebView = ({
1128
1135
  }, _recoveryTimeoutMs);
1129
1136
  });
1130
1137
  const onLoadErrorSub = native.addListener('onLoadError', event => {
1138
+ logger.debug(`[EmbeddedWebView] onLoadError domain=${event.domain} code=${event.code} url=${event.url}`);
1131
1139
  phaseTimers.recordEvent('native_error');
1132
1140
  logger.warn('EmbeddedWebView load error', event);
1133
1141
  raiseFailure('embedded_native_error', {
@@ -1147,13 +1155,28 @@ const setupEmbeddedWebView = ({
1147
1155
  // without emitting onLoadError — leaving onLoadStart (and therefore the
1148
1156
  // timer) never triggered. The onLoadStart handler clears and re-arms this
1149
1157
  // timer when it fires, so behaviour after onLoadStart is unchanged.
1158
+ logger.debug(`[EmbeddedWebView] arming loading timer (${_loadingTimeoutMs}ms) before setUrl`);
1150
1159
  loadingTimer = setTimeout(() => {
1160
+ logger.debug('[EmbeddedWebView] loading timer fired — raising html_load failure');
1151
1161
  raiseFailure('html_load');
1152
1162
  }, _loadingTimeoutMs);
1153
- native.setUrl(builtUrl.toString()).catch(err => {
1154
- logger.warn('EmbeddedWebView.setUrl failed', err);
1155
- });
1163
+ // Defer setUrl by one event-loop tick so the JS thread has fully settled
1164
+ // after listener registration. On cold boot the main thread is congested
1165
+ // with bundle evaluation — yielding here gives pending microtasks and
1166
+ // native bridge batches a chance to flush before the native side starts
1167
+ // firing navigation events that require a JS round-trip response.
1168
+ const warmupTimer = setTimeout(() => {
1169
+ var _a;
1170
+ if (tornDown) return;
1171
+ logger.debug(`[EmbeddedWebView] calling setUrl: ${builtUrl.toString()}`);
1172
+ (_a = native.setUrl(builtUrl.toString())) === null || _a === void 0 ? void 0 : _a.catch(err => {
1173
+ logger.warn('EmbeddedWebView.setUrl failed', err);
1174
+ });
1175
+ }, 0);
1156
1176
  return () => {
1177
+ logger.debug('[EmbeddedWebView] teardown — removing listeners');
1178
+ tornDown = true;
1179
+ clearTimeout(warmupTimer);
1157
1180
  clearLoadingTimer();
1158
1181
  clearRecoveryTimer();
1159
1182
  removeVisibilityHandler();
@@ -1312,6 +1335,20 @@ const setupPlatformHandler = core => {
1312
1335
  fileName,
1313
1336
  mimeType
1314
1337
  }) {
1338
+ /**
1339
+ * Sanitize the caller-provided fileName to prevent path traversal
1340
+ * (CWE-22). The fileName is used to build a path inside the app cache
1341
+ * directory, so a value containing path separators or `..` segments
1342
+ * could escape the cache directory and delete/overwrite arbitrary files
1343
+ * in the app sandbox. Reject anything that is not a plain basename:
1344
+ * empty, `.`/`..`, or containing `/` or `\` (any `..` segment within a
1345
+ * path is caught by the separator checks). Reducing to the basename here
1346
+ * would silently write attacker-chosen data under a different name, so we
1347
+ * reject instead.
1348
+ */
1349
+ if (!fileName || fileName === '.' || fileName === '..' || fileName.includes('/') || fileName.includes('\\')) {
1350
+ throw new Error(`Invalid fileName: ${fileName}`);
1351
+ }
1315
1352
  /**
1316
1353
  * Because this is an iOS and Android only package, we need to import
1317
1354
  * dynamically to avoid react-native-web build errors.
package/index.js CHANGED
@@ -13,7 +13,7 @@ import { createURL, getInitialURL, addEventListener, openURL } from 'expo-linkin
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.91.5";
16
+ var version = "4.92.0";
17
17
 
18
18
  function _extends() {
19
19
  return _extends = Object.assign ? Object.assign.bind() : function (n) {
@@ -980,6 +980,7 @@ const setupEmbeddedWebView = ({
980
980
  let recoveryTimer = null;
981
981
  let hasFailed = false;
982
982
  let hasEmittedSuccessLog = false;
983
+ let tornDown = false;
983
984
  const clearLoadingTimer = () => {
984
985
  if (loadingTimer !== null) {
985
986
  clearTimeout(loadingTimer);
@@ -1001,6 +1002,7 @@ const setupEmbeddedWebView = ({
1001
1002
  hasFailed = true;
1002
1003
  clearLoadingTimer();
1003
1004
  clearRecoveryTimer();
1005
+ logger.debug(`[EmbeddedWebView] raiseFailure phase=${phase}`);
1004
1006
  const meta = phaseTimers.getMeta({
1005
1007
  phase
1006
1008
  });
@@ -1042,6 +1044,7 @@ const setupEmbeddedWebView = ({
1042
1044
  const handleSdkReady = message => {
1043
1045
  if (message.origin !== 'webview') return;
1044
1046
  if (message.type !== sdkHasLoadedEventName) return;
1047
+ logger.debug('[EmbeddedWebView] SDK ready signal received');
1045
1048
  clearRecoveryTimer();
1046
1049
  if (hasEmittedSuccessLog || hasFailed) return;
1047
1050
  hasEmittedSuccessLog = true;
@@ -1077,11 +1080,13 @@ const setupEmbeddedWebView = ({
1077
1080
  isTopFrame: event.isTopFrame,
1078
1081
  url: event.url
1079
1082
  }, builtUrl);
1083
+ logger.debug(`[EmbeddedWebView] onShouldStartLoad id=${event.id} url=${event.url} isTopFrame=${event.isTopFrame} allow=${allow}`);
1080
1084
  native.respondToShouldStartLoad(event.id, allow).catch(err => {
1081
1085
  logger.warn('EmbeddedWebView.respondToShouldStartLoad failed', err);
1082
1086
  });
1083
1087
  });
1084
1088
  const onLoadStartSub = native.addListener('onLoadStart', () => {
1089
+ logger.debug('[EmbeddedWebView] onLoadStart');
1085
1090
  phaseTimers.recordEvent('load_start');
1086
1091
  // Re-arm both timers on every load_start. A retry from the native
1087
1092
  // side (e.g. a redirect, or a reload after a transient failure)
@@ -1094,10 +1099,12 @@ const setupEmbeddedWebView = ({
1094
1099
  }, _loadingTimeoutMs);
1095
1100
  });
1096
1101
  const onLoadSub = native.addListener('onLoad', () => {
1102
+ logger.debug('[EmbeddedWebView] onLoad (response committed)');
1097
1103
  phaseTimers.recordEvent('load');
1098
1104
  clearLoadingTimer();
1099
1105
  });
1100
1106
  const onLoadEndSub = native.addListener('onLoadEnd', () => {
1107
+ logger.debug('[EmbeddedWebView] onLoadEnd (page finished)');
1101
1108
  phaseTimers.recordEvent('load_end');
1102
1109
  clearLoadingTimer();
1103
1110
  clearRecoveryTimer();
@@ -1106,6 +1113,7 @@ const setupEmbeddedWebView = ({
1106
1113
  }, _recoveryTimeoutMs);
1107
1114
  });
1108
1115
  const onLoadErrorSub = native.addListener('onLoadError', event => {
1116
+ logger.debug(`[EmbeddedWebView] onLoadError domain=${event.domain} code=${event.code} url=${event.url}`);
1109
1117
  phaseTimers.recordEvent('native_error');
1110
1118
  logger.warn('EmbeddedWebView load error', event);
1111
1119
  raiseFailure('embedded_native_error', {
@@ -1125,13 +1133,28 @@ const setupEmbeddedWebView = ({
1125
1133
  // without emitting onLoadError — leaving onLoadStart (and therefore the
1126
1134
  // timer) never triggered. The onLoadStart handler clears and re-arms this
1127
1135
  // timer when it fires, so behaviour after onLoadStart is unchanged.
1136
+ logger.debug(`[EmbeddedWebView] arming loading timer (${_loadingTimeoutMs}ms) before setUrl`);
1128
1137
  loadingTimer = setTimeout(() => {
1138
+ logger.debug('[EmbeddedWebView] loading timer fired — raising html_load failure');
1129
1139
  raiseFailure('html_load');
1130
1140
  }, _loadingTimeoutMs);
1131
- native.setUrl(builtUrl.toString()).catch(err => {
1132
- logger.warn('EmbeddedWebView.setUrl failed', err);
1133
- });
1141
+ // Defer setUrl by one event-loop tick so the JS thread has fully settled
1142
+ // after listener registration. On cold boot the main thread is congested
1143
+ // with bundle evaluation — yielding here gives pending microtasks and
1144
+ // native bridge batches a chance to flush before the native side starts
1145
+ // firing navigation events that require a JS round-trip response.
1146
+ const warmupTimer = setTimeout(() => {
1147
+ var _a;
1148
+ if (tornDown) return;
1149
+ logger.debug(`[EmbeddedWebView] calling setUrl: ${builtUrl.toString()}`);
1150
+ (_a = native.setUrl(builtUrl.toString())) === null || _a === void 0 ? void 0 : _a.catch(err => {
1151
+ logger.warn('EmbeddedWebView.setUrl failed', err);
1152
+ });
1153
+ }, 0);
1134
1154
  return () => {
1155
+ logger.debug('[EmbeddedWebView] teardown — removing listeners');
1156
+ tornDown = true;
1157
+ clearTimeout(warmupTimer);
1135
1158
  clearLoadingTimer();
1136
1159
  clearRecoveryTimer();
1137
1160
  removeVisibilityHandler();
@@ -1290,6 +1313,20 @@ const setupPlatformHandler = core => {
1290
1313
  fileName,
1291
1314
  mimeType
1292
1315
  }) {
1316
+ /**
1317
+ * Sanitize the caller-provided fileName to prevent path traversal
1318
+ * (CWE-22). The fileName is used to build a path inside the app cache
1319
+ * directory, so a value containing path separators or `..` segments
1320
+ * could escape the cache directory and delete/overwrite arbitrary files
1321
+ * in the app sandbox. Reject anything that is not a plain basename:
1322
+ * empty, `.`/`..`, or containing `/` or `\` (any `..` segment within a
1323
+ * path is caught by the separator checks). Reducing to the basename here
1324
+ * would silently write attacker-chosen data under a different name, so we
1325
+ * reject instead.
1326
+ */
1327
+ if (!fileName || fileName === '.' || fileName === '..' || fileName.includes('/') || fileName.includes('\\')) {
1328
+ throw new Error(`Invalid fileName: ${fileName}`);
1329
+ }
1293
1330
  /**
1294
1331
  * Because this is an iOS and Android only package, we need to import
1295
1332
  * dynamically to avoid react-native-web build errors.
@@ -2,7 +2,7 @@ import UIKit
2
2
  import WebKit
3
3
 
4
4
  private let scriptHandlerName = "dynamicEmbeddedWebView"
5
- private let navigationDecisionTimeout: TimeInterval = 2.0
5
+ private let navigationDecisionTimeout: TimeInterval = 5.0
6
6
 
7
7
  // Cleartext http is only permitted in debug builds (e.g. Metro at
8
8
  // http://localhost:4202). Release builds reject http top-frame and sub-frame
@@ -366,6 +366,17 @@ extension EmbeddedWebViewController: WKNavigationDelegate {
366
366
  if let handler = self.pendingNavigationDecisions.removeValue(forKey: id) {
367
367
  self.navigationTimers.removeValue(forKey: id)?.invalidate()
368
368
  handler(.cancel)
369
+ // Surface an explicit load error so the SDK's error path runs
370
+ // immediately instead of waiting for the 20 s html_load timer.
371
+ // Matches Android's EmbeddedWebViewNavigationTimeout behavior.
372
+ let timeoutMs = Int(navigationDecisionTimeout * 1000)
373
+ self.eventEmitter?("onLoadError", [
374
+ "url": url,
375
+ "code": -1,
376
+ "domain": "EmbeddedWebViewNavigationTimeout",
377
+ "description": "Navigation decision timed out after \(timeoutMs)ms",
378
+ "isProvisional": true,
379
+ ])
369
380
  }
370
381
  }
371
382
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-labs/react-native-extension",
3
- "version": "4.91.5",
3
+ "version": "4.92.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.91.5",
22
- "@dynamic-labs/client": "4.91.5",
23
- "@dynamic-labs/logger": "4.91.5",
24
- "@dynamic-labs/message-transport": "4.91.5",
25
- "@dynamic-labs/webview-messages": "4.91.5"
21
+ "@dynamic-labs/assert-package-version": "4.92.0",
22
+ "@dynamic-labs/client": "4.92.0",
23
+ "@dynamic-labs/logger": "4.92.0",
24
+ "@dynamic-labs/message-transport": "4.92.0",
25
+ "@dynamic-labs/webview-messages": "4.92.0"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": ">=18.0.0 <20.0.0",