@swiftbrowser/native 0.17.7 → 0.18.0
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/README.md +99 -11
- package/dist/blob.d.ts +60 -0
- package/dist/blob.d.ts.map +1 -0
- package/dist/blob.js +153 -0
- package/dist/blob.js.map +1 -0
- package/dist/bridge.d.ts +65 -1
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js.map +1 -1
- package/dist/fetch.d.ts +11 -3
- package/dist/fetch.d.ts.map +1 -1
- package/dist/fetch.js +26 -2
- package/dist/fetch.js.map +1 -1
- package/dist/hostRequests.d.ts +55 -0
- package/dist/hostRequests.d.ts.map +1 -0
- package/dist/hostRequests.js +196 -0
- package/dist/hostRequests.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/matchMedia.d.ts +43 -0
- package/dist/matchMedia.d.ts.map +1 -0
- package/dist/matchMedia.js +63 -0
- package/dist/matchMedia.js.map +1 -0
- package/dist/notifications.d.ts +108 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +251 -0
- package/dist/notifications.js.map +1 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +23 -2
- package/dist/run.js.map +1 -1
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +69 -0
- package/dist/runtime.js.map +1 -1
- package/dist/secureStorage.d.ts +20 -0
- package/dist/secureStorage.d.ts.map +1 -0
- package/dist/secureStorage.js +28 -0
- package/dist/secureStorage.js.map +1 -0
- package/dist/site.d.ts +3 -3
- package/dist/site.d.ts.map +1 -1
- package/dist/site.js +1 -1
- package/dist/site.js.map +1 -1
- package/dist/subtle.d.ts +38 -0
- package/dist/subtle.d.ts.map +1 -0
- package/dist/subtle.js +72 -0
- package/dist/subtle.js.map +1 -0
- package/dist/undo.d.ts +23 -0
- package/dist/undo.d.ts.map +1 -0
- package/dist/undo.js +44 -0
- package/dist/undo.js.map +1 -0
- package/dist/webSocket.d.ts +73 -0
- package/dist/webSocket.d.ts.map +1 -0
- package/dist/webSocket.js +145 -0
- package/dist/webSocket.js.map +1 -0
- package/package.json +3 -3
- package/src/blob.ts +186 -0
- package/src/bridge.ts +74 -1
- package/src/fetch.ts +29 -3
- package/src/hostRequests.ts +216 -0
- package/src/index.ts +8 -0
- package/src/matchMedia.ts +79 -0
- package/src/notifications.ts +317 -0
- package/src/run.ts +21 -2
- package/src/runtime.ts +71 -0
- package/src/secureStorage.ts +39 -0
- package/src/site.ts +3 -3
- package/src/subtle.ts +87 -0
- package/src/undo.ts +47 -0
- package/src/webSocket.ts +175 -0
- package/swift/Sources/SwiftBrowserNative/Host/AudioRecorderBridge.swift +199 -0
- package/swift/Sources/SwiftBrowserNative/Host/FilesBridge.swift +154 -0
- package/swift/Sources/SwiftBrowserNative/Host/HostBridge.swift +211 -0
- package/swift/Sources/SwiftBrowserNative/Host/HostRequestBridge.swift +451 -0
- package/swift/Sources/SwiftBrowserNative/Host/NotificationBridge.swift +304 -0
- package/swift/Sources/SwiftBrowserNative/Host/SecureStorage.swift +60 -0
- package/swift/Sources/SwiftBrowserNative/Host/ShimAliases.swift +1 -0
- package/swift/Sources/SwiftBrowserNative/Host/TextMeasureBridge.swift +139 -0
- package/swift/Sources/SwiftBrowserNative/Host/UndoBridge.swift +170 -0
- package/swift/Sources/SwiftBrowserNative/Host/WebSocketBridge.swift +118 -0
- package/swift/Sources/SwiftBrowserNative/Runtime/NativeApp.swift +56 -0
- package/swift/Sources/SwiftBrowserNative/Runtime/NativeAppDelegate.swift +36 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Controls.swift +362 -16
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/DeleteBackward.swift +147 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/KeyboardBar.swift +84 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/More.swift +116 -11
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Navigation.swift +12 -1
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Shapes.swift +30 -9
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Stacks.swift +163 -10
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/TextAndImages.swift +17 -4
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Video.swift +8 -3
- package/swift/Sources/SwiftBrowserNative/Views/NativeRootView.swift +8 -0
- package/swift/Sources/SwiftBrowserNative/Views/StyleModifier.swift +41 -0
- package/swift/Sources/SwiftBrowserProtocol/Op.swift +27 -4
- package/swift/Sources/SwiftBrowserProtocol/Props.swift +110 -3
- package/swift/Sources/SwiftBrowserProtocol/RendererEvent.swift +47 -0
- package/swift/Sources/SwiftBrowserProtocol/SVGPath.swift +260 -0
- package/swift/Sources/SwiftBrowserProtocol/Style.swift +58 -0
- package/swift/Sources/SwiftBrowserTree/OpTree.swift +22 -2
- package/wasm/SwiftBrowserNative.wasm +2 -2
|
@@ -11,6 +11,9 @@ import AppKit
|
|
|
11
11
|
#if canImport(FoundationBridge)
|
|
12
12
|
import FoundationBridge // the shim's Foundation stand-ins (ShimAliases.swift)
|
|
13
13
|
#endif
|
|
14
|
+
#if canImport(CryptoKit)
|
|
15
|
+
import CryptoKit
|
|
16
|
+
#endif
|
|
14
17
|
|
|
15
18
|
/// The native primitives `@swiftbrowser/native` builds the web globals from
|
|
16
19
|
/// (packages/native/src/runtime.ts), installed on `__sbNative`:
|
|
@@ -27,8 +30,17 @@ import FoundationBridge // the shim's Foundation stand-ins (ShimAliases.swift)
|
|
|
27
30
|
/// | `randomBytes(count)` | base64 of random bytes (`crypto.getRandomValues`) |
|
|
28
31
|
/// | `openURL(url)` | the system opener |
|
|
29
32
|
/// | `locale()` | the viewer's locale identifier |
|
|
33
|
+
/// | `measureText(text, fontJSON, width)` | @swiftbrowser/react's `measureText`: `{ width, height, lines }` of the text in the font wrapped to the width (one line when null), from `NSAttributedString`'s bounding rect, synchronously (TextMeasureBridge.swift); nothing where there is no text system |
|
|
30
34
|
/// | `launchArguments()` | the process's arguments (`-screen list` from a scheme or `simctl launch`) |
|
|
35
|
+
/// | `appDirectories()` | the app's standard directories as `file:` URLs, a JSON object `{ documents, applicationSupport, caches, temporary }`, for `fileManager` (FilesBridge.swift); its `files` requests go through `hostRequest`. Apple platforms only |
|
|
31
36
|
/// | `mediaSession(update)` | `navigator.mediaSession`: the lock screen's now-playing info and the remote commands the app handles, on MediaPlayer (NowPlayingBridge.swift); a command the viewer gives comes to `__sbMediaSessionCommand(json)` |
|
|
37
|
+
/// | `notificationsReady()`, `notificationPermission(id, request)`, `notificationShow(json)`, `notificationClose(json)`, `setBadge(count)`, `pushRegister(id, key?)` | notifications on UserNotifications (NotificationBridge.swift): the permission answers `__sbNotificationPermission(id, state)`, a registration `__sbPushRegistered(id, result)`, and what the viewer opens or what arrives comes to `__sbNotification(json)` |
|
|
38
|
+
/// | `wsOpen(id, url, protocols)`, `wsSend(id, json)`, `wsClose(id, code, reason)` | `WebSocket` on `URLSessionWebSocketTask` (WebSocketBridge.swift); what a socket receives comes to `__sbWebSocketEvent(id, json)` |
|
|
39
|
+
/// | `digest(algorithm, base64)`, `hmac(algorithm, key, base64)` | `crypto.subtle` on CryptoKit, synchronously: the base64 digest or HMAC, or nothing when the algorithm is not one of SHA-1, SHA-256, SHA-384 and SHA-512 |
|
|
40
|
+
/// | `haptic(kind)` | `navigator.vibrate`: a feedback generator's `impact`, `selection`, `success`, `warning` or `error` |
|
|
41
|
+
/// | `hostRequest(id, kind, json)` | the web renderer's `share`, `pickFiles`, `clipboard`, `pickColor` and `webAuth` requests, answered with the system's share sheet, pickers, pasteboard and sign-in session (HostRequestBridge.swift); the answer comes to `__sbHostResponse(id, result)` |
|
|
42
|
+
/// | `secureGet(key)`, `secureSet(key, value)`, `secureRemove(key)` | `secureStorage` over the Keychain (SecureStorage.swift) |
|
|
43
|
+
/// | `undoState(json)` | the app's undo manager's `canUndo`, `canRedo`, `undoActionName` and `redoActionName`, mirrored on the window's `UndoManager` (UndoBridge.swift) so the shake, the three-finger swipe, ⌘Z and the Edit menu reach the app; what the system asks comes to `__sbUndoCommand("undo" \| "redo")` |
|
|
32
44
|
/// | `workerStart(id, name, engine)`, `workerPost(id, text)`, `workerTerminate(id)` | workers (JavaScriptCore only): a `JSContext` on a thread (`"context"`, WorkerHost.swift) or a hidden web view with WebKit's JIT (`"webview"`, WebViewWorkerHost.swift); their messages come to `__sbWorkerMessage(id, text)`, their errors to `__sbWorkerError(id, message)` |
|
|
33
45
|
///
|
|
34
46
|
/// Everything JavaScript-facing runs on the main thread; work that is not
|
|
@@ -46,6 +58,16 @@ final class HostBridge {
|
|
|
46
58
|
#if canImport(MediaPlayer)
|
|
47
59
|
private var nowPlaying: NowPlayingBridge?
|
|
48
60
|
#endif
|
|
61
|
+
#if canImport(UserNotifications)
|
|
62
|
+
private var notifications: NotificationBridge?
|
|
63
|
+
#endif
|
|
64
|
+
private var webSockets: WebSocketBridge?
|
|
65
|
+
private var hostRequests: HostRequestBridge?
|
|
66
|
+
#if !canImport(FoundationBridge)
|
|
67
|
+
private var undo: UndoBridge?
|
|
68
|
+
/// The window's undo manager, for the undo bridge when the app first sends a state.
|
|
69
|
+
private weak var undoManager: UndoManager?
|
|
70
|
+
#endif
|
|
49
71
|
/// Keys of `localStorage` live under this prefix in `UserDefaults`.
|
|
50
72
|
static let storagePrefix = "swiftbrowser.storage."
|
|
51
73
|
|
|
@@ -59,6 +81,10 @@ final class HostBridge {
|
|
|
59
81
|
func invalidate() {
|
|
60
82
|
for timer in timers.values { timer.invalidate() }
|
|
61
83
|
timers.removeAll()
|
|
84
|
+
webSockets?.invalidate()
|
|
85
|
+
#if !canImport(FoundationBridge)
|
|
86
|
+
undo?.manager = nil
|
|
87
|
+
#endif
|
|
62
88
|
#if canImport(JavaScriptCore)
|
|
63
89
|
for worker in workers.values { worker.terminate() }
|
|
64
90
|
workers.removeAll()
|
|
@@ -194,8 +220,18 @@ final class HostBridge {
|
|
|
194
220
|
|
|
195
221
|
host.install("locale") { _ in .string(Locale.current.identifier) }
|
|
196
222
|
|
|
223
|
+
host.install("measureText") { arguments in
|
|
224
|
+
guard let text = arguments[safe: 0]?.string else { return nil }
|
|
225
|
+
let font = (try? JSONValue.parse(arguments[safe: 1]?.string ?? "{}")) ?? .object([:])
|
|
226
|
+
return TextMeasureBridge.measure(text: text, font: font, width: arguments[safe: 2]?.double)
|
|
227
|
+
}
|
|
228
|
+
|
|
197
229
|
host.install("launchArguments") { _ in .array(ProcessInfo.processInfo.arguments.map(JSONValue.string)) }
|
|
198
230
|
|
|
231
|
+
#if !canImport(FoundationBridge)
|
|
232
|
+
host.install("appDirectories") { _ in .string(FilesBridge.directories().json) }
|
|
233
|
+
#endif
|
|
234
|
+
|
|
199
235
|
#if canImport(MediaPlayer)
|
|
200
236
|
host.install("mediaSession") { [weak self] arguments in
|
|
201
237
|
guard let self, let update = arguments.first?.string else { return .string("mediaSession: expected a JSON string") }
|
|
@@ -206,6 +242,100 @@ final class HostBridge {
|
|
|
206
242
|
}
|
|
207
243
|
#endif
|
|
208
244
|
|
|
245
|
+
#if canImport(UserNotifications)
|
|
246
|
+
host.install("notificationsReady") { [weak self] _ in
|
|
247
|
+
self?.notificationBridge.listen()
|
|
248
|
+
return nil
|
|
249
|
+
}
|
|
250
|
+
host.install("notificationPermission") { [weak self] arguments in
|
|
251
|
+
guard let self, let id = arguments[safe: 0]?.int else { return nil }
|
|
252
|
+
notificationBridge.permission(id: id, request: arguments[safe: 1]?.bool ?? false)
|
|
253
|
+
return nil
|
|
254
|
+
}
|
|
255
|
+
host.install("notificationShow") { [weak self] arguments in
|
|
256
|
+
guard let self, let json = arguments.first?.string else { return .string("notification: expected a JSON string") }
|
|
257
|
+
return notificationBridge.show(json).map(JSONValue.string)
|
|
258
|
+
}
|
|
259
|
+
host.install("notificationClose") { [weak self] arguments in
|
|
260
|
+
guard let self, let json = arguments.first?.string else { return nil }
|
|
261
|
+
notificationBridge.close(json)
|
|
262
|
+
return nil
|
|
263
|
+
}
|
|
264
|
+
host.install("setBadge") { [weak self] arguments in
|
|
265
|
+
guard let self, let count = arguments.first?.int else { return nil }
|
|
266
|
+
notificationBridge.setBadge(count)
|
|
267
|
+
return nil
|
|
268
|
+
}
|
|
269
|
+
host.install("pushRegister") { [weak self] arguments in
|
|
270
|
+
guard let self, let id = arguments[safe: 0]?.int else { return nil }
|
|
271
|
+
notificationBridge.registerForPush(id: id, applicationServerKey: arguments[safe: 1]?.string)
|
|
272
|
+
return nil
|
|
273
|
+
}
|
|
274
|
+
#endif
|
|
275
|
+
|
|
276
|
+
host.install("wsOpen") { [weak self] arguments in
|
|
277
|
+
guard let self, let id = arguments[safe: 0]?.int, let url = arguments[safe: 1]?.string else { return nil }
|
|
278
|
+
let protocols = arguments[safe: 2]?.array?.compactMap(\.string) ?? []
|
|
279
|
+
webSocketBridge.open(id: id, url: url, protocols: protocols)
|
|
280
|
+
return nil
|
|
281
|
+
}
|
|
282
|
+
host.install("wsSend") { [weak self] arguments in
|
|
283
|
+
guard let self, let id = arguments[safe: 0]?.int, let json = arguments[safe: 1]?.string else { return nil }
|
|
284
|
+
webSocketBridge.send(id: id, json: json)
|
|
285
|
+
return nil
|
|
286
|
+
}
|
|
287
|
+
host.install("wsClose") { [weak self] arguments in
|
|
288
|
+
guard let self, let id = arguments[safe: 0]?.int else { return nil }
|
|
289
|
+
webSocketBridge.close(id: id, code: arguments[safe: 1]?.int ?? 1000, reason: arguments[safe: 2]?.string ?? "")
|
|
290
|
+
return nil
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
host.install("digest") { arguments in
|
|
294
|
+
guard let algorithm = arguments[safe: 0]?.string, let encoded = arguments[safe: 1]?.string, let data = Data(base64Encoded: encoded) else { return nil }
|
|
295
|
+
return Self.digest(algorithm, data).map { .string($0.base64EncodedString()) }
|
|
296
|
+
}
|
|
297
|
+
host.install("hmac") { arguments in
|
|
298
|
+
guard let algorithm = arguments[safe: 0]?.string, let key = arguments[safe: 1]?.string, let encoded = arguments[safe: 2]?.string,
|
|
299
|
+
let keyData = Data(base64Encoded: key), let data = Data(base64Encoded: encoded) else { return nil }
|
|
300
|
+
return Self.hmac(algorithm, key: keyData, data).map { .string($0.base64EncodedString()) }
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
host.install("haptic") { arguments in
|
|
304
|
+
Self.haptic(arguments.first?.string ?? "impact")
|
|
305
|
+
return nil
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
host.install("hostRequest") { [weak self] arguments in
|
|
309
|
+
guard let self, let id = arguments[safe: 0]?.int, let kind = arguments[safe: 1]?.string else { return nil }
|
|
310
|
+
if hostRequests == nil {
|
|
311
|
+
hostRequests = HostRequestBridge { [weak self] id, result in self?.callIn("__sbHostResponse", [.number(Double(id)), .string(result.json)]) }
|
|
312
|
+
}
|
|
313
|
+
hostRequests?.perform(id: id, kind: kind, json: arguments[safe: 2]?.string ?? "{}")
|
|
314
|
+
return nil
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
#if !canImport(FoundationBridge)
|
|
318
|
+
host.install("undoState") { [weak self] arguments in
|
|
319
|
+
guard let self, let json = arguments.first?.string else { return .string("undoState: expected a JSON string") }
|
|
320
|
+
return undoBridge.update(json).map(JSONValue.string)
|
|
321
|
+
}
|
|
322
|
+
#endif
|
|
323
|
+
|
|
324
|
+
host.install("secureGet") { arguments in
|
|
325
|
+
guard let key = arguments.first?.string else { return nil }
|
|
326
|
+
return SecureStorage.get(key).map(JSONValue.string) ?? .null
|
|
327
|
+
}
|
|
328
|
+
host.install("secureSet") { arguments in
|
|
329
|
+
guard let key = arguments[safe: 0]?.string, let value = arguments[safe: 1]?.string else { return nil }
|
|
330
|
+
SecureStorage.set(key, value)
|
|
331
|
+
return nil
|
|
332
|
+
}
|
|
333
|
+
host.install("secureRemove") { arguments in
|
|
334
|
+
guard let key = arguments.first?.string else { return nil }
|
|
335
|
+
SecureStorage.remove(key)
|
|
336
|
+
return nil
|
|
337
|
+
}
|
|
338
|
+
|
|
209
339
|
#if canImport(JavaScriptCore)
|
|
210
340
|
host.install("workerStart") { [weak self] arguments in
|
|
211
341
|
guard let self, let id = arguments[safe: 0]?.int, let name = arguments[safe: 1]?.string else {
|
|
@@ -255,6 +385,87 @@ final class HostBridge {
|
|
|
255
385
|
#endif
|
|
256
386
|
}
|
|
257
387
|
|
|
388
|
+
#if canImport(UserNotifications)
|
|
389
|
+
/// The notification bridge, made when the app first uses notifications.
|
|
390
|
+
private var notificationBridge: NotificationBridge {
|
|
391
|
+
if let notifications { return notifications }
|
|
392
|
+
let bridge = NotificationBridge { [weak self] function, arguments in self?.callIn(function, arguments) }
|
|
393
|
+
notifications = bridge
|
|
394
|
+
return bridge
|
|
395
|
+
}
|
|
396
|
+
#endif
|
|
397
|
+
|
|
398
|
+
#if !canImport(FoundationBridge)
|
|
399
|
+
/// The undo bridge, made when the app first sends its undo manager's state.
|
|
400
|
+
private var undoBridge: UndoBridge {
|
|
401
|
+
if let undo { return undo }
|
|
402
|
+
let bridge = UndoBridge { [weak self] command in self?.callIn("__sbUndoCommand", [.string(command)]) }
|
|
403
|
+
bridge.manager = undoManager
|
|
404
|
+
undo = bridge
|
|
405
|
+
return bridge
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/// The window's `UndoManager` (the root view's environment), which mirrors the app's.
|
|
409
|
+
func setUndoManager(_ manager: UndoManager?) {
|
|
410
|
+
undoManager = manager
|
|
411
|
+
undo?.manager = manager
|
|
412
|
+
}
|
|
413
|
+
#endif
|
|
414
|
+
|
|
415
|
+
/// The WebSocket bridge, made when the app opens its first socket.
|
|
416
|
+
private var webSocketBridge: WebSocketBridge {
|
|
417
|
+
if let webSockets { return webSockets }
|
|
418
|
+
let bridge = WebSocketBridge(session: session) { [weak self] id, event in self?.callIn("__sbWebSocketEvent", [.number(Double(id)), .string(event.json)]) }
|
|
419
|
+
webSockets = bridge
|
|
420
|
+
return bridge
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// MARK: crypto.subtle and haptics
|
|
424
|
+
|
|
425
|
+
static func digest(_ algorithm: String, _ data: Data) -> Data? {
|
|
426
|
+
#if canImport(CryptoKit)
|
|
427
|
+
switch algorithm {
|
|
428
|
+
case "SHA-256": Data(SHA256.hash(data: data))
|
|
429
|
+
case "SHA-384": Data(SHA384.hash(data: data))
|
|
430
|
+
case "SHA-512": Data(SHA512.hash(data: data))
|
|
431
|
+
case "SHA-1": Data(Insecure.SHA1.hash(data: data))
|
|
432
|
+
default: nil
|
|
433
|
+
}
|
|
434
|
+
#else
|
|
435
|
+
nil
|
|
436
|
+
#endif
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
static func hmac(_ algorithm: String, key: Data, _ data: Data) -> Data? {
|
|
440
|
+
#if canImport(CryptoKit) && !canImport(FoundationBridge)
|
|
441
|
+
let symmetricKey = SymmetricKey(data: key)
|
|
442
|
+
return switch algorithm {
|
|
443
|
+
case "SHA-256": Data(HMAC<SHA256>.authenticationCode(for: data, using: symmetricKey))
|
|
444
|
+
case "SHA-384": Data(HMAC<SHA384>.authenticationCode(for: data, using: symmetricKey))
|
|
445
|
+
case "SHA-512": Data(HMAC<SHA512>.authenticationCode(for: data, using: symmetricKey))
|
|
446
|
+
case "SHA-1": Data(HMAC<Insecure.SHA1>.authenticationCode(for: data, using: symmetricKey))
|
|
447
|
+
default: nil
|
|
448
|
+
}
|
|
449
|
+
#else
|
|
450
|
+
// The shim's CryptoKit has no HMAC.
|
|
451
|
+
nil
|
|
452
|
+
#endif
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
static func haptic(_ kind: String) {
|
|
456
|
+
#if canImport(UIKit) && !os(tvOS)
|
|
457
|
+
switch kind {
|
|
458
|
+
case "selection": UISelectionFeedbackGenerator().selectionChanged()
|
|
459
|
+
case "success": UINotificationFeedbackGenerator().notificationOccurred(.success)
|
|
460
|
+
case "warning": UINotificationFeedbackGenerator().notificationOccurred(.warning)
|
|
461
|
+
case "error": UINotificationFeedbackGenerator().notificationOccurred(.error)
|
|
462
|
+
default: UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
|
463
|
+
}
|
|
464
|
+
#elseif canImport(AppKit)
|
|
465
|
+
NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .now)
|
|
466
|
+
#endif
|
|
467
|
+
}
|
|
468
|
+
|
|
258
469
|
/// Calls into JavaScript from a timer or a completion, where a throw has nowhere to go but the log.
|
|
259
470
|
private func callIn(_ function: String, _ arguments: [JSONValue]) {
|
|
260
471
|
do {
|