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