@capgo/capacitor-inappbrowser 8.14.3 → 8.15.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.
@@ -81,6 +81,7 @@ import androidx.core.view.WindowInsetsControllerCompat;
81
81
  import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
82
82
  import androidx.webkit.WebMessageCompat;
83
83
  import androidx.webkit.WebSettingsCompat;
84
+ import androidx.webkit.WebViewAssetLoader;
84
85
  import androidx.webkit.WebViewCompat;
85
86
  import androidx.webkit.WebViewFeature;
86
87
  import com.caverock.androidsvg.SVG;
@@ -344,6 +345,8 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
344
345
  private String instanceId = "";
345
346
  private final Map<String, ProxiedRequest> proxiedRequestsHashmap = new ConcurrentHashMap<>();
346
347
  private ProxyBridge proxyBridge;
348
+ private volatile WebViewAssetLoader bundledAssetLoader;
349
+ private final Object bundledAssetLoaderLock = new Object();
347
350
  private String proxyBridgeScript;
348
351
  private String proxyAccessToken;
349
352
  private final ExecutorService executorService = Executors.newCachedThreadPool();
@@ -2232,6 +2235,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2232
2235
  _webView.addJavascriptInterface(proxyBridge, "__capgoProxy");
2233
2236
  proxyBridgeScript = loadProxyBridgeScript();
2234
2237
  }
2238
+ ensureBundledAssetLoader();
2235
2239
  _webView.getSettings().setJavaScriptEnabled(true);
2236
2240
  _webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
2237
2241
  _webView.getSettings().setDatabaseEnabled(true);
@@ -4364,6 +4368,60 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4364
4368
  return proxyHeaders;
4365
4369
  }
4366
4370
 
