@craft-native/ios 0.0.340 → 0.0.341
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/package.json
CHANGED
|
@@ -203,7 +203,13 @@ enum CraftNativeActions {
|
|
|
203
203
|
case ("Clipboard", "read"):
|
|
204
204
|
sync = perform(action: "clipboardRead", body: [:], config: config)
|
|
205
205
|
case ("Storage", _):
|
|
206
|
-
DispatchQueue.main.async {
|
|
206
|
+
DispatchQueue.main.async {
|
|
207
|
+
cancellationLock.lock()
|
|
208
|
+
let cancelled = cancelledRequests.remove(requestToken) != nil
|
|
209
|
+
cancellationLock.unlock()
|
|
210
|
+
guard !cancelled else { return }
|
|
211
|
+
complete(requestToken, storage(method: method, args: args), completion)
|
|
212
|
+
}
|
|
207
213
|
return
|
|
208
214
|
case ("SecureStorage", _):
|
|
209
215
|
guard config.enableSecureStorage else {
|
|
@@ -15,6 +15,45 @@ import UIKit
|
|
|
15
15
|
|
|
16
16
|
private let craftNativeCapabilityTimeoutMilliseconds = 30_000
|
|
17
17
|
|
|
18
|
+
/// A retained JavaScript context must lose synchronous storage access when
|
|
19
|
+
/// its screen closes. Only the actual synchronous unmount callback may finish
|
|
20
|
+
/// its cleanup; queued script work runs after that allowance ends.
|
|
21
|
+
private final class CraftNativeStorageLifetime {
|
|
22
|
+
private let lock = NSLock()
|
|
23
|
+
private var closed = false
|
|
24
|
+
private var unmounting = false
|
|
25
|
+
|
|
26
|
+
func close() {
|
|
27
|
+
lock.lock()
|
|
28
|
+
closed = true
|
|
29
|
+
lock.unlock()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func unmount(_ work: () -> Void) {
|
|
33
|
+
lock.lock()
|
|
34
|
+
unmounting = true
|
|
35
|
+
lock.unlock()
|
|
36
|
+
defer {
|
|
37
|
+
lock.lock()
|
|
38
|
+
unmounting = false
|
|
39
|
+
lock.unlock()
|
|
40
|
+
}
|
|
41
|
+
work()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func access<T>(closedValue: T, _ work: () -> T) -> T {
|
|
45
|
+
lock.lock()
|
|
46
|
+
defer { lock.unlock() }
|
|
47
|
+
guard !closed || unmounting else {
|
|
48
|
+
if let context = JSContext.current() {
|
|
49
|
+
context.exception = JSValue(newErrorFromMessage: "SCREEN_CLOSED: The native screen is closed", in: context)
|
|
50
|
+
}
|
|
51
|
+
return closedValue
|
|
52
|
+
}
|
|
53
|
+
return work()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
18
57
|
/// Foundation's `NSNumber(0/1) is Bool` is true even for JSON numbers.
|
|
19
58
|
/// Check the Core Foundation identity so real booleans stay invalid numeric
|
|
20
59
|
/// props while zero lengths, flex factors and opacity 1 remain numbers.
|
|
@@ -5751,6 +5790,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5751
5790
|
weak var hybridEvents: CraftHybridScreenEvents?
|
|
5752
5791
|
private var hybridRendered = false
|
|
5753
5792
|
private var runtimeDisposed = false
|
|
5793
|
+
private let storageLifetime = CraftNativeStorageLifetime()
|
|
5754
5794
|
/// The app script (`nativeApp`, CraftHybrid.swift): a bundle route with
|
|
5755
5795
|
/// no screen, loaded once and never shown, that runs for the app's life.
|
|
5756
5796
|
/// It hears every `APP_STATE` and the app's events (`CRAFT_EVENT`), no
|
|
@@ -5998,6 +6038,11 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
5998
6038
|
private func disposeRuntime() {
|
|
5999
6039
|
guard !runtimeDisposed else { return }
|
|
6000
6040
|
runtimeDisposed = true
|
|
6041
|
+
storageLifetime.close()
|
|
6042
|
+
pendingCapabilityDeadlines.values.forEach { $0.cancel() }
|
|
6043
|
+
pendingCapabilityDeadlines.removeAll()
|
|
6044
|
+
pendingCapabilityRequests.forEach { CraftNativeActions.cancel(requestToken: $0) }
|
|
6045
|
+
pendingCapabilityRequests.removeAll()
|
|
6001
6046
|
if let owner = voiceRecordingOwner {
|
|
6002
6047
|
voiceRecordingOwner = nil
|
|
6003
6048
|
CraftVoiceRecording.shared.perform(method: "cancel", args: [owner]) { _ in }
|
|
@@ -6007,7 +6052,10 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
6007
6052
|
disposeRenderedTree()
|
|
6008
6053
|
let context = jsContext
|
|
6009
6054
|
let route = routeName ?? "screen"
|
|
6010
|
-
|
|
6055
|
+
let storageLifetime = self.storageLifetime
|
|
6056
|
+
CraftNativeScriptThread.run(label: "unmount \(route)") {
|
|
6057
|
+
storageLifetime.unmount { context.objectForKeyedSubscript("__stxNativeUnmount")?.call(withArguments: []) }
|
|
6058
|
+
}
|
|
6011
6059
|
audio.close()
|
|
6012
6060
|
sounds.close()
|
|
6013
6061
|
streams.close()
|
|
@@ -6469,17 +6517,22 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
6469
6517
|
let log: @convention(block) (String, String, String) -> Void = { level, category, message in
|
|
6470
6518
|
CraftNativeConsole.write(level, category: category, message: message)
|
|
6471
6519
|
}
|
|
6520
|
+
let storageLifetime = self.storageLifetime
|
|
6472
6521
|
let storageGet: @convention(block) (String) -> String? = { key in
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
context
|
|
6522
|
+
storageLifetime.access(closedValue: nil) {
|
|
6523
|
+
do { return try CraftNativeActions.storageJSON(forKey: key) }
|
|
6524
|
+
catch {
|
|
6525
|
+
if let context = JSContext.current() {
|
|
6526
|
+
context.exception = JSValue(newErrorFromMessage: "STORAGE_ERROR: Unable to read stored value", in: context)
|
|
6527
|
+
}
|
|
6528
|
+
return nil
|
|
6477
6529
|
}
|
|
6478
|
-
return nil
|
|
6479
6530
|
}
|
|
6480
6531
|
}
|
|
6481
6532
|
let storageSet: @convention(block) (String, JSValue) -> Bool = { key, value in
|
|
6482
|
-
|
|
6533
|
+
storageLifetime.access(closedValue: false) {
|
|
6534
|
+
CraftNativeActions.setStorageJSON(value.isNull || value.isUndefined ? nil : value.toString(), forKey: key)
|
|
6535
|
+
}
|
|
6483
6536
|
}
|
|
6484
6537
|
let snapshotGet: @convention(block) (String) -> String? = { name in
|
|
6485
6538
|
CraftSnapshots.read(name)
|
|
@@ -7613,6 +7666,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
7613
7666
|
navigationController?.popToRootViewController(animated: true)
|
|
7614
7667
|
}
|
|
7615
7668
|
case "API_REQUEST":
|
|
7669
|
+
guard !runtimeDisposed else { return }
|
|
7616
7670
|
guard let id = message["id"] as? String else { return }
|
|
7617
7671
|
let args = payload["args"] as? [Any] ?? []
|
|
7618
7672
|
let requestToken = "\(capabilityScope)/\(id)"
|
|
@@ -7682,7 +7736,7 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
|
|
|
7682
7736
|
case "API_CANCEL":
|
|
7683
7737
|
guard let id = payload["requestId"] as? String else { return }
|
|
7684
7738
|
let requestToken = "\(capabilityScope)/\(id)"
|
|
7685
|
-
pendingCapabilityRequests.remove(requestToken)
|
|
7739
|
+
guard pendingCapabilityRequests.remove(requestToken) != nil else { return }
|
|
7686
7740
|
pendingCapabilityDeadlines.removeValue(forKey: requestToken)?.cancel()
|
|
7687
7741
|
screenRequestCancellers.removeValue(forKey: requestToken)?()
|
|
7688
7742
|
CraftNativeActions.cancel(requestToken: requestToken)
|