craft-native 0.0.338 → 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/dist/android/templates/CraftNativeCapabilities.kt.template +11 -2
- package/dist/cli.js +5 -1
- package/dist/index.cjs +5 -1
- package/dist/index.js +5 -1
- package/dist/ios/templates/CraftNativeActions.swift +7 -1
- package/dist/ios/templates/CraftNativeFlatList.swift +40 -10
- package/dist/ios/templates/CraftNativeScreen.swift +62 -8
- package/package.json +1 -1
|
@@ -37,6 +37,7 @@ import java.net.URLConnection
|
|
|
37
37
|
import java.util.UUID
|
|
38
38
|
import java.util.concurrent.ConcurrentHashMap
|
|
39
39
|
import java.util.concurrent.Executors
|
|
40
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
40
41
|
import kotlin.math.max
|
|
41
42
|
import kotlin.math.roundToInt
|
|
42
43
|
|
|
@@ -86,6 +87,7 @@ internal class CraftNativeCapabilities(
|
|
|
86
87
|
private val databaseExecutor = Executors.newSingleThreadExecutor()
|
|
87
88
|
private val networkExecutor = Executors.newCachedThreadPool()
|
|
88
89
|
private val cancelled = ConcurrentHashMap.newKeySet<String>()
|
|
90
|
+
private val closed = AtomicBoolean(false)
|
|
89
91
|
private val fetchConnections = ConcurrentHashMap<String, HttpURLConnection>()
|
|
90
92
|
private val fileRoot by lazy { File(context.cacheDir, "craft-files").apply { mkdirs() } }
|
|
91
93
|
private var pendingFilePick: PendingFilePick? = null
|
|
@@ -135,6 +137,10 @@ internal class CraftNativeCapabilities(
|
|
|
135
137
|
args: JSONArray,
|
|
136
138
|
completion: (CraftNativeCapabilityResult) -> Unit,
|
|
137
139
|
) {
|
|
140
|
+
if (closed.get()) {
|
|
141
|
+
mainHandler.post { completion(failure("HOST_CLOSED", "The native capability host is closed")) }
|
|
142
|
+
return
|
|
143
|
+
}
|
|
138
144
|
cancelled.remove(requestToken)
|
|
139
145
|
if (version != CRAFT_NATIVE_CAPABILITY_PROTOCOL_VERSION) {
|
|
140
146
|
complete(requestToken, failure("UNSUPPORTED_VERSION", "Unsupported native capability protocol version"), completion)
|
|
@@ -150,6 +156,7 @@ internal class CraftNativeCapabilities(
|
|
|
150
156
|
else bluetooth.perform(method, args) { complete(requestToken, it, completion) }
|
|
151
157
|
}
|
|
152
158
|
"Storage" -> mainHandler.post {
|
|
159
|
+
if (cancelled.remove(requestToken) || closed.get()) return@post
|
|
153
160
|
val answer = runCatching { storage(method, args) }
|
|
154
161
|
.getOrElse { failure("STORAGE_ERROR", it.message ?: "Storage operation failed") }
|
|
155
162
|
complete(requestToken, answer, completion)
|
|
@@ -199,6 +206,8 @@ internal class CraftNativeCapabilities(
|
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
fun close() {
|
|
209
|
+
if (!closed.compareAndSet(false, true)) return
|
|
210
|
+
cancelled.clear()
|
|
202
211
|
if (config.optBoolean("enableBluetooth")) bluetooth.disconnect()
|
|
203
212
|
databaseExecutor.shutdownNow()
|
|
204
213
|
networkExecutor.shutdownNow()
|
|
@@ -653,9 +662,9 @@ internal class CraftNativeCapabilities(
|
|
|
653
662
|
completion: (CraftNativeCapabilityResult) -> Unit,
|
|
654
663
|
) {
|
|
655
664
|
if (android.os.Looper.myLooper() == android.os.Looper.getMainLooper()) {
|
|
656
|
-
if (!cancelled.remove(requestToken)) completion(result)
|
|
665
|
+
if (!cancelled.remove(requestToken) && !closed.get()) completion(result)
|
|
657
666
|
} else mainHandler.post {
|
|
658
|
-
if (!cancelled.remove(requestToken)) completion(result)
|
|
667
|
+
if (!cancelled.remove(requestToken) && !closed.get()) completion(result)
|
|
659
668
|
}
|
|
660
669
|
}
|
|
661
670
|
|
package/dist/cli.js
CHANGED
|
@@ -245,6 +245,10 @@ function macOSAppStoreRuntimePath(providerPath) {
|
|
|
245
245
|
const runtime = basename(providerPath) === "craft-app-store" ? providerPath : join2(dirname(providerPath), "craft-app-store");
|
|
246
246
|
if (!existsSync4(runtime))
|
|
247
247
|
throw new Error("App Store packaging requires Pantry\u2019s craft-app-store runtime. Upgrade Craft with Pantry before building.");
|
|
248
|
+
const bytes = readFileSync2(runtime);
|
|
249
|
+
const privateMarkers = ["MenuBarClientCore", "MBAssessmentMode", "developerExtrasEnabled", "javaScriptCanAccessClipboard", "drawsBackground"];
|
|
250
|
+
if (privateMarkers.some((marker) => bytes.includes(Buffer.from(marker))))
|
|
251
|
+
throw new Error("App Store packaging refused a runtime that includes private platform APIs.");
|
|
248
252
|
const probe = spawnSync(runtime, ["--distribution"], { encoding: "utf8", timeout: 1e4 });
|
|
249
253
|
if (probe.status !== 0 || probe.stdout.trim() !== "app-store")
|
|
250
254
|
throw new Error("App Store packaging refused a runtime that includes private platform APIs.");
|
|
@@ -3018,7 +3022,7 @@ function pinCraftNativeDependency(packagePath, craftVersion) {
|
|
|
3018
3022
|
`);
|
|
3019
3023
|
}
|
|
3020
3024
|
// package.json
|
|
3021
|
-
var version = "0.0.
|
|
3025
|
+
var version = "0.0.341";
|
|
3022
3026
|
|
|
3023
3027
|
// bin/cli.ts
|
|
3024
3028
|
var spawnedFrom = process6.env[CRAFT_CLI_SPAWN_MARKER];
|
package/dist/index.cjs
CHANGED
|
@@ -375,7 +375,7 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
375
375
|
`);
|
|
376
376
|
}
|
|
377
377
|
// package.json
|
|
378
|
-
var version = "0.0.
|
|
378
|
+
var version = "0.0.341";
|
|
379
379
|
|
|
380
380
|
// src/sdk-version.ts
|
|
381
381
|
var SDK_VERSION = version;
|
|
@@ -611,6 +611,10 @@ function macOSAppStoreRuntimePath(providerPath) {
|
|
|
611
611
|
const runtime = import_path.basename(providerPath) === "craft-app-store" ? providerPath : import_path.join(import_path.dirname(providerPath), "craft-app-store");
|
|
612
612
|
if (!import_fs.existsSync(runtime))
|
|
613
613
|
throw new Error("App Store packaging requires Pantry’s craft-app-store runtime. Upgrade Craft with Pantry before building.");
|
|
614
|
+
const bytes = import_fs.readFileSync(runtime);
|
|
615
|
+
const privateMarkers = ["MenuBarClientCore", "MBAssessmentMode", "developerExtrasEnabled", "javaScriptCanAccessClipboard", "drawsBackground"];
|
|
616
|
+
if (privateMarkers.some((marker) => bytes.includes(Buffer.from(marker))))
|
|
617
|
+
throw new Error("App Store packaging refused a runtime that includes private platform APIs.");
|
|
614
618
|
const probe = import_child_process.spawnSync(runtime, ["--distribution"], { encoding: "utf8", timeout: 1e4 });
|
|
615
619
|
if (probe.status !== 0 || probe.stdout.trim() !== "app-store")
|
|
616
620
|
throw new Error("App Store packaging refused a runtime that includes private platform APIs.");
|
package/dist/index.js
CHANGED
|
@@ -117,7 +117,7 @@ function craftBinaryNotFoundMessage(triedPath) {
|
|
|
117
117
|
`);
|
|
118
118
|
}
|
|
119
119
|
// package.json
|
|
120
|
-
var version = "0.0.
|
|
120
|
+
var version = "0.0.341";
|
|
121
121
|
|
|
122
122
|
// src/sdk-version.ts
|
|
123
123
|
var SDK_VERSION = version;
|
|
@@ -367,6 +367,10 @@ function macOSAppStoreRuntimePath(providerPath) {
|
|
|
367
367
|
const runtime = basename(providerPath) === "craft-app-store" ? providerPath : join2(dirname(providerPath), "craft-app-store");
|
|
368
368
|
if (!existsSync3(runtime))
|
|
369
369
|
throw new Error("App Store packaging requires Pantry\u2019s craft-app-store runtime. Upgrade Craft with Pantry before building.");
|
|
370
|
+
const bytes = readFileSync(runtime);
|
|
371
|
+
const privateMarkers = ["MenuBarClientCore", "MBAssessmentMode", "developerExtrasEnabled", "javaScriptCanAccessClipboard", "drawsBackground"];
|
|
372
|
+
if (privateMarkers.some((marker) => bytes.includes(Buffer.from(marker))))
|
|
373
|
+
throw new Error("App Store packaging refused a runtime that includes private platform APIs.");
|
|
370
374
|
const probe = spawnSync(runtime, ["--distribution"], { encoding: "utf8", timeout: 1e4 });
|
|
371
375
|
if (probe.status !== 0 || probe.stdout.trim() !== "app-store")
|
|
372
376
|
throw new Error("App Store packaging refused a runtime that includes private platform APIs.");
|
|
@@ -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 {
|
|
@@ -60,7 +60,7 @@ final class CraftNativeFlatList: UICollectionView, UICollectionViewDelegateFlowL
|
|
|
60
60
|
/// The row this cell showed is gone; its views stay in the cell, and
|
|
61
61
|
/// the next row it shows reuses them when it has the same shape.
|
|
62
62
|
func recycle() {
|
|
63
|
-
if let identity = representedIdentity { onRecycle?(identity) }
|
|
63
|
+
if let identity = representedIdentity, showsHostedView { onRecycle?(identity) }
|
|
64
64
|
representedIdentity = nil
|
|
65
65
|
onRecycle = nil
|
|
66
66
|
}
|
|
@@ -81,6 +81,7 @@ final class CraftNativeFlatList: UICollectionView, UICollectionViewDelegateFlowL
|
|
|
81
81
|
// since moved to another cell is that cell's now, and pulling it
|
|
82
82
|
// from there blanked a row on screen.
|
|
83
83
|
if let shown = hostedView, shown !== view, shown.superview === contentView { shown.removeFromSuperview() }
|
|
84
|
+
NSLayoutConstraint.deactivate(hostedConstraints)
|
|
84
85
|
hostedView = view
|
|
85
86
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
86
87
|
contentView.addSubview(view)
|
|
@@ -110,6 +111,8 @@ final class CraftNativeFlatList: UICollectionView, UICollectionViewDelegateFlowL
|
|
|
110
111
|
func releaseHostedView() {
|
|
111
112
|
recycle()
|
|
112
113
|
if let shown = hostedView, shown.superview === contentView { shown.removeFromSuperview() }
|
|
114
|
+
NSLayoutConstraint.deactivate(hostedConstraints)
|
|
115
|
+
hostedConstraints = []
|
|
113
116
|
hostedView = nil
|
|
114
117
|
}
|
|
115
118
|
|
|
@@ -195,8 +198,11 @@ final class CraftNativeFlatList: UICollectionView, UICollectionViewDelegateFlowL
|
|
|
195
198
|
private var endReachedThreshold = 0.1
|
|
196
199
|
private var endReachedSignature: String?
|
|
197
200
|
private var dataContentSignature = ""
|
|
201
|
+
private var dataOrdinals: [Int] = []
|
|
202
|
+
private var dataItemCount = 0
|
|
198
203
|
private var columns = 1
|
|
199
204
|
private var isApplyingSnapshot = false
|
|
205
|
+
private var snapshotNeedsReset = false
|
|
200
206
|
private var pendingApply: (() -> Void)?
|
|
201
207
|
private var pendingThemeRefresh = false
|
|
202
208
|
private let nativeRefreshControl = UIRefreshControl()
|
|
@@ -224,6 +230,10 @@ final class CraftNativeFlatList: UICollectionView, UICollectionViewDelegateFlowL
|
|
|
224
230
|
backgroundColor = .clear
|
|
225
231
|
alwaysBounceVertical = true
|
|
226
232
|
delegate = self
|
|
233
|
+
// The renderer keeps one live view per keyed row. UIKit's speculative
|
|
234
|
+
// cell prefetch can otherwise move that view out of its visible cell.
|
|
235
|
+
// UICollectionView still recycles and self-sizes the visible window.
|
|
236
|
+
isPrefetchingEnabled = false
|
|
227
237
|
keyboardTapGesture.cancelsTouchesInView = false
|
|
228
238
|
keyboardTapGesture.delegate = self
|
|
229
239
|
keyboardTapGesture.addTarget(self, action: #selector(keyboardTap))
|
|
@@ -382,13 +392,15 @@ final class CraftNativeFlatList: UICollectionView, UICollectionViewDelegateFlowL
|
|
|
382
392
|
let padding = nonNegative(raw?["padding"]) ?? 0
|
|
383
393
|
let horizontal = nonNegative(raw?["paddingHorizontal"]) ?? padding
|
|
384
394
|
let vertical = nonNegative(raw?["paddingVertical"]) ?? padding
|
|
385
|
-
|
|
395
|
+
let inset = UIEdgeInsets(
|
|
386
396
|
top: nonNegative(raw?["paddingTop"]) ?? vertical,
|
|
387
397
|
left: nonNegative(raw?["paddingLeft"]) ?? horizontal,
|
|
388
398
|
bottom: nonNegative(raw?["paddingBottom"]) ?? vertical,
|
|
389
399
|
right: nonNegative(raw?["paddingRight"]) ?? horizontal
|
|
390
400
|
)
|
|
391
|
-
|
|
401
|
+
guard contentInset != inset else { return }
|
|
402
|
+
contentInset = inset
|
|
403
|
+
scrollIndicatorInsets = inset
|
|
392
404
|
collectionViewLayout.invalidateLayout()
|
|
393
405
|
}
|
|
394
406
|
|
|
@@ -479,7 +491,9 @@ final class CraftNativeFlatList: UICollectionView, UICollectionViewDelegateFlowL
|
|
|
479
491
|
self.removeItem = removeItem
|
|
480
492
|
self.endReached = endReached
|
|
481
493
|
self.endReachedThreshold = max(0, endReachedThreshold)
|
|
482
|
-
|
|
494
|
+
let nextColumns = max(1, columns)
|
|
495
|
+
let layoutChanged = self.columns != nextColumns || flowLayout.scrollDirection != (horizontal ? .horizontal : .vertical)
|
|
496
|
+
self.columns = nextColumns
|
|
483
497
|
|
|
484
498
|
layoutIfNeeded()
|
|
485
499
|
let oldItems = itemsByIdentity
|
|
@@ -496,6 +510,19 @@ final class CraftNativeFlatList: UICollectionView, UICollectionViewDelegateFlowL
|
|
|
496
510
|
return Item(identity: identity, node: node, signature: signature, isChrome: role != nil && role != "item")
|
|
497
511
|
}
|
|
498
512
|
if inverted { next.reverse() }
|
|
513
|
+
// Reapplying an identical keyed tree during a timer or badge update
|
|
514
|
+
// must not reconfigure cells, invalidate self-sizing, or reset a drag.
|
|
515
|
+
if !snapshotNeedsReset, !layoutChanged, next.count == items.count,
|
|
516
|
+
zip(next, items).allSatisfy({ $0.identity == $1.identity && $0.signature == $1.signature }) {
|
|
517
|
+
finishApplyingSnapshot()
|
|
518
|
+
return
|
|
519
|
+
}
|
|
520
|
+
snapshotNeedsReset = false
|
|
521
|
+
dataItemCount = 0
|
|
522
|
+
dataOrdinals = next.map { item in
|
|
523
|
+
if !item.isChrome { dataItemCount += 1 }
|
|
524
|
+
return dataItemCount - 1
|
|
525
|
+
}
|
|
499
526
|
items = next
|
|
500
527
|
itemsByIdentity = Dictionary(uniqueKeysWithValues: next.map { ($0.identity, $0) })
|
|
501
528
|
|
|
@@ -504,7 +531,7 @@ final class CraftNativeFlatList: UICollectionView, UICollectionViewDelegateFlowL
|
|
|
504
531
|
alwaysBounceVertical = !horizontal
|
|
505
532
|
showsHorizontalScrollIndicator = horizontal
|
|
506
533
|
showsVerticalScrollIndicator = !horizontal
|
|
507
|
-
flowLayout.invalidateLayout()
|
|
534
|
+
if layoutChanged { flowLayout.invalidateLayout() }
|
|
508
535
|
|
|
509
536
|
let nextIds = Set(itemsByIdentity.keys)
|
|
510
537
|
for identity in oldItems.keys where !nextIds.contains(identity) { removeItem(identity) }
|
|
@@ -550,9 +577,12 @@ final class CraftNativeFlatList: UICollectionView, UICollectionViewDelegateFlowL
|
|
|
550
577
|
}
|
|
551
578
|
|
|
552
579
|
func discardAll() {
|
|
580
|
+
snapshotNeedsReset = true
|
|
553
581
|
visibleCells.compactMap { $0 as? Cell }.forEach { $0.releaseHostedView() }
|
|
554
582
|
itemsByIdentity.keys.forEach { recycleItem?($0) }
|
|
555
583
|
items = []
|
|
584
|
+
dataOrdinals = []
|
|
585
|
+
dataItemCount = 0
|
|
556
586
|
itemsByIdentity = [:]
|
|
557
587
|
renderItem = nil
|
|
558
588
|
recycleItem = nil
|
|
@@ -638,11 +668,11 @@ final class CraftNativeFlatList: UICollectionView, UICollectionViewDelegateFlowL
|
|
|
638
668
|
guard !isApplyingSnapshot, pendingApply == nil,
|
|
639
669
|
let endReached = endReached,
|
|
640
670
|
let lastVisible = indexPathsForVisibleItems.map(\.item).max() else { return }
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
let remaining =
|
|
645
|
-
let thresholdItems = max(1, Int(ceil(Double(
|
|
671
|
+
guard dataItemCount > 0, dataOrdinals.indices.contains(lastVisible) else { return }
|
|
672
|
+
let lastDataOrdinal = dataOrdinals[lastVisible]
|
|
673
|
+
guard lastDataOrdinal >= 0 else { return }
|
|
674
|
+
let remaining = dataItemCount - lastDataOrdinal - 1
|
|
675
|
+
let thresholdItems = max(1, Int(ceil(Double(dataItemCount) * endReachedThreshold)))
|
|
646
676
|
guard remaining <= thresholdItems else { return }
|
|
647
677
|
let signature = dataContentSignature
|
|
648
678
|
guard endReachedSignature != signature else { return }
|
|
@@ -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)
|