@capgo/capacitor-inappbrowser 8.6.32 → 8.6.34

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.
@@ -11,6 +11,7 @@ Pod::Spec.new do |s|
11
11
  s.author = package['author']
12
12
  s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
13
  s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.resources = ['ios/Sources/InAppBrowserPlugin/Resources/proxy-bridge.js']
14
15
  s.ios.deployment_target = '15.0'
15
16
  s.dependency 'Capacitor'
16
17
  s.swift_version = '5.1'
package/Package.swift CHANGED
@@ -19,7 +19,8 @@ let package = Package(
19
19
  .product(name: "Capacitor", package: "capacitor-swift-pm"),
20
20
  .product(name: "Cordova", package: "capacitor-swift-pm")
21
21
  ],
22
- path: "ios/Sources/InAppBrowserPlugin"),
22
+ path: "ios/Sources/InAppBrowserPlugin",
23
+ resources: [.process("Resources")]),
23
24
  .testTarget(
24
25
  name: "InappbrowserPluginTests",
25
26
  dependencies: ["InappbrowserPlugin"],
@@ -6053,6 +6053,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
6053
6053
 
6054
6054
  // If both width and height are specified, use custom dimensions
6055
6055
  if (width != null && height != null) {
6056
+ params.gravity = Gravity.TOP | Gravity.LEFT;
6056
6057
  params.width = (int) getPixels(width);
6057
6058
  params.height = (int) getPixels(height);
6058
6059
  params.x = (x != null) ? (int) getPixels(x) : 0;
@@ -6060,6 +6061,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
6060
6061
  params.gravity = Gravity.TOP | Gravity.START;
6061
6062
  } else if (height != null && width == null) {
6062
6063
  // If only height is specified, use custom height with fullscreen width
6064
+ params.gravity = Gravity.TOP | Gravity.LEFT;
6063
6065
  params.width = WindowManager.LayoutParams.MATCH_PARENT;
6064
6066
  params.height = (int) getPixels(height);
6065
6067
  params.x = 0;
@@ -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.6.32"
101
+ private let pluginVersion: String = "8.6.34"
102
102
  public let identifier = "CapgoInAppBrowserPlugin"
103
103
  public let jsName = "CapgoInAppBrowser"
104
104
  public let pluginMethods: [CAPPluginMethod] = [
@@ -135,6 +135,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
135
135
  var webViewController: WKWebViewController?
136
136
  private var webViewControllers: [String: WKWebViewController] = [:]
137
137
  private var proxySchemeHandlers: [String: ProxySchemeHandler] = [:]
138
+ private var proxyBridges: [String: ProxyBridge] = [:]
138
139
  private var webViewStack: [String] = []
139
140
  private var activeWebViewId: String?
140
141
  private weak var presentationContainerView: UIView?
@@ -186,6 +187,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
186
187
  }
187
188
  proxySchemeHandlers[id]?.cancelAllPendingTasks()
188
189
  proxySchemeHandlers[id] = nil
190
+ proxyBridges[id] = nil
189
191
  webViewControllers[id] = nil
190
192
  navigationControllers[id] = nil
191
193
  webViewStack.removeAll { $0 == id }
@@ -893,16 +895,23 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
893
895
  }
894
896
 
895
897
  var proxyHandler: ProxySchemeHandler?
898
+ var proxyBridge: ProxyBridge?
899
+ var proxyBridgeAccessToken: String?
896
900
  if legacyProxyRequests.isEnabled || !outboundProxyRules.isEmpty || !inboundProxyRules.isEmpty {
901
+ let bridgeAccessToken = UUID().uuidString
902
+ proxyBridgeAccessToken = bridgeAccessToken
903
+ proxyBridge = ProxyBridge(accessToken: bridgeAccessToken)
897
904
  proxyHandler = ProxySchemeHandler(
898
905
  plugin: self,
899
906
  webviewId: webViewId,
900
907
  legacyProxyRequests: legacyProxyRequests.isEnabled,
901
908
  legacyProxyRequestURLRegex: legacyProxyRequests.urlRegex,
902
909
  outboundRules: outboundProxyRules,
903
- inboundRules: inboundProxyRules
910
+ inboundRules: inboundProxyRules,
911
+ proxyBridge: proxyBridge
904
912
  )
905
913
  self.proxySchemeHandlers[webViewId] = proxyHandler
914
+ self.proxyBridges[webViewId] = proxyBridge
906
915
  }
907
916
 
908
917
  self.webViewController = WKWebViewController.init(
@@ -921,6 +930,9 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
921
930
  captureConsoleLogs: captureConsoleLogs,
922
931
  proxyRequests: legacyProxyRequests.isEnabled,
923
932
  proxySchemeHandler: proxyHandler,
933
+ proxyBridge: proxyBridge,
934
+ proxyBridgeAccessToken: proxyBridgeAccessToken,
935
+ legacyProxyRequestURLRegexPattern: legacyProxyRequests.urlRegex?.pattern,
924
936
  documentStartUserScripts: self.documentStartUserScripts(
925
937
  authorizedAppLinks: authorizedAppLinks,
926
938
  openBlankTargetInWebView: openBlankTargetInWebView
@@ -0,0 +1,210 @@
1
+ import Foundation
2
+
3
+ struct ProxyBridgeStoredRequest {
4
+ let method: String
5
+ let headersJson: String
6
+ let base64Body: String
7
+ let credentialsMode: String
8
+ let storedAt: Date
9
+ }
10
+
11
+ public final class ProxyBridge {
12
+ static let storedRequestTTL: TimeInterval = 120
13
+
14
+ private let accessToken: String
15
+ private var storedRequests: [String: ProxyBridgeStoredRequest] = [:]
16
+ private var storedRequestOrder: [String] = []
17
+ private let lock = NSLock()
18
+ private let clock: () -> Date
19
+
20
+ init(accessToken: String, clock: @escaping () -> Date = Date.init) {
21
+ self.accessToken = accessToken
22
+ self.clock = clock
23
+ }
24
+
25
+ func storeRequest(
26
+ token: String,
27
+ requestId: String,
28
+ method: String,
29
+ headersJson: String,
30
+ base64Body: String,
31
+ credentialsMode: String
32
+ ) {
33
+ guard token == accessToken, !requestId.isEmpty else {
34
+ return
35
+ }
36
+
37
+ let now = clock()
38
+ lock.lock()
39
+ cleanupExpiredRequests(now: now)
40
+ storedRequests[requestId] = ProxyBridgeStoredRequest(
41
+ method: method,
42
+ headersJson: headersJson,
43
+ base64Body: base64Body,
44
+ credentialsMode: credentialsMode,
45
+ storedAt: now
46
+ )
47
+ storedRequestOrder.append(requestId)
48
+ lock.unlock()
49
+ }
50
+
51
+ func getAndRemove(requestId: String) -> ProxyBridgeStoredRequest? {
52
+ lock.lock()
53
+ defer { lock.unlock() }
54
+ guard let storedRequest = storedRequests.removeValue(forKey: requestId) else {
55
+ return nil
56
+ }
57
+ storedRequestOrder.removeAll { $0 == requestId }
58
+ return storedRequest
59
+ }
60
+
61
+ private func cleanupExpiredRequests(now: Date) {
62
+ while let oldestRequestId = storedRequestOrder.first {
63
+ guard let oldestRequest = storedRequests[oldestRequestId] else {
64
+ storedRequestOrder.removeFirst()
65
+ continue
66
+ }
67
+
68
+ if now.timeIntervalSince(oldestRequest.storedAt) < Self.storedRequestTTL {
69
+ return
70
+ }
71
+
72
+ storedRequests.removeValue(forKey: oldestRequestId)
73
+ storedRequestOrder.removeFirst()
74
+ }
75
+ }
76
+ }
77
+
78
+ enum ProxyBridgeSupport {
79
+ struct BridgeMarkerRequest {
80
+ let originalURL: String
81
+ let requestId: String
82
+ }
83
+
84
+ static func isBridgeMarkerRequestURL(_ requestURL: String) -> Bool {
85
+ guard let components = URLComponents(string: requestURL),
86
+ components.path == "/_capgo_proxy_",
87
+ let query = components.query,
88
+ !query.isEmpty else {
89
+ return false
90
+ }
91
+ return true
92
+ }
93
+
94
+ static func parseBridgeMarkerRequest(_ requestURL: String) -> BridgeMarkerRequest? {
95
+ guard isBridgeMarkerRequestURL(requestURL),
96
+ let components = URLComponents(string: requestURL) else {
97
+ return nil
98
+ }
99
+
100
+ let queryItems = components.queryItems ?? []
101
+ guard let originalURL = queryItems.first(where: { $0.name == "u" })?.value,
102
+ let requestId = queryItems.first(where: { $0.name == "rid" })?.value,
103
+ !originalURL.isEmpty,
104
+ !requestId.isEmpty else {
105
+ return nil
106
+ }
107
+
108
+ return BridgeMarkerRequest(originalURL: originalURL, requestId: requestId)
109
+ }
110
+
111
+ static func decodeStoredHeaders(_ headersJson: String) -> [String: String] {
112
+ guard let data = headersJson.data(using: .utf8),
113
+ let headers = try? JSONSerialization.jsonObject(with: data) as? [String: String] else {
114
+ return [:]
115
+ }
116
+ return headers
117
+ }
118
+
119
+ static func shouldInjectBridge(hasProxySchemeHandler: Bool) -> Bool {
120
+ hasProxySchemeHandler
121
+ }
122
+
123
+ static func usesLegacyJsProxyMode(
124
+ legacyProxyRequests: Bool,
125
+ legacyProxyRequestURLRegex: NSRegularExpression?,
126
+ hasOutboundRules: Bool,
127
+ hasInboundRules: Bool
128
+ ) -> Bool {
129
+ (legacyProxyRequests || legacyProxyRequestURLRegex != nil) &&
130
+ hasOutboundRules == false &&
131
+ hasInboundRules == false
132
+ }
133
+
134
+ static func shouldDelegateLegacyJsProxyRequest(
135
+ legacyProxyRequests: Bool,
136
+ legacyProxyRequestURLRegex: NSRegularExpression?,
137
+ hasOutboundRules: Bool,
138
+ hasInboundRules: Bool,
139
+ requestURL: String
140
+ ) -> Bool {
141
+ guard usesLegacyJsProxyMode(
142
+ legacyProxyRequests: legacyProxyRequests,
143
+ legacyProxyRequestURLRegex: legacyProxyRequestURLRegex,
144
+ hasOutboundRules: hasOutboundRules,
145
+ hasInboundRules: hasInboundRules
146
+ ) else {
147
+ return false
148
+ }
149
+
150
+ guard let legacyProxyRequestURLRegex else {
151
+ return legacyProxyRequests
152
+ }
153
+
154
+ let range = NSRange(location: 0, length: requestURL.utf16.count)
155
+ return legacyProxyRequestURLRegex.firstMatch(in: requestURL, options: [], range: range) != nil
156
+ }
157
+
158
+ static func escapeJavaScriptLiteral(_ value: String) -> String {
159
+ value
160
+ .replacingOccurrences(of: "\\", with: "\\\\")
161
+ .replacingOccurrences(of: "'", with: "\\'")
162
+ .replacingOccurrences(of: "\r", with: "\\r")
163
+ .replacingOccurrences(of: "\n", with: "\\n")
164
+ .replacingOccurrences(of: "\u{2028}", with: "\\u2028")
165
+ .replacingOccurrences(of: "\u{2029}", with: "\\u2029")
166
+ }
167
+
168
+ static func bootstrapScriptSource() -> String {
169
+ """
170
+ (function() {
171
+ if (window.__capgoProxy && window.__capgoProxy.__capgoNativeBridgeInstalled) {
172
+ return;
173
+ }
174
+ window.__capgoProxy = {
175
+ __capgoNativeBridgeInstalled: true,
176
+ storeRequest: function(token, requestId, method, headersJson, base64Body, credentialsMode) {
177
+ if (!window.webkit || !window.webkit.messageHandlers || !window.webkit.messageHandlers.capgoProxyBridge) {
178
+ return;
179
+ }
180
+ window.webkit.messageHandlers.capgoProxyBridge.postMessage({
181
+ token: token,
182
+ requestId: requestId,
183
+ method: method,
184
+ headersJson: headersJson,
185
+ base64Body: base64Body,
186
+ credentialsMode: credentialsMode
187
+ });
188
+ }
189
+ };
190
+ })();
191
+ """
192
+ }
193
+
194
+ static func loadBundledBridgeScript(accessToken: String, proxyRegexSource: String) -> String? {
195
+ #if SWIFT_PACKAGE
196
+ let bundle = Bundle.module
197
+ #else
198
+ let bundle = Bundle(for: ProxyBridge.self)
199
+ #endif
200
+
201
+ guard let scriptURL = bundle.url(forResource: "proxy-bridge", withExtension: "js"),
202
+ let rawScript = try? String(contentsOf: scriptURL) else {
203
+ return nil
204
+ }
205
+
206
+ return rawScript
207
+ .replacingOccurrences(of: "___CAPGO_PROXY_TOKEN___", with: escapeJavaScriptLiteral(accessToken))
208
+ .replacingOccurrences(of: "___CAPGO_PROXY_REGEX___", with: escapeJavaScriptLiteral(proxyRegexSource))
209
+ }
210
+ }
@@ -460,16 +460,17 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
460
460
  private let legacyProxyRequestURLRegex: NSRegularExpression?
461
461
  private let outboundRules: [NativeProxyRule]
462
462
  private let inboundRules: [NativeProxyRule]
463
+ private weak var proxyBridge: ProxyBridge?
463
464
 
464
465
  private lazy var session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
465
-
466
466
  init(
467
467
  plugin: CapgoInAppBrowserPlugin,
468
468
  webviewId: String,
469
469
  legacyProxyRequests: Bool,
470
470
  legacyProxyRequestURLRegex: NSRegularExpression?,
471
471
  outboundRules: [NativeProxyRule],
472
- inboundRules: [NativeProxyRule]
472
+ inboundRules: [NativeProxyRule],
473
+ proxyBridge: ProxyBridge? = nil
473
474
  ) {
474
475
  self.plugin = plugin
475
476
  self.webviewId = webviewId
@@ -477,6 +478,7 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
477
478
  self.legacyProxyRequestURLRegex = legacyProxyRequestURLRegex
478
479
  self.outboundRules = outboundRules
479
480
  self.inboundRules = inboundRules
481
+ self.proxyBridge = proxyBridge
480
482
  super.init()
481
483
  }
482
484
 
@@ -488,7 +490,8 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
488
490
  legacyProxyRequests: legacyProxyRequests,
489
491
  legacyProxyRequestURLRegex: legacyProxyRequestURLRegex,
490
492
  outboundRules: outboundRules,
491
- inboundRules: inboundRules
493
+ inboundRules: inboundRules,
494
+ proxyBridge: proxyBridge
492
495
  )
493
496
  }
494
497
 
@@ -505,13 +508,10 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
505
508
  }
