@tokamakdev/platform-ios-simulator-x64 0.1.1-beta.106 → 0.1.1-beta.112
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/build/entrypoint +25 -5
- package/ios-simulator-x64/frameworks/TokamakRuntime.framework/Headers/TokamakRuntime.h +10 -4
- package/ios-simulator-x64/frameworks/TokamakRuntime.framework/TokamakRuntime +0 -0
- package/ios-simulator-x64/native-shell/TokamakApp.swift +81 -63
- package/ios-simulator-x64/native-shell/TokamakBindings.swift +347 -0
- package/ios-simulator-x64/native-shell/TokamakControls.swift +98 -0
- package/ios-simulator-x64/native-shell/TokamakOrigin.swift +7 -0
- package/ios-simulator-x64/native-shell/TokamakPlugins.swift +307 -156
- package/ios-simulator-x64/native-shell/TokamakResources.swift +74 -0
- package/ios-simulator-x64/native-shell/TokamakShell.swift +3 -20
- package/ios-simulator-x64/platform-pack.json +1 -0
- package/ios-simulator-x64/tools/tokamak-apple-signing +0 -0
- package/package.json +1 -1
|
@@ -171,18 +171,38 @@ fi
|
|
|
171
171
|
# A plugin's class is a Swift type name, which the registry creates, and its
|
|
172
172
|
# sources are Swift.
|
|
173
173
|
swift_type='^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$'
|
|
174
|
+
# Swift requires unique file names in a module, so the source staged as
|
|
175
|
+
# <plugin>/<key>/<name> compiles as <plugin>-<key>-<name>.
|
|
176
|
+
plugin_sources=$temporary/plugin-sources
|
|
177
|
+
mkdir "$plugin_sources"
|
|
178
|
+
add_plugin_source() {
|
|
179
|
+
local plugin_id=$1 source=$2
|
|
180
|
+
local key=${source%/*}
|
|
181
|
+
local named=$plugin_sources/$plugin_id-${key##*/}-${source##*/}
|
|
182
|
+
cp "$source" "$named"
|
|
183
|
+
swift_sources+=("$named")
|
|
184
|
+
}
|
|
174
185
|
registry=$temporary/TokamakPluginRegistry.swift
|
|
175
186
|
printf '%s\n\n%s\n%s\n' 'import Foundation' 'func tokamakPlugins(app: TokamakApp) -> [any TokamakPlugin] {' ' [' > "$registry"
|
|
176
187
|
for plugin in "$input/plugins"/*/; do
|
|
188
|
+
plugin_id=$(basename "$plugin")
|
|
177
189
|
class=$(cat "$plugin/class" 2>/dev/null || true)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
190
|
+
# A plugin whose native code is its generated bindings alone has no class.
|
|
191
|
+
if [[ -n "$class" || ! -d "$plugin/bindings" ]]; then
|
|
192
|
+
if [[ ! "$class" =~ $swift_type ]]; then
|
|
193
|
+
echo "plugin '$plugin_id' has invalid $platform class '$class'" >&2
|
|
194
|
+
exit 1
|
|
195
|
+
fi
|
|
196
|
+
printf ' %s(host: TokamakHost(app: app, plugin: "%s")),\n' "$class" "$plugin_id" >> "$registry"
|
|
181
197
|
fi
|
|
182
|
-
|
|
198
|
+
# `tok bindings` names the generated class TokamakBindings_<id>, with - as _.
|
|
199
|
+
for source in "$plugin/bindings"/*.swift; do
|
|
200
|
+
printf ' TokamakBindings_%s(host: TokamakHost(app: app, plugin: "%s")),\n' "${plugin_id//-/_}" "$plugin_id" >> "$registry"
|
|
201
|
+
add_plugin_source "$plugin_id" "$source"
|
|
202
|
+
done
|
|
183
203
|
for source in "$plugin/sources"/*; do
|
|
184
204
|
case "$source" in
|
|
185
|
-
*.swift)
|
|
205
|
+
*.swift) add_plugin_source "$plugin_id" "$source" ;;
|
|
186
206
|
*)
|
|
187
207
|
echo "Apple plugin source must be Swift: $source" >&2
|
|
188
208
|
exit 1
|
|
@@ -23,13 +23,16 @@ typedef struct {
|
|
|
23
23
|
|
|
24
24
|
typedef void (*TokamakPluginRequest)(void *context, uint64_t id,
|
|
25
25
|
const char *plugin, const char *method,
|
|
26
|
-
const
|
|
26
|
+
const uint8_t *payload, size_t payload_len);
|
|
27
27
|
|
|
28
28
|
typedef struct {
|
|
29
29
|
void *context;
|
|
30
30
|
TokamakPluginRequest call;
|
|
31
|
+
TokamakPluginRequest call_with_body;
|
|
32
|
+
void (*chunk)(void *context, uint64_t id, const uint8_t *payload,
|
|
33
|
+
size_t payload_len, bool failed, bool done);
|
|
31
34
|
TokamakPluginRequest subscribe;
|
|
32
|
-
void (*
|
|
35
|
+
void (*cancel)(void *context, uint64_t id);
|
|
33
36
|
} TokamakPluginHandler;
|
|
34
37
|
|
|
35
38
|
void *tokamak_runtime_start(const char *packaged_dir, const char *state_dir,
|
|
@@ -47,14 +50,17 @@ uint16_t tokamak_runtime_restore_gateway(const void *runtime, char *error,
|
|
|
47
50
|
size_t error_len);
|
|
48
51
|
void tokamak_runtime_stop(void *runtime);
|
|
49
52
|
bool tokamak_runtime_emit(const void *runtime, const char *name,
|
|
50
|
-
const
|
|
53
|
+
const uint8_t *event, size_t event_len, uint64_t timeout_ms,
|
|
51
54
|
TokamakBytes *reply, char *error, size_t error_len);
|
|
52
55
|
bool tokamak_runtime_fetch(const void *runtime, const char *path,
|
|
53
56
|
uint64_t timeout_ms, TokamakBytes *body, char *error,
|
|
54
57
|
size_t error_len);
|
|
55
58
|
bool tokamak_runtime_set_foreground(const void *runtime, bool foreground);
|
|
56
59
|
bool tokamak_runtime_is_foreground(const void *runtime);
|
|
57
|
-
void tokamak_runtime_reply(const void *runtime, uint64_t id,
|
|
60
|
+
void tokamak_runtime_reply(const void *runtime, uint64_t id,
|
|
61
|
+
const uint8_t *payload, size_t payload_len,
|
|
62
|
+
bool failed, bool done);
|
|
63
|
+
void tokamak_runtime_pull(const void *runtime, uint64_t id);
|
|
58
64
|
|
|
59
65
|
int32_t tokamak_runtime_server_authority(const void *runtime, const char *host,
|
|
60
66
|
TokamakBytes *authority);
|
|
Binary file
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import TokamakRuntime
|
|
3
|
+
import WebKit
|
|
3
4
|
|
|
4
5
|
#if os(iOS)
|
|
5
6
|
import UIKit
|
|
@@ -17,19 +18,15 @@ 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 {
|
|
21
|
+
weak var webView: WKWebView?
|
|
22
|
+
let resources = TokamakResources()
|
|
20
23
|
/// Every plugin, by ID.
|
|
21
24
|
private(set) var plugins: [String: any TokamakPlugin] = [:]
|
|
25
|
+
private let runtimeLock = NSLock()
|
|
22
26
|
private var runtime: Result<RuntimeHandle, Error>?
|
|
23
27
|
private var waiting: [(Result<RuntimeHandle, Error>) -> Void] = []
|
|
24
28
|
/// The Worker's calls and subscriptions on the plugins.
|
|
25
|
-
private
|
|
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
|
-
}
|
|
29
|
+
private var workerRequests: TokamakPluginRequests<UInt64>!
|
|
33
30
|
/// Delivers `resume` and `suspend` one at a time, in order.
|
|
34
31
|
private let lifecycle = DispatchQueue(label: "tokamak.lifecycle")
|
|
35
32
|
#if os(iOS)
|
|
@@ -39,9 +36,22 @@ final class TokamakApp {
|
|
|
39
36
|
|
|
40
37
|
/// Creates the plugins. Call it before launch finishes.
|
|
41
38
|
init() {
|
|
39
|
+
plugins["tokamak"] = TokamakCorePlugin(host: TokamakHost(app: self, plugin: "tokamak"))
|
|
42
40
|
for plugin in tokamakPlugins(app: self) {
|
|
43
41
|
precondition(plugins.updateValue(plugin, forKey: plugin.id) == nil, "Duplicate plugin ID")
|
|
44
42
|
}
|
|
43
|
+
workerRequests = TokamakPluginRequests(
|
|
44
|
+
plugins: plugins,
|
|
45
|
+
send: { [unowned self] id, result in
|
|
46
|
+
ready { started in
|
|
47
|
+
if case .success(let runtime) = started { runtime.reply(id, result: result) }
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
pull: { [unowned self] id in
|
|
51
|
+
ready { started in
|
|
52
|
+
if case .success(let runtime) = started { runtime.pull(id) }
|
|
53
|
+
}
|
|
54
|
+
})
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
/// Starts the runtime in the background, which delivers `start` with
|
|
@@ -60,15 +70,24 @@ final class TokamakApp {
|
|
|
60
70
|
private var workerPlugins: TokamakPluginHandler {
|
|
61
71
|
TokamakPluginHandler(
|
|
62
72
|
context: Unmanaged.passUnretained(self).toOpaque(),
|
|
63
|
-
call: { context, id, plugin, method,
|
|
64
|
-
TokamakApp.receive(context, id,
|
|
73
|
+
call: { context, id, plugin, method, payload, count in
|
|
74
|
+
TokamakApp.receive(context, id, .call, plugin, method, payload, count)
|
|
65
75
|
},
|
|
66
|
-
|
|
67
|
-
TokamakApp.receive(context, id,
|
|
76
|
+
call_with_body: { context, id, plugin, method, payload, count in
|
|
77
|
+
TokamakApp.receive(context, id, .callWithBody, plugin, method, payload, count)
|
|
68
78
|
},
|
|
69
|
-
|
|
79
|
+
chunk: { context, id, payload, count, failed, done in
|
|
70
80
|
let app = Unmanaged<TokamakApp>.fromOpaque(context!).takeUnretainedValue()
|
|
71
|
-
|
|
81
|
+
let payload = count == 0 ? Data() : Data(bytes: payload!, count: count)
|
|
82
|
+
app.workerRequests.chunk(
|
|
83
|
+
id, TokamakPluginResult(payload: payload, failed: failed, done: done))
|
|
84
|
+
},
|
|
85
|
+
subscribe: { context, id, plugin, method, payload, count in
|
|
86
|
+
TokamakApp.receive(context, id, .subscribe, plugin, method, payload, count)
|
|
87
|
+
},
|
|
88
|
+
cancel: { context, id in
|
|
89
|
+
let app = Unmanaged<TokamakApp>.fromOpaque(context!).takeUnretainedValue()
|
|
90
|
+
app.workerRequests.cancel(id)
|
|
72
91
|
}
|
|
73
92
|
)
|
|
74
93
|
}
|
|
@@ -77,48 +96,47 @@ final class TokamakApp {
|
|
|
77
96
|
private static func receive(
|
|
78
97
|
_ context: UnsafeMutableRawPointer?,
|
|
79
98
|
_ id: UInt64,
|
|
80
|
-
|
|
99
|
+
_ kind: TokamakPluginRequest,
|
|
81
100
|
_ plugin: UnsafePointer<CChar>?,
|
|
82
101
|
_ method: UnsafePointer<CChar>?,
|
|
83
|
-
_
|
|
102
|
+
_ payload: UnsafePointer<UInt8>?,
|
|
103
|
+
_ count: Int
|
|
84
104
|
) {
|
|
85
105
|
let app = Unmanaged<TokamakApp>.fromOpaque(context!).takeUnretainedValue()
|
|
86
106
|
let (plugin, method) = (String(cString: plugin!), String(cString: method!))
|
|
87
|
-
let
|
|
88
|
-
|
|
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
|
-
}
|
|
107
|
+
let payload = count == 0 ? Data() : Data(bytes: payload!, count: count)
|
|
108
|
+
app.workerRequests.run(id, kind, plugin: plugin, method: method, payload: payload)
|
|
94
109
|
}
|
|
95
110
|
|
|
96
111
|
/// Calls `completion` on the main thread once the runtime has started or
|
|
97
112
|
/// failed to start.
|
|
98
113
|
func whenStarted(_ completion: @escaping (Result<RuntimeHandle, Error>) -> Void) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
114
|
+
ready { result in DispatchQueue.main.async { completion(result) } }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private func ready(_ completion: @escaping (Result<RuntimeHandle, Error>) -> Void) {
|
|
118
|
+
let current = runtimeLock.withLock {
|
|
119
|
+
if runtime == nil { waiting.append(completion) }
|
|
120
|
+
return runtime
|
|
104
121
|
}
|
|
122
|
+
if let current { completion(current) }
|
|
105
123
|
}
|
|
106
124
|
|
|
107
|
-
/// Delivers the event `name
|
|
125
|
+
/// Delivers the event `name`, whose encoded data is `payload`, to the
|
|
108
126
|
/// Worker's listeners on `queue`, and calls `completion` on the main thread
|
|
109
|
-
/// with their reply,
|
|
110
|
-
/// within `timeout`, which includes runtime startup. Call it on
|
|
111
|
-
/// thread.
|
|
127
|
+
/// with their encoded reply, which is empty when none replied. Fails unless
|
|
128
|
+
/// they return within `timeout`, which includes runtime startup. Call it on
|
|
129
|
+
/// the main thread.
|
|
112
130
|
func emit(
|
|
113
131
|
_ name: String,
|
|
114
|
-
|
|
132
|
+
payload: Data,
|
|
115
133
|
timeout: TimeInterval,
|
|
116
134
|
on queue: DispatchQueue = .global(qos: .userInitiated),
|
|
117
|
-
completion: @escaping (Result<
|
|
135
|
+
completion: @escaping (Result<Data, Error>) -> Void
|
|
118
136
|
) {
|
|
119
137
|
let deadline = Date() + timeout
|
|
120
138
|
withRuntime(on: queue, completion: completion) { runtime in
|
|
121
|
-
try runtime.emit(name,
|
|
139
|
+
try runtime.emit(name, payload: payload, timeout: deadline.timeIntervalSinceNow)
|
|
122
140
|
}
|
|
123
141
|
}
|
|
124
142
|
|
|
@@ -184,7 +202,7 @@ final class TokamakApp {
|
|
|
184
202
|
finish()
|
|
185
203
|
return
|
|
186
204
|
}
|
|
187
|
-
self.emit(name,
|
|
205
|
+
self.emit(name, payload: Data("{}".utf8), timeout: timeout, on: self.lifecycle) { result in
|
|
188
206
|
if case .failure(let error) = result {
|
|
189
207
|
print("tokamak \(name) failed: \(error)")
|
|
190
208
|
}
|
|
@@ -262,9 +280,12 @@ final class TokamakApp {
|
|
|
262
280
|
if case .failure(let error) = result {
|
|
263
281
|
RuntimeHandle.recordStartupFailure(error)
|
|
264
282
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
283
|
+
let callbacks = runtimeLock.withLock {
|
|
284
|
+
runtime = result
|
|
285
|
+
let callbacks = waiting
|
|
286
|
+
waiting.removeAll()
|
|
287
|
+
return callbacks
|
|
288
|
+
}
|
|
268
289
|
for callback in callbacks {
|
|
269
290
|
callback(result)
|
|
270
291
|
}
|
|
@@ -316,10 +337,7 @@ final class RuntimeHandle {
|
|
|
316
337
|
/// Starts the runtime, which delivers `start` with `foreground`, and whose
|
|
317
338
|
/// Worker calls `plugins`.
|
|
318
339
|
init(foreground: Bool, plugins: TokamakPluginHandler) throws {
|
|
319
|
-
guard
|
|
320
|
-
let appHost = Bundle.main.object(forInfoDictionaryKey: "TokamakHost") as? String,
|
|
321
|
-
!appHost.isEmpty
|
|
322
|
-
else {
|
|
340
|
+
guard let appHost = Bundle.main.tokamakHost, !appHost.isEmpty else {
|
|
323
341
|
throw RuntimeError.configuration("TokamakHost is required")
|
|
324
342
|
}
|
|
325
343
|
self.appHost = appHost
|
|
@@ -404,26 +422,20 @@ final class RuntimeHandle {
|
|
|
404
422
|
return port
|
|
405
423
|
}
|
|
406
424
|
|
|
407
|
-
/// Delivers the event `name
|
|
425
|
+
/// Delivers the event `name`, whose encoded data is `payload`, to the
|
|
408
426
|
/// Worker's listeners, blocking until they return or `timeout` passes, and
|
|
409
|
-
/// returns their reply,
|
|
410
|
-
func emit(_ name: String,
|
|
411
|
-
guard JSONSerialization.isValidJSONObject([event]) else {
|
|
412
|
-
throw RuntimeError.runtime("the \(name) event is not JSON")
|
|
413
|
-
}
|
|
414
|
-
let event = String(
|
|
415
|
-
decoding: try JSONSerialization.data(withJSONObject: event, options: .fragmentsAllowed),
|
|
416
|
-
as: UTF8.self
|
|
417
|
-
)
|
|
427
|
+
/// returns their encoded reply, which is empty when none replied.
|
|
428
|
+
func emit(_ name: String, payload: Data, timeout: TimeInterval) throws -> Data {
|
|
418
429
|
var reply = TokamakBytes()
|
|
419
430
|
var error = [CChar](repeating: 0, count: 1024)
|
|
420
431
|
let succeeded = name.withCString { name in
|
|
421
|
-
|
|
432
|
+
payload.withUnsafeBytes { payload in
|
|
422
433
|
error.withUnsafeMutableBufferPointer { error in
|
|
423
434
|
tokamak_runtime_emit(
|
|
424
435
|
handle,
|
|
425
436
|
name,
|
|
426
|
-
|
|
437
|
+
payload.bindMemory(to: UInt8.self).baseAddress,
|
|
438
|
+
payload.count,
|
|
427
439
|
UInt64(max(timeout, 0) * 1000),
|
|
428
440
|
&reply,
|
|
429
441
|
error.baseAddress,
|
|
@@ -433,11 +445,10 @@ final class RuntimeHandle {
|
|
|
433
445
|
}
|
|
434
446
|
}
|
|
435
447
|
defer { tokamak_bytes_free(reply) }
|
|
436
|
-
guard succeeded
|
|
448
|
+
guard succeeded else {
|
|
437
449
|
throw RuntimeError.runtime(String(cString: error))
|
|
438
450
|
}
|
|
439
|
-
|
|
440
|
-
return value is NSNull ? nil : value
|
|
451
|
+
return reply.contents ?? Data()
|
|
441
452
|
}
|
|
442
453
|
|
|
443
454
|
/// What the app serves at `path`, blocking until it arrives or `timeout`
|
|
@@ -476,11 +487,18 @@ final class RuntimeHandle {
|
|
|
476
487
|
tokamak_runtime_is_foreground(handle)
|
|
477
488
|
}
|
|
478
489
|
|
|
479
|
-
///
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
490
|
+
/// Asks the Worker for the next chunk of the body of the call `id`.
|
|
491
|
+
func pull(_ id: UInt64) {
|
|
492
|
+
tokamak_runtime_pull(handle, id)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/// Passes an encoded plugin result to its native invocation.
|
|
496
|
+
func reply(_ id: UInt64, result: TokamakPluginResult) {
|
|
497
|
+
result.payload.withUnsafeBytes {
|
|
498
|
+
tokamak_runtime_reply(
|
|
499
|
+
handle, id, $0.bindMemory(to: UInt8.self).baseAddress,
|
|
500
|
+
$0.count, result.failed, result.done)
|
|
501
|
+
}
|
|
484
502
|
}
|
|
485
503
|
|
|
486
504
|
func serverAuthority(host: String) -> AuthenticationMaterial<Data> {
|