@dynamic-labs/react-native-extension 4.83.1 → 4.83.2-alpha.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.
Files changed (25) hide show
  1. package/android/EmbeddedWebViewController.kt +26 -0
  2. package/android/EmbeddedWebViewModule.kt +8 -1
  3. package/android/dynamic/embeddedwebview/EmbeddedWebViewController.kt +26 -0
  4. package/android/dynamic/embeddedwebview/EmbeddedWebViewModule.kt +8 -1
  5. package/android/embeddedwebview/EmbeddedWebViewController.kt +26 -0
  6. package/android/embeddedwebview/EmbeddedWebViewModule.kt +8 -1
  7. package/android/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewController.kt +26 -0
  8. package/android/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewModule.kt +8 -1
  9. package/android/main/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewController.kt +26 -0
  10. package/android/main/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewModule.kt +8 -1
  11. package/android/src/main/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewController.kt +26 -0
  12. package/android/src/main/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewModule.kt +8 -1
  13. package/android/xyz/dynamic/embeddedwebview/EmbeddedWebViewController.kt +26 -0
  14. package/android/xyz/dynamic/embeddedwebview/EmbeddedWebViewModule.kt +8 -1
  15. package/index.cjs +350 -24
  16. package/index.js +351 -25
  17. package/ios/EmbeddedWebViewController.swift +22 -0
  18. package/ios/EmbeddedWebViewModule.swift +8 -1
  19. package/package.json +6 -6
  20. package/src/components/WebView/EmbeddedWebView/EmbeddedWebView.d.ts +20 -1
  21. package/src/components/WebView/EmbeddedWebView/embeddedWebViewPhaseTimers/embeddedWebViewPhaseTimers.d.ts +52 -0
  22. package/src/components/WebView/EmbeddedWebView/embeddedWebViewPhaseTimers/index.d.ts +1 -0
  23. package/src/components/WebView/useWebViewPhaseTimers/index.d.ts +1 -0
  24. package/src/components/WebView/useWebViewPhaseTimers/useWebViewPhaseTimers.d.ts +45 -0
  25. package/src/nativeModules/EmbeddedWebView.d.ts +27 -2
package/index.cjs CHANGED
@@ -8,6 +8,7 @@ var react = require('react');
8
8
  var reactNativeWebview = require('react-native-webview');
9
9
  var logger$1 = require('@dynamic-labs/logger');
10
10
  var messageTransport = require('@dynamic-labs/message-transport');
11
+ var webviewMessages = require('@dynamic-labs/webview-messages');
11
12
  var jsxRuntime = require('react/jsx-runtime');
12
13
  var expoModulesCore = require('expo-modules-core');
13
14
  var reactNativePasskey = require('react-native-passkey');
@@ -34,7 +35,7 @@ function _interopNamespace(e) {
34
35
  return Object.freeze(n);
35
36
  }
36
37
 
37
- var version = "4.83.1";
38
+ var version = "4.83.2-alpha.0";
38
39
 