506
509
 
507
510
  let requestId = UUID().uuidString
508
- let requestContext = NativeRequestContext(
509
- url: url.absoluteString,
510
- method: ProxySchemeRequestSupport.normalizedRequestMethod(urlSchemeTask.request.httpMethod),
511
- headers: urlSchemeTask.request.allHTTPHeaderFields ?? [:],
512
- base64Body: extractBody(from: urlSchemeTask.request),
513
- isMainFrame: ProxySchemeRequestSupport.isMainFrameRequest(urlSchemeTask.request)
514
- )
511
+ guard let requestContext = makeInitialRequestContext(from: urlSchemeTask, requestURL: url) else {
512
+ finishCanceledSchemeTask(urlSchemeTask)
513
+ return
514
+ }
515
515
  let pendingTask = PendingProxyTask(requestId: requestId, schemeTask: urlSchemeTask, requestContext: requestContext, phase: "outbound")
516
516
 
517
517
  taskLock.lock()
@@ -1169,6 +1169,80 @@ public class ProxySchemeHandler: NSObject, WKURLSchemeHandler, URLSessionTaskDel
1169
1169
  }
1170
1170
  }
1171
1171
 
1172
+
1173
+ private func makeInitialRequestContext(from urlSchemeTask: WKURLSchemeTask, requestURL: URL) -> NativeRequestContext? {
1174
+ let requestURLString = requestURL.absoluteString
1175
+
1176
+ if let bridgeRequest = ProxyBridgeSupport.parseBridgeMarkerRequest(requestURLString) {
1177
+ if ProxyBridgeSupport.usesLegacyJsProxyMode(
1178
+ legacyProxyRequests: legacyProxyRequests,
1179
+ legacyProxyRequestURLRegex: legacyProxyRequestURLRegex,
1180
+ hasOutboundRules: !outboundRules.isEmpty,
1181
+ hasInboundRules: !inboundRules.isEmpty
1182
+ ),
1183
+ !ProxyBridgeSupport.shouldDelegateLegacyJsProxyRequest(
1184
+ legacyProxyRequests: legacyProxyRequests,
1185
+ legacyProxyRequestURLRegex: legacyProxyRequestURLRegex,
1186
+ hasOutboundRules: !outboundRules.isEmpty,
1187
+ hasInboundRules: !inboundRules.isEmpty,
1188
+ requestURL: bridgeRequest.originalURL
1189
+ ) {
1190
+ _ = proxyBridge?.getAndRemove(requestId: bridgeRequest.requestId)
1191
+ return nil
1192
+ }
1193
+
1194
+ guard let stored = proxyBridge?.getAndRemove(requestId: bridgeRequest.requestId) else {
1195
+ return nil
1196
+ }
1197
+
1198
+ var headers = ProxyBridgeSupport.decodeStoredHeaders(stored.headersJson)
1199
+ let markerHeaders = urlSchemeTask.request.allHTTPHeaderFields ?? [:]
1200
+ for (key, value) in markerHeaders where headers[key] == nil {
1201
+ headers[key] = value
1202
+ }
1203
+
1204
+ let base64Body = stored.base64Body.isEmpty ? nil : stored.base64Body
1205
+ return NativeRequestContext(
1206
+ url: bridgeRequest.originalURL,
1207
+ method: ProxySchemeRequestSupport.normalizedRequestMethod(stored.method),
1208
+ headers: headers,
1209
+ base64Body: base64Body,
1210
+ isMainFrame: ProxySchemeRequestSupport.isMainFrameRequest(urlSchemeTask.request)
1211
+ )
1212
+ }
1213
+
1214
+ return NativeRequestContext(
1215
+ url: requestURLString,
1216
+ method: ProxySchemeRequestSupport.normalizedRequestMethod(urlSchemeTask.request.httpMethod),
1217
+ headers: urlSchemeTask.request.allHTTPHeaderFields ?? [:],
1218
+ base64Body: extractBody(from: urlSchemeTask.request),
1219
+ isMainFrame: ProxySchemeRequestSupport.isMainFrameRequest(urlSchemeTask.request)
1220
+ )
1221
+ }
1222
+
1223
+ private func finishCanceledSchemeTask(_ schemeTask: WKURLSchemeTask) {
1224
+ guard let url = schemeTask.request.url,
1225
+ let httpResponse = HTTPURLResponse(
1226
+ url: url,
1227
+ statusCode: 204,
1228
+ httpVersion: "HTTP/1.1",
1229
+ headerFields: [:]
1230
+ )
1231
+ else {
1232
+ schemeTask.didFailWithError(
1233
+ NSError(
1234
+ domain: "ProxySchemeHandler",
1235
+ code: -4,
1236
+ userInfo: [NSLocalizedDescriptionKey: "Failed to create canceled response"]
1237
+ )
1238
+ )
1239
+ return
1240
+ }
1241
+
1242
+ schemeTask.didReceive(httpResponse)
1243
+ schemeTask.didReceive(Data())
1244
+ schemeTask.didFinish()
1245
+ }
1172
1246
  private func extractBody(from request: URLRequest) -> String? {
1173
1247
  if let body = request.httpBody {
1174
1248
  return body.base64EncodedString()
@@ -0,0 +1,586 @@
1
+ "use strict";
2
+ (() => {
3
+ var __async = (__this, __arguments, generator) => {
4
+ return new Promise((resolve, reject) => {
5
+ var fulfilled = (value) => {
6
+ try {
7
+ step(generator.next(value));
8
+ } catch (e) {
9
+ reject(e);
10
+ }
11
+ };
12
+ var rejected = (value) => {
13
+ try {
14
+ step(generator.throw(value));
15
+ } catch (e) {
16
+ reject(e);
17
+ }
18
+ };
19
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
20
+ step((generator = generator.apply(__this, __arguments)).next());
21
+ });
22
+ };
23
+
24
+ // src/proxy-bridge-support.ts
25
+ function findCapturedHeaderKey(headers, headerName) {
26
+ const normalizedHeaderName = headerName.toLowerCase();
27
+ for (const key of Object.keys(headers)) {
28
+ if (key.toLowerCase() === normalizedHeaderName) {
29
+ return key;
30
+ }
31
+ }
32
+ return null;
33
+ }
34
+ function isNativeProxyErrorResponse(response) {
35
+ return response.status === 599 && response.headers.get("x-capgo-proxy-error") !== null;
36
+ }
37
+ function nativeProxyErrorMessage(response) {
38
+ const nativeErrorHeader = response.headers.get("x-capgo-proxy-error");
39
+ if (!nativeErrorHeader) {
40
+ return "Network request failed";
41
+ }
42
+ const nativeMessage = nativeErrorHeader.trim();
43
+ return nativeMessage || "Network request failed";
44
+ }
45
+ function replaceCapturedHeader(headers, name, value) {
46
+ const existingKey = findCapturedHeaderKey(headers, name);
47
+ if (existingKey && existingKey !== name) {
48
+ delete headers[existingKey];
49
+ }
50
+ headers[name] = value;
51
+ }
52
+ function appendCapturedHeader(headers, name, value) {
53
+ const existingKey = findCapturedHeaderKey(headers, name);
54
+ if (!existingKey) {
55
+ headers[name] = value;
56
+ return;
57
+ }
58
+ headers[existingKey] = headers[existingKey] ? `${headers[existingKey]}, ${value}` : value;
59
+ }
60
+ function inferContentTypeFromBody(body) {
61
+ if (typeof body === "string") {
62
+ return "text/plain;charset=UTF-8";
63
+ }
64
+ if (body instanceof URLSearchParams) {
65
+ return "application/x-www-form-urlencoded;charset=UTF-8";
66
+ }
67
+ if (typeof Blob !== "undefined" && body instanceof Blob) {
68
+ return body.type || null;
69
+ }
70
+ return null;
71
+ }
72
+ function ensureInferredContentType(headers, body) {
73
+ if (findCapturedHeaderKey(headers, "content-type")) {
74
+ return;
75
+ }
76
+ const inferredContentType = inferContentTypeFromBody(body);
77
+ if (inferredContentType) {
78
+ headers["content-type"] = inferredContentType;
79
+ }
80
+ }
81
+ function resolveProxyBridgeUrl(rawUrl, baseUrl) {
82
+ try {
83
+ return new URL(rawUrl, baseUrl).href;
84
+ } catch (_error) {
85
+ return null;
86
+ }
87
+ }
88
+ function shouldProxyBridgeUrl(rawUrl, baseUrl, urlRegex) {
89
+ const resolvedUrl = resolveProxyBridgeUrl(rawUrl, baseUrl);
90
+ if (!resolvedUrl) {
91
+ return false;
92
+ }
93
+ const protocol = new URL(resolvedUrl).protocol.toLowerCase();
94
+ if (protocol !== "http:" && protocol !== "https:") {
95
+ return false;
96
+ }
97
+ return !urlRegex || urlRegex.test(resolvedUrl);
98
+ }
99
+ function getSubmitEventSubmitter(event, submitEventCtor) {
100
+ var _a, _b;
101
+ const resolvedSubmitEventCtor = submitEventCtor != null ? submitEventCtor : typeof SubmitEvent !== "undefined" ? SubmitEvent : void 0;
102
+ if (resolvedSubmitEventCtor && event instanceof resolvedSubmitEventCtor) {
103
+ return (_a = event.submitter) != null ? _a : null;
104
+ }
105
+ if ("submitter" in event) {
106
+ return (_b = event.submitter) != null ? _b : null;
107
+ }
108
+ return null;
109
+ }
110
+ function shouldProxySubmitEvent(defaultPrevented, canProxyTarget) {
111
+ return !defaultPrevented && canProxyTarget;
112
+ }
113
+ function shouldProxySubmitRequest(defaultPrevented, canProxyTarget, rawUrl, baseUrl, urlRegex) {
114
+ return shouldProxySubmitEvent(defaultPrevented, canProxyTarget) && shouldProxyBridgeUrl(rawUrl, baseUrl, urlRegex);
115
+ }
116
+ function consumeProxySubmitReplayBypass(form) {
117
+ if (!form.__capgoSkipNextProxySubmit) {
118
+ return false;
119
+ }
120
+ delete form.__capgoSkipNextProxySubmit;
121
+ return true;
122
+ }
123
+ function replaySubmitAfterProxyFailure(form, originalSubmit, submitter) {
124
+ if (typeof form.requestSubmit === "function") {
125
+ form.__capgoSkipNextProxySubmit = true;
126
+ try {
127
+ if (submitter !== null && submitter !== void 0) {
128
+ form.requestSubmit(submitter);
129
+ } else {
130
+ form.requestSubmit();
131
+ }
132
+ return;
133
+ } catch (_error) {
134
+ delete form.__capgoSkipNextProxySubmit;
135
+ }
136
+ }
137
+ originalSubmit.call(form);
138
+ }
139
+ function captureXhrReplayState(xhr) {
140
+ var _a, _b, _c, _d;
141
+ return {
142
+ responseType: (_a = xhr.responseType) != null ? _a : "",
143
+ timeout: (_b = xhr.timeout) != null ? _b : 0,
144
+ withCredentials: (_c = xhr.withCredentials) != null ? _c : false,
145
+ overrideMimeType: (_d = xhr.__proxyOverrideMimeType) != null ? _d : null
146
+ };
147
+ }
148
+ function restoreXhrReplayState(xhr, state) {
149
+ xhr.responseType = state.responseType;
150
+ xhr.timeout = state.timeout;
151
+ xhr.withCredentials = state.withCredentials;
152
+ xhr.__proxyOverrideMimeType = state.overrideMimeType;
153
+ if (state.overrideMimeType && typeof xhr.overrideMimeType === "function") {
154
+ xhr.overrideMimeType(state.overrideMimeType);
155
+ }
156
+ }
157
+
158
+ // src/proxy-bridge.ts
159
+ (function() {
160
+ const proxyBridge = window.__capgoProxy;
161
+ if (!proxyBridge) {
162
+ return;
163
+ }
164
+ const accessToken = "___CAPGO_PROXY_TOKEN___";
165
+ const proxyRegexSource = "___CAPGO_PROXY_REGEX___";
166
+ let requestCounter = 0;
167
+ let proxyRequestPattern = null;
168
+ if (proxyRegexSource) {
169
+ try {
170
+ proxyRequestPattern = new RegExp(proxyRegexSource);
171
+ } catch (_error) {
172
+ proxyRequestPattern = null;
173
+ }
174
+ }
175
+ function generateRequestId() {
176
+ return "pr_" + Date.now() + "_" + requestCounter++;
177
+ }
178
+ function arrayBufferToBase64(buffer) {
179
+ const bytes = new Uint8Array(buffer);
180
+ let binary = "";
181
+ for (let i = 0; i < bytes.byteLength; i++) {
182
+ binary += String.fromCharCode(bytes[i]);
183
+ }
184
+ return btoa(binary);
185
+ }
186
+ function stringToBase64(value) {
187
+ return btoa(unescape(encodeURIComponent(value)));
188
+ }
189
+ function hasHeader(headers, headerName) {
190
+ const normalizedHeaderName = headerName.toLowerCase();
191
+ return Object.keys(headers).some((key) => key.toLowerCase() === normalizedHeaderName);
192
+ }
193
+ function normalizeMethod(method) {
194
+ return (method || "GET").toUpperCase();
195
+ }
196
+ function normalizeCredentialsMode(mode) {
197
+ if (mode === "omit" || mode === "include") {
198
+ return mode;
199
+ }
200
+ return "same-origin";
201
+ }
202
+ function getDocumentBaseUrl() {
203
+ if (typeof document !== "undefined" && typeof document.baseURI === "string" && document.baseURI) {
204
+ return document.baseURI;
205
+ }
206
+ return window.location.href;
207
+ }
208
+ function resolveUrl(url) {
209
+ if (url && !url.match(/^[a-zA-Z][a-zA-Z0-9+\-.]*:\/\//)) {
210
+ try {
211
+ return new URL(url, getDocumentBaseUrl()).href;
212
+ } catch (_error) {
213
+ return url;
214
+ }
215
+ }
216
+ return url;
217
+ }
218
+ function methodSupportsRequestBody(method) {
219
+ const normalizedMethod = normalizeMethod(method);
220
+ return normalizedMethod !== "GET" && normalizedMethod !== "HEAD";
221
+ }
222
+ function bodyToBase64(body) {
223
+ return __async(this, null, function* () {
224
+ if (body === null || body === void 0) {
225
+ return null;
226
+ }
227
+ if (typeof body === "string") {
228
+ return stringToBase64(body);
229
+ }
230
+ if (body instanceof ArrayBuffer) {
231
+ return arrayBufferToBase64(body);
232
+ }
233
+ if (ArrayBuffer.isView(body)) {
234
+ return arrayBufferToBase64(body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength));
235
+ }
236
+ if (body instanceof Blob) {
237
+ const buffer = yield body.arrayBuffer();
238
+ return arrayBufferToBase64(buffer);
239
+ }
240
+ if (body instanceof FormData) {
241
+ const buffer = yield new Response(body).arrayBuffer();
242
+ return arrayBufferToBase64(buffer);
243
+ }
244
+ if (body instanceof URLSearchParams) {
245
+ return stringToBase64(body.toString());
246
+ }
247
+ return null;
248
+ });
249
+ }
250
+ function storeInterceptedRequest(url, method, headers, body, credentialsMode) {
251
+ return __async(this, null, function* () {
252
+ const requestId = generateRequestId();
253
+ const normalizedMethod = normalizeMethod(method);
254
+ let normalizedBody = body;
255
+ if (normalizedBody instanceof FormData) {
256
+ const encoded = new Response(normalizedBody);
257
+ const contentType = encoded.headers.get("content-type");
258
+ if (contentType) {
259
+ replaceCapturedHeader(headers, "content-type", contentType);
260
+ }
261
+ normalizedBody = yield encoded.arrayBuffer();
262
+ }
263
+ if (!methodSupportsRequestBody(normalizedMethod)) {
264
+ normalizedBody = null;
265
+ }
266
+ ensureInferredContentType(headers, normalizedBody);
267
+ const base64Body = yield bodyToBase64(normalizedBody);
268
+ if (normalizedBody !== null && normalizedBody !== void 0 && base64Body === null && methodSupportsRequestBody(normalizedMethod)) {
269
+ throw new Error(`[proxy-bridge] Unsupported request body for ${normalizedMethod} proxy replay`);
270
+ }
271
+ proxyBridge.storeRequest(
272
+ accessToken,
273
+ requestId,
274
+ normalizedMethod,
275
+ JSON.stringify(headers),
276
+ base64Body || "",
277
+ normalizeCredentialsMode(credentialsMode)
278
+ );
279
+ return "/_capgo_proxy_?u=" + encodeURIComponent(url) + "&rid=" + requestId;
280
+ });
281
+ }
282
+ function getSubmitterAttribute(submitter, attributeName) {
283
+ if (!(submitter instanceof HTMLElement)) {
284
+ return null;
285
+ }
286
+ return submitter.getAttribute(attributeName);
287
+ }
288
+ function createFormData(form, submitter) {
289
+ if (submitter instanceof HTMLElement) {
290
+ try {
291
+ return new FormData(form, submitter);
292
+ } catch (_error) {
293
+ }
294
+ }
295
+ return new FormData(form);
296
+ }
297
+ function appendFormDataToUrl(url, formData) {
298
+ const resolvedUrl = new URL(url, getDocumentBaseUrl());
299
+ const searchParams = new URLSearchParams(resolvedUrl.search);
300
+ formData.forEach((value, key) => {
301
+ searchParams.append(key, typeof value === "string" ? value : value.name);
302
+ });
303
+ resolvedUrl.search = searchParams.toString();
304
+ return resolvedUrl.toString();
305
+ }
306
+ function formDataToUrlSearchParams(formData) {
307
+ const searchParams = new URLSearchParams();
308
+ formData.forEach((value, key) => {
309
+ searchParams.append(key, typeof value === "string" ? value : value.name);
310
+ });
311
+ return searchParams;
312
+ }
313
+ function formDataToPlainText(formData) {
314
+ const lines = [];
315
+ formData.forEach((value, key) => {
316
+ lines.push(key + "=" + (typeof value === "string" ? value : value.name));
317
+ });
318
+ return lines.join("\r\n");
319
+ }
320
+ function resolveFormMethod(form, submitter) {
321
+ return normalizeMethod(getSubmitterAttribute(submitter, "formmethod") || form.getAttribute("method"));
322
+ }
323
+ function resolveFormAction(form, submitter) {
324
+ return resolveUrl(
325
+ getSubmitterAttribute(submitter, "formaction") || form.getAttribute("action") || getDocumentBaseUrl()
326
+ );
327
+ }
328
+ function resolveFormTarget(form, submitter) {
329
+ return (getSubmitterAttribute(submitter, "formtarget") || form.getAttribute("target") || "").trim();
330
+ }
331
+ function resolveFormEnctype(form, submitter) {
332
+ return (getSubmitterAttribute(submitter, "formenctype") || form.getAttribute("enctype") || "application/x-www-form-urlencoded").toLowerCase();
333
+ }
334
+ function canProxyFormTarget(target) {
335
+ const normalizedTarget = target.toLowerCase();
336
+ return normalizedTarget === "" || normalizedTarget === "_self" || normalizedTarget === "_top" || normalizedTarget === "_parent";
337
+ }
338
+ function navigateFormProxy(proxyUrl, target) {
339
+ const normalizedTarget = target.toLowerCase();
340
+ if (!normalizedTarget || normalizedTarget === "_self") {
341
+ window.location.assign(proxyUrl);
342
+ return;
343
+ }
344
+ if (normalizedTarget === "_top" && window.top) {
345
+ window.top.location.assign(proxyUrl);
346
+ return;
347
+ }
348
+ if (normalizedTarget === "_parent" && window.parent) {
349
+ window.parent.location.assign(proxyUrl);
350
+ return;
351
+ }
352
+ window.location.assign(proxyUrl);
353
+ }
354
+ function proxyFormSubmission(form, submitter) {
355
+ return __async(this, null, function* () {
356
+ const target = resolveFormTarget(form, submitter);
357
+ if (!canProxyFormTarget(target)) {
358
+ return false;
359
+ }
360
+ const method = resolveFormMethod(form, submitter);
361
+ const actionUrl = resolveFormAction(form, submitter);
362
+ const formData = createFormData(form, submitter);
363
+ const headers = {};
364
+ let requestUrl = actionUrl;
365
+ let body = null;
366
+ if (method === "GET" || method === "HEAD") {
367
+ requestUrl = appendFormDataToUrl(actionUrl, formData);
368
+ } else {
369
+ const enctype = resolveFormEnctype(form, submitter);
370
+ if (enctype === "text/plain") {
371
+ headers["content-type"] = "text/plain;charset=UTF-8";
372
+ body = formDataToPlainText(formData);
373
+ } else if (enctype === "multipart/form-data") {
374
+ body = formData;
375
+ } else {
376
+ headers["content-type"] = "application/x-www-form-urlencoded;charset=UTF-8";
377
+ body = formDataToUrlSearchParams(formData);
378
+ }
379
+ }
380
+ if (!shouldProxyBridgeUrl(requestUrl, getDocumentBaseUrl(), proxyRequestPattern)) {
381
+ return false;
382
+ }
383
+ const proxyUrl = yield storeInterceptedRequest(requestUrl, method, headers, body, "include");
384
+ navigateFormProxy(proxyUrl, target);
385
+ return true;
386
+ });
387
+ }
388
+ const originalFetch = window.fetch;
389
+ window.fetch = function(input, init) {
390
+ return __async(this, null, function* () {
391
+ var _a;
392
+ let url;
393
+ let method = "GET";
394
+ const headers = {};
395
+ let inheritedHeaders = false;
396
+ let body = null;
397
+ let credentialsMode = "same-origin";
398
+ let requestCloneError = null;
399
+ if (input instanceof Request) {
400
+ url = input.url;
401
+ method = input.method;
402
+ credentialsMode = normalizeCredentialsMode(input.credentials);
403
+ inheritedHeaders = true;
404
+ input.headers.forEach((value, key) => {
405
+ headers[key] = value;
406
+ });
407
+ try {
408
+ const cloned = input.clone();
409
+ const buffer = yield cloned.arrayBuffer();
410
+ if (buffer.byteLength > 0) {
411
+ body = buffer;
412
+ }
413
+ } catch (error) {
414
+ requestCloneError = error;
415
+ }
416
+ } else {
417
+ url = input instanceof URL ? input.toString() : input;
418
+ }
419
+ url = resolveUrl(url);
420
+ if (init) {
421
+ if (init.method) {
422
+ method = init.method;
423
+ }
424
+ if (init.headers) {
425
+ if (inheritedHeaders) {
426
+ Object.keys(headers).forEach((key) => {
427
+ delete headers[key];
428
+ });
429
+ inheritedHeaders = false;
430
+ }
431
+ const normalized = new Headers(init.headers);
432
+ normalized.forEach((value, key) => {
433
+ headers[key] = value;
434
+ });
435
+ }
436
+ if (init.body !== void 0) {
437
+ body = init.body;
438
+ }
439
+ if (init.credentials) {
440
+ credentialsMode = normalizeCredentialsMode(init.credentials);
441
+ }
442
+ }
443
+ if (requestCloneError != null) {
444
+ return originalFetch.call(window, input, init);
445
+ }
446
+ if (!shouldProxyBridgeUrl(url, getDocumentBaseUrl(), proxyRequestPattern)) {
447
+ return originalFetch.call(window, input, init);
448
+ }
449
+ const signal = (_a = init == null ? void 0 : init.signal) != null ? _a : input instanceof Request ? input.signal : void 0;
450
+ let proxyUrl;
451
+ try {
452
+ proxyUrl = yield storeInterceptedRequest(url, method, headers, body, credentialsMode);
453
+ } catch (_error) {
454
+ return originalFetch.call(window, input, init);
455
+ }
456
+ const proxyResponse = yield originalFetch.call(globalThis, proxyUrl, {
457
+ method: "GET",
458
+ signal
459
+ });
460
+ if (isNativeProxyErrorResponse(proxyResponse)) {
461
+ throw new TypeError(nativeProxyErrorMessage(proxyResponse));
462
+ }
463
+ return proxyResponse;
464
+ });
465
+ };
466
+ const originalXhrOpen = XMLHttpRequest.prototype.open;
467
+ const originalXhrSend = XMLHttpRequest.prototype.send;
468
+ const originalXhrAbort = XMLHttpRequest.prototype.abort;
469
+ const originalXhrSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
470
+ const originalXhrOverrideMimeType = XMLHttpRequest.prototype.overrideMimeType;
471
+ XMLHttpRequest.prototype.open = function(method, url, ...rest) {
472
+ this.__proxyMethod = method;
473
+ this.__proxyUrl = resolveUrl(url instanceof URL ? url.toString() : url);
474
+ this.__proxyHeaders = {};
475
+ this.__proxyAsync = rest[0] !== false;
476
+ this.__proxyCredentials = this.withCredentials ? "include" : "same-origin";
477
+ this.__proxyUsername = typeof rest[1] === "string" ? rest[1] : null;
478
+ this.__proxyPassword = typeof rest[2] === "string" ? rest[2] : "";
479
+ this.__proxyAborted = false;
480
+ this.__proxyOverrideMimeType = null;
481
+ return originalXhrOpen.apply(this, [method, url, ...rest]);
482
+ };
483
+ XMLHttpRequest.prototype.setRequestHeader = function(name, value) {
484
+ if (this.__proxyHeaders) {
485
+ appendCapturedHeader(this.__proxyHeaders, name, value);
486
+ }
487
+ return originalXhrSetRequestHeader.call(this, name, value);
488
+ };
489
+ XMLHttpRequest.prototype.abort = function() {
490
+ this.__proxyAborted = true;
491
+ return originalXhrAbort.call(this);
492
+ };
493
+ if (typeof originalXhrOverrideMimeType === "function") {
494
+ XMLHttpRequest.prototype.overrideMimeType = function(mimeType) {
495
+ this.__proxyOverrideMimeType = mimeType;
496
+ return originalXhrOverrideMimeType.call(this, mimeType);
497
+ };
498
+ }
499
+ XMLHttpRequest.prototype.send = function(body) {
500
+ const xhr = this;
501
+ const method = xhr.__proxyMethod || "GET";
502
+ const url = xhr.__proxyUrl || "";
503
+ const headers = xhr.__proxyHeaders || {};
504
+ const isAsync = xhr.__proxyAsync !== false;
505
+ const credentialsMode = xhr.withCredentials ? "include" : "same-origin";
506
+ const username = xhr.__proxyUsername;
507
+ const password = xhr.__proxyPassword;
508
+ if (typeof username === "string" && username.length > 0 && !hasHeader(headers, "Authorization")) {
509
+ headers["Authorization"] = "Basic " + stringToBase64(username + ":" + (typeof password === "string" ? password : ""));
510
+ }
511
+ function completeSend(proxyUrl) {
512
+ if (xhr.__proxyAborted) {
513
+ return;
514
+ }
515
+ const replayState = captureXhrReplayState(xhr);
516
+ originalXhrOpen.call(xhr, "GET", proxyUrl, true);
517
+ restoreXhrReplayState(xhr, replayState);
518
+ originalXhrSend.call(xhr, null);
519
+ }
520
+ if (!shouldProxyBridgeUrl(url, getDocumentBaseUrl(), proxyRequestPattern)) {
521
+ originalXhrSend.call(xhr, body != null ? body : null);
522
+ return;
523
+ }
524
+ if (!isAsync) {
525
+ console.warn("[proxy-bridge] Synchronous XMLHttpRequest cannot be proxied; falling back to the original request");
526
+ originalXhrSend.call(xhr, body != null ? body : null);
527
+ return;
528
+ }
529
+ storeInterceptedRequest(url, method, headers, body, credentialsMode).then((proxyUrl) => {
530
+ completeSend(proxyUrl);
531
+ }).catch((_error) => {
532
+ if (xhr.__proxyAborted) {
533
+ return;
534
+ }
535
+ console.error("[proxy-bridge] Failed to encode XMLHttpRequest body");
536
+ originalXhrSend.call(xhr, body != null ? body : null);
537
+ });
538
+ };
539
+ const originalFormSubmit = HTMLFormElement.prototype.submit;
540
+ document.addEventListener("submit", (event) => {
541
+ const form = event.target instanceof HTMLFormElement ? event.target : null;
542
+ if (!form) {
543
+ return;
544
+ }
545
+ if (consumeProxySubmitReplayBypass(form)) {
546
+ return;
547
+ }
548
+ const submitter = getSubmitEventSubmitter(event);
549
+ const target = resolveFormTarget(form, submitter);
550
+ const method = resolveFormMethod(form, submitter);
551
+ let requestUrl = resolveFormAction(form, submitter);
552
+ if (method === "GET" || method === "HEAD") {
553
+ requestUrl = appendFormDataToUrl(requestUrl, createFormData(form, submitter));
554
+ }
555
+ if (!shouldProxySubmitRequest(
556
+ event.defaultPrevented,
557
+ canProxyFormTarget(target),
558
+ requestUrl,
559
+ getDocumentBaseUrl(),
560
+ proxyRequestPattern
561
+ )) {
562
+ return;
563
+ }
564
+ event.preventDefault();
565
+ proxyFormSubmission(form, submitter).then((handled) => {
566
+ if (!handled) {
567
+ replaySubmitAfterProxyFailure(form, originalFormSubmit, submitter);
568
+ }
569
+ }).catch((error) => {
570
+ console.error("[proxy-bridge] Failed to proxy form submission", error);
571
+ replaySubmitAfterProxyFailure(form, originalFormSubmit, submitter);
572
+ });
573
+ });
574
+ HTMLFormElement.prototype.submit = function() {
575
+ const form = this;
576
+ proxyFormSubmission(form).then((handled) => {
577
+ if (!handled) {
578
+ originalFormSubmit.call(form);
579
+ }
580
+ }).catch((_error) => {
581
+ console.error("[proxy-bridge] Failed to proxy programmatic form submission");
582
+ originalFormSubmit.call(form);
583
+ });
584
+ };
585
+ })();
586
+ })();
@@ -354,7 +354,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
354
354
  self.initWebview(isInspectable: isInspectable)
355
355
  }
356
356
 
357
- public init(url: URL, headers: [String: String], isInspectable: Bool, credentials: WKWebViewCredentials? = nil, preventDeeplink: Bool, blankNavigationTab: Bool, enabledSafeBottomMargin: Bool, enabledSafeTopMargin: Bool = true, blockedHosts: [String], authorizedAppLinks: [String], allowWebViewJsVisibilityControl: Bool = false, allowScreenshotsFromWebPage: Bool = false, captureConsoleLogs: Bool = false, proxyRequests: Bool = false, proxySchemeHandler: ProxySchemeHandler? = nil, documentStartUserScripts: [String] = [], openBlankTargetInWebView: Bool = false) {
357
+ public init(url: URL, headers: [String: String], isInspectable: Bool, credentials: WKWebViewCredentials? = nil, preventDeeplink: Bool, blankNavigationTab: Bool, enabledSafeBottomMargin: Bool, enabledSafeTopMargin: Bool = true, blockedHosts: [String], authorizedAppLinks: [String], allowWebViewJsVisibilityControl: Bool = false, allowScreenshotsFromWebPage: Bool = false, captureConsoleLogs: Bool = false, proxyRequests: Bool = false, proxySchemeHandler: ProxySchemeHandler? = nil, proxyBridge: ProxyBridge? = nil, proxyBridgeAccessToken: String? = nil, legacyProxyRequestURLRegexPattern: String? = nil, documentStartUserScripts: [String] = [], openBlankTargetInWebView: Bool = false) {
358
358
  super.init(nibName: nil, bundle: nil)
359
359
  self.blankNavigationTab = blankNavigationTab
360
360
  self.enabledSafeBottomMargin = enabledSafeBottomMargin
@@ -366,6 +366,9 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
366
366
  self.captureConsoleLogs = captureConsoleLogs
367
367
  self.proxyRequests = proxyRequests
368
368
  self.proxySchemeHandler = proxySchemeHandler
369
+ self.proxyBridge = proxyBridge
370
+ self.proxyBridgeAccessToken = proxyBridgeAccessToken
371
+ self.legacyProxyRequestURLRegexPattern = legacyProxyRequestURLRegexPattern
369
372
  self.openBlankTargetInWebView = openBlankTargetInWebView
370
373
  self.setHeaders(headers: headers)
371
374
  self.setPreventDeeplink(preventDeeplink: preventDeeplink)
@@ -422,6 +425,9 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
422
425
  var disableOverscroll: Bool = false
423
426
  var proxyRequests: Bool = false
424
427
  var proxySchemeHandler: ProxySchemeHandler?
428
+ var proxyBridge: ProxyBridge?
429
+ var proxyBridgeAccessToken: String?
430
+ var legacyProxyRequestURLRegexPattern: String?
425
431
  var initialWebConfiguration: WKWebViewConfiguration?
426
432
  var waitsForPopupNavigation = false
427
433
  var hiddenPopupWindow = false
@@ -1298,6 +1304,25 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
1298
1304
  }
1299
1305
  print("[InAppBrowser - preShowScriptError]: Error!!!!")
1300
1306
  semaphore.signal()
1307
+ } else if message.name == "capgoProxyBridge" {
1308
+ guard let messageBody = message.body as? [String: Any],
1309
+ let token = messageBody["token"] as? String,
1310
+ let requestId = messageBody["requestId"] as? String,
1311
+ let method = messageBody["method"] as? String,
1312
+ let headersJson = messageBody["headersJson"] as? String else {
1313
+ return
1314
+ }
1315
+
1316
+ let base64Body = messageBody["base64Body"] as? String ?? ""
1317
+ let credentialsMode = messageBody["credentialsMode"] as? String ?? "same-origin"
1318
+ proxyBridge?.storeRequest(
1319
+ token: token,
1320
+ requestId: requestId,
1321
+ method: method,
1322
+ headersJson: headersJson,
1323
+ base64Body: base64Body,
1324
+ credentialsMode: credentialsMode
1325
+ )
1301
1326
  } else if message.name == "close" {
1302
1327
  closeView()
1303
1328
  } else if message.name == "hide" {
@@ -1432,6 +1457,35 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
1432
1457
  userContentController.addUserScript(script)
1433
1458
  }
1434
1459
 
1460
+ private func addProxyBridgeUserScripts(to userContentController: WKUserContentController) {
1461
+ guard let accessToken = proxyBridgeAccessToken else {
1462
+ return
1463
+ }
1464
+
1465
+ let bootstrapScript = WKUserScript(
1466
+ source: ProxyBridgeSupport.bootstrapScriptSource(),
1467
+ injectionTime: .atDocumentStart,
1468
+ forMainFrameOnly: false
1469
+ )
1470
+ userContentController.addUserScript(bootstrapScript)
1471
+
1472
+ let proxyRegexSource = legacyProxyRequestURLRegexPattern ?? ""
1473
+ guard let bridgeScriptSource = ProxyBridgeSupport.loadBundledBridgeScript(
1474
+ accessToken: accessToken,
1475
+ proxyRegexSource: proxyRegexSource
1476
+ ) else {
1477
+ print("[InAppBrowser][Proxy] WARNING: Failed to load proxy-bridge.js for iOS injection")
1478
+ return
1479
+ }
1480
+
1481
+ let bridgeScript = WKUserScript(
1482
+ source: bridgeScriptSource,
1483
+ injectionTime: .atDocumentStart,
1484
+ forMainFrameOnly: false
1485
+ )
1486
+ userContentController.addUserScript(bridgeScript)
1487
+ }
1488
+
1435
1489
  func injectJavaScriptInterface() {
1436
1490
  let script = mobileAppScriptSource()
1437
1491
  DispatchQueue.main.async {
@@ -1470,6 +1524,7 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
1470
1524
  userContentController.removeScriptMessageHandler(forName: "takeScreenshot")
1471
1525
  userContentController.removeScriptMessageHandler(forName: "consoleMessageHandler")
1472
1526
  userContentController.removeScriptMessageHandler(forName: "magicPrint")
1527
+ userContentController.removeScriptMessageHandler(forName: "capgoProxyBridge")
1473
1528
 
1474
1529
  if proxyRequests || proxySchemeHandler != nil, let handler = proxySchemeHandler {
1475
1530
  WKWebView.enableCustomSchemeHandling(for: ["https", "http"])
@@ -1481,6 +1536,10 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
1481
1536
  }
1482
1537
  }
1483
1538
 
1539
+ if ProxyBridgeSupport.shouldInjectBridge(hasProxySchemeHandler: proxySchemeHandler != nil) {
1540
+ addProxyBridgeUserScripts(to: userContentController)
1541
+ }
1542
+
1484
1543
  let weakHandler = WeakScriptMessageHandler(self)
1485
1544
  userContentController.add(weakHandler, name: "messageHandler")
1486
1545
  userContentController.add(weakHandler, name: "preShowScriptError")
@@ -1516,6 +1575,9 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
1516
1575
  )
1517
1576
  )
