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