@dynamic-labs/react-native-extension 4.83.1 → 4.84.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/android/EmbeddedWebViewController.kt +26 -0
- package/android/EmbeddedWebViewModule.kt +8 -1
- package/android/dynamic/embeddedwebview/EmbeddedWebViewController.kt +26 -0
- package/android/dynamic/embeddedwebview/EmbeddedWebViewModule.kt +8 -1
- package/android/embeddedwebview/EmbeddedWebViewController.kt +26 -0
- package/android/embeddedwebview/EmbeddedWebViewModule.kt +8 -1
- package/android/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewController.kt +26 -0
- package/android/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewModule.kt +8 -1
- package/android/main/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewController.kt +26 -0
- package/android/main/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewModule.kt +8 -1
- package/android/src/main/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewController.kt +26 -0
- package/android/src/main/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewModule.kt +8 -1
- package/android/xyz/dynamic/embeddedwebview/EmbeddedWebViewController.kt +26 -0
- package/android/xyz/dynamic/embeddedwebview/EmbeddedWebViewModule.kt +8 -1
- package/index.cjs +601 -57
- package/index.js +602 -58
- package/ios/EmbeddedWebViewController.swift +22 -0
- package/ios/EmbeddedWebViewModule.swift +8 -1
- package/package.json +6 -6
- package/src/components/WebView/EmbeddedWebView/EmbeddedWebView.d.ts +20 -1
- package/src/components/WebView/EmbeddedWebView/embeddedWebViewPhaseTimers/embeddedWebViewPhaseTimers.d.ts +66 -0
- package/src/components/WebView/EmbeddedWebView/embeddedWebViewPhaseTimers/index.d.ts +1 -0
- package/src/components/WebView/successLog/emitSuccessLog.d.ts +35 -0
- package/src/components/WebView/successLog/index.d.ts +3 -0
- package/src/components/WebView/successLog/successLogCooldown.d.ts +39 -0
- package/src/components/WebView/useWebViewPhaseTimers/index.d.ts +1 -0
- package/src/components/WebView/useWebViewPhaseTimers/useWebViewPhaseTimers.d.ts +46 -0
- package/src/components/WebView/useWebViewSuccessLog/index.d.ts +1 -0
- package/src/components/WebView/useWebViewSuccessLog/useWebViewSuccessLog.d.ts +28 -0
- package/src/components/WebView/webViewPhaseTimerCore/index.d.ts +2 -0
- package/src/components/WebView/webViewPhaseTimerCore/webViewPhaseTimerCore.d.ts +123 -0
- package/src/nativeModules/EmbeddedWebView.d.ts +27 -2
package/index.cjs
CHANGED
|
@@ -8,12 +8,13 @@ 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');
|
|
12
|
+
var expoSecureStore = require('expo-secure-store');
|
|
11
13
|
var jsxRuntime = require('react/jsx-runtime');
|
|
12
14
|
var expoModulesCore = require('expo-modules-core');
|
|
13
15
|
var reactNativePasskey = require('react-native-passkey');
|
|
14
16
|
var expoLinking = require('expo-linking');
|
|
15
17
|
var expoWebBrowser = require('expo-web-browser');
|
|
16
|
-
var expoSecureStore = require('expo-secure-store');
|
|
17
18
|
var reactNativePasskeyStamper = require('@turnkey/react-native-passkey-stamper');
|
|
18
19
|
|
|
19
20
|
function _interopNamespace(e) {
|
|
@@ -34,7 +35,7 @@ function _interopNamespace(e) {
|
|
|
34
35
|
return Object.freeze(n);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
var version = "4.
|
|
38
|
+
var version = "4.84.0";
|
|
38
39
|
|
|
39
40
|
function _extends() {
|
|
40
41
|
return _extends = Object.assign ? Object.assign.bind() : function (n) {
|
|
@@ -122,6 +123,396 @@ 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
|
+
/**
|
|
133
|
+
* Wire-format `type` of a webview-side `manifest` request.
|
|
134
|
+
*
|
|
135
|
+
* `requestChannel.request('manifest')` sends a `MessageTransportData` whose
|
|
136
|
+
* `type` is the literal request name. The host replies with `manifest__ack`,
|
|
137
|
+
* which we ignore: receiving the request is sufficient evidence that the
|
|
138
|
+
* webview JS bundle is alive and the message bridge works.
|
|
139
|
+
*/
|
|
140
|
+
const MANIFEST_MESSAGE_TYPE = 'manifest';
|
|
141
|
+
const emptyTimings = () => ({
|
|
142
|
+
htmlLoadStartedAt: null,
|
|
143
|
+
htmlLoadedAt: null,
|
|
144
|
+
manifestReceivedAt: null,
|
|
145
|
+
onLoadEndAt: null,
|
|
146
|
+
sdkReadyAt: null
|
|
147
|
+
});
|
|
148
|
+
const diffMs = (later, earlier) => later !== null && earlier !== null ? later - earlier : null;
|
|
149
|
+
/**
|
|
150
|
+
* Derive cross-phase durations from raw timestamps. The four durations
|
|
151
|
+
* are what end up in the failure / success log meta:
|
|
152
|
+
*
|
|
153
|
+
* - `htmlLoadMs` — `onLoadStart` to `onLoad`; native HTML fetch
|
|
154
|
+
* - `onLoadToOnLoadEndMs` — `onLoad` to `onLoadEnd`; native render/parse
|
|
155
|
+
* - `manifestReceivedMs` — `onLoadEnd` to first `manifest` request from
|
|
156
|
+
* the webview JS; "JS bundle is alive" signal
|
|
157
|
+
* - `sdkReadyMs` — `onLoadEnd` to first `sdkHasLoaded` event from the
|
|
158
|
+
* webview JS; "SDK fully bootstrapped" signal
|
|
159
|
+
*/
|
|
160
|
+
const computePhaseDurations = timings => ({
|
|
161
|
+
htmlLoadMs: diffMs(timings.htmlLoadedAt, timings.htmlLoadStartedAt),
|
|
162
|
+
manifestReceivedMs: diffMs(timings.manifestReceivedAt, timings.onLoadEndAt),
|
|
163
|
+
onLoadToOnLoadEndMs: diffMs(timings.onLoadEndAt, timings.htmlLoadedAt),
|
|
164
|
+
sdkReadyMs: diffMs(timings.sdkReadyAt, timings.onLoadEndAt)
|
|
165
|
+
});
|
|
166
|
+
/**
|
|
167
|
+
* Build the success-shape meta from a {@link WebViewPhaseTimerCore}
|
|
168
|
+
* snapshot plus the path-specific fields. Both call sites call this
|
|
169
|
+
* with their own context (RN reads `hadClearState` / `retryCount` from
|
|
170
|
+
* the URL; embedded passes constants) so the success-log payload stays
|
|
171
|
+
* identical across the two paths.
|
|
172
|
+
*/
|
|
173
|
+
const buildSuccessMeta = ({
|
|
174
|
+
core,
|
|
175
|
+
hadClearState,
|
|
176
|
+
loadingTimeoutMs,
|
|
177
|
+
recoveryTimeoutMs,
|
|
178
|
+
retryCount,
|
|
179
|
+
webviewUrl
|
|
180
|
+
}) => Object.assign(Object.assign({
|
|
181
|
+
hadClearState,
|
|
182
|
+
loadingTimeoutMs,
|
|
183
|
+
recoveryTimeoutMs,
|
|
184
|
+
retryCount,
|
|
185
|
+
webviewUrl
|
|
186
|
+
}, computePhaseDurations(core.getTimings())), core.getCounters());
|
|
187
|
+
/**
|
|
188
|
+
* Shared state machine driving WebView load-phase instrumentation.
|
|
189
|
+
*
|
|
190
|
+
* Both the React `<WebView>` (RN bridge) and the native embedded WebView
|
|
191
|
+
* (native bridge) feed lifecycle events into this core and produce
|
|
192
|
+
* matching meta from the same timings + counters. Each path layers its
|
|
193
|
+
* own path-specific fields (`hadClearState`, `retryCount`, etc.) on top
|
|
194
|
+
* of the raw state this core exposes via `getTimings()` / `getCounters()`.
|
|
195
|
+
*
|
|
196
|
+
* The core owns the `core.messageTransport` subscription that captures
|
|
197
|
+
* the two webview-originated signals bracketing SDK bootstrap
|
|
198
|
+
* (`manifest` once the JS bundle is alive; `sdkHasLoadedEventName`
|
|
199
|
+
* once the SDK is fully ready). Native bridge lifecycle events come in
|
|
200
|
+
* via `recordEvent` instead.
|
|
201
|
+
*/
|
|
202
|
+
const createWebViewPhaseTimerCore = ({
|
|
203
|
+
core
|
|
204
|
+
}) => {
|
|
205
|
+
let timings = emptyTimings();
|
|
206
|
+
let nativeErrorCount = 0;
|
|
207
|
+
let osKillCount = 0;
|
|
208
|
+
/**
|
|
209
|
+
* We only capture the first occurrence per attempt so that a healthy
|
|
210
|
+
* reload doesn't overwrite the timing from the attempt that ultimately
|
|
211
|
+
* failed.
|
|
212
|
+
*/
|
|
213
|
+
const handleMessage = message => {
|
|
214
|
+
if (message.origin !== 'webview') return;
|
|
215
|
+
if (message.type === MANIFEST_MESSAGE_TYPE && timings.manifestReceivedAt === null) {
|
|
216
|
+
timings.manifestReceivedAt = Date.now();
|
|
217
|
+
}
|
|
218
|
+
if (message.type === webviewMessages.sdkHasLoadedEventName && timings.sdkReadyAt === null) {
|
|
219
|
+
timings.sdkReadyAt = Date.now();
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
core.messageTransport.on(handleMessage);
|
|
223
|
+
return {
|
|
224
|
+
dispose: () => {
|
|
225
|
+
core.messageTransport.off(handleMessage);
|
|
226
|
+
},
|
|
227
|
+
getCounters: () => ({
|
|
228
|
+
nativeErrorCount,
|
|
229
|
+
osKillCount
|
|
230
|
+
}),
|
|
231
|
+
getTimings: () => timings,
|
|
232
|
+
recordEvent: event => {
|
|
233
|
+
const now = Date.now();
|
|
234
|
+
switch (event) {
|
|
235
|
+
case 'load_start':
|
|
236
|
+
timings = emptyTimings();
|
|
237
|
+
timings.htmlLoadStartedAt = now;
|
|
238
|
+
break;
|
|
239
|
+
case 'load':
|
|
240
|
+
timings.htmlLoadedAt = now;
|
|
241
|
+
break;
|
|
242
|
+
case 'load_end':
|
|
243
|
+
timings.onLoadEndAt = now;
|
|
244
|
+
break;
|
|
245
|
+
case 'native_error':
|
|
246
|
+
nativeErrorCount += 1;
|
|
247
|
+
break;
|
|
248
|
+
case 'os_kill':
|
|
249
|
+
osKillCount += 1;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const RETRY_QUERY_PARAM = 'retry';
|
|
257
|
+
const readRetryCount = webViewUrl => {
|
|
258
|
+
const raw = webViewUrl.searchParams.get(RETRY_QUERY_PARAM);
|
|
259
|
+
if (raw === null) return 0;
|
|
260
|
+
const parsed = Number(raw);
|
|
261
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* Snapshot returned by the phase-timer core when it hasn't been
|
|
265
|
+
* instantiated yet (briefly true between hook construction and the
|
|
266
|
+
* `useEffect` running). Lets `getSuccessMeta` / `getMeta` produce a
|
|
267
|
+
* well-formed empty meta in that window without special-casing each
|
|
268
|
+
* field.
|
|
269
|
+
*/
|
|
270
|
+
const EMPTY_CORE_SNAPSHOT = {
|
|
271
|
+
getCounters: () => ({
|
|
272
|
+
nativeErrorCount: 0,
|
|
273
|
+
osKillCount: 0
|
|
274
|
+
}),
|
|
275
|
+
getTimings: () => ({
|
|
276
|
+
htmlLoadStartedAt: null,
|
|
277
|
+
htmlLoadedAt: null,
|
|
278
|
+
manifestReceivedAt: null,
|
|
279
|
+
onLoadEndAt: null,
|
|
280
|
+
sdkReadyAt: null
|
|
281
|
+
})
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Tracks how long each step of the WebView load took, so when we raise
|
|
285
|
+
* `WebViewFailedToLoadError` we can attach the timings to the error log.
|
|
286
|
+
*
|
|
287
|
+
* The timings cover the host-side phases of the load — the webview-side
|
|
288
|
+
* timings (`webview.time_to_load_manifest`, `webview.time_to_sdk_ready`)
|
|
289
|
+
* still come from the webview itself once it boots, but those don't help
|
|
290
|
+
* when the webview never boots in the first place.
|
|
291
|
+
*
|
|
292
|
+
* The actual state machine lives in {@link createWebViewPhaseTimerCore},
|
|
293
|
+
* which is also reused by the embedded native WebView path. This hook
|
|
294
|
+
* only layers the React-specific bits (ref-based core lifetime tied to
|
|
295
|
+
* `useEffect`, URL-derived `hadClearState` / `retryCount`).
|
|
296
|
+
*/
|
|
297
|
+
const useWebViewPhaseTimers = ({
|
|
298
|
+
core,
|
|
299
|
+
loadingTimeout,
|
|
300
|
+
recoveryTimeout,
|
|
301
|
+
webViewUrl
|
|
302
|
+
}) => {
|
|
303
|
+
const phaseCoreRef = react.useRef(null);
|
|
304
|
+
const webViewUrlRef = react.useRef(webViewUrl);
|
|
305
|
+
webViewUrlRef.current = webViewUrl;
|
|
306
|
+
/**
|
|
307
|
+
* Owning the message-transport subscription in `useEffect` matches the
|
|
308
|
+
* lifetime semantics any other React subscription would have, and lets
|
|
309
|
+
* us cleanly re-subscribe if `core` is ever swapped at runtime.
|
|
310
|
+
*/
|
|
311
|
+
react.useEffect(() => {
|
|
312
|
+
const phaseCore = createWebViewPhaseTimerCore({
|
|
313
|
+
core
|
|
314
|
+
});
|
|
315
|
+
phaseCoreRef.current = phaseCore;
|
|
316
|
+
return () => {
|
|
317
|
+
phaseCore.dispose();
|
|
318
|
+
if (phaseCoreRef.current === phaseCore) {
|
|
319
|
+
phaseCoreRef.current = null;
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}, [core]);
|
|
323
|
+
const recordEvent = react.useCallback(event => {
|
|
324
|
+
var _a;
|
|
325
|
+
(_a = phaseCoreRef.current) === null || _a === void 0 ? void 0 : _a.recordEvent(event);
|
|
326
|
+
}, []);
|
|
327
|
+
const getSuccessMeta = react.useCallback(() => {
|
|
328
|
+
var _a;
|
|
329
|
+
const url = webViewUrlRef.current;
|
|
330
|
+
return buildSuccessMeta({
|
|
331
|
+
core: (_a = phaseCoreRef.current) !== null && _a !== void 0 ? _a : EMPTY_CORE_SNAPSHOT,
|
|
332
|
+
hadClearState: hasClearStateInUrl(url),
|
|
333
|
+
loadingTimeoutMs: loadingTimeout,
|
|
334
|
+
recoveryTimeoutMs: recoveryTimeout,
|
|
335
|
+
retryCount: readRetryCount(url),
|
|
336
|
+
webviewUrl: url.toString()
|
|
337
|
+
});
|
|
338
|
+
}, [loadingTimeout, recoveryTimeout]);
|
|
339
|
+
const getMeta = react.useCallback(({
|
|
340
|
+
phase
|
|
341
|
+
}) => Object.assign(Object.assign({}, getSuccessMeta()), {
|
|
342
|
+
phase
|
|
343
|
+
}), [getSuccessMeta]);
|
|
344
|
+
return {
|
|
345
|
+
getMeta,
|
|
346
|
+
getSuccessMeta,
|
|
347
|
+
recordEvent
|
|
348
|
+
};
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
/******************************************************************************
|
|
352
|
+
Copyright (c) Microsoft Corporation.
|
|
353
|
+
|
|
354
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
355
|
+
purpose with or without fee is hereby granted.
|
|
356
|
+
|
|
357
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
358
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
359
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
360
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
361
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
362
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
363
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
364
|
+
***************************************************************************** */
|
|
365
|
+
|
|
366
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
367
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
368
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
369
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
370
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
371
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
372
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
377
|
+
var e = new Error(message);
|
|
378
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const SUCCESS_LOG_KEY = 'webview.load_succeeded';
|
|
382
|
+
/**
|
|
383
|
+
* Shared emission pattern for the `webview.load_succeeded`
|
|
384
|
+
* instrumentation log. Both the RN `<WebView>` and the embedded
|
|
385
|
+
* WebView fire the same event with the same payload shape after
|
|
386
|
+
* checking the same cooldown gate — this helper centralises that
|
|
387
|
+
* sequence so the two call sites stay aligned and any future field we
|
|
388
|
+
* add to the success log lands in both places automatically.
|
|
389
|
+
*/
|
|
390
|
+
const emitSuccessLog = _a => __awaiter(void 0, [_a], void 0, function* ({
|
|
391
|
+
cooldown,
|
|
392
|
+
core,
|
|
393
|
+
getMeta,
|
|
394
|
+
name
|
|
395
|
+
}) {
|
|
396
|
+
if (!(yield cooldown.shouldEmit())) return;
|
|
397
|
+
logger.instrument(`${name} loaded successfully`, Object.assign({
|
|
398
|
+
environmentId: core.environmentId,
|
|
399
|
+
hostSdkSessionId: core.hostSdkSessionId,
|
|
400
|
+
key: SUCCESS_LOG_KEY,
|
|
401
|
+
time: 0
|
|
402
|
+
}, getMeta()));
|
|
403
|
+
yield cooldown.recordEmitted();
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
const SUCCESS_LOG_COOLDOWN_MS = 60 * 60 * 1000;
|
|
407
|
+
const parseStoredTimestamp = raw => {
|
|
408
|
+
if (raw === null) return null;
|
|
409
|
+
const parsed = Number(raw);
|
|
410
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
411
|
+
};
|
|
412
|
+
/**
|
|
413
|
+
* Create a device-local cooldown gate for a `webview.load_succeeded`
|
|
414
|
+
* instrumentation log.
|
|
415
|
+
*
|
|
416
|
+
* Both `<WebView>` (RN bridge) and `<EmbeddedWebView>` (native bridge)
|
|
417
|
+
* emit the same success log; each instantiates its own cooldown with a
|
|
418
|
+
* dedicated storage key so emitting one log doesn't starve the other
|
|
419
|
+
* for an app that mounts both.
|
|
420
|
+
*
|
|
421
|
+
* - `shouldEmit` returns `true` when the cooldown has elapsed (or no
|
|
422
|
+
* timestamp has ever been stored) and `false` while inside the
|
|
423
|
+
* window. If secure-store throws on read we default to emitting —
|
|
424
|
+
* losing a diagnostic log is worse than the rare duplicate.
|
|
425
|
+
* - `recordEmitted` persists the current timestamp so subsequent calls
|
|
426
|
+
* within the window are suppressed. Write failures are swallowed
|
|
427
|
+
* (logged as a warning) so they don't break the host wrapper —
|
|
428
|
+
* worst case the next emit also goes through.
|
|
429
|
+
*/
|
|
430
|
+
const createSuccessLogCooldown = ({
|
|
431
|
+
name,
|
|
432
|
+
storageKey
|
|
433
|
+
}) => ({
|
|
434
|
+
recordEmitted: (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (now = Date.now()) {
|
|
435
|
+
try {
|
|
436
|
+
yield expoSecureStore.setItemAsync(storageKey, now.toString());
|
|
437
|
+
} catch (error) {
|
|
438
|
+
logger.warn(`Failed to persist ${name} success-log cooldown timestamp`, {
|
|
439
|
+
error: String(error)
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}),
|
|
443
|
+
shouldEmit: (...args_2) => __awaiter(void 0, [...args_2], void 0, function* (now = Date.now()) {
|
|
444
|
+
try {
|
|
445
|
+
const raw = yield expoSecureStore.getItemAsync(storageKey);
|
|
446
|
+
const lastEmittedAt = parseStoredTimestamp(raw);
|
|
447
|
+
if (lastEmittedAt === null) return true;
|
|
448
|
+
return now - lastEmittedAt >= SUCCESS_LOG_COOLDOWN_MS;
|
|
449
|
+
} catch (error) {
|
|
450
|
+
logger.warn(`Failed to read ${name} success-log cooldown timestamp; emitting anyway`, {
|
|
451
|
+
error: String(error)
|
|
452
|
+
});
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
})
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const SUCCESS_LOG_COOLDOWN_STORAGE_KEY = 'dynamic.webview.successLogLastEmittedAt';
|
|
459
|
+
/**
|
|
460
|
+
* Emit a `webview.load_succeeded` instrumentation log the first time
|
|
461
|
+
* the SDK signals it's ready, gated by a device-local 1h cooldown so a
|
|
462
|
+
* customer with many WebView mounts (or many app launches) doesn't spam
|
|
463
|
+
* the logs pipeline.
|
|
464
|
+
*
|
|
465
|
+
* The error path is unaffected: failures keep going through
|
|
466
|
+
* `logger.error(WebViewFailedToLoadError, meta)` with no cooldown.
|
|
467
|
+
*
|
|
468
|
+
* The hook intentionally fires only once per mount (`hasEmittedRef`):
|
|
469
|
+
* after a successful boot we don't expect another `sdkHasLoaded` for
|
|
470
|
+
* the same WebView lifetime, and emitting twice for the same load would
|
|
471
|
+
* be misleading.
|
|
472
|
+
*
|
|
473
|
+
* Cooldown gating + log emission are delegated to the shared
|
|
474
|
+
* `successLog` helpers so this hook and the embedded native WebView
|
|
475
|
+
* path stay in lockstep on payload shape and storage semantics; the
|
|
476
|
+
* dedicated storage key here keeps the RN-path cooldown independent
|
|
477
|
+
* from the embedded path's.
|
|
478
|
+
*/
|
|
479
|
+
const useWebViewSuccessLog = ({
|
|
480
|
+
core,
|
|
481
|
+
getSuccessMeta
|
|
482
|
+
}) => {
|
|
483
|
+
const hasEmittedRef = react.useRef(false);
|
|
484
|
+
const getSuccessMetaRef = react.useRef(getSuccessMeta);
|
|
485
|
+
getSuccessMetaRef.current = getSuccessMeta;
|
|
486
|
+
react.useEffect(() => {
|
|
487
|
+
const cooldown = createSuccessLogCooldown({
|
|
488
|
+
name: 'webview',
|
|
489
|
+
storageKey: SUCCESS_LOG_COOLDOWN_STORAGE_KEY
|
|
490
|
+
});
|
|
491
|
+
const handler = message => __awaiter(void 0, void 0, void 0, function* () {
|
|
492
|
+
if (message.origin !== 'webview') return;
|
|
493
|
+
if (message.type !== webviewMessages.sdkHasLoadedEventName) return;
|
|
494
|
+
if (hasEmittedRef.current) return;
|
|
495
|
+
hasEmittedRef.current = true;
|
|
496
|
+
try {
|
|
497
|
+
yield emitSuccessLog({
|
|
498
|
+
cooldown,
|
|
499
|
+
core,
|
|
500
|
+
getMeta: getSuccessMetaRef.current,
|
|
501
|
+
name: 'Webview'
|
|
502
|
+
});
|
|
503
|
+
} catch (err) {
|
|
504
|
+
logger.warn('useWebViewSuccessLog.emitSuccessLog failed', {
|
|
505
|
+
error: String(err)
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
core.messageTransport.on(handler);
|
|
510
|
+
return () => {
|
|
511
|
+
core.messageTransport.off(handler);
|
|
512
|
+
};
|
|
513
|
+
}, [core]);
|
|
514
|
+
};
|
|
515
|
+
|
|
125
516
|
const useWebViewVisibility = core => {
|
|
126
517
|
const webViewVisibilityRequestChannelRef = react.useRef(messageTransport.createRequestChannel(core.messageTransport));
|
|
127
518
|
const [visible, setVisible] = react.useState(false);
|
|
@@ -158,10 +549,6 @@ const styles = reactNative.StyleSheet.create({
|
|
|
158
549
|
}
|
|
159
550
|
});
|
|
160
551
|
|
|
161
|
-
const CLEAR_STATE_PARAM = 'clear-state';
|
|
162
|
-
const ENVIRONMENT_ID_PARAM = 'environmentId';
|
|
163
|
-
const WEBVIEW_START_TIME_PARAM = 'webviewStartTime';
|
|
164
|
-
|
|
165
552
|
const setClearStateToUrl = webViewUrl => {
|
|
166
553
|
const newWebViewUrl = new URL(webViewUrl);
|
|
167
554
|
newWebViewUrl.searchParams.set(CLEAR_STATE_PARAM, 'true');
|
|
@@ -169,8 +556,6 @@ const setClearStateToUrl = webViewUrl => {
|
|
|
169
556
|
return newWebViewUrl;
|
|
170
557
|
};
|
|
171
558
|
|
|
172
|
-
const hasClearStateInUrl = webViewUrl => Boolean(webViewUrl.searchParams.get(CLEAR_STATE_PARAM));
|
|
173
|
-
|
|
174
559
|
const useWebViewRecoveryTimeout = ({
|
|
175
560
|
core,
|
|
176
561
|
webViewUrl,
|
|
@@ -323,15 +708,15 @@ const shouldAllowNavigation = (request, webViewUrl) => {
|
|
|
323
708
|
return false;
|
|
324
709
|
};
|
|
325
710
|
|
|
326
|
-
const DEFAULT_RECOVERY_TIMEOUT_MS = 20000;
|
|
327
|
-
const DEFAULT_LOADING_TIMEOUT_MS = 20000;
|
|
711
|
+
const DEFAULT_RECOVERY_TIMEOUT_MS$1 = 20000;
|
|
712
|
+
const DEFAULT_LOADING_TIMEOUT_MS$1 = 20000;
|
|
328
713
|
const WebView = ({
|
|
329
714
|
webviewUrl: initialWebViewUrl,
|
|
330
715
|
core,
|
|
331
716
|
webviewDebuggingEnabled: _webviewDebuggingEnabled = false,
|
|
332
717
|
disableRecovery: _disableRecovery = false,
|
|
333
|
-
recoveryTimeout: _recoveryTimeout = DEFAULT_RECOVERY_TIMEOUT_MS,
|
|
334
|
-
loadingTimeout: _loadingTimeout = DEFAULT_LOADING_TIMEOUT_MS
|
|
718
|
+
recoveryTimeout: _recoveryTimeout = DEFAULT_RECOVERY_TIMEOUT_MS$1,
|
|
719
|
+
loadingTimeout: _loadingTimeout = DEFAULT_LOADING_TIMEOUT_MS$1
|
|
335
720
|
}) => {
|
|
336
721
|
const webViewRef = react.useRef(null);
|
|
337
722
|
const {
|
|
@@ -341,6 +726,20 @@ const WebView = ({
|
|
|
341
726
|
const {
|
|
342
727
|
onMessageHandler
|
|
343
728
|
} = useMessageTransportWebViewBridge(core, webViewRef);
|
|
729
|
+
const {
|
|
730
|
+
recordEvent,
|
|
731
|
+
getMeta,
|
|
732
|
+
getSuccessMeta
|
|
733
|
+
} = useWebViewPhaseTimers({
|
|
734
|
+
core,
|
|
735
|
+
loadingTimeout: _loadingTimeout,
|
|
736
|
+
recoveryTimeout: _recoveryTimeout,
|
|
737
|
+
webViewUrl
|
|
738
|
+
});
|
|
739
|
+
useWebViewSuccessLog({
|
|
740
|
+
core,
|
|
741
|
+
getSuccessMeta
|
|
742
|
+
});
|
|
344
743
|
const containerStyles = [styles['container'], visible ? styles.show : styles.hide];
|
|
345
744
|
/**
|
|
346
745
|
* Block the message transport when the webview is unmounted
|
|
@@ -382,13 +781,23 @@ const WebView = ({
|
|
|
382
781
|
return newWebViewUrl;
|
|
383
782
|
});
|
|
384
783
|
}, [core]);
|
|
385
|
-
const blockAndReloadWebViewOsKill = react.useCallback(() =>
|
|
784
|
+
const blockAndReloadWebViewOsKill = react.useCallback(() => {
|
|
785
|
+
recordEvent('os_kill');
|
|
786
|
+
blockAndReloadWebView('os_kill');
|
|
787
|
+
}, [blockAndReloadWebView, recordEvent]);
|
|
386
788
|
react.useEffect(() => core.messageTransport.recoveryManager.onRecoveryRequested(() => blockAndReloadWebView('recovery')), [core, blockAndReloadWebView]);
|
|
387
|
-
const setWebViewLoadError = react.useCallback(
|
|
789
|
+
const setWebViewLoadError = react.useCallback(phase => {
|
|
388
790
|
// Error was already thrown, do not throw again
|
|
389
791
|
if (core.initialization.error instanceof WebViewFailedToLoadError) return;
|
|
390
|
-
core.initialization.error = new WebViewFailedToLoadError(
|
|
391
|
-
|
|
792
|
+
core.initialization.error = new WebViewFailedToLoadError(getMeta({
|
|
793
|
+
phase
|
|
794
|
+
}));
|
|
795
|
+
}, [core, getMeta]);
|
|
796
|
+
/**
|
|
797
|
+
* Wraps `setWebViewLoadError` so the consumer doesn't need to know
|
|
798
|
+
* about the phase enum; each callsite below binds its own phase.
|
|
799
|
+
*/
|
|
800
|
+
const setWebViewLoadErrorWithPhase = react.useCallback(phase => () => setWebViewLoadError(phase), [setWebViewLoadError]);
|
|
392
801
|
/**
|
|
393
802
|
* Reload the webview with a clean state when a timeout is reached
|
|
394
803
|
* and the webview did not get to the loaded state yet
|
|
@@ -396,7 +805,7 @@ const WebView = ({
|
|
|
396
805
|
const startRecoveryTimeout = useWebViewRecoveryTimeout({
|
|
397
806
|
core,
|
|
398
807
|
disableRecovery: _disableRecovery,
|
|
399
|
-
onFailedToLoadAfterClearState:
|
|
808
|
+
onFailedToLoadAfterClearState: setWebViewLoadErrorWithPhase('after_clear_state'),
|
|
400
809
|
recoveryTimeout: _recoveryTimeout,
|
|
401
810
|
setWebViewUrl,
|
|
402
811
|
webViewUrl
|
|
@@ -404,26 +813,40 @@ const WebView = ({
|
|
|
404
813
|
const webViewLoadErrorCountRef = react.useRef(0);
|
|
405
814
|
const onWebViewLoadError = react.useCallback(() => {
|
|
406
815
|
webViewLoadErrorCountRef.current = webViewLoadErrorCountRef.current + 1;
|
|
816
|
+
recordEvent('native_error');
|
|
407
817
|
/**
|
|
408
818
|
* This is the first attempt to load the webview, do not throw an error
|
|
409
819
|
* because the recovery system will attempt to reload the webview
|
|
410
820
|
*/
|
|
411
821
|
if (webViewLoadErrorCountRef.current === 1) return;
|
|
412
|
-
setWebViewLoadError();
|
|
413
|
-
}, [setWebViewLoadError]);
|
|
822
|
+
setWebViewLoadError('native_error');
|
|
823
|
+
}, [recordEvent, setWebViewLoadError]);
|
|
414
824
|
const {
|
|
415
|
-
onLoad,
|
|
416
|
-
onLoadStart
|
|
417
|
-
} = useWebViewLoadingTimeout(_loadingTimeout,
|
|
825
|
+
onLoad: onLoadFromTimeout,
|
|
826
|
+
onLoadStart: onLoadStartFromTimeout
|
|
827
|
+
} = useWebViewLoadingTimeout(_loadingTimeout, setWebViewLoadErrorWithPhase('html_load'));
|
|
828
|
+
const onLoadStart = react.useCallback(() => {
|
|
829
|
+
recordEvent('load_start');
|
|
830
|
+
onLoadStartFromTimeout();
|
|
831
|
+
}, [onLoadStartFromTimeout, recordEvent]);
|
|
832
|
+
const onLoad = react.useCallback(() => {
|
|
833
|
+
recordEvent('load');
|
|
834
|
+
onLoadFromTimeout();
|
|
835
|
+
}, [onLoadFromTimeout, recordEvent]);
|
|
836
|
+
const onLoadEnd = react.useCallback(() => {
|
|
837
|
+
recordEvent('load_end');
|
|
838
|
+
startRecoveryTimeout();
|
|
839
|
+
}, [recordEvent, startRecoveryTimeout]);
|
|
840
|
+
const webViewUrlString = react.useMemo(() => webViewUrl.toString(), [webViewUrl]);
|
|
418
841
|
return /*#__PURE__*/jsxRuntime.jsx(reactNativeWebview.WebView, {
|
|
419
842
|
ref: webViewRef,
|
|
420
843
|
source: {
|
|
421
|
-
uri:
|
|
844
|
+
uri: webViewUrlString
|
|
422
845
|
},
|
|
423
846
|
containerStyle: containerStyles,
|
|
424
847
|
style: styles['webview'],
|
|
425
848
|
onMessage: onMessageHandler,
|
|
426
|
-
onLoadEnd:
|
|
849
|
+
onLoadEnd: onLoadEnd,
|
|
427
850
|
onLoadStart: onLoadStart,
|
|
428
851
|
onLoad: onLoad,
|
|
429
852
|
hideKeyboardAccessoryView: true,
|
|
@@ -436,7 +859,7 @@ const WebView = ({
|
|
|
436
859
|
isTopFrame: request.isTopFrame,
|
|
437
860
|
url: request.url
|
|
438
861
|
}, webViewUrl)
|
|
439
|
-
});
|
|
862
|
+
}, webViewUrlString);
|
|
440
863
|
};
|
|
441
864
|
const createWebView = internalProps => {
|
|
442
865
|
const WebViewWrapper = props => /*#__PURE__*/jsxRuntime.jsx(WebView, _extends({}, internalProps, props));
|
|
@@ -475,6 +898,53 @@ const getEmbeddedWebView = () => {
|
|
|
475
898
|
}
|
|
476
899
|
};
|
|
477
900
|
|
|
901
|
+
/**
|
|
902
|
+
* Non-React equivalent of `useWebViewPhaseTimers` for the embedded native
|
|
903
|
+
* webview path. The embedded path is a singleton wired by
|
|
904
|
+
* `setupEmbeddedWebView` (not a React component), so we expose the same
|
|
905
|
+
* phase-timing surface as a plain factory.
|
|
906
|
+
*
|
|
907
|
+
* Both this factory and the hook delegate the actual state machine to
|
|
908
|
+
* {@link createWebViewPhaseTimerCore}; the only path-specific bits are:
|
|
909
|
+
*
|
|
910
|
+
* - `hadClearState` is always `false` (embedded path has no clear-state
|
|
911
|
+
* recovery model — that's exclusive to the React `<WebView>` path's
|
|
912
|
+
* loading-timeout recovery flow)
|
|
913
|
+
* - `retryCount` is always `0` (embedded path has no retry counter URL
|
|
914
|
+
* query param either)
|
|
915
|
+
*/
|
|
916
|
+
const createEmbeddedWebViewPhaseTimers = ({
|
|
917
|
+
core,
|
|
918
|
+
loadingTimeoutMs,
|
|
919
|
+
recoveryTimeoutMs,
|
|
920
|
+
webViewUrl
|
|
921
|
+
}) => {
|
|
922
|
+
const phaseCore = createWebViewPhaseTimerCore({
|
|
923
|
+
core
|
|
924
|
+
});
|
|
925
|
+
const getSuccessMeta = () => buildSuccessMeta({
|
|
926
|
+
core: phaseCore,
|
|
927
|
+
hadClearState: false,
|
|
928
|
+
loadingTimeoutMs,
|
|
929
|
+
recoveryTimeoutMs,
|
|
930
|
+
retryCount: 0,
|
|
931
|
+
webviewUrl: webViewUrl.toString()
|
|
932
|
+
});
|
|
933
|
+
return {
|
|
934
|
+
dispose: phaseCore.dispose,
|
|
935
|
+
getMeta: ({
|
|
936
|
+
phase
|
|
937
|
+
}) => Object.assign(Object.assign({}, getSuccessMeta()), {
|
|
938
|
+
phase
|
|
939
|
+
}),
|
|
940
|
+
getSuccessMeta,
|
|
941
|
+
recordEvent: phaseCore.recordEvent
|
|
942
|
+
};
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
const EMBEDDED_SUCCESS_LOG_COOLDOWN_STORAGE_KEY = 'dynamic.embeddedWebView.successLogLastEmittedAt';
|
|
946
|
+
const DEFAULT_LOADING_TIMEOUT_MS = 20000;
|
|
947
|
+
const DEFAULT_RECOVERY_TIMEOUT_MS = 20000;
|
|
478
948
|
// Wires the JS-side bridge to the native WKWebView singleton owned by Swift.
|
|
479
949
|
// This is intentionally not a React component: the embedded webview lives
|
|
480
950
|
// outside the React tree (in a dedicated UIWindow on iOS), so its setup runs
|
|
@@ -487,10 +957,52 @@ const getEmbeddedWebView = () => {
|
|
|
487
957
|
const setupEmbeddedWebView = ({
|
|
488
958
|
webviewUrl,
|
|
489
959
|
core,
|
|
490
|
-
webviewDebuggingEnabled: _webviewDebuggingEnabled = false
|
|
960
|
+
webviewDebuggingEnabled: _webviewDebuggingEnabled = false,
|
|
961
|
+
loadingTimeoutMs: _loadingTimeoutMs = DEFAULT_LOADING_TIMEOUT_MS,
|
|
962
|
+
recoveryTimeoutMs: _recoveryTimeoutMs = DEFAULT_RECOVERY_TIMEOUT_MS
|
|
491
963
|
}) => {
|
|
492
964
|
const native = getEmbeddedWebView();
|
|
493
965
|
const builtUrl = assignStartTimeToUrl(assignEnvironmentIdToUrl(webviewUrl, core.environmentId));
|
|
966
|
+
const phaseTimers = createEmbeddedWebViewPhaseTimers({
|
|
967
|
+
core,
|
|
968
|
+
loadingTimeoutMs: _loadingTimeoutMs,
|
|
969
|
+
recoveryTimeoutMs: _recoveryTimeoutMs,
|
|
970
|
+
webViewUrl: builtUrl
|
|
971
|
+
});
|
|
972
|
+
const successLogCooldown = createSuccessLogCooldown({
|
|
973
|
+
name: 'embedded webview',
|
|
974
|
+
storageKey: EMBEDDED_SUCCESS_LOG_COOLDOWN_STORAGE_KEY
|
|
975
|
+
});
|
|
976
|
+
let loadingTimer = null;
|
|
977
|
+
let recoveryTimer = null;
|
|
978
|
+
let hasFailed = false;
|
|
979
|
+
let hasEmittedSuccessLog = false;
|
|
980
|
+
const clearLoadingTimer = () => {
|
|
981
|
+
if (loadingTimer !== null) {
|
|
982
|
+
clearTimeout(loadingTimer);
|
|
983
|
+
loadingTimer = null;
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
const clearRecoveryTimer = () => {
|
|
987
|
+
if (recoveryTimer !== null) {
|
|
988
|
+
clearTimeout(recoveryTimer);
|
|
989
|
+
recoveryTimer = null;
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
// Raise `WebViewFailedToLoadError` once per setup. Subsequent triggers
|
|
993
|
+
// (e.g. recovery timeout firing after html_load already failed) are
|
|
994
|
+
// suppressed so we don't spam the initialization-error setter with
|
|
995
|
+
// duplicate logs for the same load attempt.
|
|
996
|
+
const raiseFailure = (phase, extraMeta = {}) => {
|
|
997
|
+
if (hasFailed) return;
|
|
998
|
+
hasFailed = true;
|
|
999
|
+
clearLoadingTimer();
|
|
1000
|
+
clearRecoveryTimer();
|
|
1001
|
+
const meta = phaseTimers.getMeta({
|
|
1002
|
+
phase
|
|
1003
|
+
});
|
|
1004
|
+
core.initialization.error = new WebViewFailedToLoadError(Object.assign(Object.assign({}, meta), extraMeta));
|
|
1005
|
+
};
|
|
494
1006
|
const visibilityChannel = messageTransport.createRequestChannel(core.messageTransport);
|
|
495
1007
|
const removeVisibilityHandler = visibilityChannel.handle('setVisibility', visible => {
|
|
496
1008
|
native.setVisible(visible).catch(err => {
|
|
@@ -504,6 +1016,30 @@ const setupEmbeddedWebView = ({
|
|
|
504
1016
|
});
|
|
505
1017
|
};
|
|
506
1018
|
core.messageTransport.on(handleHostMessage);
|
|
1019
|
+
// Watch for the webview-side SDK ready signal so we can clear the
|
|
1020
|
+
// recovery timer and emit the success log. The phase-timer factory
|
|
1021
|
+
// also subscribes for its own bookkeeping — both subscriptions are
|
|
1022
|
+
// independent and idempotent. The factory subscribes first and so
|
|
1023
|
+
// sees `sdkReadyAt` before this handler runs, which means the meta
|
|
1024
|
+
// we emit below has the freshest `sdkReadyMs`. We only emit once
|
|
1025
|
+
// per setup; a second SDK-ready event for the same load wouldn't
|
|
1026
|
+
// represent a new successful boot.
|
|
1027
|
+
const handleSdkReady = message => {
|
|
1028
|
+
if (message.origin !== 'webview') return;
|
|
1029
|
+
if (message.type !== webviewMessages.sdkHasLoadedEventName) return;
|
|
1030
|
+
clearRecoveryTimer();
|
|
1031
|
+
if (hasEmittedSuccessLog || hasFailed) return;
|
|
1032
|
+
hasEmittedSuccessLog = true;
|
|
1033
|
+
emitSuccessLog({
|
|
1034
|
+
cooldown: successLogCooldown,
|
|
1035
|
+
core,
|
|
1036
|
+
getMeta: phaseTimers.getSuccessMeta,
|
|
1037
|
+
name: 'Embedded webview'
|
|
1038
|
+
}).catch(err => {
|
|
1039
|
+
logger.warn('EmbeddedWebView.emitSuccessLog failed', err);
|
|
1040
|
+
});
|
|
1041
|
+
};
|
|
1042
|
+
core.messageTransport.on(handleSdkReady);
|
|
507
1043
|
const onMessageSub = native.addListener('onMessage', event => {
|
|
508
1044
|
let parsed = null;
|
|
509
1045
|
try {
|
|
@@ -526,7 +1062,32 @@ const setupEmbeddedWebView = ({
|
|
|
526
1062
|
logger.warn('EmbeddedWebView.respondToShouldStartLoad failed', err);
|
|
527
1063
|
});
|
|
528
1064
|
});
|
|
1065
|
+
const onLoadStartSub = native.addListener('onLoadStart', () => {
|
|
1066
|
+
phaseTimers.recordEvent('load_start');
|
|
1067
|
+
// Re-arm both timers on every load_start. A retry from the native
|
|
1068
|
+
// side (e.g. a redirect, or a reload after a transient failure)
|
|
1069
|
+
// resets the html_load window cleanly without leaving a stale timer
|
|
1070
|
+
// around from the previous attempt.
|
|
1071
|
+
clearLoadingTimer();
|
|
1072
|
+
clearRecoveryTimer();
|
|
1073
|
+
loadingTimer = setTimeout(() => {
|
|
1074
|
+
raiseFailure('html_load');
|
|
1075
|
+
}, _loadingTimeoutMs);
|
|
1076
|
+
});
|
|
1077
|
+
const onLoadSub = native.addListener('onLoad', () => {
|
|
1078
|
+
phaseTimers.recordEvent('load');
|
|
1079
|
+
clearLoadingTimer();
|
|
1080
|
+
});
|
|
1081
|
+
const onLoadEndSub = native.addListener('onLoadEnd', () => {
|
|
1082
|
+
phaseTimers.recordEvent('load_end');
|
|
1083
|
+
clearLoadingTimer();
|
|
1084
|
+
clearRecoveryTimer();
|
|
1085
|
+
recoveryTimer = setTimeout(() => {
|
|
1086
|
+
raiseFailure('sdk_bootstrap');
|
|
1087
|
+
}, _recoveryTimeoutMs);
|
|
1088
|
+
});
|
|
529
1089
|
const onLoadErrorSub = native.addListener('onLoadError', event => {
|
|
1090
|
+
phaseTimers.recordEvent('native_error');
|
|
530
1091
|
logger.warn('EmbeddedWebView load error', event);
|
|
531
1092
|
logger.instrument('Embedded webview load error', {
|
|
532
1093
|
environmentId: core.environmentId,
|
|
@@ -540,7 +1101,13 @@ const setupEmbeddedWebView = ({
|
|
|
540
1101
|
time: 0,
|
|
541
1102
|
webviewUrl: builtUrl.toString()
|
|
542
1103
|
});
|
|
543
|
-
|
|
1104
|
+
raiseFailure('embedded_native_error', {
|
|
1105
|
+
nativeErrorCode: event.code,
|
|
1106
|
+
nativeErrorDescription: event.description,
|
|
1107
|
+
nativeErrorDomain: event.domain,
|
|
1108
|
+
nativeErrorFailedUrl: event.url,
|
|
1109
|
+
nativeErrorIsProvisional: event.isProvisional
|
|
1110
|
+
});
|
|
544
1111
|
});
|
|
545
1112
|
native.setDebuggingEnabled(_webviewDebuggingEnabled).catch(err => {
|
|
546
1113
|
logger.warn('EmbeddedWebView.setDebuggingEnabled failed', err);
|
|
@@ -549,44 +1116,21 @@ const setupEmbeddedWebView = ({
|
|
|
549
1116
|
logger.warn('EmbeddedWebView.setUrl failed', err);
|
|
550
1117
|
});
|
|
551
1118
|
return () => {
|
|
1119
|
+
clearLoadingTimer();
|
|
1120
|
+
clearRecoveryTimer();
|
|
552
1121
|
removeVisibilityHandler();
|
|
553
1122
|
core.messageTransport.off(handleHostMessage);
|
|
1123
|
+
core.messageTransport.off(handleSdkReady);
|
|
554
1124
|
onMessageSub.remove();
|
|
555
1125
|
onShouldStartLoadSub.remove();
|
|
1126
|
+
onLoadStartSub.remove();
|
|
1127
|
+
onLoadSub.remove();
|
|
1128
|
+
onLoadEndSub.remove();
|
|
556
1129
|
onLoadErrorSub.remove();
|
|
1130
|
+
phaseTimers.dispose();
|
|
557
1131
|
};
|
|
558
1132
|
};
|
|
559
1133
|
|
|
560
|
-
/******************************************************************************
|
|
561
|
-
Copyright (c) Microsoft Corporation.
|
|
562
|
-
|
|
563
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
564
|
-
purpose with or without fee is hereby granted.
|
|
565
|
-
|
|
566
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
567
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
568
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
569
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
570
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
571
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
572
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
573
|
-
***************************************************************************** */
|
|
574
|
-
|
|
575
|
-
function __awaiter(thisArg, _arguments, P, generator) {
|
|
576
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
577
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
578
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
579
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
580
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
581
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
586
|
-
var e = new Error(message);
|
|
587
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
588
|
-
};
|
|
589
|
-
|
|
590
1134
|
const setupPasskeyHandler = core => {
|
|
591
1135
|
const passkeyRequestChannel = messageTransport.createRequestChannel(core.messageTransport);
|
|
592
1136
|
passkeyRequestChannel.handle('passkey_register', _a => __awaiter(void 0, [_a], void 0, function* ({
|