@capgo/capacitor-inappbrowser 8.6.31 → 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.31"
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()