@tokamakdev/platform-ios-simulator-x64 0.1.1-beta.83 → 0.1.1-beta.84
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/ios-simulator-x64/frameworks/TokamakRuntime.framework/Headers/TokamakRuntime.h +18 -3
- package/ios-simulator-x64/frameworks/TokamakRuntime.framework/TokamakRuntime +0 -0
- package/ios-simulator-x64/native-shell/TokamakApp.swift +104 -22
- package/ios-simulator-x64/native-shell/TokamakPlugins.swift +119 -71
- package/package.json +1 -1
|
@@ -21,14 +21,27 @@ typedef struct {
|
|
|
21
21
|
TokamakBytes private_key;
|
|
22
22
|
} TokamakIdentity;
|
|
23
23
|
|
|
24
|
+
typedef void (*TokamakPluginRequest)(void *context, uint64_t id,
|
|
25
|
+
const char *plugin, const char *method,
|
|
26
|
+
const char *arguments);
|
|
27
|
+
|
|
28
|
+
typedef struct {
|
|
29
|
+
void *context;
|
|
30
|
+
TokamakPluginRequest call;
|
|
31
|
+
TokamakPluginRequest subscribe;
|
|
32
|
+
void (*unsubscribe)(void *context, uint64_t id);
|
|
33
|
+
} TokamakPluginHandler;
|
|
34
|
+
|
|
24
35
|
void *tokamak_runtime_start(const char *packaged_dir, const char *state_dir,
|
|
25
36
|
const char *storage_dir, const char *host,
|
|
26
|
-
bool foreground,
|
|
37
|
+
bool foreground, TokamakPluginHandler plugins,
|
|
38
|
+
char *error, size_t error_len);
|
|
27
39
|
void *tokamak_runtime_start_development(const char *state_dir, const char *host,
|
|
28
40
|
const char *endpoint,
|
|
29
41
|
const char *session_token,
|
|
30
|
-
bool foreground,
|
|
31
|
-
|
|
42
|
+
bool foreground,
|
|
43
|
+
TokamakPluginHandler plugins,
|
|
44
|
+
char *error, size_t error_len);
|
|
32
45
|
uint16_t tokamak_runtime_port(const void *runtime);
|
|
33
46
|
uint16_t tokamak_runtime_restore_gateway(const void *runtime, char *error,
|
|
34
47
|
size_t error_len);
|
|
@@ -36,6 +49,8 @@ void tokamak_runtime_stop(void *runtime);
|
|
|
36
49
|
bool tokamak_runtime_emit(const void *runtime, const char *name,
|
|
37
50
|
const char *event, uint64_t timeout_ms,
|
|
38
51
|
TokamakBytes *reply, char *error, size_t error_len);
|
|
52
|
+
bool tokamak_runtime_set_foreground(const void *runtime, bool foreground);
|
|
53
|
+
void tokamak_runtime_reply(const void *runtime, uint64_t id, const char *result);
|
|
39
54
|
|
|
40
55
|
int32_t tokamak_runtime_server_authority(const void *runtime, const char *host,
|
|
41
56
|
TokamakBytes *authority);
|
|
Binary file
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import TokamakRuntime
|
|
3
|
+
import os
|
|
3
4
|
|
|
4
5
|
#if os(iOS)
|
|
5
6
|
import UIKit
|
|
@@ -17,31 +18,82 @@ private let lifecycleDeadline: TimeInterval = 10
|
|
|
17
18
|
/// WebView, so plugins receive app events before a page loads and a Worker
|
|
18
19
|
/// event can run while no page is loaded.
|
|
19
20
|
final class TokamakApp {
|
|
20
|
-
|
|
21
|
+
/// Every plugin, by ID.
|
|
22
|
+
private(set) var plugins: [String: any TokamakPlugin] = [:]
|
|
21
23
|
private var runtime: Result<RuntimeHandle, Error>?
|
|
22
24
|
private var waiting: [(Result<RuntimeHandle, Error>) -> Void] = []
|
|
25
|
+
/// The Worker's calls and subscriptions on the plugins.
|
|
26
|
+
private lazy var workerRequests = TokamakPluginRequests<UInt64>(plugins: plugins) {
|
|
27
|
+
[unowned self] id, result in
|
|
28
|
+
whenStarted { started in
|
|
29
|
+
if case .success(let runtime) = started {
|
|
30
|
+
runtime.reply(id, result: result)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
23
34
|
/// Delivers `resume` and `suspend` one at a time, in order.
|
|
24
35
|
private let lifecycle = DispatchQueue(label: "tokamak.lifecycle")
|
|
25
36
|
#if os(iOS)
|
|
26
|
-
/// The scenes in the foreground.
|
|
27
|
-
private
|
|
37
|
+
/// The scenes in the foreground, which plugins read on any thread.
|
|
38
|
+
private let foregroundScenes = OSAllocatedUnfairLock(initialState: 0)
|
|
28
39
|
#endif
|
|
29
40
|
|
|
30
41
|
/// Creates the plugins. Call it before launch finishes.
|
|
31
42
|
init() {
|
|
32
|
-
|
|
43
|
+
for plugin in tokamakPlugins(app: self) {
|
|
44
|
+
precondition(plugins.updateValue(plugin, forKey: plugin.id) == nil, "Duplicate plugin ID")
|
|
45
|
+
}
|
|
33
46
|
}
|
|
34
47
|
|
|
35
48
|
/// Starts the runtime in the background, which delivers `start` with
|
|
36
49
|
/// `foreground`. Call it once, on the main thread.
|
|
37
50
|
func start(foreground: Bool) {
|
|
38
51
|
dispatchPrecondition(condition: .onQueue(.main))
|
|
52
|
+
let plugins = workerPlugins
|
|
39
53
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
40
|
-
let result = Result { try RuntimeHandle(foreground: foreground) }
|
|
54
|
+
let result = Result { try RuntimeHandle(foreground: foreground, plugins: plugins) }
|
|
41
55
|
DispatchQueue.main.async { self.started(result) }
|
|
42
56
|
}
|
|
43
57
|
}
|
|
44
58
|
|
|
59
|
+
/// The plugins as the runtime calls them, on its own threads. The app
|
|
60
|
+
/// outlives the runtime, which holds it unretained.
|
|
61
|
+
private var workerPlugins: TokamakPluginHandler {
|
|
62
|
+
TokamakPluginHandler(
|
|
63
|
+
context: Unmanaged.passUnretained(self).toOpaque(),
|
|
64
|
+
call: { context, id, plugin, method, arguments in
|
|
65
|
+
TokamakApp.receive(context, id, subscribe: false, plugin, method, arguments)
|
|
66
|
+
},
|
|
67
|
+
subscribe: { context, id, plugin, method, arguments in
|
|
68
|
+
TokamakApp.receive(context, id, subscribe: true, plugin, method, arguments)
|
|
69
|
+
},
|
|
70
|
+
unsubscribe: { context, id in
|
|
71
|
+
let app = Unmanaged<TokamakApp>.fromOpaque(context!).takeUnretainedValue()
|
|
72
|
+
DispatchQueue.main.async { app.workerRequests.cancel(id) }
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Runs a call or subscription the Worker made through `context`'s app.
|
|
78
|
+
private static func receive(
|
|
79
|
+
_ context: UnsafeMutableRawPointer?,
|
|
80
|
+
_ id: UInt64,
|
|
81
|
+
subscribe: Bool,
|
|
82
|
+
_ plugin: UnsafePointer<CChar>?,
|
|
83
|
+
_ method: UnsafePointer<CChar>?,
|
|
84
|
+
_ arguments: UnsafePointer<CChar>?
|
|
85
|
+
) {
|
|
86
|
+
let app = Unmanaged<TokamakApp>.fromOpaque(context!).takeUnretainedValue()
|
|
87
|
+
let (plugin, method) = (String(cString: plugin!), String(cString: method!))
|
|
88
|
+
let arguments =
|
|
89
|
+
(try? JSONSerialization.jsonObject(
|
|
90
|
+
with: Data(String(cString: arguments!).utf8), options: .fragmentsAllowed)) ?? NSNull()
|
|
91
|
+
DispatchQueue.main.async {
|
|
92
|
+
app.workerRequests.run(
|
|
93
|
+
id, subscribe: subscribe, plugin: plugin, method: method, arguments: arguments)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
45
97
|
/// Calls `completion` on the main thread once the runtime has started or
|
|
46
98
|
/// failed to start.
|
|
47
99
|
func whenStarted(_ completion: @escaping (Result<RuntimeHandle, Error>) -> Void) {
|
|
@@ -81,8 +133,9 @@ final class TokamakApp {
|
|
|
81
133
|
}
|
|
82
134
|
}
|
|
83
135
|
|
|
84
|
-
///
|
|
85
|
-
///
|
|
136
|
+
/// Records the app moving into or out of the foreground once the runtime
|
|
137
|
+
/// has started, and delivers `resume` or `suspend` when that changed its
|
|
138
|
+
/// stage. Call it on the main thread.
|
|
86
139
|
func moved(toForeground foreground: Bool) {
|
|
87
140
|
let name = foreground ? "resume" : "suspend"
|
|
88
141
|
#if os(iOS)
|
|
@@ -103,45 +156,57 @@ final class TokamakApp {
|
|
|
103
156
|
let finish = {}
|
|
104
157
|
let timeout = lifecycleDeadline
|
|
105
158
|
#endif
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
159
|
+
whenStarted { started in
|
|
160
|
+
guard case .success(let runtime) = started, runtime.setForeground(foreground) else {
|
|
161
|
+
finish()
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
self.emit(name, event: [String: Any](), timeout: timeout, on: self.lifecycle) { result in
|
|
165
|
+
if case .failure(let error) = result {
|
|
166
|
+
print("tokamak \(name) failed: \(error)")
|
|
167
|
+
}
|
|
168
|
+
finish()
|
|
109
169
|
}
|
|
110
|
-
finish()
|
|
111
170
|
}
|
|
112
171
|
}
|
|
113
172
|
|
|
114
173
|
#if os(iOS)
|
|
115
174
|
/// Delivers `resume` as the first scene enters the foreground.
|
|
116
175
|
func sceneWillEnterForeground() {
|
|
117
|
-
foregroundScenes
|
|
118
|
-
|
|
176
|
+
let first = foregroundScenes.withLock { scenes in
|
|
177
|
+
scenes += 1
|
|
178
|
+
return scenes == 1
|
|
179
|
+
}
|
|
180
|
+
if first {
|
|
119
181
|
moved(toForeground: true)
|
|
120
182
|
}
|
|
121
183
|
}
|
|
122
184
|
|
|
123
185
|
/// Delivers `suspend` as the last scene leaves the foreground.
|
|
124
186
|
func sceneDidEnterBackground() {
|
|
125
|
-
foregroundScenes
|
|
126
|
-
|
|
187
|
+
let last = foregroundScenes.withLock { scenes in
|
|
188
|
+
scenes -= 1
|
|
189
|
+
return scenes == 0
|
|
190
|
+
}
|
|
191
|
+
if last {
|
|
127
192
|
moved(toForeground: false)
|
|
128
193
|
}
|
|
129
194
|
}
|
|
130
195
|
|
|
131
|
-
/// Whether a scene is in the foreground.
|
|
196
|
+
/// Whether a scene is in the foreground. Read it on any thread.
|
|
132
197
|
var isInForeground: Bool {
|
|
133
|
-
foregroundScenes > 0
|
|
198
|
+
foregroundScenes.withLock { $0 > 0 }
|
|
134
199
|
}
|
|
135
200
|
#endif
|
|
136
201
|
|
|
137
202
|
func didRegisterForRemoteNotifications(deviceToken: Data) {
|
|
138
|
-
for plugin in plugins {
|
|
203
|
+
for plugin in plugins.values {
|
|
139
204
|
plugin.didRegisterForRemoteNotifications(deviceToken: deviceToken)
|
|
140
205
|
}
|
|
141
206
|
}
|
|
142
207
|
|
|
143
208
|
func didFailToRegisterForRemoteNotifications(error: Error) {
|
|
144
|
-
for plugin in plugins {
|
|
209
|
+
for plugin in plugins.values {
|
|
145
210
|
plugin.didFailToRegisterForRemoteNotifications(error: error)
|
|
146
211
|
}
|
|
147
212
|
}
|
|
@@ -154,7 +219,7 @@ final class TokamakApp {
|
|
|
154
219
|
) {
|
|
155
220
|
let group = DispatchGroup()
|
|
156
221
|
var results: [TokamakBackgroundResult] = []
|
|
157
|
-
for plugin in plugins {
|
|
222
|
+
for plugin in plugins.values {
|
|
158
223
|
group.enter()
|
|
159
224
|
plugin.didReceiveRemoteNotification(userInfo) { result in
|
|
160
225
|
DispatchQueue.main.async {
|
|
@@ -223,8 +288,9 @@ final class RuntimeHandle {
|
|
|
223
288
|
let appHost: String
|
|
224
289
|
private var handle: UnsafeMutableRawPointer?
|
|
225
290
|
|
|
226
|
-
/// Starts the runtime, which delivers `start` with `foreground
|
|
227
|
-
|
|
291
|
+
/// Starts the runtime, which delivers `start` with `foreground`, and whose
|
|
292
|
+
/// Worker calls `plugins`.
|
|
293
|
+
init(foreground: Bool, plugins: TokamakPluginHandler) throws {
|
|
228
294
|
guard
|
|
229
295
|
let appHost = Bundle.main.object(forInfoDictionaryKey: "TokamakHost") as? String,
|
|
230
296
|
!appHost.isEmpty
|
|
@@ -250,6 +316,7 @@ final class RuntimeHandle {
|
|
|
250
316
|
endpoint,
|
|
251
317
|
sessionToken,
|
|
252
318
|
foreground,
|
|
319
|
+
plugins,
|
|
253
320
|
error.baseAddress,
|
|
254
321
|
error.count
|
|
255
322
|
)
|
|
@@ -275,6 +342,7 @@ final class RuntimeHandle {
|
|
|
275
342
|
storagePath,
|
|
276
343
|
appHost,
|
|
277
344
|
foreground,
|
|
345
|
+
plugins,
|
|
278
346
|
error.baseAddress,
|
|
279
347
|
error.count
|
|
280
348
|
)
|
|
@@ -347,6 +415,20 @@ final class RuntimeHandle {
|
|
|
347
415
|
return value is NSNull ? nil : value
|
|
348
416
|
}
|
|
349
417
|
|
|
418
|
+
/// Records whether the app is in the foreground, which the Worker's
|
|
419
|
+
/// `getLifecycleStage()` reports from then on, and returns whether that
|
|
420
|
+
/// changed.
|
|
421
|
+
func setForeground(_ foreground: Bool) -> Bool {
|
|
422
|
+
tokamak_runtime_set_foreground(handle, foreground)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/// Passes a plugin's `result`, JSON-serialisable, to the Worker's call or
|
|
426
|
+
/// subscription `id`.
|
|
427
|
+
func reply(_ id: UInt64, result: [String: Any]) {
|
|
428
|
+
guard let json = try? JSONSerialization.data(withJSONObject: result) else { return }
|
|
429
|
+
String(decoding: json, as: UTF8.self).withCString { tokamak_runtime_reply(handle, id, $0) }
|
|
430
|
+
}
|
|
431
|
+
|
|
350
432
|
func serverAuthority(host: String) -> AuthenticationMaterial<Data> {
|
|
351
433
|
var bytes = TokamakBytes()
|
|
352
434
|
let decision = host.withCString {
|
|
@@ -8,6 +8,12 @@ struct TokamakPluginError: Error {
|
|
|
8
8
|
static func notSupported(_ message: String) -> Self {
|
|
9
9
|
Self(name: "NotSupportedError", message: message)
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
/// UI was needed while the app cannot show it.
|
|
13
|
+
static let needsUI = Self(
|
|
14
|
+
name: "NeedsUIError",
|
|
15
|
+
message: "The app cannot show UI while it is in the background"
|
|
16
|
+
)
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
typealias TokamakPluginReply = (Result<Any?, TokamakPluginError>) -> Void
|
|
@@ -22,6 +28,15 @@ final class TokamakHost {
|
|
|
22
28
|
self.plugin = plugin
|
|
23
29
|
}
|
|
24
30
|
|
|
31
|
+
/// Throws `NeedsUIError` unless the app can show UI now: on iOS, while a
|
|
32
|
+
/// scene is in the foreground. A hidden or minimised macOS app still shows
|
|
33
|
+
/// system prompts. Call it where the plugin would show UI, on any thread.
|
|
34
|
+
func requireUI() throws(TokamakPluginError) {
|
|
35
|
+
#if os(iOS)
|
|
36
|
+
guard app.isInForeground else { throw .needsUI }
|
|
37
|
+
#endif
|
|
38
|
+
}
|
|
39
|
+
|
|
25
40
|
/// Delivers the plugin's event `name`, which the Worker's listeners
|
|
26
41
|
/// receive as `<plugin>.<name>`, with `event`, JSON-serialisable, and calls
|
|
27
42
|
/// `completion` on the main thread with their reply, parsed from JSON, or
|
|
@@ -37,7 +52,8 @@ final class TokamakHost {
|
|
|
37
52
|
}
|
|
38
53
|
}
|
|
39
54
|
|
|
40
|
-
/// A native plugin, created once per process when the app launches.
|
|
55
|
+
/// A native plugin, created once per process when the app launches. The page
|
|
56
|
+
/// and the Worker call it on the main thread.
|
|
41
57
|
protocol TokamakPlugin: AnyObject {
|
|
42
58
|
var id: String { get }
|
|
43
59
|
|
|
@@ -49,11 +65,13 @@ protocol TokamakPlugin: AnyObject {
|
|
|
49
65
|
reply: @escaping TokamakPluginReply
|
|
50
66
|
)
|
|
51
67
|
|
|
68
|
+
/// Replies with each value until the returned function is called. Throws
|
|
69
|
+
/// when the subscription cannot start, which ends it.
|
|
52
70
|
func subscribe(
|
|
53
71
|
method: String,
|
|
54
72
|
arguments: Any,
|
|
55
73
|
reply: @escaping TokamakPluginReply
|
|
56
|
-
) -> (() -> Void)
|
|
74
|
+
) throws(TokamakPluginError) -> (() -> Void)
|
|
57
75
|
|
|
58
76
|
func didRegisterForRemoteNotifications(deviceToken: Data)
|
|
59
77
|
|
|
@@ -80,9 +98,8 @@ extension TokamakPlugin {
|
|
|
80
98
|
method: String,
|
|
81
99
|
arguments: Any,
|
|
82
100
|
reply: @escaping TokamakPluginReply
|
|
83
|
-
) -> (() -> Void) {
|
|
84
|
-
|
|
85
|
-
return {}
|
|
101
|
+
) throws(TokamakPluginError) -> (() -> Void) {
|
|
102
|
+
throw .notSupported("\(id).\(method) is not supported")
|
|
86
103
|
}
|
|
87
104
|
|
|
88
105
|
func didRegisterForRemoteNotifications(deviceToken: Data) {}
|
|
@@ -97,6 +114,82 @@ extension TokamakPlugin {
|
|
|
97
114
|
}
|
|
98
115
|
}
|
|
99
116
|
|
|
117
|
+
/// Runs one caller's calls and subscriptions, the page's or the Worker's, on
|
|
118
|
+
/// the app's plugins, keyed by `Key`. `send` receives each result on the main
|
|
119
|
+
/// thread, as the JSON object the page's native transport receives without its
|
|
120
|
+
/// session and ID. Use it on the main thread.
|
|
121
|
+
final class TokamakPluginRequests<Key: Hashable> {
|
|
122
|
+
private let plugins: [String: any TokamakPlugin]
|
|
123
|
+
private let send: (Key, [String: Any]) -> Void
|
|
124
|
+
private var cancellations: [Key: () -> Void] = [:]
|
|
125
|
+
|
|
126
|
+
init(plugins: [String: any TokamakPlugin], send: @escaping (Key, [String: Any]) -> Void) {
|
|
127
|
+
self.plugins = plugins
|
|
128
|
+
self.send = send
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Calls `method` of `plugin`, or subscribes to it when `subscribe`.
|
|
132
|
+
func run(_ key: Key, subscribe: Bool, plugin: String, method: String, arguments: Any) {
|
|
133
|
+
guard let target = plugins[plugin], !method.isEmpty else {
|
|
134
|
+
fail(key, .notSupported("Plugin is not supported"))
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
// Plugins reply from any thread.
|
|
138
|
+
let reply: TokamakPluginReply = { [weak self] result in
|
|
139
|
+
DispatchQueue.main.async {
|
|
140
|
+
self?.send(key, Self.result(result, done: !subscribe))
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
guard subscribe else {
|
|
144
|
+
target.call(method: method, arguments: arguments, reply: reply)
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
do {
|
|
148
|
+
cancellations[key] = try target.subscribe(method: method, arguments: arguments, reply: reply)
|
|
149
|
+
} catch {
|
|
150
|
+
fail(key, error)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// Ends the request `key` with `error`.
|
|
155
|
+
func fail(_ key: Key, _ error: TokamakPluginError) {
|
|
156
|
+
send(key, Self.result(.failure(error), done: true))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// Ends the subscription `key`.
|
|
160
|
+
func cancel(_ key: Key) {
|
|
161
|
+
cancellations.removeValue(forKey: key)?()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Ends every subscription.
|
|
165
|
+
func cancelAll() {
|
|
166
|
+
let cancelAll = Array(cancellations.values)
|
|
167
|
+
cancellations.removeAll()
|
|
168
|
+
for cancel in cancelAll {
|
|
169
|
+
cancel()
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// `result` as JSON, or the failure to make it JSON.
|
|
174
|
+
private static func result(
|
|
175
|
+
_ result: Result<Any?, TokamakPluginError>,
|
|
176
|
+
done: Bool
|
|
177
|
+
) -> [String: Any] {
|
|
178
|
+
switch result {
|
|
179
|
+
case .success(let value):
|
|
180
|
+
let json: [String: Any] = ["value": value ?? NSNull(), "done": done]
|
|
181
|
+
if JSONSerialization.isValidJSONObject(json) {
|
|
182
|
+
return json
|
|
183
|
+
}
|
|
184
|
+
let error = TokamakPluginError(
|
|
185
|
+
name: "OperationError", message: "The plugin's value is not JSON")
|
|
186
|
+
return Self.result(.failure(error), done: done)
|
|
187
|
+
case .failure(let error):
|
|
188
|
+
return ["error": ["name": error.name, "message": error.message], "done": done]
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
100
193
|
final class TokamakPluginBridge: NSObject, WKScriptMessageHandler {
|
|
101
194
|
private struct RequestKey: Hashable {
|
|
102
195
|
let session: String
|
|
@@ -105,17 +198,15 @@ final class TokamakPluginBridge: NSObject, WKScriptMessageHandler {
|
|
|
105
198
|
|
|
106
199
|
private let host: String
|
|
107
200
|
private let plugins: [String: any TokamakPlugin]
|
|
108
|
-
private var
|
|
201
|
+
private lazy var requests = TokamakPluginRequests<RequestKey>(plugins: plugins) {
|
|
202
|
+
[weak self] key, result in
|
|
203
|
+
self?.deliver(key: key, result: result)
|
|
204
|
+
}
|
|
109
205
|
weak var webView: WKWebView?
|
|
110
206
|
|
|
111
|
-
init(host: String, plugins: [any TokamakPlugin]) {
|
|
207
|
+
init(host: String, plugins: [String: any TokamakPlugin]) {
|
|
112
208
|
self.host = host
|
|
113
|
-
|
|
114
|
-
for plugin in plugins {
|
|
115
|
-
precondition(pluginsById[plugin.id] == nil, "Duplicate plugin ID")
|
|
116
|
-
pluginsById[plugin.id] = plugin
|
|
117
|
-
}
|
|
118
|
-
self.plugins = pluginsById
|
|
209
|
+
self.plugins = plugins
|
|
119
210
|
}
|
|
120
211
|
|
|
121
212
|
func install(in controller: WKUserContentController) {
|
|
@@ -130,11 +221,7 @@ final class TokamakPluginBridge: NSObject, WKScriptMessageHandler {
|
|
|
130
221
|
}
|
|
131
222
|
|
|
132
223
|
func close() {
|
|
133
|
-
|
|
134
|
-
cancellations.removeAll()
|
|
135
|
-
for cancel in cancelAll {
|
|
136
|
-
cancel()
|
|
137
|
-
}
|
|
224
|
+
requests.cancelAll()
|
|
138
225
|
}
|
|
139
226
|
|
|
140
227
|
func userContentController(
|
|
@@ -159,66 +246,27 @@ final class TokamakPluginBridge: NSObject, WKScriptMessageHandler {
|
|
|
159
246
|
}
|
|
160
247
|
guard let id = request["id"] as? Int else { return }
|
|
161
248
|
let key = RequestKey(session: session, id: id)
|
|
162
|
-
if type == "cancel" {
|
|
163
|
-
cancellations.removeValue(forKey: key)?()
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
dispatch(type: type, key: key, request: request)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
private func dispatch(type: String, key: RequestKey, request: [String: Any]) {
|
|
170
|
-
guard
|
|
171
|
-
let pluginId = request["plugin"] as? String,
|
|
172
|
-
let method = request["method"] as? String,
|
|
173
|
-
let plugin = plugins[pluginId]
|
|
174
|
-
else {
|
|
175
|
-
send(key: key, result: .failure(.notSupported("Plugin is not supported")), done: true)
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
let arguments = request["arguments"] ?? NSNull()
|
|
179
|
-
let reply: TokamakPluginReply = { [weak self] result in
|
|
180
|
-
self?.send(key: key, result: result, done: type == "call")
|
|
181
|
-
}
|
|
182
249
|
switch type {
|
|
183
|
-
case "
|
|
184
|
-
|
|
185
|
-
case "subscribe":
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
250
|
+
case "cancel":
|
|
251
|
+
requests.cancel(key)
|
|
252
|
+
case "call", "subscribe":
|
|
253
|
+
requests.run(
|
|
254
|
+
key,
|
|
255
|
+
subscribe: type == "subscribe",
|
|
256
|
+
plugin: request["plugin"] as? String ?? "",
|
|
257
|
+
method: request["method"] as? String ?? "",
|
|
258
|
+
arguments: request["arguments"] ?? NSNull()
|
|
190
259
|
)
|
|
191
|
-
cancellations[key] = cancellation
|
|
192
260
|
default:
|
|
193
|
-
|
|
194
|
-
key: key, result: .failure(.notSupported("Plugin operation is not supported")), done: true)
|
|
261
|
+
requests.fail(key, .notSupported("Plugin operation is not supported"))
|
|
195
262
|
}
|
|
196
263
|
}
|
|
197
264
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private func deliver(key: RequestKey, result: Result<Any?, TokamakPluginError>, done: Bool) {
|
|
206
|
-
if done {
|
|
207
|
-
cancellations.removeValue(forKey: key)?()
|
|
208
|
-
}
|
|
209
|
-
var response: [String: Any] = [
|
|
210
|
-
"session": key.session,
|
|
211
|
-
"id": key.id,
|
|
212
|
-
"done": done,
|
|
213
|
-
]
|
|
214
|
-
switch result {
|
|
215
|
-
case .success(let value):
|
|
216
|
-
response["value"] = value ?? NSNull()
|
|
217
|
-
case .failure(let error):
|
|
218
|
-
response["error"] = ["name": error.name, "message": error.message]
|
|
219
|
-
}
|
|
265
|
+
private func deliver(key: RequestKey, result: [String: Any]) {
|
|
266
|
+
var response = result
|
|
267
|
+
response["session"] = key.session
|
|
268
|
+
response["id"] = key.id
|
|
220
269
|
guard
|
|
221
|
-
JSONSerialization.isValidJSONObject(response),
|
|
222
270
|
let data = try? JSONSerialization.data(withJSONObject: response),
|
|
223
271
|
let json = String(data: data, encoding: .utf8),
|
|
224
272
|
webView?.url?.isAppOrigin(host) == true
|