@dynamic-labs/react-native-extension 4.91.3 → 4.91.5
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 +40 -0
- package/android/dynamic/embeddedwebview/EmbeddedWebViewController.kt +40 -0
- package/android/embeddedwebview/EmbeddedWebViewController.kt +40 -0
- package/android/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewController.kt +40 -0
- package/android/main/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewController.kt +40 -0
- package/android/src/main/java/xyz/dynamic/embeddedwebview/EmbeddedWebViewController.kt +40 -0
- package/android/xyz/dynamic/embeddedwebview/EmbeddedWebViewController.kt +40 -0
- package/index.cjs +1 -1
- package/index.js +1 -1
- package/ios/EmbeddedWebViewController.swift +40 -0
- package/package.json +6 -6
|
@@ -66,6 +66,12 @@ object EmbeddedWebViewController {
|
|
|
66
66
|
// prompting JS again — avoiding a potential approval loop.
|
|
67
67
|
private var bypassUrl: String? = null
|
|
68
68
|
|
|
69
|
+
// Origin pre-approved by `setUrl`. When `shouldOverrideUrlLoading` sees a
|
|
70
|
+
// top-frame navigation whose origin matches, it allows it immediately — no
|
|
71
|
+
// JS round-trip required. Cleared after first use so subsequent navigations
|
|
72
|
+
// still go through the JS allowlist.
|
|
73
|
+
private var preApprovedOrigin: String? = null
|
|
74
|
+
|
|
69
75
|
private var emitterToken: java.util.UUID? = null
|
|
70
76
|
|
|
71
77
|
// Module-side hook: assign the emitter under a token. The token lets
|
|
@@ -108,6 +114,11 @@ object EmbeddedWebViewController {
|
|
|
108
114
|
return@runOnMain
|
|
109
115
|
}
|
|
110
116
|
val webView = ensureWebView() ?: return@runOnMain
|
|
117
|
+
// Pre-approve the origin so the first `shouldOverrideUrlLoading` call
|
|
118
|
+
// can skip the JS round-trip. On cold boot the JS thread may be too
|
|
119
|
+
// congested to respond within the navigation-decision timeout, silently
|
|
120
|
+
// cancelling the load.
|
|
121
|
+
preApprovedOrigin = originString(android.net.Uri.parse(url))
|
|
111
122
|
webView.loadUrl(url)
|
|
112
123
|
}
|
|
113
124
|
}
|
|
@@ -280,6 +291,7 @@ object EmbeddedWebViewController {
|
|
|
280
291
|
pendingTimeouts.values.forEach { mainHandler.removeCallbacks(it) }
|
|
281
292
|
pendingTimeouts.clear()
|
|
282
293
|
bypassUrl = null
|
|
294
|
+
preApprovedOrigin = null
|
|
283
295
|
}
|
|
284
296
|
|
|
285
297
|
private fun runOnMain(action: () -> Unit) {
|
|
@@ -332,6 +344,7 @@ object EmbeddedWebViewController {
|
|
|
332
344
|
val isAllowedTopFrameScheme =
|
|
333
345
|
scheme == "https" || (httpAllowed && scheme == "http")
|
|
334
346
|
if (!isAllowedTopFrameScheme) {
|
|
347
|
+
preApprovedOrigin = null
|
|
335
348
|
emitLoadError(
|
|
336
349
|
url = url,
|
|
337
350
|
code = -1,
|
|
@@ -342,6 +355,17 @@ object EmbeddedWebViewController {
|
|
|
342
355
|
return true
|
|
343
356
|
}
|
|
344
357
|
|
|
358
|
+
// Fast-path: if the URL's origin matches the pre-approved origin (set
|
|
359
|
+
// by `setUrl`), allow immediately. This eliminates the cold-boot race
|
|
360
|
+
// where the JS thread is too congested to respond to
|
|
361
|
+
// `onShouldStartLoad` within the navigation-decision timeout.
|
|
362
|
+
val approved = preApprovedOrigin
|
|
363
|
+
if (approved != null && approved == originString(request.url)) {
|
|
364
|
+
preApprovedOrigin = null
|
|
365
|
+
bypassUrl = url
|
|
366
|
+
return false
|
|
367
|
+
}
|
|
368
|
+
|
|
345
369
|
val id = UUID.randomUUID().toString()
|
|
346
370
|
pendingNavigationUrls[id] = url
|
|
347
371
|
|
|
@@ -486,6 +510,22 @@ object EmbeddedWebViewController {
|
|
|
486
510
|
}
|
|
487
511
|
}
|
|
488
512
|
|
|
513
|
+
// Extract the scheme + host + port origin from a URI (e.g.
|
|
514
|
+
// "https://webview.dynamicauth.com"). Returns null for URIs without a host.
|
|
515
|
+
// Default ports (443 for HTTPS, 80 for HTTP) are omitted so that
|
|
516
|
+
// "https://example.com" and "https://example.com:443" produce the same
|
|
517
|
+
// origin string.
|
|
518
|
+
private fun originString(uri: android.net.Uri?): String? {
|
|
519
|
+
val scheme = uri?.scheme ?: return null
|
|
520
|
+
val host = uri.host ?: return null
|
|
521
|
+
val port = uri.port
|
|
522
|
+
|
|
523
|
+
val shouldIncludePort = port != -1 &&
|
|
524
|
+
!((scheme == "https" && port == 443) || (scheme == "http" && port == 80))
|
|
525
|
+
|
|
526
|
+
return if (shouldIncludePort) "$scheme://$host:$port" else "$scheme://$host"
|
|
527
|
+
}
|
|
528
|
+
|
|
489
529
|
// Refuse target=_blank / window.open popups (matches react-native-webview's
|
|
490
530
|
// `setSupportMultipleWindows={false}` semantics: there's nowhere reasonable
|
|
491
531
|
// to open the new window inside the overlay).
|
|
@@ -66,6 +66,12 @@ object EmbeddedWebViewController {
|
|
|
66
66
|
// prompting JS again — avoiding a potential approval loop.
|
|
67
67
|
private var bypassUrl: String? = null
|
|
68
68
|
|
|
69
|
+
// Origin pre-approved by `setUrl`. When `shouldOverrideUrlLoading` sees a
|
|
70
|
+
// top-frame navigation whose origin matches, it allows it immediately — no
|
|
71
|
+
// JS round-trip required. Cleared after first use so subsequent navigations
|
|
72
|
+
// still go through the JS allowlist.
|
|
73
|
+
private var preApprovedOrigin: String? = null
|
|
74
|
+
|
|
69
75
|
private var emitterToken: java.util.UUID? = null
|
|
70
76
|
|
|
71
77
|
// Module-side hook: assign the emitter under a token. The token lets
|
|
@@ -108,6 +114,11 @@ object EmbeddedWebViewController {
|
|
|
108
114
|
return@runOnMain
|
|
109
115
|
}
|
|
110
116
|
val webView = ensureWebView() ?: return@runOnMain
|
|
117
|
+
// Pre-approve the origin so the first `shouldOverrideUrlLoading` call
|
|
118
|
+
// can skip the JS round-trip. On cold boot the JS thread may be too
|
|
119
|
+
// congested to respond within the navigation-decision timeout, silently
|
|
120
|
+
// cancelling the load.
|
|
121
|
+
preApprovedOrigin = originString(android.net.Uri.parse(url))
|
|
111
122
|
webView.loadUrl(url)
|
|
112
123
|
}
|
|
113
124
|
}
|
|
@@ -280,6 +291,7 @@ object EmbeddedWebViewController {
|
|
|
280
291
|
pendingTimeouts.values.forEach { mainHandler.removeCallbacks(it) }
|
|
281
292
|
pendingTimeouts.clear()
|
|
282
293
|
bypassUrl = null
|
|
294
|
+
preApprovedOrigin = null
|
|
283
295
|
}
|
|
284
296
|
|
|
285
297
|
private fun runOnMain(action: () -> Unit) {
|
|
@@ -332,6 +344,7 @@ object EmbeddedWebViewController {
|
|
|
332
344
|
val isAllowedTopFrameScheme =
|
|
333
345
|
scheme == "https" || (httpAllowed && scheme == "http")
|
|
334
346
|
if (!isAllowedTopFrameScheme) {
|
|
347
|
+
preApprovedOrigin = null
|
|
335
348
|
emitLoadError(
|
|
336
349
|
url = url,
|
|
337
350
|
code = -1,
|
|
@@ -342,6 +355,17 @@ object EmbeddedWebViewController {
|
|
|
342
355
|
return true
|
|
343
356
|
}
|
|
344
357
|
|
|
358
|
+
// Fast-path: if the URL's origin matches the pre-approved origin (set
|
|
359
|
+
// by `setUrl`), allow immediately. This eliminates the cold-boot race
|
|
360
|
+
// where the JS thread is too congested to respond to
|
|
361
|
+
// `onShouldStartLoad` within the navigation-decision timeout.
|
|
362
|
+
val approved = preApprovedOrigin
|
|
363
|
+
if (approved != null && approved == originString(request.url)) {
|
|
364
|
+
preApprovedOrigin = null
|
|
365
|
+
bypassUrl = url
|
|
366
|
+
return false
|
|
367
|
+
}
|
|
368
|
+
|
|
345
369
|
val id = UUID.randomUUID().toString()
|
|
346
370
|
pendingNavigationUrls[id] = url
|
|
347
371
|
|
|
@@ -486,6 +510,22 @@ object EmbeddedWebViewController {
|
|
|
486
510
|
}
|
|
487
511
|
}
|
|
488
512
|
|
|
513
|
+
// Extract the scheme + host + port origin from a URI (e.g.
|
|
514
|
+
// "https://webview.dynamicauth.com"). Returns null for URIs without a host.
|
|
515
|
+
// Default ports (443 for HTTPS, 80 for HTTP) are omitted so that
|
|
516
|
+
// "https://example.com" and "https://example.com:443" produce the same
|
|
517
|
+
// origin string.
|
|
518
|
+
private fun originString(uri: android.net.Uri?): String? {
|
|
519
|
+
val scheme = uri?.scheme ?: return null
|
|
520
|
+
val host = uri.host ?: return null
|
|
521
|
+
val port = uri.port
|
|
522
|
+
|
|
523
|
+
val shouldIncludePort = port != -1 &&
|
|
524
|
+
!((scheme == "https" && port == 443) || (scheme == "http" && port == 80))
|
|
525
|
+
|
|
526
|
+
return if (shouldIncludePort) "$scheme://$host:$port" else "$scheme://$host"
|
|
527
|
+
}
|
|
528
|
+
|
|
489
529
|
// Refuse target=_blank / window.open popups (matches react-native-webview's
|
|
490
530
|
// `setSupportMultipleWindows={false}` semantics: there's nowhere reasonable
|
|
491
531
|
// to open the new window inside the overlay).
|
|
@@ -66,6 +66,12 @@ object EmbeddedWebViewController {
|
|
|
66
66
|
// prompting JS again — avoiding a potential approval loop.
|
|
67
67
|
private var bypassUrl: String? = null
|
|
68
68
|
|
|
69
|
+
// Origin pre-approved by `setUrl`. When `shouldOverrideUrlLoading` sees a
|
|
70
|
+
// top-frame navigation whose origin matches, it allows it immediately — no
|
|
71
|
+
// JS round-trip required. Cleared after first use so subsequent navigations
|
|
72
|
+
// still go through the JS allowlist.
|
|
73
|
+
private var preApprovedOrigin: String? = null
|
|
74
|
+
|
|
69
75
|
private var emitterToken: java.util.UUID? = null
|
|
70
76
|
|
|
71
77
|
// Module-side hook: assign the emitter under a token. The token lets
|
|
@@ -108,6 +114,11 @@ object EmbeddedWebViewController {
|
|
|
108
114
|
return@runOnMain
|
|
109
115
|
}
|
|
110
116
|
val webView = ensureWebView() ?: return@runOnMain
|
|
117
|
+
// Pre-approve the origin so the first `shouldOverrideUrlLoading` call
|
|
118
|
+
// can skip the JS round-trip. On cold boot the JS thread may be too
|
|
119
|
+
// congested to respond within the navigation-decision timeout, silently
|
|
120
|
+
// cancelling the load.
|
|
121
|
+
preApprovedOrigin = originString(android.net.Uri.parse(url))
|
|
111
122
|
webView.loadUrl(url)
|
|
112
123
|
}
|
|
113
124
|
}
|
|
@@ -280,6 +291,7 @@ object EmbeddedWebViewController {
|
|
|
280
291
|
pendingTimeouts.values.forEach { mainHandler.removeCallbacks(it) }
|
|
281
292
|
pendingTimeouts.clear()
|
|
282
293
|
bypassUrl = null
|
|
294
|
+
preApprovedOrigin = null
|
|
283
295
|
}
|
|
284
296
|
|
|
285
297
|
private fun runOnMain(action: () -> Unit) {
|
|
@@ -332,6 +344,7 @@ object EmbeddedWebViewController {
|
|
|
332
344
|
val isAllowedTopFrameScheme =
|
|
333
345
|
scheme == "https" || (httpAllowed && scheme == "http")
|
|
334
346
|
if (!isAllowedTopFrameScheme) {
|
|
347
|
+
preApprovedOrigin = null
|
|
335
348
|
emitLoadError(
|
|
336
349
|
url = url,
|
|
337
350
|
code = -1,
|
|
@@ -342,6 +355,17 @@ object EmbeddedWebViewController {
|
|
|
342
355
|
return true
|
|
343
356
|
}
|
|
344
357
|
|
|
358
|
+
// Fast-path: if the URL's origin matches the pre-approved origin (set
|
|
359
|
+
// by `setUrl`), allow immediately. This eliminates the cold-boot race
|
|
360
|
+
// where the JS thread is too congested to respond to
|
|
361
|
+
// `onShouldStartLoad` within the navigation-decision timeout.
|
|
362
|
+
val approved = preApprovedOrigin
|
|
363
|
+
if (approved != null && approved == originString(request.url)) {
|
|
364
|
+
preApprovedOrigin = null
|
|
365
|
+
bypassUrl = url
|
|
366
|
+
return false
|
|
367
|
+
}
|
|
368
|
+
|
|
345
369
|
val id = UUID.randomUUID().toString()
|
|
346
370
|
pendingNavigationUrls[id] = url
|
|
347
371
|
|
|
@@ -486,6 +510,22 @@ object EmbeddedWebViewController {
|
|
|
486
510
|
}
|
|
487
511
|
}
|
|
488
512
|
|
|
513
|
+
// Extract the scheme + host + port origin from a URI (e.g.
|
|
514
|
+
// "https://webview.dynamicauth.com"). Returns null for URIs without a host.
|
|
515
|
+
// Default ports (443 for HTTPS, 80 for HTTP) are omitted so that
|
|
516
|
+
// "https://example.com" and "https://example.com:443" produce the same
|
|
517
|
+
// origin string.
|
|
518
|
+
private fun originString(uri: android.net.Uri?): String? {
|
|
519
|
+
val scheme = uri?.scheme ?: return null
|
|
520
|
+
val host = uri.host ?: return null
|
|
521
|
+
val port = uri.port
|
|
522
|
+
|
|
523
|
+
val shouldIncludePort = port != -1 &&
|
|
524
|
+
!((scheme == "https" && port == 443) || (scheme == "http" && port == 80))
|
|
525
|
+
|
|
526
|
+
return if (shouldIncludePort) "$scheme://$host:$port" else "$scheme://$host"
|
|
527
|
+
}
|
|
528
|
+
|
|
489
529
|
// Refuse target=_blank / window.open popups (matches react-native-webview's
|
|
490
530
|
// `setSupportMultipleWindows={false}` semantics: there's nowhere reasonable
|
|
491
531
|
// to open the new window inside the overlay).
|
|
@@ -66,6 +66,12 @@ object EmbeddedWebViewController {
|
|
|
66
66
|
// prompting JS again — avoiding a potential approval loop.
|
|
67
67
|
private var bypassUrl: String? = null
|
|
68
68
|
|
|
69
|
+
// Origin pre-approved by `setUrl`. When `shouldOverrideUrlLoading` sees a
|
|
70
|
+
// top-frame navigation whose origin matches, it allows it immediately — no
|
|
71
|
+
// JS round-trip required. Cleared after first use so subsequent navigations
|
|
72
|
+
// still go through the JS allowlist.
|
|
73
|
+
private var preApprovedOrigin: String? = null
|
|
74
|
+
|
|
69
75
|
private var emitterToken: java.util.UUID? = null
|
|
70
76
|
|
|
71
77
|
// Module-side hook: assign the emitter under a token. The token lets
|
|
@@ -108,6 +114,11 @@ object EmbeddedWebViewController {
|
|
|
108
114
|
return@runOnMain
|
|
109
115
|
}
|
|
110
116
|
val webView = ensureWebView() ?: return@runOnMain
|
|
117
|
+
// Pre-approve the origin so the first `shouldOverrideUrlLoading` call
|
|
118
|
+
// can skip the JS round-trip. On cold boot the JS thread may be too
|
|
119
|
+
// congested to respond within the navigation-decision timeout, silently
|
|
120
|
+
// cancelling the load.
|
|
121
|
+
preApprovedOrigin = originString(android.net.Uri.parse(url))
|
|
111
122
|
webView.loadUrl(url)
|
|
112
123
|
}
|
|
113
124
|
}
|
|
@@ -280,6 +291,7 @@ object EmbeddedWebViewController {
|
|
|
280
291
|
pendingTimeouts.values.forEach { mainHandler.removeCallbacks(it) }
|
|
281
292
|
pendingTimeouts.clear()
|
|
282
293
|
bypassUrl = null
|
|
294
|
+
preApprovedOrigin = null
|
|
283
295
|
}
|
|
284
296
|
|
|
285
297
|
private fun runOnMain(action: () -> Unit) {
|
|
@@ -332,6 +344,7 @@ object EmbeddedWebViewController {
|
|
|
332
344
|
val isAllowedTopFrameScheme =
|
|
333
345
|
scheme == "https" || (httpAllowed && scheme == "http")
|
|
334
346
|
if (!isAllowedTopFrameScheme) {
|
|
347
|
+
preApprovedOrigin = null
|
|
335
348
|
emitLoadError(
|
|
336
349
|
url = url,
|
|
337
350
|
code = -1,
|
|
@@ -342,6 +355,17 @@ object EmbeddedWebViewController {
|
|
|
342
355
|
return true
|
|
343
356
|
}
|
|
344
357
|
|
|
358
|
+
// Fast-path: if the URL's origin matches the pre-approved origin (set
|
|
359
|
+
// by `setUrl`), allow immediately. This eliminates the cold-boot race
|
|
360
|
+
// where the JS thread is too congested to respond to
|
|
361
|
+
// `onShouldStartLoad` within the navigation-decision timeout.
|
|
362
|
+
val approved = preApprovedOrigin
|
|
363
|
+
if (approved != null && approved == originString(request.url)) {
|
|
364
|
+
preApprovedOrigin = null
|
|
365
|
+
bypassUrl = url
|
|
366
|
+
return false
|
|
367
|
+
}
|
|
368
|
+
|
|
345
369
|
val id = UUID.randomUUID().toString()
|
|
346
370
|
pendingNavigationUrls[id] = url
|
|
347
371
|
|
|
@@ -486,6 +510,22 @@ object EmbeddedWebViewController {
|
|
|
486
510
|
}
|
|
487
511
|
}
|
|
488
512
|
|
|
513
|
+
// Extract the scheme + host + port origin from a URI (e.g.
|
|
514
|
+
// "https://webview.dynamicauth.com"). Returns null for URIs without a host.
|
|
515
|
+
// Default ports (443 for HTTPS, 80 for HTTP) are omitted so that
|
|
516
|
+
// "https://example.com" and "https://example.com:443" produce the same
|
|
517
|
+
// origin string.
|
|
518
|
+
private fun originString(uri: android.net.Uri?): String? {
|
|
519
|
+
val scheme = uri?.scheme ?: return null
|
|
520
|
+
val host = uri.host ?: return null
|
|
521
|
+
val port = uri.port
|
|
522
|
+
|
|
523
|
+
val shouldIncludePort = port != -1 &&
|
|
524
|
+
!((scheme == "https" && port == 443) || (scheme == "http" && port == 80))
|
|
525
|
+
|
|
526
|
+
return if (shouldIncludePort) "$scheme://$host:$port" else "$scheme://$host"
|
|
527
|
+
}
|
|
528
|
+
|
|
489
529
|
// Refuse target=_blank / window.open popups (matches react-native-webview's
|
|
490
530
|
// `setSupportMultipleWindows={false}` semantics: there's nowhere reasonable
|
|
491
531
|
// to open the new window inside the overlay).
|
|
@@ -66,6 +66,12 @@ object EmbeddedWebViewController {
|
|
|
66
66
|
// prompting JS again — avoiding a potential approval loop.
|
|
67
67
|
private var bypassUrl: String? = null
|
|
68
68
|
|
|
69
|
+
// Origin pre-approved by `setUrl`. When `shouldOverrideUrlLoading` sees a
|
|
70
|
+
// top-frame navigation whose origin matches, it allows it immediately — no
|
|
71
|
+
// JS round-trip required. Cleared after first use so subsequent navigations
|
|
72
|
+
// still go through the JS allowlist.
|
|
73
|
+
private var preApprovedOrigin: String? = null
|
|
74
|
+
|
|
69
75
|
private var emitterToken: java.util.UUID? = null
|
|
70
76
|
|
|
71
77
|
// Module-side hook: assign the emitter under a token. The token lets
|
|
@@ -108,6 +114,11 @@ object EmbeddedWebViewController {
|
|
|
108
114
|
return@runOnMain
|
|
109
115
|
}
|
|
110
116
|
val webView = ensureWebView() ?: return@runOnMain
|
|
117
|
+
// Pre-approve the origin so the first `shouldOverrideUrlLoading` call
|
|
118
|
+
// can skip the JS round-trip. On cold boot the JS thread may be too
|
|
119
|
+
// congested to respond within the navigation-decision timeout, silently
|
|
120
|
+
// cancelling the load.
|
|
121
|
+
preApprovedOrigin = originString(android.net.Uri.parse(url))
|
|
111
122
|
webView.loadUrl(url)
|
|
112
123
|
}
|
|
113
124
|
}
|
|
@@ -280,6 +291,7 @@ object EmbeddedWebViewController {
|
|
|
280
291
|
pendingTimeouts.values.forEach { mainHandler.removeCallbacks(it) }
|
|
281
292
|
pendingTimeouts.clear()
|
|
282
293
|
bypassUrl = null
|
|
294
|
+
preApprovedOrigin = null
|
|
283
295
|
}
|
|
284
296
|
|
|
285
297
|
private fun runOnMain(action: () -> Unit) {
|
|
@@ -332,6 +344,7 @@ object EmbeddedWebViewController {
|
|
|
332
344
|
val isAllowedTopFrameScheme =
|
|
333
345
|
scheme == "https" || (httpAllowed && scheme == "http")
|
|
334
346
|
if (!isAllowedTopFrameScheme) {
|
|
347
|
+
preApprovedOrigin = null
|
|
335
348
|
emitLoadError(
|
|
336
349
|
url = url,
|
|
337
350
|
code = -1,
|
|
@@ -342,6 +355,17 @@ object EmbeddedWebViewController {
|
|
|
342
355
|
return true
|
|
343
356
|
}
|
|
344
357
|
|
|
358
|
+
// Fast-path: if the URL's origin matches the pre-approved origin (set
|
|
359
|
+
// by `setUrl`), allow immediately. This eliminates the cold-boot race
|
|
360
|
+
// where the JS thread is too congested to respond to
|
|
361
|
+
// `onShouldStartLoad` within the navigation-decision timeout.
|
|
362
|
+
val approved = preApprovedOrigin
|
|
363
|
+
if (approved != null && approved == originString(request.url)) {
|
|
364
|
+
preApprovedOrigin = null
|
|
365
|
+
bypassUrl = url
|
|
366
|
+
return false
|
|
367
|
+
}
|
|
368
|
+
|
|
345
369
|
val id = UUID.randomUUID().toString()
|
|
346
370
|
pendingNavigationUrls[id] = url
|
|
347
371
|
|
|
@@ -486,6 +510,22 @@ object EmbeddedWebViewController {
|
|
|
486
510
|
}
|
|
487
511
|
}
|
|
488
512
|
|
|
513
|
+
// Extract the scheme + host + port origin from a URI (e.g.
|
|
514
|
+
// "https://webview.dynamicauth.com"). Returns null for URIs without a host.
|
|
515
|
+
// Default ports (443 for HTTPS, 80 for HTTP) are omitted so that
|
|
516
|
+
// "https://example.com" and "https://example.com:443" produce the same
|
|
517
|
+
// origin string.
|
|
518
|
+
private fun originString(uri: android.net.Uri?): String? {
|
|
519
|
+
val scheme = uri?.scheme ?: return null
|
|
520
|
+
val host = uri.host ?: return null
|
|
521
|
+
val port = uri.port
|
|
522
|
+
|
|
523
|
+
val shouldIncludePort = port != -1 &&
|
|
524
|
+
!((scheme == "https" && port == 443) || (scheme == "http" && port == 80))
|
|
525
|
+
|
|
526
|
+
return if (shouldIncludePort) "$scheme://$host:$port" else "$scheme://$host"
|
|
527
|
+
}
|
|
528
|
+
|
|
489
529
|
// Refuse target=_blank / window.open popups (matches react-native-webview's
|
|
490
530
|
// `setSupportMultipleWindows={false}` semantics: there's nowhere reasonable
|
|
491
531
|
// to open the new window inside the overlay).
|
|
@@ -66,6 +66,12 @@ object EmbeddedWebViewController {
|
|
|
66
66
|
// prompting JS again — avoiding a potential approval loop.
|
|
67
67
|
private var bypassUrl: String? = null
|
|
68
68
|
|
|
69
|
+
// Origin pre-approved by `setUrl`. When `shouldOverrideUrlLoading` sees a
|
|
70
|
+
// top-frame navigation whose origin matches, it allows it immediately — no
|
|
71
|
+
// JS round-trip required. Cleared after first use so subsequent navigations
|
|
72
|
+
// still go through the JS allowlist.
|
|
73
|
+
private var preApprovedOrigin: String? = null
|
|
74
|
+
|
|
69
75
|
private var emitterToken: java.util.UUID? = null
|
|
70
76
|
|
|
71
77
|
// Module-side hook: assign the emitter under a token. The token lets
|
|
@@ -108,6 +114,11 @@ object EmbeddedWebViewController {
|
|
|
108
114
|
return@runOnMain
|
|
109
115
|
}
|
|
110
116
|
val webView = ensureWebView() ?: return@runOnMain
|
|
117
|
+
// Pre-approve the origin so the first `shouldOverrideUrlLoading` call
|
|
118
|
+
// can skip the JS round-trip. On cold boot the JS thread may be too
|
|
119
|
+
// congested to respond within the navigation-decision timeout, silently
|
|
120
|
+
// cancelling the load.
|
|
121
|
+
preApprovedOrigin = originString(android.net.Uri.parse(url))
|
|
111
122
|
webView.loadUrl(url)
|
|
112
123
|
}
|
|
113
124
|
}
|
|
@@ -280,6 +291,7 @@ object EmbeddedWebViewController {
|
|
|
280
291
|
pendingTimeouts.values.forEach { mainHandler.removeCallbacks(it) }
|
|
281
292
|
pendingTimeouts.clear()
|
|
282
293
|
bypassUrl = null
|
|
294
|
+
preApprovedOrigin = null
|
|
283
295
|
}
|
|
284
296
|
|
|
285
297
|
private fun runOnMain(action: () -> Unit) {
|
|
@@ -332,6 +344,7 @@ object EmbeddedWebViewController {
|
|
|
332
344
|
val isAllowedTopFrameScheme =
|
|
333
345
|
scheme == "https" || (httpAllowed && scheme == "http")
|
|
334
346
|
if (!isAllowedTopFrameScheme) {
|
|
347
|
+
preApprovedOrigin = null
|
|
335
348
|
emitLoadError(
|
|
336
349
|
url = url,
|
|
337
350
|
code = -1,
|
|
@@ -342,6 +355,17 @@ object EmbeddedWebViewController {
|
|
|
342
355
|
return true
|
|
343
356
|
}
|
|
344
357
|
|
|
358
|
+
// Fast-path: if the URL's origin matches the pre-approved origin (set
|
|
359
|
+
// by `setUrl`), allow immediately. This eliminates the cold-boot race
|
|
360
|
+
// where the JS thread is too congested to respond to
|
|
361
|
+
// `onShouldStartLoad` within the navigation-decision timeout.
|
|
362
|
+
val approved = preApprovedOrigin
|
|
363
|
+
if (approved != null && approved == originString(request.url)) {
|
|
364
|
+
preApprovedOrigin = null
|
|
365
|
+
bypassUrl = url
|
|
366
|
+
return false
|
|
367
|
+
}
|
|
368
|
+
|
|
345
369
|
val id = UUID.randomUUID().toString()
|
|
346
370
|
pendingNavigationUrls[id] = url
|
|
347
371
|
|
|
@@ -486,6 +510,22 @@ object EmbeddedWebViewController {
|
|
|
486
510
|
}
|
|
487
511
|
}
|
|
488
512
|
|
|
513
|
+
// Extract the scheme + host + port origin from a URI (e.g.
|
|
514
|
+
// "https://webview.dynamicauth.com"). Returns null for URIs without a host.
|
|
515
|
+
// Default ports (443 for HTTPS, 80 for HTTP) are omitted so that
|
|
516
|
+
// "https://example.com" and "https://example.com:443" produce the same
|
|
517
|
+
// origin string.
|
|
518
|
+
private fun originString(uri: android.net.Uri?): String? {
|
|
519
|
+
val scheme = uri?.scheme ?: return null
|
|
520
|
+
val host = uri.host ?: return null
|
|
521
|
+
val port = uri.port
|
|
522
|
+
|
|
523
|
+
val shouldIncludePort = port != -1 &&
|
|
524
|
+
!((scheme == "https" && port == 443) || (scheme == "http" && port == 80))
|
|
525
|
+
|
|
526
|
+
return if (shouldIncludePort) "$scheme://$host:$port" else "$scheme://$host"
|
|
527
|
+
}
|
|
528
|
+
|
|
489
529
|
// Refuse target=_blank / window.open popups (matches react-native-webview's
|
|
490
530
|
// `setSupportMultipleWindows={false}` semantics: there's nowhere reasonable
|
|
491
531
|
// to open the new window inside the overlay).
|
|
@@ -66,6 +66,12 @@ object EmbeddedWebViewController {
|
|
|
66
66
|
// prompting JS again — avoiding a potential approval loop.
|
|
67
67
|
private var bypassUrl: String? = null
|
|
68
68
|
|
|
69
|
+
// Origin pre-approved by `setUrl`. When `shouldOverrideUrlLoading` sees a
|
|
70
|
+
// top-frame navigation whose origin matches, it allows it immediately — no
|
|
71
|
+
// JS round-trip required. Cleared after first use so subsequent navigations
|
|
72
|
+
// still go through the JS allowlist.
|
|
73
|
+
private var preApprovedOrigin: String? = null
|
|
74
|
+
|
|
69
75
|
private var emitterToken: java.util.UUID? = null
|
|
70
76
|
|
|
71
77
|
// Module-side hook: assign the emitter under a token. The token lets
|
|
@@ -108,6 +114,11 @@ object EmbeddedWebViewController {
|
|
|
108
114
|
return@runOnMain
|
|
109
115
|
}
|
|
110
116
|
val webView = ensureWebView() ?: return@runOnMain
|
|
117
|
+
// Pre-approve the origin so the first `shouldOverrideUrlLoading` call
|
|
118
|
+
// can skip the JS round-trip. On cold boot the JS thread may be too
|
|
119
|
+
// congested to respond within the navigation-decision timeout, silently
|
|
120
|
+
// cancelling the load.
|
|
121
|
+
preApprovedOrigin = originString(android.net.Uri.parse(url))
|
|
111
122
|
webView.loadUrl(url)
|
|
112
123
|
}
|
|
113
124
|
}
|
|
@@ -280,6 +291,7 @@ object EmbeddedWebViewController {
|
|
|
280
291
|
pendingTimeouts.values.forEach { mainHandler.removeCallbacks(it) }
|
|
281
292
|
pendingTimeouts.clear()
|
|
282
293
|
bypassUrl = null
|
|
294
|
+
preApprovedOrigin = null
|
|
283
295
|
}
|
|
284
296
|
|
|
285
297
|
private fun runOnMain(action: () -> Unit) {
|
|
@@ -332,6 +344,7 @@ object EmbeddedWebViewController {
|
|
|
332
344
|
val isAllowedTopFrameScheme =
|
|
333
345
|
scheme == "https" || (httpAllowed && scheme == "http")
|
|
334
346
|
if (!isAllowedTopFrameScheme) {
|
|
347
|
+
preApprovedOrigin = null
|
|
335
348
|
emitLoadError(
|
|
336
349
|
url = url,
|
|
337
350
|
code = -1,
|
|
@@ -342,6 +355,17 @@ object EmbeddedWebViewController {
|
|
|
342
355
|
return true
|
|
343
356
|
}
|
|
344
357
|
|
|
358
|
+
// Fast-path: if the URL's origin matches the pre-approved origin (set
|
|
359
|
+
// by `setUrl`), allow immediately. This eliminates the cold-boot race
|
|
360
|
+
// where the JS thread is too congested to respond to
|
|
361
|
+
// `onShouldStartLoad` within the navigation-decision timeout.
|
|
362
|
+
val approved = preApprovedOrigin
|
|
363
|
+
if (approved != null && approved == originString(request.url)) {
|
|
364
|
+
preApprovedOrigin = null
|
|
365
|
+
bypassUrl = url
|
|
366
|
+
return false
|
|
367
|
+
}
|
|
368
|
+
|
|
345
369
|
val id = UUID.randomUUID().toString()
|
|
346
370
|
pendingNavigationUrls[id] = url
|
|
347
371
|
|
|
@@ -486,6 +510,22 @@ object EmbeddedWebViewController {
|
|
|
486
510
|
}
|
|
487
511
|
}
|
|
488
512
|
|
|
513
|
+
// Extract the scheme + host + port origin from a URI (e.g.
|
|
514
|
+
// "https://webview.dynamicauth.com"). Returns null for URIs without a host.
|
|
515
|
+
// Default ports (443 for HTTPS, 80 for HTTP) are omitted so that
|
|
516
|
+
// "https://example.com" and "https://example.com:443" produce the same
|
|
517
|
+
// origin string.
|
|
518
|
+
private fun originString(uri: android.net.Uri?): String? {
|
|
519
|
+
val scheme = uri?.scheme ?: return null
|
|
520
|
+
val host = uri.host ?: return null
|
|
521
|
+
val port = uri.port
|
|
522
|
+
|
|
523
|
+
val shouldIncludePort = port != -1 &&
|
|
524
|
+
!((scheme == "https" && port == 443) || (scheme == "http" && port == 80))
|
|
525
|
+
|
|
526
|
+
return if (shouldIncludePort) "$scheme://$host:$port" else "$scheme://$host"
|
|
527
|
+
}
|
|
528
|
+
|
|
489
529
|
// Refuse target=_blank / window.open popups (matches react-native-webview's
|
|
490
530
|
// `setSupportMultipleWindows={false}` semantics: there's nowhere reasonable
|
|
491
531
|
// to open the new window inside the overlay).
|
package/index.cjs
CHANGED
package/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { createURL, getInitialURL, addEventListener, openURL } from 'expo-linkin
|
|
|
13
13
|
import { openAuthSessionAsync } from 'expo-web-browser';
|
|
14
14
|
import { createPasskey, PasskeyStamper } from '@turnkey/react-native-passkey-stamper';
|
|
15
15
|
|
|
16
|
-
var version = "4.91.
|
|
16
|
+
var version = "4.91.5";
|
|
17
17
|
|
|
18
18
|
function _extends() {
|
|
19
19
|
return _extends = Object.assign ? Object.assign.bind() : function (n) {
|
|
@@ -28,6 +28,11 @@ public final class EmbeddedWebViewController: NSObject {
|
|
|
28
28
|
private var pendingNavigationDecisions: [String: (WKNavigationActionPolicy) -> Void] = [:]
|
|
29
29
|
private var navigationTimers: [String: Timer] = [:]
|
|
30
30
|
private var emitterToken: UUID?
|
|
31
|
+
// Origin pre-approved by `setUrl`. When `decidePolicyFor` sees a top-frame
|
|
32
|
+
// navigation whose origin matches, it allows it immediately — no JS
|
|
33
|
+
// round-trip required. Cleared after first use so subsequent navigations
|
|
34
|
+
// still go through the JS allowlist.
|
|
35
|
+
private var preApprovedOrigin: String?
|
|
31
36
|
|
|
32
37
|
private override init() {
|
|
33
38
|
super.init()
|
|
@@ -65,6 +70,11 @@ public final class EmbeddedWebViewController: NSObject {
|
|
|
65
70
|
}
|
|
66
71
|
ensureWebView()
|
|
67
72
|
hasLoaded = true
|
|
73
|
+
// Pre-approve the origin so the first `decidePolicyFor` call can skip
|
|
74
|
+
// the JS round-trip. On cold boot the JS thread may be too congested to
|
|
75
|
+
// respond within the 2 s navigation-decision timeout, silently
|
|
76
|
+
// cancelling the load.
|
|
77
|
+
preApprovedOrigin = originString(for: parsed)
|
|
68
78
|
webView?.load(URLRequest(url: parsed))
|
|
69
79
|
}
|
|
70
80
|
|
|
@@ -124,6 +134,7 @@ public final class EmbeddedWebViewController: NSObject {
|
|
|
124
134
|
navigationTimers.values.forEach { $0.invalidate() }
|
|
125
135
|
navigationTimers.removeAll()
|
|
126
136
|
hasLoaded = false
|
|
137
|
+
preApprovedOrigin = nil
|
|
127
138
|
}
|
|
128
139
|
|
|
129
140
|
public func postMessage(_ message: String) {
|
|
@@ -248,6 +259,24 @@ public final class EmbeddedWebViewController: NSObject {
|
|
|
248
259
|
return nil
|
|
249
260
|
}
|
|
250
261
|
|
|
262
|
+
// Extract the scheme + host + port origin from a URL (e.g.
|
|
263
|
+
// "https://webview.dynamicauth.com"). Returns nil for URLs without a host.
|
|
264
|
+
// Default ports (443 for HTTPS, 80 for HTTP) are omitted so that
|
|
265
|
+
// "https://example.com" and "https://example.com:443" produce the same
|
|
266
|
+
// origin string.
|
|
267
|
+
private func originString(for url: URL?) -> String? {
|
|
268
|
+
guard let url = url, let scheme = url.scheme, let host = url.host else {
|
|
269
|
+
return nil
|
|
270
|
+
}
|
|
271
|
+
if let port = url.port {
|
|
272
|
+
let isDefaultPort = (scheme == "https" && port == 443) || (scheme == "http" && port == 80)
|
|
273
|
+
if !isDefaultPort {
|
|
274
|
+
return "\(scheme)://\(host):\(port)"
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return "\(scheme)://\(host)"
|
|
278
|
+
}
|
|
279
|
+
|
|
251
280
|
// Use JSONSerialization to safely encode an arbitrary string as a JS string literal.
|
|
252
281
|
private func jsStringLiteral(_ raw: String) -> String {
|
|
253
282
|
guard
|
|
@@ -317,6 +346,17 @@ extension EmbeddedWebViewController: WKNavigationDelegate {
|
|
|
317
346
|
return
|
|
318
347
|
}
|
|
319
348
|
|
|
349
|
+
// Fast-path: if the URL's origin matches the pre-approved origin (set
|
|
350
|
+
// by `setUrl`), allow immediately. This eliminates the cold-boot race
|
|
351
|
+
// where the JS thread is too congested to respond to
|
|
352
|
+
// `onShouldStartLoad` within the 2 s navigation-decision timeout.
|
|
353
|
+
if let approved = preApprovedOrigin,
|
|
354
|
+
approved == originString(for: navigationAction.request.url) {
|
|
355
|
+
preApprovedOrigin = nil
|
|
356
|
+
decisionHandler(.allow)
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
|
|
320
360
|
let id = UUID().uuidString
|
|
321
361
|
pendingNavigationDecisions[id] = decisionHandler
|
|
322
362
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamic-labs/react-native-extension",
|
|
3
|
-
"version": "4.91.
|
|
3
|
+
"version": "4.91.5",
|
|
4
4
|
"main": "./index.cjs",
|
|
5
5
|
"module": "./index.js",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
"@turnkey/react-native-passkey-stamper": "1.2.7",
|
|
19
19
|
"@react-native-documents/picker": "^11.0.0",
|
|
20
20
|
"react-native-fs": ">=2.20.0",
|
|
21
|
-
"@dynamic-labs/assert-package-version": "4.91.
|
|
22
|
-
"@dynamic-labs/client": "4.91.
|
|
23
|
-
"@dynamic-labs/logger": "4.91.
|
|
24
|
-
"@dynamic-labs/message-transport": "4.91.
|
|
25
|
-
"@dynamic-labs/webview-messages": "4.91.
|
|
21
|
+
"@dynamic-labs/assert-package-version": "4.91.5",
|
|
22
|
+
"@dynamic-labs/client": "4.91.5",
|
|
23
|
+
"@dynamic-labs/logger": "4.91.5",
|
|
24
|
+
"@dynamic-labs/message-transport": "4.91.5",
|
|
25
|
+
"@dynamic-labs/webview-messages": "4.91.5"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"react": ">=18.0.0 <20.0.0",
|