@capgo/capacitor-inappbrowser 8.7.5 → 8.7.7
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/src/main/java/ee/forgr/capacitor_inappbrowser/CapgoInAppBrowserPlugin.java +15 -2
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/ProxyBridge.java +4 -5
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/ProxyRequestSupport.java +177 -2
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +95 -34
- package/ios/Sources/InAppBrowserPlugin/InAppBrowserPlugin.swift +4 -2
- package/ios/Sources/InAppBrowserPlugin/ProxyBridge.swift +2 -5
- package/package.json +1 -1
|
@@ -50,6 +50,8 @@ import java.util.Iterator;
|
|
|
50
50
|
import java.util.List;
|
|
51
51
|
import java.util.Map;
|
|
52
52
|
import java.util.UUID;
|
|
53
|
+
import java.util.concurrent.ExecutorService;
|
|
54
|
+
import java.util.concurrent.Executors;
|
|
53
55
|
import java.util.regex.Pattern;
|
|
54
56
|
import java.util.regex.PatternSyntaxException;
|
|
55
57
|
import org.json.JSONException;
|
|
@@ -66,6 +68,15 @@ import org.json.JSONObject;
|
|
|
66
68
|
)
|
|
67
69
|
public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.PermissionHandler {
|
|
68
70
|
|
|
71
|
+
private static final ExecutorService PROXY_RESPONSE_EXECUTOR = Executors.newFixedThreadPool(
|
|
72
|
+
Math.max(4, Runtime.getRuntime().availableProcessors()),
|
|
73
|
+
(runnable) -> {
|
|
74
|
+
Thread thread = new Thread(runnable, "CapgoInAppBrowser-ProxyResponse");
|
|
75
|
+
thread.setDaemon(true);
|
|
76
|
+
return thread;
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
69
80
|
private final String pluginVersion = "8.6.17";
|
|
70
81
|
|
|
71
82
|
private boolean resolveClientCertificatePrompt(PluginCall call) {
|
|
@@ -1684,8 +1695,10 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
|
|
|
1684
1695
|
return;
|
|
1685
1696
|
}
|
|
1686
1697
|
|
|
1687
|
-
|
|
1688
|
-
|
|
1698
|
+
PROXY_RESPONSE_EXECUTOR.execute(() -> {
|
|
1699
|
+
dialog.handleProxyResponse(requestId, decision);
|
|
1700
|
+
call.resolve();
|
|
1701
|
+
});
|
|
1689
1702
|
}
|
|
1690
1703
|
|
|
1691
1704
|
@PluginMethod
|
|
@@ -58,11 +58,10 @@ public class ProxyBridge {
|
|
|
58
58
|
if (requestId == null) {
|
|
59
59
|
return null;
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return storedRequest;
|
|
61
|
+
// Do not scan storedRequestOrder here: ConcurrentLinkedQueue.remove is O(n) and
|
|
62
|
+
// serializes parallel bridge-backed proxy requests. Stale queue ids are purged lazily
|
|
63
|
+
// in cleanupExpiredRequests().
|
|
64
|
+
return storedRequests.remove(requestId);
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
private void cleanupExpiredRequests(long now) {
|
|
@@ -3,6 +3,7 @@ package ee.forgr.capacitor_inappbrowser;
|
|
|
3
3
|
import com.getcapacitor.JSObject;
|
|
4
4
|
import java.io.IOException;
|
|
5
5
|
import java.net.URL;
|
|
6
|
+
import java.net.URLConnection;
|
|
6
7
|
import java.nio.charset.StandardCharsets;
|
|
7
8
|
import java.util.Base64;
|
|
8
9
|
import java.util.HashMap;
|
|
@@ -22,6 +23,8 @@ final class ProxyRequestSupport {
|
|
|
22
23
|
|
|
23
24
|
record ParsedResponseHeaders(Map<String, String> responseHeaders, List<String> cookieHeaders) {}
|
|
24
25
|
|
|
26
|
+
record ProxiedResponseMetadata(String contentType, Map<String, String> headers) {}
|
|
27
|
+
|
|
25
28
|
static final int SYNTHETIC_NATIVE_FAILURE_STATUS = 599;
|
|
26
29
|
static final String SYNTHETIC_NATIVE_FAILURE_HEADER = "X-Capgo-Proxy-Error";
|
|
27
30
|
|
|
@@ -59,7 +62,7 @@ final class ProxyRequestSupport {
|
|
|
59
62
|
private ProxyRequestSupport() {}
|
|
60
63
|
|
|
61
64
|
static boolean shouldInjectBridge(Options options) {
|
|
62
|
-
return options != null && options
|
|
65
|
+
return options != null && usesLegacyJsProxyMode(options);
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
static boolean usesLegacyJsProxyMode(Options options) {
|
|
@@ -109,6 +112,27 @@ final class ProxyRequestSupport {
|
|
|
109
112
|
return !usesLegacyJsProxyMode(options) || matchesProxyRequestsPattern(options.getProxyRequestsPattern(), requestUrl);
|
|
110
113
|
}
|
|
111
114
|
|
|
115
|
+
static boolean shouldBootstrapInitialProxyLoad(Options options) {
|
|
116
|
+
if (options == null || options.isPopupWindowMode()) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
String initialUrl = options.getUrl();
|
|
120
|
+
if (initialUrl == null || initialUrl.isBlank()) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
if (!options.shouldEnableNativeProxy()) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
if (shouldDelegateLegacyJsProxyRequest(options, initialUrl)) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return shouldHandleNonBridgeRequest(options, initialUrl) && !usesLegacyJsProxyMode(options);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static boolean shouldReturnSyntheticNativeFailure(boolean bridgeBackedRequest, Options options, String requestUrl) {
|
|
133
|
+
return bridgeBackedRequest || shouldHandleNonBridgeRequest(options, requestUrl);
|
|
134
|
+
}
|
|
135
|
+
|
|
112
136
|
static boolean isBridgeMarkerRequestUrl(String requestUrl) {
|
|
113
137
|
if (requestUrl == null || requestUrl.isBlank()) {
|
|
114
138
|
return false;
|
|
@@ -405,16 +429,164 @@ final class ProxyRequestSupport {
|
|
|
405
429
|
return new WebResourceResponseMetadata(mimeType, encoding);
|
|
406
430
|
}
|
|
407
431
|
|
|
432
|
+
static ProxiedResponseMetadata normalizeProxiedResponseMetadata(
|
|
433
|
+
String contentType,
|
|
434
|
+
Map<String, String> responseHeaders,
|
|
435
|
+
String requestUrl
|
|
436
|
+
) {
|
|
437
|
+
Map<String, String> headers = responseHeaders != null ? new LinkedHashMap<>(responseHeaders) : new LinkedHashMap<>();
|
|
438
|
+
String headerContentType = findHeaderIgnoreCase(headers, "Content-Type");
|
|
439
|
+
String resolvedContentType = resolveProxiedContentType(firstNonEmpty(contentType, headerContentType), requestUrl);
|
|
440
|
+
if (resolvedContentType == null || resolvedContentType.isBlank()) {
|
|
441
|
+
resolvedContentType = "application/octet-stream";
|
|
442
|
+
}
|
|
443
|
+
if (!hasHeaderIgnoreCase(headers, "Content-Type")) {
|
|
444
|
+
headers.put("Content-Type", resolvedContentType);
|
|
445
|
+
}
|
|
446
|
+
return new ProxiedResponseMetadata(resolvedContentType, headers);
|
|
447
|
+
}
|
|
448
|
+
|
|
408
449
|
static WebResourceResponseMetadata resolveWebResourceResponseConstructorMetadata(
|
|
409
450
|
String contentType,
|
|
410
451
|
Map<String, String> responseHeaders
|
|
411
452
|
) {
|
|
412
|
-
|
|
453
|
+
String headerContentType = findHeaderIgnoreCase(responseHeaders, "Content-Type");
|
|
454
|
+
if (headerContentType != null && shouldDeferConstructorMimeTypeToContentTypeHeader(headerContentType)) {
|
|
413
455
|
return new WebResourceResponseMetadata(null, null);
|
|
414
456
|
}
|
|
415
457
|
return resolveWebResourceResponseMetadata(contentType, responseHeaders);
|
|
416
458
|
}
|
|
417
459
|
|
|
460
|
+
static String resolveProxiedContentType(String contentType, String requestUrl) {
|
|
461
|
+
String mimeOnly = extractMimeType(contentType);
|
|
462
|
+
if (mimeOnly != null && !mimeOnly.isBlank() && !isGenericProxiedMimeType(mimeOnly)) {
|
|
463
|
+
if (shouldNormalizeSvgContentType(requestUrl, mimeOnly)) {
|
|
464
|
+
return "image/svg+xml";
|
|
465
|
+
}
|
|
466
|
+
return contentType.trim();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
String guessedContentType = guessContentTypeFromRequestUrl(requestUrl);
|
|
470
|
+
if (guessedContentType != null) {
|
|
471
|
+
return guessedContentType;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return contentType != null && !contentType.isBlank() ? contentType.trim() : null;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
static String guessContentTypeFromRequestUrl(String requestUrl) {
|
|
478
|
+
if (requestUrl == null || requestUrl.isBlank()) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
String fileName;
|
|
483
|
+
try {
|
|
484
|
+
fileName = new URL(requestUrl).getPath();
|
|
485
|
+
} catch (Exception error) {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
if (fileName == null || fileName.isBlank()) {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
int slashIndex = fileName.lastIndexOf('/');
|
|
493
|
+
if (slashIndex >= 0) {
|
|
494
|
+
fileName = fileName.substring(slashIndex + 1);
|
|
495
|
+
}
|
|
496
|
+
if (fileName.isBlank()) {
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
String guessedType = URLConnection.guessContentTypeFromName(fileName);
|
|
501
|
+
if (guessedType != null && !guessedType.isBlank()) {
|
|
502
|
+
return guessedType;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
int dotIndex = fileName.lastIndexOf('.');
|
|
506
|
+
if (dotIndex < 0 || dotIndex == fileName.length() - 1) {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
String extension = fileName.substring(dotIndex + 1).toLowerCase(Locale.US);
|
|
510
|
+
return switch (extension) {
|
|
511
|
+
case "svg" -> "image/svg+xml";
|
|
512
|
+
case "png" -> "image/png";
|
|
513
|
+
case "jpg", "jpeg" -> "image/jpeg";
|
|
514
|
+
case "gif" -> "image/gif";
|
|
515
|
+
case "webp" -> "image/webp";
|
|
516
|
+
case "pdf" -> "application/pdf";
|
|
517
|
+
case "json" -> "application/json";
|
|
518
|
+
case "txt" -> "text/plain";
|
|
519
|
+
case "csv" -> "text/csv";
|
|
520
|
+
case "html", "htm" -> "text/html";
|
|
521
|
+
case "xhtml" -> "application/xhtml+xml";
|
|
522
|
+
default -> null;
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
private static boolean shouldNormalizeSvgContentType(String requestUrl, String mimeType) {
|
|
527
|
+
if (!"svg".equalsIgnoreCase(fileExtensionFromRequestUrl(requestUrl))) {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
String normalizedMimeType = mimeType.toLowerCase(Locale.US);
|
|
531
|
+
return (
|
|
532
|
+
isGenericProxiedMimeType(normalizedMimeType) ||
|
|
533
|
+
"text/xml".equals(normalizedMimeType) ||
|
|
534
|
+
"application/xml".equals(normalizedMimeType)
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
private static String fileExtensionFromRequestUrl(String requestUrl) {
|
|
539
|
+
if (requestUrl == null || requestUrl.isBlank()) {
|
|
540
|
+
return "";
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
String path = new URL(requestUrl).getPath();
|
|
544
|
+
if (path == null || path.isBlank()) {
|
|
545
|
+
return "";
|
|
546
|
+
}
|
|
547
|
+
int slashIndex = path.lastIndexOf('/');
|
|
548
|
+
String fileName = slashIndex >= 0 ? path.substring(slashIndex + 1) : path;
|
|
549
|
+
int dotIndex = fileName.lastIndexOf('.');
|
|
550
|
+
if (dotIndex < 0 || dotIndex == fileName.length() - 1) {
|
|
551
|
+
return "";
|
|
552
|
+
}
|
|
553
|
+
return fileName.substring(dotIndex + 1);
|
|
554
|
+
} catch (Exception error) {
|
|
555
|
+
return "";
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private static String extractMimeType(String contentType) {
|
|
560
|
+
if (contentType == null || contentType.isBlank()) {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
String mimeType = contentType.split(";")[0].trim();
|
|
564
|
+
return mimeType.isEmpty() ? null : mimeType;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private static boolean isGenericProxiedMimeType(String mimeType) {
|
|
568
|
+
if (mimeType == null || mimeType.isBlank()) {
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
String normalizedMimeType = mimeType.trim().toLowerCase(Locale.US);
|
|
572
|
+
return (
|
|
573
|
+
normalizedMimeType.equals("application/octet-stream") ||
|
|
574
|
+
normalizedMimeType.equals("binary/octet-stream") ||
|
|
575
|
+
normalizedMimeType.equals("application/download") ||
|
|
576
|
+
normalizedMimeType.equals("application/x-download") ||
|
|
577
|
+
normalizedMimeType.equals("application/binary") ||
|
|
578
|
+
normalizedMimeType.equals("application/x-binary")
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
private static boolean shouldDeferConstructorMimeTypeToContentTypeHeader(String contentTypeHeader) {
|
|
583
|
+
String mimeType = extractMimeType(contentTypeHeader);
|
|
584
|
+
if (mimeType == null) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
return mimeType.toLowerCase(Locale.US).startsWith("multipart/");
|
|
588
|
+
}
|
|
589
|
+
|
|
418
590
|
static ParsedResponseHeaders splitResponseHeaders(Map<String, List<String>> rawHeaders) {
|
|
419
591
|
Map<String, String> responseHeaders = new HashMap<>();
|
|
420
592
|
List<String> cookieHeaders = new java.util.ArrayList<>();
|
|
@@ -831,6 +1003,9 @@ final class ProxyRequestSupport {
|
|
|
831
1003
|
return false;
|
|
832
1004
|
}
|
|
833
1005
|
String normalizedMimeType = mimeType.trim().toLowerCase(Locale.US);
|
|
1006
|
+
if (normalizedMimeType.startsWith("image/")) {
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
834
1009
|
return (
|
|
835
1010
|
normalizedMimeType.startsWith("text/") ||
|
|
836
1011
|
normalizedMimeType.equals("application/json") ||
|
|
@@ -334,12 +334,13 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
334
334
|
private boolean datePickerInjected = false; // Track if we've injected date picker fixes
|
|
335
335
|
private final WebView capacitorWebView;
|
|
336
336
|
private String instanceId = "";
|
|
337
|
-
private final Map<String, ProxiedRequest> proxiedRequestsHashmap = new
|
|
337
|
+
private final Map<String, ProxiedRequest> proxiedRequestsHashmap = new ConcurrentHashMap<>();
|
|
338
338
|
private ProxyBridge proxyBridge;
|
|
339
339
|
private String proxyBridgeScript;
|
|
340
340
|
private String proxyAccessToken;
|
|
341
341
|
private final ExecutorService executorService = Executors.newCachedThreadPool();
|
|
342
342
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
343
|
+
private final AtomicBoolean cookieFlushScheduled = new AtomicBoolean(false);
|
|
343
344
|
private final Map<String, BlobDownloadSession> blobDownloadSessions = new ConcurrentHashMap<>();
|
|
344
345
|
private final Map<String, ClientCertificateIdentity> clientCertificateIdentities = new ConcurrentHashMap<>();
|
|
345
346
|
private int iconColor = Color.BLACK; // Default icon color
|
|
@@ -4906,7 +4907,9 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
4906
4907
|
}
|
|
4907
4908
|
String initiatorUrl = request.getRequestHeaders().get("Referer");
|
|
4908
4909
|
if (initiatorUrl == null || initiatorUrl.isBlank()) {
|
|
4909
|
-
initiatorUrl =
|
|
4910
|
+
initiatorUrl = getWebViewUrlOrFallback(
|
|
4911
|
+
_options != null && _options.getUrl() != null ? _options.getUrl() : originalUrl
|
|
4912
|
+
);
|
|
4910
4913
|
}
|
|
4911
4914
|
String targetCookies = CookieManager.getInstance().getCookie(originalUrl);
|
|
4912
4915
|
if (
|
|
@@ -5027,7 +5030,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
5027
5030
|
nativeResponse = performNativeRequest(requestContext);
|
|
5028
5031
|
} catch (IOException error) {
|
|
5029
5032
|
Log.e("InAppBrowserProxy", "Native request failed for: " + requestContext.url, error);
|
|
5030
|
-
return
|
|
5033
|
+
return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
|
|
5031
5034
|
}
|
|
5032
5035
|
}
|
|
5033
5036
|
|
|
@@ -5040,7 +5043,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
5040
5043
|
redirectReplay = followRedirectForWebView(requestContext, nativeResponse, redirectsFollowed);
|
|
5041
5044
|
} catch (IOException error) {
|
|
5042
5045
|
Log.e("InAppBrowserProxy", "Native redirect replay failed for: " + requestContext.url, error);
|
|
5043
|
-
return
|
|
5046
|
+
return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
|
|
5044
5047
|
}
|
|
5045
5048
|
if (redirectReplay != null) {
|
|
5046
5049
|
requestContext = redirectReplay.requestContext;
|
|
@@ -5103,7 +5106,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
5103
5106
|
redirectReplay = followRedirectForWebView(requestContext, nativeResponse, redirectsFollowed);
|
|
5104
5107
|
} catch (IOException error) {
|
|
5105
5108
|
Log.e("InAppBrowserProxy", "Native redirect replay failed for: " + requestContext.url, error);
|
|
5106
|
-
return
|
|
5109
|
+
return createProxiedNativeFailureResponse(requestContext.url, bridgeBackedRequest, error);
|
|
5107
5110
|
}
|
|
5108
5111
|
if (redirectReplay != null) {
|
|
5109
5112
|
requestContext = redirectReplay.requestContext;
|
|
@@ -5283,8 +5286,13 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
5283
5286
|
if (view == null || _webView == null) {
|
|
5284
5287
|
return;
|
|
5285
5288
|
}
|
|
5286
|
-
|
|
5287
|
-
|
|
5289
|
+
if (request == null || !request.isForMainFrame()) {
|
|
5290
|
+
return;
|
|
5291
|
+
}
|
|
5292
|
+
if (_options.getCallbacks() != null) {
|
|
5293
|
+
_options.getCallbacks().pageLoadError();
|
|
5294
|
+
}
|
|
5295
|
+
if (!isInitialized) {
|
|
5288
5296
|
CharSequence description = error != null ? error.getDescription() : null;
|
|
5289
5297
|
String message = description != null ? "Initial page load failed: " + description : "Initial page load failed";
|
|
5290
5298
|
rejectOpenWebViewIfNeeded(message);
|
|
@@ -5538,9 +5546,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
5538
5546
|
shutdownExecutorServiceAsync();
|
|
5539
5547
|
|
|
5540
5548
|
// Clear any remaining proxied requests
|
|
5541
|
-
|
|
5542
|
-
proxiedRequestsHashmap.clear();
|
|
5543
|
-
}
|
|
5549
|
+
proxiedRequestsHashmap.clear();
|
|
5544
5550
|
|
|
5545
5551
|
try {
|
|
5546
5552
|
super.dismiss();
|
|
@@ -5768,10 +5774,29 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
5768
5774
|
}
|
|
5769
5775
|
|
|
5770
5776
|
private boolean shouldBootstrapInitialLegacyProxyLoad() {
|
|
5771
|
-
|
|
5772
|
-
|
|
5777
|
+
return ProxyRequestSupport.shouldBootstrapInitialProxyLoad(_options);
|
|
5778
|
+
}
|
|
5779
|
+
|
|
5780
|
+
private void loadInitialUrlDirect(Map<String, String> requestHeaders, String httpMethod, String httpBody) {
|
|
5781
|
+
if (_webView == null || _options == null) {
|
|
5782
|
+
return;
|
|
5783
|
+
}
|
|
5784
|
+
|
|
5785
|
+
Map<String, String> headers = requestHeaders != null ? requestHeaders : new HashMap<>();
|
|
5786
|
+
if (supportsRequestBody(httpMethod) && httpBody != null) {
|
|
5787
|
+
byte[] postData = httpBody.getBytes(StandardCharsets.UTF_8);
|
|
5788
|
+
_webView.postUrl(_options.getUrl(), postData);
|
|
5789
|
+
if (!headers.isEmpty()) {
|
|
5790
|
+
Log.w(
|
|
5791
|
+
"InAppBrowser",
|
|
5792
|
+
"Custom headers were provided but may not be sent with POST request. " +
|
|
5793
|
+
"Android WebView's postUrl method has limited header support."
|
|
5794
|
+
);
|
|
5795
|
+
}
|
|
5796
|
+
return;
|
|
5773
5797
|
}
|
|
5774
|
-
|
|
5798
|
+
|
|
5799
|
+
_webView.loadUrl(_options.getUrl(), headers);
|
|
5775
5800
|
}
|
|
5776
5801
|
|
|
5777
5802
|
private void loadInitialLegacyProxyContent(Map<String, String> requestHeaders, String httpMethod, String httpBody) {
|
|
@@ -5802,7 +5827,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
5802
5827
|
|
|
5803
5828
|
NativeResponseData responseData = proxyResult.responseData;
|
|
5804
5829
|
if (responseData == null || !canBootstrapHtmlResponse(responseData.contentType)) {
|
|
5805
|
-
|
|
5830
|
+
if (_webView == null) {
|
|
5831
|
+
return;
|
|
5832
|
+
}
|
|
5833
|
+
_webView.post(() -> loadInitialUrlDirect(initialHeaders, initialMethod, httpBody));
|
|
5806
5834
|
return;
|
|
5807
5835
|
}
|
|
5808
5836
|
|
|
@@ -5826,7 +5854,19 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
5826
5854
|
});
|
|
5827
5855
|
} catch (IOException error) {
|
|
5828
5856
|
Log.e("InAppBrowserProxy", "Initial legacy proxy bootstrap failed for: " + initialUrl, error);
|
|
5829
|
-
|
|
5857
|
+
if (_webView == null) {
|
|
5858
|
+
return;
|
|
5859
|
+
}
|
|
5860
|
+
String failureBody = new String(
|
|
5861
|
+
ProxyRequestSupport.createNativeRequestFailureBody(initialUrl, error),
|
|
5862
|
+
StandardCharsets.UTF_8
|
|
5863
|
+
);
|
|
5864
|
+
_webView.post(() -> {
|
|
5865
|
+
if (_webView == null) {
|
|
5866
|
+
return;
|
|
5867
|
+
}
|
|
5868
|
+
_webView.loadDataWithBaseURL(initialUrl, failureBody, "text/plain", "utf-8", initialUrl);
|
|
5869
|
+
});
|
|
5830
5870
|
}
|
|
5831
5871
|
});
|
|
5832
5872
|
}
|
|
@@ -5973,6 +6013,13 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
5973
6013
|
return response;
|
|
5974
6014
|
}
|
|
5975
6015
|
|
|
6016
|
+
private WebResourceResponse createProxiedNativeFailureResponse(String requestUrl, boolean bridgeBackedRequest, IOException error) {
|
|
6017
|
+
if (!ProxyRequestSupport.shouldReturnSyntheticNativeFailure(bridgeBackedRequest, _options, requestUrl)) {
|
|
6018
|
+
return null;
|
|
6019
|
+
}
|
|
6020
|
+
return createNativeFailureResponse(requestUrl, error);
|
|
6021
|
+
}
|
|
6022
|
+
|
|
5976
6023
|
private WebResourceResponse createNativeFailureResponse(String requestUrl, IOException error) {
|
|
5977
6024
|
NativeResponseData responseData = new NativeResponseData(
|
|
5978
6025
|
ProxyRequestSupport.SYNTHETIC_NATIVE_FAILURE_STATUS,
|
|
@@ -6135,7 +6182,13 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
6135
6182
|
contentType = conn.getContentType();
|
|
6136
6183
|
}
|
|
6137
6184
|
|
|
6138
|
-
|
|
6185
|
+
ProxyRequestSupport.ProxiedResponseMetadata normalizedResponse = ProxyRequestSupport.normalizeProxiedResponseMetadata(
|
|
6186
|
+
contentType,
|
|
6187
|
+
responseHeaders,
|
|
6188
|
+
requestContext.url
|
|
6189
|
+
);
|
|
6190
|
+
|
|
6191
|
+
return new NativeResponseData(status, normalizedResponse.contentType(), normalizedResponse.headers(), bodyBytes);
|
|
6139
6192
|
} finally {
|
|
6140
6193
|
conn.disconnect();
|
|
6141
6194
|
}
|
|
@@ -6155,15 +6208,22 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
6155
6208
|
wroteCookie = true;
|
|
6156
6209
|
}
|
|
6157
6210
|
if (wroteCookie) {
|
|
6158
|
-
|
|
6211
|
+
scheduleCookieFlush();
|
|
6159
6212
|
}
|
|
6160
6213
|
}
|
|
6161
6214
|
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
proxiedRequest = proxiedRequestsHashmap.get(requestId);
|
|
6215
|
+
private void scheduleCookieFlush() {
|
|
6216
|
+
if (!cookieFlushScheduled.compareAndSet(false, true)) {
|
|
6217
|
+
return;
|
|
6166
6218
|
}
|
|
6219
|
+
mainHandler.post(() -> {
|
|
6220
|
+
cookieFlushScheduled.set(false);
|
|
6221
|
+
CookieManager.getInstance().flush();
|
|
6222
|
+
});
|
|
6223
|
+
}
|
|
6224
|
+
|
|
6225
|
+
public void handleProxyResponse(String requestId, JSObject response) {
|
|
6226
|
+
ProxiedRequest proxiedRequest = proxiedRequestsHashmap.get(requestId);
|
|
6167
6227
|
if (proxiedRequest == null) {
|
|
6168
6228
|
Log.e("InAppBrowserProxy", "No pending request for id: " + requestId);
|
|
6169
6229
|
return;
|
|
@@ -6175,9 +6235,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
6175
6235
|
}
|
|
6176
6236
|
|
|
6177
6237
|
if (response == null) {
|
|
6178
|
-
|
|
6179
|
-
proxiedRequestsHashmap.remove(requestId);
|
|
6180
|
-
}
|
|
6238
|
+
proxiedRequestsHashmap.remove(requestId);
|
|
6181
6239
|
proxiedRequest.semaphore.release();
|
|
6182
6240
|
return;
|
|
6183
6241
|
}
|
|
@@ -6261,14 +6319,21 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
6261
6319
|
if (contentType == null) {
|
|
6262
6320
|
contentType = responseHeaders.get("Content-Type");
|
|
6263
6321
|
}
|
|
6264
|
-
|
|
6265
|
-
contentType
|
|
6266
|
-
|
|
6322
|
+
ProxyRequestSupport.ProxiedResponseMetadata normalizedResponse = ProxyRequestSupport.normalizeProxiedResponseMetadata(
|
|
6323
|
+
contentType,
|
|
6324
|
+
responseHeaders,
|
|
6325
|
+
proxiedRequest.requestContext != null ? proxiedRequest.requestContext.url : null
|
|
6326
|
+
);
|
|
6267
6327
|
|
|
6268
6328
|
if (status < 100 || status > 599) {
|
|
6269
6329
|
status = 200;
|
|
6270
6330
|
}
|
|
6271
|
-
proxiedRequest.nativeResponse = new NativeResponseData(
|
|
6331
|
+
proxiedRequest.nativeResponse = new NativeResponseData(
|
|
6332
|
+
status,
|
|
6333
|
+
normalizedResponse.contentType(),
|
|
6334
|
+
normalizedResponse.headers(),
|
|
6335
|
+
bodyBytes
|
|
6336
|
+
);
|
|
6272
6337
|
proxiedRequest.response = buildWebResourceResponse(proxiedRequest.nativeResponse);
|
|
6273
6338
|
}
|
|
6274
6339
|
} catch (IOException invalidBodyError) {
|
|
@@ -6279,17 +6344,13 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
|
|
|
6279
6344
|
}
|
|
6280
6345
|
}
|
|
6281
6346
|
|
|
6282
|
-
|
|
6283
|
-
proxiedRequestsHashmap.remove(requestId);
|
|
6284
|
-
}
|
|
6347
|
+
proxiedRequestsHashmap.remove(requestId);
|
|
6285
6348
|
proxiedRequest.semaphore.release();
|
|
6286
6349
|
}
|
|
6287
6350
|
|
|
6288
6351
|
@Override
|
|
6289
6352
|
public boolean hasPendingProxyRequest(String requestId) {
|
|
6290
|
-
|
|
6291
|
-
return proxiedRequestsHashmap.containsKey(requestId);
|
|
6292
|
-
}
|
|
6353
|
+
return proxiedRequestsHashmap.containsKey(requestId);
|
|
6293
6354
|
}
|
|
6294
6355
|
|
|
6295
6356
|
private void shareUrl() {
|
|
@@ -98,7 +98,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
98
98
|
case aware = "AWARE"
|
|
99
99
|
case fakeVisible = "FAKE_VISIBLE"
|
|
100
100
|
}
|
|
101
|
-
private let pluginVersion: String = "8.7.
|
|
101
|
+
private let pluginVersion: String = "8.7.7"
|
|
102
102
|
public let identifier = "CapgoInAppBrowserPlugin"
|
|
103
103
|
public let jsName = "CapgoInAppBrowser"
|
|
104
104
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -2129,7 +2129,9 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2129
2129
|
responseDict = dict
|
|
2130
2130
|
}
|
|
2131
2131
|
|
|
2132
|
-
|
|
2132
|
+
DispatchQueue.global(qos: .userInitiated).async { [proxyHandler] in
|
|
2133
|
+
proxyHandler.handleResponse(requestId: requestId, phase: phase, responseData: responseDict)
|
|
2134
|
+
}
|
|
2133
2135
|
call.resolve()
|
|
2134
2136
|
}
|
|
2135
2137
|
|
|
@@ -51,11 +51,8 @@ public final class ProxyBridge {
|
|
|
51
51
|
func getAndRemove(requestId: String) -> ProxyBridgeStoredRequest? {
|
|
52
52
|
lock.lock()
|
|
53
53
|
defer { lock.unlock() }
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
storedRequestOrder.removeAll { $0 == requestId }
|
|
58
|
-
return storedRequest
|
|
54
|
+
// Avoid O(n) queue scans on every parallel bridge request; stale ids are purged lazily.
|
|
55
|
+
return storedRequests.removeValue(forKey: requestId)
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
private func cleanupExpiredRequests(now: Date) {
|