1518
1577
  }
1578
+ if ProxyBridgeSupport.shouldInjectBridge(hasProxySchemeHandler: proxySchemeHandler != nil) {
1579
+ userContentController.add(weakHandler, name: "capgoProxyBridge")
1580
+ }
1519
1581
  userContentController.add(weakHandler, name: "magicPrint")
1520
1582
 
1521
1583
  // Inject JavaScript to override window.print
@@ -1704,7 +1766,9 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
1704
1766
  self.disableOverscroll = parent.disableOverscroll
1705
1767
  self.proxyRequests = parent.proxyRequests
1706
1768
  self.proxySchemeHandler = proxySchemeHandler
1707
- self.initialWebConfiguration = configuration
1769
+ self.proxyBridge = parent.proxyBridge
1770
+ self.proxyBridgeAccessToken = parent.proxyBridgeAccessToken
1771
+ self.legacyProxyRequestURLRegexPattern = parent.legacyProxyRequestURLRegexPattern
1708
1772
  self.waitsForPopupNavigation = true
1709
1773
  self.hiddenPopupWindow = parent.hiddenPopupWindow
1710
1774
  self.opensHidden = parent.hiddenPopupWindow
@@ -1984,6 +2048,7 @@ public extension WKWebViewController {
1984
2048
  webView.configuration.userContentController.removeScriptMessageHandler(forName: "preShowScriptSuccess")
1985
2049
  webView.configuration.userContentController.removeScriptMessageHandler(forName: "preShowScriptError")
1986
2050
  webView.configuration.userContentController.removeScriptMessageHandler(forName: "magicPrint")
2051
+ webView.configuration.userContentController.removeScriptMessageHandler(forName: "capgoProxyBridge")
1987
2052
 
1988
2053
  webView.removeFromSuperview()
1989
2054
  // Also clean progress bar view if present
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-inappbrowser",
3
- "version": "8.6.32",
3
+ "version": "8.6.34",
4
4
  "description": "Capacitor plugin in app browser",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
@@ -32,7 +32,7 @@
32
32
  ],