39
40
  function _extends() {
40
41
  return _extends = Object.assign ? Object.assign.bind() : function (n) {
@@ -122,6 +123,122 @@ const useMessageTransportWebViewBridge = (core, webViewRef) => {
122
123
  };
123
124
  };
124
125
 
126
+ const CLEAR_STATE_PARAM = 'clear-state';
127
+ const ENVIRONMENT_ID_PARAM = 'environmentId';
128
+ const WEBVIEW_START_TIME_PARAM = 'webviewStartTime';
129
+
130
+ const hasClearStateInUrl = webViewUrl => Boolean(webViewUrl.searchParams.get(CLEAR_STATE_PARAM));
131
+
132
+ const MANIFEST_MESSAGE_TYPE$1 = 'manifest';
133
+ const RETRY_QUERY_PARAM = 'retry';
134
+ const emptyPerAttemptState$1 = () => ({
135
+ htmlLoadStartedAt: null,
136
+ htmlLoadedAt: null,
137
+ manifestReceivedAt: null,
138
+ onLoadEndAt: null,
139
+ sdkReadyAt: null
140
+ });
141
+ const diffMs$1 = (later, earlier) => later !== null && earlier !== null ? later - earlier : null;
142
+ const readRetryCount = webViewUrl => {
143
+ const raw = webViewUrl.searchParams.get(RETRY_QUERY_PARAM);
144
+ if (raw === null) return 0;
145
+ const parsed = Number(raw);
146
+ return Number.isFinite(parsed) ? parsed : 0;
147
+ };
148
+ /**
149
+ * Tracks how long each step of the WebView load took, so when we raise
150
+ * `WebViewFailedToLoadError` we can attach the timings to the error log.
151
+ *
152
+ * The timings cover the host-side phases of the load — the webview-side
153
+ * timings (`webview.time_to_load_manifest`, `webview.time_to_sdk_ready`)
154
+ * still come from the webview itself once it boots, but those don't help
155
+ * when the webview never boots in the first place.
156
+ */
157
+ const useWebViewPhaseTimers = ({
158
+ core,
159
+ webViewUrl,
160
+ loadingTimeout,
161
+ recoveryTimeout
162
+ }) => {
163
+ const perAttemptRef = react.useRef(emptyPerAttemptState$1());
164
+ const nativeErrorCountRef = react.useRef(0);
165
+ const osKillCountRef = react.useRef(0);
166
+ const webViewUrlRef = react.useRef(webViewUrl);
167
+ webViewUrlRef.current = webViewUrl;
168
+ /**
169
+ * Listen on the message transport for the two webview-originated
170
+ * messages that mark "JS bundle alive" and "SDK ready".
171
+ *
172
+ * We only record the first occurrence per attempt so that a healthy
173
+ * reload doesn't overwrite the timing from the attempt that ultimately
174
+ * failed.
175
+ */
176
+ react.useEffect(() => {
177
+ const handler = message => {
178
+ if (message.origin !== 'webview') return;
179
+ if (message.type === MANIFEST_MESSAGE_TYPE$1 && perAttemptRef.current.manifestReceivedAt === null) {
180
+ perAttemptRef.current.manifestReceivedAt = Date.now();
181
+ }
182
+ if (message.type === webviewMessages.sdkHasLoadedEventName && perAttemptRef.current.sdkReadyAt === null) {
183
+ perAttemptRef.current.sdkReadyAt = Date.now();
184
+ }
185
+ };
186
+ core.messageTransport.on(handler);
187
+ return () => core.messageTransport.off(handler);
188
+ }, [core]);
189
+ const recordEvent = react.useCallback(event => {
190
+ const now = Date.now();
191
+ switch (event) {
192
+ case 'load_start':
193
+ perAttemptRef.current = emptyPerAttemptState$1();
194
+ perAttemptRef.current.htmlLoadStartedAt = now;
195
+ break;
196
+ case 'load':
197
+ perAttemptRef.current.htmlLoadedAt = now;
198
+ break;
199
+ case 'load_end':
200
+ perAttemptRef.current.onLoadEndAt = now;
201
+ break;
202
+ case 'native_error':
203
+ nativeErrorCountRef.current += 1;
204
+ break;
205
+ case 'os_kill':
206
+ osKillCountRef.current += 1;
207
+ break;
208
+ }
209
+ }, []);
210
+ const getMeta = react.useCallback(({
211
+ phase
212
+ }) => {
213
+ const url = webViewUrlRef.current;
214
+ const {
215
+ htmlLoadStartedAt,
216
+ htmlLoadedAt,
217
+ onLoadEndAt,
218
+ manifestReceivedAt,
219
+ sdkReadyAt
220
+ } = perAttemptRef.current;
221
+ return {
222
+ hadClearState: hasClearStateInUrl(url),
223
+ htmlLoadMs: diffMs$1(htmlLoadedAt, htmlLoadStartedAt),
224
+ loadingTimeoutMs: loadingTimeout,
225
+ manifestReceivedMs: diffMs$1(manifestReceivedAt, onLoadEndAt),
226
+ nativeErrorCount: nativeErrorCountRef.current,
227
+ onLoadToOnLoadEndMs: diffMs$1(onLoadEndAt, htmlLoadedAt),
228
+ osKillCount: osKillCountRef.current,
229
+ phase,
230
+ recoveryTimeoutMs: recoveryTimeout,
231
+ retryCount: readRetryCount(url),
232
+ sdkReadyMs: diffMs$1(sdkReadyAt, onLoadEndAt),
233
+ webviewUrl: url.toString()
234
+ };
235
+ }, [loadingTimeout, recoveryTimeout]);
236
+ return {
237
+ getMeta,
238
+ recordEvent
239
+ };
240
+ };
241
+
125
242
  const useWebViewVisibility = core => {
126
243
  const webViewVisibilityRequestChannelRef = react.useRef(messageTransport.createRequestChannel(core.messageTransport));
127
244
  const [visible, setVisible] = react.useState(false);
@@ -158,10 +275,6 @@ const styles = reactNative.StyleSheet.create({
158
275
  }
159
276
  });
160
277
 
161
- const CLEAR_STATE_PARAM = 'clear-state';
162
- const ENVIRONMENT_ID_PARAM = 'environmentId';
163
- const WEBVIEW_START_TIME_PARAM = 'webviewStartTime';
164
-
165
278
  const setClearStateToUrl = webViewUrl => {
166
279
  const newWebViewUrl = new URL(webViewUrl);
167
280
  newWebViewUrl.searchParams.set(CLEAR_STATE_PARAM, 'true');
@@ -169,8 +282,6 @@ const setClearStateToUrl = webViewUrl => {
169
282
  return newWebViewUrl;
170
283
  };
171
284
 
172
- const hasClearStateInUrl = webViewUrl => Boolean(webViewUrl.searchParams.get(CLEAR_STATE_PARAM));
173
-
174
285
  const useWebViewRecoveryTimeout = ({
175
286
  core,
176
287
  webViewUrl,
@@ -323,15 +434,15 @@ const shouldAllowNavigation = (request, webViewUrl) => {
323
434
  return false;
324
435
  };
325
436
 
326
- const DEFAULT_RECOVERY_TIMEOUT_MS = 20000;
327
- const DEFAULT_LOADING_TIMEOUT_MS = 20000;
437
+ const DEFAULT_RECOVERY_TIMEOUT_MS$1 = 20000;
438
+ const DEFAULT_LOADING_TIMEOUT_MS$1 = 20000;
328
439
  const WebView = ({
329
440
  webviewUrl: initialWebViewUrl,
330
441
  core,
331
442
  webviewDebuggingEnabled: _webviewDebuggingEnabled = false,
332
443
  disableRecovery: _disableRecovery = false,
333
- recoveryTimeout: _recoveryTimeout = DEFAULT_RECOVERY_TIMEOUT_MS,
334
- loadingTimeout: _loadingTimeout = DEFAULT_LOADING_TIMEOUT_MS
444
+ recoveryTimeout: _recoveryTimeout = DEFAULT_RECOVERY_TIMEOUT_MS$1,
445
+ loadingTimeout: _loadingTimeout = DEFAULT_LOADING_TIMEOUT_MS$1
335
446
  }) => {
336
447
  const webViewRef = react.useRef(null);
337
448
  const {
@@ -341,6 +452,15 @@ const WebView = ({
341
452
  const {
342
453
  onMessageHandler
343
454
  } = useMessageTransportWebViewBridge(core, webViewRef);
455
+ const {
456
+ recordEvent,
457
+ getMeta
458
+ } = useWebViewPhaseTimers({
459
+ core,
460
+ loadingTimeout: _loadingTimeout,
461
+ recoveryTimeout: _recoveryTimeout,
462
+ webViewUrl
463
+ });
344
464
  const containerStyles = [styles['container'], visible ? styles.show : styles.hide];
345
465
  /**
346
466
  * Block the message transport when the webview is unmounted
@@ -382,13 +502,23 @@ const WebView = ({
382
502
  return newWebViewUrl;
383
503
  });
384
504
  }, [core]);
385
- const blockAndReloadWebViewOsKill = react.useCallback(() => blockAndReloadWebView('os_kill'), [blockAndReloadWebView]);
505
+ const blockAndReloadWebViewOsKill = react.useCallback(() => {
506
+ recordEvent('os_kill');
507
+ blockAndReloadWebView('os_kill');
508
+ }, [blockAndReloadWebView, recordEvent]);
386
509
  react.useEffect(() => core.messageTransport.recoveryManager.onRecoveryRequested(() => blockAndReloadWebView('recovery')), [core, blockAndReloadWebView]);
387
- const setWebViewLoadError = react.useCallback(() => {
510
+ const setWebViewLoadError = react.useCallback(phase => {
388
511
  // Error was already thrown, do not throw again
389
512
  if (core.initialization.error instanceof WebViewFailedToLoadError) return;
390
- core.initialization.error = new WebViewFailedToLoadError();
391
- }, [core]);
513
+ core.initialization.error = new WebViewFailedToLoadError(getMeta({
514
+ phase
515
+ }));
516
+ }, [core, getMeta]);
517
+ /**
518
+ * Wraps `setWebViewLoadError` so the consumer doesn't need to know
519
+ * about the phase enum; each callsite below binds its own phase.
520
+ */
521
+ const setWebViewLoadErrorWithPhase = react.useCallback(phase => () => setWebViewLoadError(phase), [setWebViewLoadError]);
392
522
  /**
393
523
  * Reload the webview with a clean state when a timeout is reached
394
524
  * and the webview did not get to the loaded state yet
@@ -396,7 +526,7 @@ const WebView = ({
396
526
  const startRecoveryTimeout = useWebViewRecoveryTimeout({
397
527
  core,
398
528
  disableRecovery: _disableRecovery,
399
- onFailedToLoadAfterClearState: setWebViewLoadError,
529
+ onFailedToLoadAfterClearState: setWebViewLoadErrorWithPhase('after_clear_state'),
400
530
  recoveryTimeout: _recoveryTimeout,
401
531
  setWebViewUrl,
402
532
  webViewUrl
@@ -404,17 +534,30 @@ const WebView = ({
404
534
  const webViewLoadErrorCountRef = react.useRef(0);
405
535
  const onWebViewLoadError = react.useCallback(() => {
406
536
  webViewLoadErrorCountRef.current = webViewLoadErrorCountRef.current + 1;
537
+ recordEvent('native_error');
407
538
  /**
408
539
  * This is the first attempt to load the webview, do not throw an error
409
540
  * because the recovery system will attempt to reload the webview
410
541
  */
411
542
  if (webViewLoadErrorCountRef.current === 1) return;
412
- setWebViewLoadError();
413
- }, [setWebViewLoadError]);
543
+ setWebViewLoadError('native_error');
544
+ }, [recordEvent, setWebViewLoadError]);
414
545
  const {
415
- onLoad,
416
- onLoadStart
417
- } = useWebViewLoadingTimeout(_loadingTimeout, setWebViewLoadError);
546
+ onLoad: onLoadFromTimeout,
547
+ onLoadStart: onLoadStartFromTimeout
548
+ } = useWebViewLoadingTimeout(_loadingTimeout, setWebViewLoadErrorWithPhase('html_load'));
549
+ const onLoadStart = react.useCallback(() => {
550
+ recordEvent('load_start');
551
+ onLoadStartFromTimeout();
552
+ }, [onLoadStartFromTimeout, recordEvent]);
553
+ const onLoad = react.useCallback(() => {
554
+ recordEvent('load');
555
+ onLoadFromTimeout();
556
+ }, [onLoadFromTimeout, recordEvent]);
557
+ const onLoadEnd = react.useCallback(() => {
558
+ recordEvent('load_end');
559
+ startRecoveryTimeout();
560
+ }, [recordEvent, startRecoveryTimeout]);
418
561
  return /*#__PURE__*/jsxRuntime.jsx(reactNativeWebview.WebView, {
419
562
  ref: webViewRef,
420
563
  source: {
@@ -423,7 +566,7 @@ const WebView = ({
423
566
  containerStyle: containerStyles,
424
567
  style: styles['webview'],
425
568
  onMessage: onMessageHandler,
426
- onLoadEnd: () => startRecoveryTimeout(),
569
+ onLoadEnd: onLoadEnd,
427
570
  onLoadStart: onLoadStart,
428
571
  onLoad: onLoad,
429
572
  hideKeyboardAccessoryView: true,
@@ -475,6 +618,105 @@ const getEmbeddedWebView = () => {
475
618
  }
476
619
  };
477
620
 
621
+ const MANIFEST_MESSAGE_TYPE = 'manifest';
622
+ const emptyPerAttemptState = () => ({
623
+ htmlLoadStartedAt: null,
624
+ htmlLoadedAt: null,
625
+ manifestReceivedAt: null,
626
+ onLoadEndAt: null,
627
+ sdkReadyAt: null
628
+ });
629
+ const diffMs = (later, earlier) => later !== null && earlier !== null ? later - earlier : null;
630
+ /**
631
+ * Non-React equivalent of `useWebViewPhaseTimers` for the embedded native
632
+ * webview path. The embedded path is a singleton wired by `setupEmbeddedWebView`
633
+ * (not a React component), so we expose the same phase-timing surface as a
634
+ * plain factory.
635
+ *
636
+ * The hook and the factory are intentionally separate: the embedded path does
637
+ * not have a retry or clear-state model, so `retryCount` is always `0` and
638
+ * `hadClearState` is always `false` in the meta produced here. Sharing one
639
+ * implementation would require either pulling React into a hot non-React path
640
+ * or threading a stateless adapter through both consumers \u2014 the duplication
641
+ * is small enough that keeping them separate stays cleaner.
642
+ */
643
+ const createEmbeddedWebViewPhaseTimers = ({
644
+ core,
645
+ webViewUrl,
646
+ loadingTimeoutMs,
647
+ recoveryTimeoutMs
648
+ }) => {
649
+ let perAttempt = emptyPerAttemptState();
650
+ let nativeErrorCount = 0;
651
+ let osKillCount = 0;
652
+ const handleMessage = message => {
653
+ if (message.origin !== 'webview') return;
654
+ if (message.type === MANIFEST_MESSAGE_TYPE && perAttempt.manifestReceivedAt === null) {
655
+ perAttempt.manifestReceivedAt = Date.now();
656
+ }
657
+ if (message.type === webviewMessages.sdkHasLoadedEventName && perAttempt.sdkReadyAt === null) {
658
+ perAttempt.sdkReadyAt = Date.now();
659
+ }
660
+ };
661
+ core.messageTransport.on(handleMessage);
662
+ const recordEvent = event => {
663
+ const now = Date.now();
664
+ switch (event) {
665
+ case 'load_start':
666
+ perAttempt = emptyPerAttemptState();
667
+ perAttempt.htmlLoadStartedAt = now;
668
+ break;
669
+ case 'load':
670
+ perAttempt.htmlLoadedAt = now;
671
+ break;
672
+ case 'load_end':
673
+ perAttempt.onLoadEndAt = now;
674
+ break;
675
+ case 'native_error':
676
+ nativeErrorCount += 1;
677
+ break;
678
+ case 'os_kill':
679
+ osKillCount += 1;
680
+ break;
681
+ }
682
+ };
683
+ const getMeta = ({
684
+ phase
685
+ }) => {
686
+ const {
687
+ htmlLoadStartedAt,
688
+ htmlLoadedAt,
689
+ onLoadEndAt,
690
+ manifestReceivedAt,
691
+ sdkReadyAt
692
+ } = perAttempt;
693
+ return {
694
+ hadClearState: false,
695
+ htmlLoadMs: diffMs(htmlLoadedAt, htmlLoadStartedAt),
696
+ loadingTimeoutMs,
697
+ manifestReceivedMs: diffMs(manifestReceivedAt, onLoadEndAt),
698
+ nativeErrorCount,
699
+ onLoadToOnLoadEndMs: diffMs(onLoadEndAt, htmlLoadedAt),
700
+ osKillCount,
701
+ phase,
702
+ recoveryTimeoutMs,
703
+ retryCount: 0,
704
+ sdkReadyMs: diffMs(sdkReadyAt, onLoadEndAt),
705
+ webviewUrl: webViewUrl.toString()
706
+ };
707
+ };
708
+ const dispose = () => {
709
+ core.messageTransport.off(handleMessage);
710
+ };
711
+ return {
712
+ dispose,
713
+ getMeta,
714
+ recordEvent
715
+ };
716
+ };
717
+
718
+ const DEFAULT_LOADING_TIMEOUT_MS = 20000;
719
+ const DEFAULT_RECOVERY_TIMEOUT_MS = 20000;
478
720
  // Wires the JS-side bridge to the native WKWebView singleton owned by Swift.
479
721
  // This is intentionally not a React component: the embedded webview lives
480
722
  // outside the React tree (in a dedicated UIWindow on iOS), so its setup runs
@@ -487,10 +729,47 @@ const getEmbeddedWebView = () => {
487
729
  const setupEmbeddedWebView = ({
488
730
  webviewUrl,
489
731
  core,
490
- webviewDebuggingEnabled: _webviewDebuggingEnabled = false
732
+ webviewDebuggingEnabled: _webviewDebuggingEnabled = false,
733
+ loadingTimeoutMs: _loadingTimeoutMs = DEFAULT_LOADING_TIMEOUT_MS,
734
+ recoveryTimeoutMs: _recoveryTimeoutMs = DEFAULT_RECOVERY_TIMEOUT_MS
491
735
  }) => {
492
736
  const native = getEmbeddedWebView();
493
737
  const builtUrl = assignStartTimeToUrl(assignEnvironmentIdToUrl(webviewUrl, core.environmentId));
738
+ const phaseTimers = createEmbeddedWebViewPhaseTimers({
739
+ core,
740
+ loadingTimeoutMs: _loadingTimeoutMs,
741
+ recoveryTimeoutMs: _recoveryTimeoutMs,
742
+ webViewUrl: builtUrl
743
+ });
744
+ let loadingTimer = null;
745
+ let recoveryTimer = null;
746
+ let hasFailed = false;
747
+ const clearLoadingTimer = () => {
748
+ if (loadingTimer !== null) {
749
+ clearTimeout(loadingTimer);
750
+ loadingTimer = null;
751
+ }
752
+ };
753
+ const clearRecoveryTimer = () => {
754
+ if (recoveryTimer !== null) {
755
+ clearTimeout(recoveryTimer);
756
+ recoveryTimer = null;
757
+ }
758
+ };
759
+ // Raise `WebViewFailedToLoadError` once per setup. Subsequent triggers
760
+ // (e.g. recovery timeout firing after html_load already failed) are
761
+ // suppressed so we don't spam the initialization-error setter with
762
+ // duplicate logs for the same load attempt.
763
+ const raiseFailure = (phase, extraMeta = {}) => {
764
+ if (hasFailed) return;
765
+ hasFailed = true;
766
+ clearLoadingTimer();
767
+ clearRecoveryTimer();
768
+ const meta = phaseTimers.getMeta({
769
+ phase
770
+ });
771
+ core.initialization.error = new WebViewFailedToLoadError(Object.assign(Object.assign({}, meta), extraMeta));
772
+ };
494
773
  const visibilityChannel = messageTransport.createRequestChannel(core.messageTransport);
495
774
  const removeVisibilityHandler = visibilityChannel.handle('setVisibility', visible => {
496
775
  native.setVisible(visible).catch(err => {
@@ -504,6 +783,15 @@ const setupEmbeddedWebView = ({
504
783
  });
505
784
  };
506
785
  core.messageTransport.on(handleHostMessage);
786
+ // Watch for the webview-side SDK ready signal so we can clear the
787
+ // recovery timer. The phase-timer factory also subscribes for its own
788
+ // bookkeeping — both subscriptions are independent and idempotent.
789
+ const handleSdkReady = message => {
790
+ if (message.origin !== 'webview') return;
791
+ if (message.type !== webviewMessages.sdkHasLoadedEventName) return;
792
+ clearRecoveryTimer();
793
+ };
794
+ core.messageTransport.on(handleSdkReady);
507
795
  const onMessageSub = native.addListener('onMessage', event => {
508
796
  let parsed = null;
509
797
  try {
@@ -526,7 +814,32 @@ const setupEmbeddedWebView = ({
526
814
  logger.warn('EmbeddedWebView.respondToShouldStartLoad failed', err);
527
815
  });
528
816
  });
817
+ const onLoadStartSub = native.addListener('onLoadStart', () => {
818
+ phaseTimers.recordEvent('load_start');
819
+ // Re-arm both timers on every load_start. A retry from the native
820
+ // side (e.g. a redirect, or a reload after a transient failure)
821
+ // resets the html_load window cleanly without leaving a stale timer
822
+ // around from the previous attempt.
823
+ clearLoadingTimer();
824
+ clearRecoveryTimer();
825
+ loadingTimer = setTimeout(() => {
826
+ raiseFailure('html_load');
827
+ }, _loadingTimeoutMs);
828
+ });
829
+ const onLoadSub = native.addListener('onLoad', () => {
830
+ phaseTimers.recordEvent('load');
831
+ clearLoadingTimer();
832
+ });
833
+ const onLoadEndSub = native.addListener('onLoadEnd', () => {
834
+ phaseTimers.recordEvent('load_end');
835
+ clearLoadingTimer();
836
+ clearRecoveryTimer();
837
+ recoveryTimer = setTimeout(() => {
838
+ raiseFailure('sdk_bootstrap');
839
+ }, _recoveryTimeoutMs);
840
+ });
529
841
  const onLoadErrorSub = native.addListener('onLoadError', event => {
842
+ phaseTimers.recordEvent('native_error');
530
843
  logger.warn('EmbeddedWebView load error', event);
531
844
  logger.instrument('Embedded webview load error', {
532
845
  environmentId: core.environmentId,
@@ -540,7 +853,13 @@ const setupEmbeddedWebView = ({
540
853
  time: 0,
541
854
  webviewUrl: builtUrl.toString()
542
855
  });
543
- core.initialization.error = new WebViewFailedToLoadError();
856
+ raiseFailure('embedded_native_error', {
857
+ nativeErrorCode: event.code,
858
+ nativeErrorDescription: event.description,
859
+ nativeErrorDomain: event.domain,
860
+ nativeErrorFailedUrl: event.url,
861
+ nativeErrorIsProvisional: event.isProvisional
862
+ });
544
863
  });
545
864
  native.setDebuggingEnabled(_webviewDebuggingEnabled).catch(err => {
546
865
  logger.warn('EmbeddedWebView.setDebuggingEnabled failed', err);
@@ -549,11 +868,18 @@ const setupEmbeddedWebView = ({
549
868
  logger.warn('EmbeddedWebView.setUrl failed', err);
550
869
  });
551
870
  return () => {
871
+ clearLoadingTimer();
872
+ clearRecoveryTimer();
552
873
  removeVisibilityHandler();
553
874
  core.messageTransport.off(handleHostMessage);
875
+ core.messageTransport.off(handleSdkReady);
554
876
  onMessageSub.remove();
555
877
  onShouldStartLoadSub.remove();
878
+ onLoadStartSub.remove();
879
+ onLoadSub.remove();
880
+ onLoadEndSub.remove();
556
881
  onLoadErrorSub.remove();
882
+ phaseTimers.dispose();
557
883
  };
558
884
  };
559
885