@capgo/capacitor-inappbrowser 8.14.2 → 8.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -5
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/BundledAssetSupport.java +397 -0
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/CapgoInAppBrowserPlugin.java +42 -4
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/Options.java +39 -1
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/SafeAreaInsetsSupport.java +34 -48
- package/android/src/main/java/ee/forgr/capacitor_inappbrowser/WebViewDialog.java +462 -335
- package/dist/docs.json +17 -5
- package/dist/esm/bundled-asset-support.d.ts +40 -0
- package/dist/esm/bundled-asset-support.js +157 -0
- package/dist/esm/bundled-asset-support.js.map +1 -0
- package/dist/esm/definitions.d.ts +26 -4
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.js +43 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/plugin.cjs.js +199 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +199 -1
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/InAppBrowserPlugin/BundledAssetSchemeHandler.swift +188 -0
- package/ios/Sources/InAppBrowserPlugin/BundledAssetSupport.swift +221 -0
- package/ios/Sources/InAppBrowserPlugin/InAppBrowserPlugin.swift +26 -4
- package/ios/Sources/InAppBrowserPlugin/WKWebViewController.swift +21 -0
- package/package.json +2 -2
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
import UniformTypeIdentifiers
|
|
4
|
+
import WebKit
|
|
5
|
+
|
|
6
|
+
final class BundledAssetSchemeHandler: NSObject, WKURLSchemeHandler {
|
|
7
|
+
private let basePath: String
|
|
8
|
+
private let expectedHost: String
|
|
9
|
+
private var activeTasks: [ObjectIdentifier: WKURLSchemeTask] = [:]
|
|
10
|
+
private let tasksLock = NSLock()
|
|
11
|
+
|
|
12
|
+
init(expectedHost: String) {
|
|
13
|
+
self.basePath = Self.resolveBasePath()
|
|
14
|
+
self.expectedHost = expectedHost.lowercased()
|
|
15
|
+
super.init()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
|
|
19
|
+
guard let requestURL = urlSchemeTask.request.url else {
|
|
20
|
+
urlSchemeTask.didFailWithError(
|
|
21
|
+
NSError(domain: "BundledAssetSchemeHandler", code: -1, userInfo: [NSLocalizedDescriptionKey: "Missing request URL"])
|
|
22
|
+
)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let taskID = ObjectIdentifier(urlSchemeTask)
|
|
27
|
+
tasksLock.lock()
|
|
28
|
+
activeTasks[taskID] = urlSchemeTask
|
|
29
|
+
tasksLock.unlock()
|
|
30
|
+
|
|
31
|
+
guard Self.isAllowedBundledAssetRequest(requestURL, expectedHost: expectedHost) else {
|
|
32
|
+
finish(task: urlSchemeTask, taskID: taskID) {
|
|
33
|
+
urlSchemeTask.didFailWithError(
|
|
34
|
+
NSError(
|
|
35
|
+
domain: "BundledAssetSchemeHandler",
|
|
36
|
+
code: 403,
|
|
37
|
+
userInfo: [NSLocalizedDescriptionKey: "Blocked bundled asset host: \(requestURL.absoluteString)"]
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
guard let filePath = BundledAssetSupport.routeAssetPath(for: requestURL.path, basePath: basePath) else {
|
|
45
|
+
finish(task: urlSchemeTask, taskID: taskID) {
|
|
46
|
+
urlSchemeTask.didFailWithError(
|
|
47
|
+
NSError(
|
|
48
|
+
domain: "BundledAssetSchemeHandler",
|
|
49
|
+
code: 403,
|
|
50
|
+
userInfo: [NSLocalizedDescriptionKey: "Blocked bundled asset path: \(requestURL.path)"]
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let fileURL = URL(fileURLWithPath: filePath)
|
|
58
|
+
|
|
59
|
+
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
|
|
60
|
+
guard let self else {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
guard FileManager.default.fileExists(atPath: fileURL.path) else {
|
|
65
|
+
self.finish(task: urlSchemeTask, taskID: taskID) {
|
|
66
|
+
urlSchemeTask.didFailWithError(
|
|
67
|
+
NSError(
|
|
68
|
+
domain: "BundledAssetSchemeHandler",
|
|
69
|
+
code: 404,
|
|
70
|
+
userInfo: [NSLocalizedDescriptionKey: "Bundled asset not found: \(requestURL.path)"]
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
do {
|
|
78
|
+
let data = try Data(contentsOf: fileURL)
|
|
79
|
+
let mimeType = self.mimeType(for: requestURL.pathExtension)
|
|
80
|
+
let response = HTTPURLResponse(
|
|
81
|
+
url: requestURL,
|
|
82
|
+
statusCode: 200,
|
|
83
|
+
httpVersion: nil,
|
|
84
|
+
headerFields: [
|
|
85
|
+
"Content-Type": mimeType,
|
|
86
|
+
"Cache-Control": "no-cache"
|
|
87
|
+
]
|
|
88
|
+
) ?? URLResponse(url: requestURL, mimeType: mimeType, expectedContentLength: data.count, textEncodingName: nil)
|
|
89
|
+
|
|
90
|
+
self.finish(task: urlSchemeTask, taskID: taskID) {
|
|
91
|
+
urlSchemeTask.didReceive(response)
|
|
92
|
+
urlSchemeTask.didReceive(data)
|
|
93
|
+
urlSchemeTask.didFinish()
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
self.finish(task: urlSchemeTask, taskID: taskID) {
|
|
97
|
+
urlSchemeTask.didFailWithError(error)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
|
|
104
|
+
let taskID = ObjectIdentifier(urlSchemeTask)
|
|
105
|
+
tasksLock.lock()
|
|
106
|
+
activeTasks.removeValue(forKey: taskID)
|
|
107
|
+
tasksLock.unlock()
|
|
108
|
+
urlSchemeTask.stopped = true
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private func finish(task: WKURLSchemeTask, taskID: ObjectIdentifier, block: @escaping () -> Void) {
|
|
112
|
+
DispatchQueue.main.async { [weak self] in
|
|
113
|
+
guard let self else {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
guard !task.stopped else {
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
self.tasksLock.lock()
|
|
122
|
+
let isActive = self.activeTasks[taskID] != nil
|
|
123
|
+
if isActive {
|
|
124
|
+
self.activeTasks.removeValue(forKey: taskID)
|
|
125
|
+
}
|
|
126
|
+
self.tasksLock.unlock()
|
|
127
|
+
|
|
128
|
+
guard isActive, !task.stopped else {
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
block()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private static func isAllowedBundledAssetRequest(_ requestURL: URL, expectedHost: String) -> Bool {
|
|
137
|
+
guard let host = requestURL.host?.lowercased(), host == expectedHost else {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return requestURL.port == nil
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private func mimeType(for pathExtension: String) -> String {
|
|
145
|
+
if !pathExtension.isEmpty,
|
|
146
|
+
let uti = UTType(filenameExtension: pathExtension),
|
|
147
|
+
let mimeType = uti.preferredMIMEType {
|
|
148
|
+
return mimeType
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return pathExtension.isEmpty ? "text/html" : "application/octet-stream"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private static func resolveBasePath() -> String {
|
|
155
|
+
if let publicDirectory = publicDirectoryURL(),
|
|
156
|
+
FileManager.default.fileExists(atPath: publicDirectory.path) {
|
|
157
|
+
return publicDirectory.path
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if let wwwDirectory = wwwDirectoryURL(),
|
|
161
|
+
FileManager.default.fileExists(atPath: wwwDirectory.path) {
|
|
162
|
+
return wwwDirectory.path
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return publicDirectoryURL()?.path ?? wwwDirectoryURL()?.path ?? ""
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private static func publicDirectoryURL() -> URL? {
|
|
169
|
+
Bundle.main.resourceURL?.appendingPathComponent("public", isDirectory: true)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private static func wwwDirectoryURL() -> URL? {
|
|
173
|
+
Bundle.main.resourceURL?.appendingPathComponent("www", isDirectory: true)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private var bundledAssetStoppedKey: UInt8 = 0
|
|
178
|
+
|
|
179
|
+
private extension WKURLSchemeTask {
|
|
180
|
+
var stopped: Bool {
|
|
181
|
+
get {
|
|
182
|
+
objc_getAssociatedObject(self, &bundledAssetStoppedKey) as? Bool ?? false
|
|
183
|
+
}
|
|
184
|
+
set {
|
|
185
|
+
objc_setAssociatedObject(self, &bundledAssetStoppedKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
enum BundledAssetSupport {
|
|
4
|
+
struct LocalConfig: Equatable {
|
|
5
|
+
let scheme: String
|
|
6
|
+
let host: String
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
struct Resolution: Equatable {
|
|
10
|
+
let url: String
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static let iosDefaults = LocalConfig(scheme: "capacitor", host: "localhost")
|
|
14
|
+
static let reservedWebKitSchemes = ["http", "https"]
|
|
15
|
+
|
|
16
|
+
static func parseLocalConfig(from localURL: URL?) -> LocalConfig? {
|
|
17
|
+
guard let localURL else {
|
|
18
|
+
return nil
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
guard let scheme = localURL.scheme?.lowercased(), !scheme.isEmpty else {
|
|
22
|
+
return nil
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let host = localURL.host?.lowercased() ?? ""
|
|
26
|
+
guard !host.isEmpty else {
|
|
27
|
+
return nil
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return LocalConfig(scheme: scheme, host: host)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static func handlerScheme(for localConfig: LocalConfig) -> String {
|
|
34
|
+
if reservedWebKitSchemes.contains(localConfig.scheme) {
|
|
35
|
+
return iosDefaults.scheme
|
|
36
|
+
}
|
|
37
|
+
return localConfig.scheme
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static func resolve(_ urlString: String, localURL: URL?) -> Resolution? {
|
|
41
|
+
let trimmed = urlString.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
42
|
+
let localConfig = parseLocalConfig(from: localURL) ?? iosDefaults
|
|
43
|
+
let navigationScheme = handlerScheme(for: localConfig)
|
|
44
|
+
|
|
45
|
+
if isRelativeBundledPath(trimmed) {
|
|
46
|
+
guard let path = normalizeBundledPath(trimmed) else {
|
|
47
|
+
return nil
|
|
48
|
+
}
|
|
49
|
+
return Resolution(url: "\(navigationScheme)://\(localConfig.host)\(path)")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if isBundledLocalURL(trimmed, localConfig: localConfig) {
|
|
53
|
+
if let rewritten = rewriteBundledLocalURL(trimmed, localConfig: localConfig, navigationScheme: navigationScheme) {
|
|
54
|
+
return Resolution(url: rewritten)
|
|
55
|
+
}
|
|
56
|
+
return Resolution(url: trimmed)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Resolution(url: trimmed)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static func isBundledLocalURL(_ urlString: String, localConfig: LocalConfig) -> Bool {
|
|
63
|
+
guard let url = URL(string: urlString),
|
|
64
|
+
let scheme = url.scheme?.lowercased(),
|
|
65
|
+
let host = url.host?.lowercased() else {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
guard host == localConfig.host else {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if url.port != nil {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if scheme == localConfig.scheme || scheme == handlerScheme(for: localConfig) {
|
|
78
|
+
return true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return reservedWebKitSchemes.contains(scheme) && reservedWebKitSchemes.contains(localConfig.scheme)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static func isRelativeBundledPath(_ urlString: String) -> Bool {
|
|
85
|
+
let trimmed = urlString.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
86
|
+
guard !trimmed.isEmpty else {
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return !isAbsoluteURL(trimmed)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private static let bundledAssetExtensions: Set<String> = [
|
|
94
|
+
"html", "htm", "js", "css", "json", "xml", "svg",
|
|
95
|
+
"png", "jpg", "jpeg", "gif", "webp", "woff", "woff2", "ttf", "map"
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
static func isLikelyBundledRelativePath(_ urlString: String) -> Bool {
|
|
99
|
+
guard isRelativeBundledPath(urlString) else {
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let trimmed = stripPathQueryAndFragment(urlString.trimmingCharacters(in: .whitespacesAndNewlines))
|
|
104
|
+
if finalPathComponentStartsWithDot(trimmed) {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if trimmed.hasPrefix("/") || trimmed.contains("/") {
|
|
109
|
+
return true
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
guard let dot = trimmed.lastIndex(of: ".") else {
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if dot == trimmed.startIndex {
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let extensionStart = trimmed.index(after: dot)
|
|
121
|
+
guard extensionStart < trimmed.endIndex else {
|
|
122
|
+
return false
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let fileExtension = String(trimmed[extensionStart...]).lowercased()
|
|
126
|
+
return bundledAssetExtensions.contains(fileExtension)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private static func finalPathComponentStartsWithDot(_ path: String) -> Bool {
|
|
130
|
+
let finalComponent = path.split(separator: "/").last.map(String.init) ?? path
|
|
131
|
+
guard let first = finalComponent.first else {
|
|
132
|
+
return false
|
|
133
|
+
}
|
|
134
|
+
return first == "."
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private static func stripPathQueryAndFragment(_ path: String) -> String {
|
|
138
|
+
let withoutQuery = path.split(separator: "?", maxSplits: 1, omittingEmptySubsequences: false).first.map(String.init) ?? path
|
|
139
|
+
return withoutQuery.split(separator: "#", maxSplits: 1, omittingEmptySubsequences: false).first.map(String.init) ?? withoutQuery
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static func normalizeBundledPath(_ path: String) -> String? {
|
|
143
|
+
let trimmed = path.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
144
|
+
if trimmed.isEmpty || trimmed == "/" {
|
|
145
|
+
return "/"
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
guard !containsPathTraversal(trimmed) else {
|
|
149
|
+
return nil
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return trimmed.hasPrefix("/") ? trimmed : "/\(trimmed)"
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static func routeAssetPath(for requestPath: String, basePath: String) -> String? {
|
|
156
|
+
guard !containsPathTraversal(requestPath) else {
|
|
157
|
+
return nil
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let normalizedPath = requestPath.isEmpty ? "/" : requestPath
|
|
161
|
+
let relativePath: String
|
|
162
|
+
if URL(fileURLWithPath: normalizedPath).pathExtension.isEmpty {
|
|
163
|
+
relativePath = "/index.html"
|
|
164
|
+
} else {
|
|
165
|
+
relativePath = normalizedPath.hasPrefix("/") ? normalizedPath : "/\(normalizedPath)"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let resolvedURL = URL(fileURLWithPath: basePath).appendingPathComponent(relativePath, isDirectory: false).standardizedFileURL
|
|
169
|
+
let baseURL = URL(fileURLWithPath: basePath).standardizedFileURL
|
|
170
|
+
let resolvedPath = resolvedURL.path
|
|
171
|
+
let basePathValue = baseURL.path
|
|
172
|
+
|
|
173
|
+
guard resolvedPath == basePathValue || resolvedPath.hasPrefix(basePathValue + "/") else {
|
|
174
|
+
return nil
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return resolvedPath
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
static func containsPathTraversal(_ path: String) -> Bool {
|
|
181
|
+
path.split(separator: "/").contains(where: { $0 == ".." })
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private static func rewriteBundledLocalURL(
|
|
185
|
+
_ urlString: String,
|
|
186
|
+
localConfig: LocalConfig,
|
|
187
|
+
navigationScheme: String
|
|
188
|
+
) -> String? {
|
|
189
|
+
guard let url = URL(string: urlString),
|
|
190
|
+
let host = url.host?.lowercased(),
|
|
191
|
+
host == localConfig.host else {
|
|
192
|
+
return nil
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
guard let scheme = url.scheme?.lowercased(), scheme != navigationScheme else {
|
|
196
|
+
return nil
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
|
|
200
|
+
components?.scheme = navigationScheme
|
|
201
|
+
return components?.url?.absoluteString
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private static func isAbsoluteURL(_ urlString: String) -> Bool {
|
|
205
|
+
if urlString.hasPrefix("//") {
|
|
206
|
+
return true
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
guard let colonIndex = urlString.firstIndex(of: ":"),
|
|
210
|
+
colonIndex > urlString.startIndex else {
|
|
211
|
+
return false
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let scheme = urlString[..<colonIndex]
|
|
215
|
+
guard let first = scheme.first, first.isLetter else {
|
|
216
|
+
return false
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return scheme.allSatisfy { $0.isLetter || $0.isNumber || $0 == "+" || $0 == "-" || $0 == "." }
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -415,7 +415,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
415
415
|
case aware = "AWARE"
|
|
416
416
|
case fakeVisible = "FAKE_VISIBLE"
|
|
417
417
|
}
|
|
418
|
-
private let pluginVersion: String = "8.
|
|
418
|
+
private let pluginVersion: String = "8.15.0"
|
|
419
419
|
public let identifier = "CapgoInAppBrowserPlugin"
|
|
420
420
|
public let jsName = "CapgoInAppBrowser"
|
|
421
421
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -1226,12 +1226,24 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1226
1226
|
throw optionError("Failed to load \(optionName) icon: \(icon)")
|
|
1227
1227
|
}
|
|
1228
1228
|
|
|
1229
|
+
private func bundledAssetLocalConfig() -> BundledAssetSupport.LocalConfig {
|
|
1230
|
+
BundledAssetSupport.parseLocalConfig(from: self.bridge?.config.localURL) ?? BundledAssetSupport.iosDefaults
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
private func applyBundledAssetSettings(to webViewController: WKWebViewController) {
|
|
1234
|
+
// Scheme handlers are registered once during initWebview; localURL is fixed for the app lifetime.
|
|
1235
|
+
let localConfig = bundledAssetLocalConfig()
|
|
1236
|
+
webViewController.bundledAssetLocalScheme = BundledAssetSupport.handlerScheme(for: localConfig)
|
|
1237
|
+
webViewController.bundledAssetLocalHost = localConfig.host
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1229
1240
|
private func webSource(for urlString: String) -> WKWebSource? {
|
|
1230
1241
|
if let html = HtmlDataUrlSupport.parseHtml(from: urlString) {
|
|
1231
1242
|
return .string(html, base: nil)
|
|
1232
1243
|
}
|
|
1233
1244
|
|
|
1234
|
-
guard let
|
|
1245
|
+
guard let resolution = BundledAssetSupport.resolve(urlString, localURL: self.bridge?.config.localURL),
|
|
1246
|
+
let url = URL(string: resolution.url) else {
|
|
1235
1247
|
return nil
|
|
1236
1248
|
}
|
|
1237
1249
|
|
|
@@ -1442,7 +1454,8 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1442
1454
|
}
|
|
1443
1455
|
|
|
1444
1456
|
DispatchQueue.main.async {
|
|
1445
|
-
|
|
1457
|
+
let webSourceResult = self.webSource(for: urlString)
|
|
1458
|
+
guard let webSource = webSourceResult else {
|
|
1446
1459
|
call.reject("Invalid URL format")
|
|
1447
1460
|
return
|
|
1448
1461
|
}
|
|
@@ -1495,6 +1508,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1495
1508
|
authorizedAppLinks: authorizedAppLinks,
|
|
1496
1509
|
openBlankTargetInWebView: openBlankTargetInWebView
|
|
1497
1510
|
)
|
|
1511
|
+
self.applyBundledAssetSettings(to: webViewController)
|
|
1498
1512
|
webViewController.enableReloadGesture = enableReloadGesture
|
|
1499
1513
|
webViewController.disableOverscroll = disableOverscroll
|
|
1500
1514
|
webViewController.initWebview(isInspectable: isInspectable)
|
|
@@ -1846,11 +1860,14 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1846
1860
|
return
|
|
1847
1861
|
}
|
|
1848
1862
|
|
|
1849
|
-
guard let
|
|
1863
|
+
guard let resolution = BundledAssetSupport.resolve(urlString, localURL: self.bridge?.config.localURL),
|
|
1864
|
+
let url = URL(string: resolution.url) else {
|
|
1850
1865
|
call.reject("Invalid URL")
|
|
1851
1866
|
return
|
|
1852
1867
|
}
|
|
1853
1868
|
|
|
1869
|
+
self.applyBundledAssetSettings(to: webViewController)
|
|
1870
|
+
|
|
1854
1871
|
webViewController.source = .remote(url)
|
|
1855
1872
|
webViewController.load(remote: url)
|
|
1856
1873
|
call.resolve()
|
|
@@ -2357,6 +2374,11 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2357
2374
|
return
|
|
2358
2375
|
}
|
|
2359
2376
|
|
|
2377
|
+
if BundledAssetSupport.isLikelyBundledRelativePath(urlString) {
|
|
2378
|
+
call.reject("Bundled assets require openWebView()")
|
|
2379
|
+
return
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2360
2382
|
guard let url = URL(string: urlString),
|
|
2361
2383
|
let scheme = url.scheme?.lowercased(),
|
|
2362
2384
|
["http", "https"].contains(scheme) else {
|
|
@@ -482,6 +482,9 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
482
482
|
var disableOverscroll: Bool = false
|
|
483
483
|
var proxyRequests: Bool = false
|
|
484
484
|
var proxySchemeHandler: ProxySchemeHandler?
|
|
485
|
+
var bundledAssetSchemeHandler: BundledAssetSchemeHandler?
|
|
486
|
+
var bundledAssetLocalScheme: String = BundledAssetSupport.iosDefaults.scheme
|
|
487
|
+
var bundledAssetLocalHost: String = BundledAssetSupport.iosDefaults.host
|
|
485
488
|
var proxyBridge: ProxyBridge?
|
|
486
489
|
var proxyBridgeAccessToken: String?
|
|
487
490
|
var legacyProxyRequestURLRegexPattern: String?
|
|
@@ -2065,6 +2068,12 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
2065
2068
|
}
|
|
2066
2069
|
}
|
|
2067
2070
|
|
|
2071
|
+
let bundledAssetHandler = BundledAssetSchemeHandler(expectedHost: bundledAssetLocalHost)
|
|
2072
|
+
self.bundledAssetSchemeHandler = bundledAssetHandler
|
|
2073
|
+
if webConfiguration.urlSchemeHandler(forURLScheme: bundledAssetLocalScheme) == nil {
|
|
2074
|
+
webConfiguration.setURLSchemeHandler(bundledAssetHandler, forURLScheme: bundledAssetLocalScheme)
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2068
2077
|
if ProxyBridgeSupport.shouldInjectBridge(hasProxySchemeHandler: proxySchemeHandler != nil) {
|
|
2069
2078
|
addProxyBridgeUserScripts(to: userContentController)
|
|
2070
2079
|
}
|
|
@@ -2309,6 +2318,8 @@ open class WKWebViewController: UIViewController, WKScriptMessageHandler {
|
|
|
2309
2318
|
self.disableOverscroll = parent.disableOverscroll
|
|
2310
2319
|
self.proxyRequests = parent.proxyRequests
|
|
2311
2320
|
self.proxySchemeHandler = proxySchemeHandler
|
|
2321
|
+
self.bundledAssetLocalScheme = parent.bundledAssetLocalScheme
|
|
2322
|
+
self.bundledAssetLocalHost = parent.bundledAssetLocalHost
|
|
2312
2323
|
self.proxyBridge = parent.proxyBridge
|
|
2313
2324
|
self.proxyBridgeAccessToken = parent.proxyBridgeAccessToken
|
|
2314
2325
|
self.legacyProxyRequestURLRegexPattern = parent.legacyProxyRequestURLRegexPattern
|
|
@@ -2981,6 +2992,16 @@ fileprivate extension WKWebViewController {
|
|
|
2981
2992
|
return
|
|
2982
2993
|
}
|
|
2983
2994
|
|
|
2995
|
+
let bundledLocalConfig = BundledAssetSupport.LocalConfig(
|
|
2996
|
+
scheme: bundledAssetLocalScheme,
|
|
2997
|
+
host: bundledAssetLocalHost
|
|
2998
|
+
)
|
|
2999
|
+
if BundledAssetSupport.isBundledLocalURL(url.absoluteString, localConfig: bundledLocalConfig) {
|
|
3000
|
+
print("[InAppBrowser] bundled local asset URL detected, allowing navigation")
|
|
3001
|
+
completion(false)
|
|
3002
|
+
return
|
|
3003
|
+
}
|
|
3004
|
+
|
|
2984
3005
|
// Handle all non-http(s) schemes by default
|
|
2985
3006
|
if scheme != "http" && scheme != "https" && scheme != "file" {
|
|
2986
3007
|
print("[InAppBrowser] not http(s) scheme, try to open URLs in external apps")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-inappbrowser",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.15.0",
|
|
4
4
|
"description": "Capacitor plugin in app browser",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
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
|
-
"test:web": "bun test src/proxy-bridge-support.test.ts",
|
|
38
|
+
"test:web": "bun test src/proxy-bridge-support.test.ts src/bundled-asset-support.test.ts",
|
|
39
39
|
"test:maestro:android": "./scripts/test-maestro-android.sh",
|
|
40
40
|
"lint": "bun run eslint && bun run prettier -- --check && bun run swiftlint -- lint",
|
|
41
41
|
"fmt": "bun run eslint -- --fix && bun run prettier -- --write && bun run swiftlint -- --fix --format",
|