33
33
  "scripts": {
34
34
  "verify": "bun run verify:ios && bun run verify:android && bun run verify:web",
35
- "verify:ios": "xcodebuild -scheme CapgoCapacitorInappbrowser -destination generic/platform=iOS",
35
+ "verify:ios": "bun run build:proxy-bridge && xcodebuild -scheme CapgoCapacitorInappbrowser -destination generic/platform=iOS",
36
36
  "verify:android": "cd android && ./gradlew clean build test && cd ..",
37
37
  "verify:web": "bun run build && bun run test:web",
38
38
  "test:web": "bun test src/proxy-bridge-support.test.ts",
@@ -43,7 +43,7 @@
43
43
  "prettier": "prettier-pretty-check \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
44
44
  "swiftlint": "node-swiftlint",
45
45
  "docgen": "docgen --api InAppBrowserPlugin --output-readme README.md --output-json dist/docs.json",
46
- "build:proxy-bridge": "esbuild src/proxy-bridge.ts --bundle --outfile=android/src/main/assets/proxy-bridge.js --format=iife --target=es2015",
46
+ "build:proxy-bridge": "esbuild src/proxy-bridge.ts --bundle --outfile=android/src/main/assets/proxy-bridge.js --format=iife --target=es2015 && mkdir -p ios/Sources/InAppBrowserPlugin/Resources && cp android/src/main/assets/proxy-bridge.js ios/Sources/InAppBrowserPlugin/Resources/proxy-bridge.js",
47
47
  "build": "bun run clean && bun run docgen && tsc && rollup -c rollup.config.mjs && bun run build:proxy-bridge",
48
48
  "clean": "rimraf ./dist",
49
49
  "watch": "tsc --watch",