4371
+ private void ensureBundledAssetLoader() {
4372
+ synchronized (bundledAssetLoaderLock) {
4373
+ ensureBundledAssetLoaderLocked();
4374
+ }
4375
+ }
4376
+
4377
+ private void ensureBundledAssetLoaderLocked() {
4378
+ if (bundledAssetLoader != null || _options == null || !_options.getServeBundledAssets()) {
4379
+ return;
4380
+ }
4381
+
4382
+ bundledAssetLoader = BundledAssetSupport.createAssetLoader(
4383
+ _context,
4384
+ _options.getBundledAssetHost(),
4385
+ _options.getBundledAssetScheme()
4386
+ );
4387
+ }
4388
+
4389
+ public void applyBundledAssetResolution(BundledAssetSupport.Resolution resolution, String localUrl) {
4390
+ if (_options == null || resolution == null) {
4391
+ return;
4392
+ }
4393
+
4394
+ synchronized (bundledAssetLoaderLock) {
4395
+ bundledAssetLoader = null;
4396
+ _options.setUrl(resolution.url);
4397
+ if (resolution.needsAssetLoader) {
4398
+ BundledAssetSupport.LocalConfig localConfig = BundledAssetSupport.parseLocalConfig(localUrl);
4399
+ _options.setBundledAssetHost(localConfig != null ? localConfig.host : "localhost");
4400
+ _options.setBundledAssetScheme(localConfig != null ? BundledAssetSupport.assetLoaderScheme(localConfig) : "https");
4401
+ _options.setServeBundledAssets(true);
4402
+ } else {
4403
+ _options.setServeBundledAssets(false);
4404
+ }
4405
+ }
4406
+ }
4407
+
4408
+ private WebResourceResponse interceptBundledAssetRequest(WebResourceRequest request) {
4409
+ final WebViewAssetLoader loader;
4410
+ synchronized (bundledAssetLoaderLock) {
4411
+ if (_options == null || !_options.getServeBundledAssets()) {
4412
+ return null;
4413
+ }
4414
+
4415
+ ensureBundledAssetLoaderLocked();
4416
+ loader = bundledAssetLoader;
4417
+ }
4418
+
4419
+ if (loader == null) {
4420
+ return null;
4421
+ }
4422
+ return loader.shouldInterceptRequest(request.getUrl());
4423
+ }
4424
+
4367
4425
  public void setUrl(String url) {
4368
4426
  if (_webView == null) {
4369
4427
  Log.w("InAppBrowser", "Cannot set URL - WebView is null");
@@ -5586,186 +5644,254 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
5586
5644
  if (view == null || _webView == null) {
5587
5645
  return null;
5588
5646
  }
5589
- if (!shouldUseNativeProxy()) {
5590
- return null;
5591
- }
5592
5647
 
5593
- String requestUrl = request.getUrl().toString();
5594
- boolean bridgeBackedRequest = ProxyRequestSupport.isBridgeMarkerRequestUrl(requestUrl);
5595
- String originalUrl;
5596
- String method;
5597
- Map<String, String> requestHeaders = new HashMap<>();
5598
- String base64Body = "";
5599
- String credentialsMode = "same-origin";
5600
-
5601
- if (bridgeBackedRequest) {
5602
- Uri uri = request.getUrl();
5603
- originalUrl = uri.getQueryParameter("u");
5604
- String requestId = uri.getQueryParameter("rid");
5605
- if (originalUrl == null || requestId == null) {
5606
- return null;
5607
- }
5648
+ if (shouldUseNativeProxy()) {
5649
+ String requestUrl = request.getUrl().toString();
5650
+ boolean bridgeBackedRequest = ProxyRequestSupport.isBridgeMarkerRequestUrl(requestUrl);
5651
+ String originalUrl;
5652
+ String method;
5653
+ Map<String, String> requestHeaders = new HashMap<>();
5654
+ String base64Body = "";
5655
+ String credentialsMode = "same-origin";
5656
+
5657
+ if (bridgeBackedRequest) {
5658
+ Uri uri = request.getUrl();
5659
+ originalUrl = uri.getQueryParameter("u");
5660
+ String requestId = uri.getQueryParameter("rid");
5661
+ if (originalUrl == null || requestId == null) {
5662
+ return null;
5663
+ }
5608
5664
 
5609
- if (
5610
- ProxyRequestSupport.usesLegacyJsProxyMode(_options) &&
5611
- !ProxyRequestSupport.shouldDelegateLegacyJsProxyRequest(_options, originalUrl)
5612
- ) {
5613
- if (proxyBridge != null) {
5614
- proxyBridge.getAndRemove(requestId);
5665
+ if (
5666
+ ProxyRequestSupport.usesLegacyJsProxyMode(_options) &&
5667
+ !ProxyRequestSupport.shouldDelegateLegacyJsProxyRequest(_options, originalUrl)
5668
+ ) {
5669
+ if (proxyBridge != null) {
5670
+ proxyBridge.getAndRemove(requestId);
5671
+ }
5672
+ Log.w("InAppBrowserProxy", "Ignoring legacy regex miss for bridge-backed request: " + originalUrl);
5673
+ return createCanceledResponse();
5615
5674
  }
5616
- Log.w("InAppBrowserProxy", "Ignoring legacy regex miss for bridge-backed request: " + originalUrl);
5617
- return createCanceledResponse();
5618
- }
5619
5675
 
5620
- ProxyBridge.StoredRequest stored = proxyBridge != null ? proxyBridge.getAndRemove(requestId) : null;
5621
- if (stored == null) {
5622
- Log.e("InAppBrowserProxy", "Missing stored proxy bridge payload for request id: " + requestId);
5623
- return createCanceledResponse();
5624
- }
5625
- method = stored.method;
5626
- base64Body = stored.base64Body;
5627
- credentialsMode = stored.credentialsMode;
5628
- Map<String, String> safeMarkerHeaders = ProxyRequestSupport.extractSafeMarkerHeaders(request.getRequestHeaders());
5629
- try {
5630
- requestHeaders = ProxyRequestSupport.mergeRequestHeaders(null, stored.headersJson);
5631
- requestHeaders = ProxyRequestSupport.mergeMissingHeaders(requestHeaders, safeMarkerHeaders);
5632
- } catch (JSONException error) {
5633
- Log.e("InAppBrowserProxy", "Failed to parse stored proxy headers", error);
5634
- return createCanceledResponse();
5635
- }
5636
- String initiatorUrl = request.getRequestHeaders().get("Referer");
5637
- if (initiatorUrl == null || initiatorUrl.isBlank()) {
5638
- initiatorUrl = getWebViewUrlOrFallback(
5639
- _options != null && _options.getUrl() != null ? _options.getUrl() : originalUrl
5676
+ ProxyBridge.StoredRequest stored = proxyBridge != null ? proxyBridge.getAndRemove(requestId) : null;
5677
+ if (stored == null) {
5678
+ Log.e("InAppBrowserProxy", "Missing stored proxy bridge payload for request id: " + requestId);
5679
+ return createCanceledResponse();
5680
+ }
5681
+ method = stored.method;
5682
+ base64Body = stored.base64Body;
5683
+ credentialsMode = stored.credentialsMode;
5684
+ Map<String, String> safeMarkerHeaders = ProxyRequestSupport.extractSafeMarkerHeaders(
5685
+ request.getRequestHeaders()
5640
5686
  );
5687
+ try {
5688
+ requestHeaders = ProxyRequestSupport.mergeRequestHeaders(null, stored.headersJson);
5689
+ requestHeaders = ProxyRequestSupport.mergeMissingHeaders(requestHeaders, safeMarkerHeaders);
5690
+ } catch (JSONException error) {
5691
+ Log.e("InAppBrowserProxy", "Failed to parse stored proxy headers", error);
5692
+ return createCanceledResponse();
5693
+ }
5694
+ String initiatorUrl = request.getRequestHeaders().get("Referer");
5695
+ if (initiatorUrl == null || initiatorUrl.isBlank()) {
5696
+ initiatorUrl = getWebViewUrlOrFallback(
5697
+ _options != null && _options.getUrl() != null ? _options.getUrl() : originalUrl
5698
+ );
5699
+ }
5700
+ String targetCookies = CookieManager.getInstance().getCookie(originalUrl);
5701
+ if (
5702
+ targetCookies != null &&
5703
+ !targetCookies.isBlank() &&
5704
+ ProxyRequestSupport.shouldInjectCookies(credentialsMode, initiatorUrl, originalUrl, requestHeaders)
5705
+ ) {
5706
+ requestHeaders.put("Cookie", targetCookies);
5707
+ }
5708
+ } else {
5709
+ if (!ProxyRequestSupport.shouldHandleNonBridgeRequest(_options, requestUrl)) {
5710
+ return interceptBundledAssetRequest(request);
5711
+ }
5712
+
5713
+ originalUrl = requestUrl;
5714
+ method = request.getMethod();
5715
+ if (request.getRequestHeaders() != null) {
5716
+ requestHeaders.putAll(request.getRequestHeaders());
5717
+ }
5641
5718
  }
5642
- String targetCookies = CookieManager.getInstance().getCookie(originalUrl);
5719
+
5720
+ NativeRequestContext requestContext = new NativeRequestContext(
5721
+ originalUrl,
5722
+ method,
5723
+ requestHeaders,
5724
+ base64Body,
5725
+ request.isForMainFrame(),
5726
+ credentialsMode
5727
+ );
5728
+
5643
5729
  if (
5644
- targetCookies != null &&
5645
- !targetCookies.isBlank() &&
5646
- ProxyRequestSupport.shouldInjectCookies(credentialsMode, initiatorUrl, originalUrl, requestHeaders)
5730
+ ProxyRequestSupport.shouldLetWebViewHandleMissingBody(
5731
+ requestUrl,
5732
+ requestContext.method,
5733
+ requestContext.base64Body
5734
+ )
5647
5735
  ) {
5648
- requestHeaders.put("Cookie", targetCookies);
5649
- }
5650
- } else {
5651
- if (!ProxyRequestSupport.shouldHandleNonBridgeRequest(_options, requestUrl)) {
5736
+ Log.w("InAppBrowserProxy", "Allowing WebView to handle request with uncaptured body: " + requestContext.url);
5652
5737
  return null;
5653
5738
  }
5654
5739
 
5655
- originalUrl = requestUrl;
5656
- method = request.getMethod();
5657
- if (request.getRequestHeaders() != null) {
5658
- requestHeaders.putAll(request.getRequestHeaders());
5740
+ boolean legacyProxyMode = ProxyRequestSupport.usesLegacyJsProxyMode(_options);
5741
+ boolean shouldDelegateLegacyRequest = ProxyRequestSupport.shouldDelegateLegacyJsProxyRequest(
5742
+ _options,
5743
+ requestContext.url
5744
+ );
5745
+ NativeResponseData directResponseData = null;
5746
+
5747
+ NativeProxyRule outboundRule =
5748
+ legacyProxyMode && shouldDelegateLegacyRequest
5749
+ ? new NativeProxyRule(
5750
+ null,
5751
+ null,
5752
+ null,
5753
+ null,
5754
+ null,
5755
+ null,
5756
+ null,
5757
+ null,
5758
+ false,
5759
+ NativeProxyRule.Action.DELEGATE_TO_JS
5760
+ )
5761
+ : findMatchingRule(_options.getOutboundProxyRules(), requestContext, null);
5762
+
5763
+ if (outboundRule != null && outboundRule.getAction() == NativeProxyRule.Action.CANCEL) {
5764
+ return createCanceledResponse();
5659
5765
  }
5660
- }
5661
-
5662
- NativeRequestContext requestContext = new NativeRequestContext(
5663
- originalUrl,
5664
- method,
5665
- requestHeaders,
5666
- base64Body,
5667
- request.isForMainFrame(),
5668
- credentialsMode
5669
- );
5670
-
5671
- if (
5672
- ProxyRequestSupport.shouldLetWebViewHandleMissingBody(requestUrl, requestContext.method, requestContext.base64Body)
5673
- ) {
5674
- Log.w("InAppBrowserProxy", "Allowing WebView to handle request with uncaptured body: " + requestContext.url);
5675
- return null;
5676
- }
5677
5766
 
5678
- boolean legacyProxyMode = ProxyRequestSupport.usesLegacyJsProxyMode(_options);
5679
- boolean shouldDelegateLegacyRequest = ProxyRequestSupport.shouldDelegateLegacyJsProxyRequest(
5680
- _options,
5681
- requestContext.url
5682
- );
5683
- NativeResponseData directResponseData = null;
5684
-
5685
- NativeProxyRule outboundRule =
5686
- legacyProxyMode && shouldDelegateLegacyRequest
5687
- ? new NativeProxyRule(
5688
- null,
5689
- null,
5690
- null,
5691
- null,
5692
- null,
5693
- null,
5694
- null,
5695
- null,
5696
- false,
5697
- NativeProxyRule.Action.DELEGATE_TO_JS
5698
- )
5699
- : findMatchingRule(_options.getOutboundProxyRules(), requestContext, null);
5700
-
5701
- if (outboundRule != null && outboundRule.getAction() == NativeProxyRule.Action.CANCEL) {
5702
- return createCanceledResponse();
5703
- }
5704
-
5705
- if (outboundRule != null && outboundRule.getAction() == NativeProxyRule.Action.DELEGATE_TO_JS) {
5706
- String proxyId = UUID.randomUUID().toString();
5707
- ProxiedRequest proxiedRequest = new ProxiedRequest();
5708
- proxiedRequest.requestContext = requestContext;
5709
- addProxiedRequest(proxyId, proxiedRequest);
5710
-
5711
- String dialogId = instanceId != null ? instanceId : "";
5712
- _options
5713
- .getCallbacks()
5714
- .proxyRequestEvent(
5715
- proxyId,
5716
- "outbound",
5717
- requestContext.url,
5718
- requestContext.method,
5719
- serializeHeaders(requestContext.headers),
5720
- requestContext.base64Body.isEmpty() ? null : requestContext.base64Body,
5721
- null,
5722
- null,
5723
- null,
5724
- dialogId
5725
- );
5767
+ if (outboundRule != null && outboundRule.getAction() == NativeProxyRule.Action.DELEGATE_TO_JS) {
5768
+ String proxyId = UUID.randomUUID().toString();
5769
+ ProxiedRequest proxiedRequest = new ProxiedRequest();
5770
+ proxiedRequest.requestContext = requestContext;
5771
+ addProxiedRequest(proxyId, proxiedRequest);
5772
+
5773
+ String dialogId = instanceId != null ? instanceId : "";
5774
+ _options
5775
+ .getCallbacks()
5776
+ .proxyRequestEvent(
5777
+ proxyId,
5778
+ "outbound",
5779
+ requestContext.url,
5780
+ requestContext.method,
5781
+ serializeHeaders(requestContext.headers),
5782
+ requestContext.base64Body.isEmpty() ? null : requestContext.base64Body,
5783
+ null,
5784
+ null,
5785
+ null,
5786
+ dialogId
5787
+ );
5726
5788
 
5727
- try {
5728
- if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
5729
- if (proxiedRequest.canceled) {
5730
- return createCanceledResponse();
5731
- }
5732
- if (proxiedRequest.response != null) {
5733
- return proxiedRequest.response;
5734
- }
5735
- if (proxiedRequest.nativeResponse != null) {
5736
- directResponseData = proxiedRequest.nativeResponse;
5789
+ try {
5790
+ if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
5791
+ if (proxiedRequest.canceled) {
5792
+ return createCanceledResponse();
5793
+ }
5794
+ if (proxiedRequest.response != null) {
5795
+ return proxiedRequest.response;
5796
+ }
5797
+ if (proxiedRequest.nativeResponse != null) {
5798
+ directResponseData = proxiedRequest.nativeResponse;
5799
+ } else {
5800
+ requestContext =
5801
+ proxiedRequest.requestContext != null ? proxiedRequest.requestContext : requestContext;
5802
+ }
5737
5803
  } else {
5738
- requestContext = proxiedRequest.requestContext != null ? proxiedRequest.requestContext : requestContext;
5739
- }
5740
- } else {
5741
- synchronized (proxiedRequest) {
5742
- proxiedRequest.timedOut = true;
5804
+ synchronized (proxiedRequest) {
5805
+ proxiedRequest.timedOut = true;
5806
+ }
5807
+ removeProxiedRequest(proxyId);
5808
+ Log.w("InAppBrowserProxy", "Proxy timeout, falling back to native replay for: " + requestContext.url);
5743
5809
  }
5810
+ } catch (InterruptedException error) {
5744
5811
  removeProxiedRequest(proxyId);
5745
- Log.w("InAppBrowserProxy", "Proxy timeout, falling back to native replay for: " + requestContext.url);
5812
+ Thread.currentThread().interrupt();
5813
+ Log.e("InAppBrowserProxy", "Semaphore wait error", error);
5814
+ return bridgeBackedRequest ? createCanceledResponse() : null;
5746
5815
  }
5747
- } catch (InterruptedException error) {
5748
- removeProxiedRequest(proxyId);
5749
- Thread.currentThread().interrupt();
5750
- Log.e("InAppBrowserProxy", "Semaphore wait error", error);
5751
- return bridgeBackedRequest ? createCanceledResponse() : null;
5752
5816
  }
5753
- }
5754
5817
 
5755
- NativeResponseData nativeResponse = directResponseData;
5756
- if (nativeResponse == null) {
5757
- try {
5758
- nativeResponse = performNativeRequest(requestContext);
5759
- } catch (IOException error) {
5760
- Log.e("InAppBrowserProxy", "Native request failed for: " + requestContext.url, error);
5761
- return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
5818
+ NativeResponseData nativeResponse = directResponseData;
5819
+ if (nativeResponse == null) {
5820
+ try {
5821
+ nativeResponse = performNativeRequest(requestContext);
5822
+ } catch (IOException error) {
5823
+ Log.e("InAppBrowserProxy", "Native request failed for: " + requestContext.url, error);
5824
+ return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
5825
+ }
5762
5826
  }
5763
- }
5764
5827
 
5765
- int redirectsFollowed = 0;
5766
- while (true) {
5767
- NativeProxyRule inboundRule = findMatchingRule(_options.getInboundProxyRules(), requestContext, nativeResponse);
5768
- if (inboundRule == null || inboundRule.getAction() == NativeProxyRule.Action.CONTINUE) {
5828
+ int redirectsFollowed = 0;
5829
+ while (true) {
5830
+ NativeProxyRule inboundRule = findMatchingRule(_options.getInboundProxyRules(), requestContext, nativeResponse);
5831
+ if (inboundRule == null || inboundRule.getAction() == NativeProxyRule.Action.CONTINUE) {
5832
+ RedirectReplayResult redirectReplay;
5833
+ try {
5834
+ redirectReplay = followRedirectForWebView(requestContext, nativeResponse, redirectsFollowed);
5835
+ } catch (IOException error) {
5836
+ Log.e("InAppBrowserProxy", "Native redirect replay failed for: " + requestContext.url, error);
5837
+ return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
5838
+ }
5839
+ if (redirectReplay != null) {
5840
+ requestContext = redirectReplay.requestContext;
5841
+ nativeResponse = redirectReplay.responseData;
5842
+ redirectsFollowed++;
5843
+ continue;
5844
+ }
5845
+ return createWebResourceResponseOrFallback(nativeResponse, bridgeBackedRequest, requestContext.url);
5846
+ }
5847
+ if (inboundRule.getAction() == NativeProxyRule.Action.CANCEL) {
5848
+ return createCanceledResponse();
5849
+ }
5850
+
5851
+ String proxyId = UUID.randomUUID().toString();
5852
+ ProxiedRequest proxiedRequest = new ProxiedRequest();
5853
+ proxiedRequest.requestContext = requestContext;
5854
+ proxiedRequest.nativeResponse = nativeResponse;
5855
+ addProxiedRequest(proxyId, proxiedRequest);
5856
+
5857
+ String dialogId = instanceId != null ? instanceId : "";
5858
+ _options
5859
+ .getCallbacks()
5860
+ .proxyRequestEvent(
5861
+ proxyId,
5862
+ "inbound",
5863
+ requestContext.url,
5864
+ requestContext.method,
5865
+ serializeHeaders(requestContext.headers),
5866
+ requestContext.base64Body.isEmpty() ? null : requestContext.base64Body,
5867
+ nativeResponse.statusCode,
5868
+ serializeHeaders(nativeResponse.headers),
5869
+ Base64.encodeToString(nativeResponse.bodyBytes, Base64.NO_WRAP),
5870
+ dialogId
5871
+ );
5872
+
5873
+ try {
5874
+ if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
5875
+ if (proxiedRequest.canceled) {
5876
+ return createCanceledResponse();
5877
+ }
5878
+ if (proxiedRequest.response != null) {
5879
+ return proxiedRequest.response;
5880
+ }
5881
+ if (proxiedRequest.nativeResponse != null) {
5882
+ nativeResponse = proxiedRequest.nativeResponse;
5883
+ }
5884
+ } else {
5885
+ synchronized (proxiedRequest) {
5886
+ proxiedRequest.timedOut = true;
5887
+ }
5888
+ removeProxiedRequest(proxyId);
5889
+ }
5890
+ } catch (InterruptedException error) {
5891
+ Thread.currentThread().interrupt();
5892
+ Log.e("InAppBrowserProxy", "Semaphore wait error", error);
5893
+ }
5894
+
5769
5895
  RedirectReplayResult redirectReplay;
5770
5896
  try {
5771
5897
  redirectReplay = followRedirectForWebView(requestContext, nativeResponse, redirectsFollowed);
@@ -5781,69 +5907,9 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
5781
5907
  }
5782
5908
  return createWebResourceResponseOrFallback(nativeResponse, bridgeBackedRequest, requestContext.url);
5783
5909
  }
5784
- if (inboundRule.getAction() == NativeProxyRule.Action.CANCEL) {
5785
- return createCanceledResponse();
5786
- }
5787
-
5788
- String proxyId = UUID.randomUUID().toString();
5789
- ProxiedRequest proxiedRequest = new ProxiedRequest();
5790
- proxiedRequest.requestContext = requestContext;
5791
- proxiedRequest.nativeResponse = nativeResponse;
5792
- addProxiedRequest(proxyId, proxiedRequest);
5793
-
5794
- String dialogId = instanceId != null ? instanceId : "";
5795
- _options
5796
- .getCallbacks()
5797
- .proxyRequestEvent(
5798
- proxyId,
5799
- "inbound",
5800
- requestContext.url,
5801
- requestContext.method,
5802
- serializeHeaders(requestContext.headers),
5803
- requestContext.base64Body.isEmpty() ? null : requestContext.base64Body,
5804
- nativeResponse.statusCode,
5805
- serializeHeaders(nativeResponse.headers),
5806
- Base64.encodeToString(nativeResponse.bodyBytes, Base64.NO_WRAP),
5807
- dialogId
5808
- );
5809
-
5810
- try {
5811
- if (proxiedRequest.semaphore.tryAcquire(1, 10, TimeUnit.SECONDS)) {
5812
- if (proxiedRequest.canceled) {
5813
- return createCanceledResponse();
5814
- }
5815
- if (proxiedRequest.response != null) {
5816
- return proxiedRequest.response;
5817
- }
5818
- if (proxiedRequest.nativeResponse != null) {
5819
- nativeResponse = proxiedRequest.nativeResponse;
5820
- }
5821
- } else {
5822
- synchronized (proxiedRequest) {
5823
- proxiedRequest.timedOut = true;
5824
- }
5825
- removeProxiedRequest(proxyId);
5826
- }
5827
- } catch (InterruptedException error) {
5828
- Thread.currentThread().interrupt();
5829
- Log.e("InAppBrowserProxy", "Semaphore wait error", error);
5830
- }
5831
-
5832
- RedirectReplayResult redirectReplay;
5833
- try {
5834
- redirectReplay = followRedirectForWebView(requestContext, nativeResponse, redirectsFollowed);
5835
- } catch (IOException error) {
5836
- Log.e("InAppBrowserProxy", "Native redirect replay failed for: " + requestContext.url, error);
5837
- return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
5838
- }
5839
- if (redirectReplay != null) {
5840
- requestContext = redirectReplay.requestContext;
5841
- nativeResponse = redirectReplay.responseData;
5842
- redirectsFollowed++;
5843
- continue;
5844
- }
5845
- return createWebResourceResponseOrFallback(nativeResponse, bridgeBackedRequest, requestContext.url);
5846
5910
  }
5911
+
5912
+ return interceptBundledAssetRequest(request);
5847
5913
  }
5848
5914
 
5849
5915
  @Override
package/dist/docs.json CHANGED
@@ -999,9 +999,13 @@
999
999
  {
1000
1000
  "text": "0.1.0",
1001
1001
  "name": "since"
1002
+ },
1003
+ {
1004
+ "text": "\"https://capgo.app\"",
1005
+ "name": "example"
1002
1006
  }
1003
1007
  ],
1004
- "docs": "Target URL to load.",
1008
+ "docs": "Target URL to load.\n\nRemote `http://` and `https://` URLs are loaded as-is. Relative bundled paths\nsuch as `/index.html` are not supported; use `openWebView()` instead.",
1005
1009
  "complexTypes": [],
1006
1010
  "type": "string"
1007
1011
  },
@@ -1403,12 +1407,20 @@
1403
1407
  "text": "0.1.0",
1404
1408
  "name": "since"
1405
1409
  },
1410
+ {
1411
+ "text": "8.15.0 Relative bundled paths (`/index.html`, `assets/page.html`) are supported.",
1412
+ "name": "since"
1413
+ },
1406
1414
  {
1407
1415
  "text": "\"https://capgo.app\"",
1408
1416
  "name": "example"
1417
+ },
1418
+ {
1419
+ "text": "\"/index.html\"",
1420
+ "name": "example"
1409
1421
  }
1410
1422
  ],
1411
- "docs": "Target URL to load.",
1423
+ "docs": "Target URL to load.\n\nRemote `http://` and `https://` URLs are loaded as-is.\n\nTo open bundled web assets from the app bundle without running a local HTTP server,\npass a relative path such as `/index.html` or `assets/page.html`. The plugin resolves it to\nthe Capacitor local URL for the current platform (defaults: `capacitor://localhost/...` on iOS,\n`https://localhost/...` on Android; actual scheme and host follow the app's configured Capacitor\nlocal URL) and serves files from the packaged `public/` directory, or `www/` on iOS when `public/`\nis absent.",
1412
1424
  "complexTypes": [],
1413
1425
  "type": "string"
1414
1426
  },
@@ -0,0 +1,40 @@
1
+ export type BundledAssetPlatform = 'ios' | 'android';
2
+ export interface BundledAssetLocalConfig {
3
+ scheme: string;
4
+ host: string;
5
+ }
6
+ export interface BundledAssetResolution {
7
+ url: string;
8
+ }
9
+ /** Default Capacitor local URL settings used when no runtime config is available. */
10
+ export declare const BUNDLED_ASSET_DEFAULTS: Record<BundledAssetPlatform, BundledAssetLocalConfig>;
11
+ /** Returns true when the input looks like an absolute URL (including protocol-relative URLs). */
12
+ export declare function isAbsoluteUrl(url: string): boolean;
13
+ /** Returns true when any path segment is `..`. */
14
+ export declare function containsPathTraversal(path: string): boolean;
15
+ /**
16
+ * Normalizes a relative bundled asset path to a leading-slash form.
17
+ * Returns null when the path contains traversal segments.
18
+ */
19
+ export declare function normalizeBundledPath(path: string): string | null;
20
+ /** Parses a Capacitor local URL into scheme and host components. */
21
+ export declare function parseBundledLocalConfig(localUrl?: string | null): BundledAssetLocalConfig | null;
22
+ /** Returns the scheme WebView navigation should use for bundled assets on a platform. */
23
+ export declare function handlerSchemeForPlatform(platform: BundledAssetPlatform, localConfig: BundledAssetLocalConfig): string;
24
+ /**
25
+ * Returns true when the URL targets the configured Capacitor local host/scheme
26
+ * (including the platform handler scheme rewrite).
27
+ */
28
+ export declare function isBundledLocalUrl(url: string, platform: BundledAssetPlatform, localConfig?: BundledAssetLocalConfig | null): boolean;
29
+ /** Returns true when the input is a non-empty relative bundled asset path. */
30
+ export declare function isRelativeBundledPath(url: string): boolean;
31
+ /**
32
+ * Resolves a bundled asset URL for the given platform.
33
+ * Relative paths become platform-local URLs; bundled local URLs may be rewritten to the handler scheme.
34
+ */
35
+ export declare function resolveBundledAssetUrl(url: string, platform: BundledAssetPlatform, localConfig?: BundledAssetLocalConfig | null): BundledAssetResolution | null;
36
+ /**
37
+ * Resolves relative bundled paths for legacy native plugins that do not perform native resolution.
38
+ * Throws when the path is invalid.
39
+ */
40
+ export declare function resolveLegacyNativeWebViewUrl(url: string, platform: BundledAssetPlatform, localConfig?: BundledAssetLocalConfig | null): string;