@stacksjs/desktop 0.2.196 → 0.2.198

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/browser.js CHANGED
@@ -1,3910 +1,116 @@
1
1
  // @bun
2
- var __require = import.meta.require;
3
-
4
- // src/alerts.ts
5
- function hasNativeNotificationSupport() {
6
- if (typeof Notification !== "undefined" && Notification.permission === "granted") {
7
- return true;
8
- }
9
- return false;
10
- }
11
- function isBrowser() {
12
- return typeof window !== "undefined" && typeof document !== "undefined";
13
- }
14
- async function requestNotificationPermission() {
15
- if (typeof Notification === "undefined") {
16
- return false;
17
- }
18
- if (Notification.permission === "granted") {
19
- return true;
20
- }
21
- if (Notification.permission === "denied") {
22
- return false;
23
- }
24
- const permission = await Notification.requestPermission();
25
- return permission === "granted";
26
- }
27
- var activeAlerts = new Map;
28
- function generateAlertId() {
29
- return `alert-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
30
- }
31
- function getAlertIcon(type) {
32
- switch (type) {
33
- case "info":
34
- return "ℹ";
35
- case "success":
36
- return "✔";
37
- case "warning":
38
- return "⚠";
39
- case "error":
40
- return "✖";
41
- default:
42
- return "ℹ";
43
- }
44
- }
45
- function getToastContainer(position = "top-right") {
46
- if (!isBrowser()) {
47
- throw new Error("Toast container requires browser environment");
48
- }
49
- try {
50
- const existingContainer = document.querySelector(`.stx-toast-container[data-position="${position}"]`);
51
- if (existingContainer) {
52
- return existingContainer;
53
- }
54
- } catch {}
55
- const container = document.createElement("div");
56
- container.className = `stx-toast-container ${position}`;
57
- container.dataset.position = position;
58
- try {
59
- if (document.body) {
60
- document.body.appendChild(container);
61
- }
62
- } catch {}
63
- return container;
64
- }
65
- function createToastHTML(state) {
66
- const { options } = state;
67
- const icon = getAlertIcon(options.type);
68
- const typeClass = options.type || "info";
69
- const hasClose = options.duration !== 0;
70
- return `
71
- <div class="stx-toast ${typeClass}" data-alert-id="${state.id}" role="alert" aria-live="polite">
72
- <div class="stx-toast-icon">${icon}</div>
73
- <div class="stx-toast-content">
74
- ${options.title ? `<div class="stx-toast-title">${escapeHtml(options.title)}</div>` : ""}
75
- <div class="stx-toast-message">${escapeHtml(options.message)}</div>
76
- </div>
77
- ${hasClose ? '<button class="stx-toast-close" aria-label="Close">&times;</button>' : ""}
78
- </div>
79
- `;
80
- }
81
- function escapeHtml(str) {
82
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
83
- }
84
- function dismissAlert(state) {
85
- if (state.timeout) {
86
- clearTimeout(state.timeout);
87
- }
88
- activeAlerts.delete(state.id);
89
- if (state.element && isBrowser()) {
90
- state.element.classList.add("dismissing");
91
- setTimeout(() => {
92
- state.element?.remove();
93
- }, 200);
94
- }
95
- }
96
- async function showAlert(options) {
97
- const id = generateAlertId();
98
- const hasNative = hasNativeNotificationSupport();
99
- const duration = options.duration ?? 5000;
100
- const state = {
101
- id,
102
- options
103
- };
104
- activeAlerts.set(id, state);
105
- if (hasNative && options.type !== "error") {
106
- try {
107
- const notification = new Notification(options.title || "Notification", {
108
- body: options.message,
109
- icon: options.type === "success" ? "\u2713" : options.type === "warning" ? "\u26A0" : "\u2139",
110
- tag: id
111
- });
112
- notification.onclick = () => {
113
- if (options.onClick) {
114
- options.onClick();
115
- }
116
- notification.close();
117
- };
118
- if (duration > 0) {
119
- state.timeout = setTimeout(() => {
120
- notification.close();
121
- activeAlerts.delete(id);
122
- }, duration);
123
- }
124
- return;
125
- } catch {}
126
- }
127
- if (isBrowser()) {
128
- try {
129
- const container = getToastContainer(options.position);
130
- const wrapper = document.createElement("div");
131
- wrapper.innerHTML = createToastHTML(state);
132
- const toast = wrapper.firstElementChild;
133
- if (!toast) {
134
- const typeLabel = (options.type || "info").toUpperCase();
135
- console.log(`[stx-alert] ${typeLabel}: ${options.title || ""}`);
136
- console.log(`[stx-alert] ${options.message}`);
137
- return;
138
- }
139
- state.element = toast;
140
- try {
141
- container.appendChild(toast);
142
- } catch {
143
- const typeLabel = (options.type || "info").toUpperCase();
144
- console.log(`[stx-alert] ${typeLabel}: ${options.title || ""}`);
145
- console.log(`[stx-alert] ${options.message}`);
146
- return;
147
- }
148
- if (typeof requestAnimationFrame === "function") {
149
- requestAnimationFrame(() => {
150
- try {
151
- toast.classList.add("visible");
152
- } catch {}
153
- });
154
- }
155
- try {
156
- toast.addEventListener("click", (e) => {
157
- if (e.target.classList?.contains("stx-toast-close")) {
158
- dismissAlert(state);
159
- return;
160
- }
161
- if (options.onClick) {
162
- options.onClick();
163
- }
164
- });
165
- } catch {}
166
- if (duration > 0) {
167
- state.timeout = setTimeout(() => {
168
- dismissAlert(state);
169
- }, duration);
170
- }
171
- } catch {
172
- const typeLabel = (options.type || "info").toUpperCase();
173
- console.log(`[stx-alert] ${typeLabel}: ${options.title || ""}`);
174
- console.log(`[stx-alert] ${options.message}`);
175
- }
176
- } else {
177
- const typeLabel = (options.type || "info").toUpperCase();
178
- console.log(`[stx-alert] ${typeLabel}: ${options.title || ""}`);
179
- console.log(`[stx-alert] ${options.message}`);
180
- if (duration > 0) {
181
- state.timeout = setTimeout(() => {
182
- activeAlerts.delete(id);
183
- }, duration);
184
- }
185
- }
186
- }
187
- async function showToast(options) {
188
- return showAlert({
189
- ...options,
190
- position: options.position || "top-right"
191
- });
192
- }
193
- async function showInfoToast(message, duration = 3000) {
194
- return showToast({ message, type: "info", duration });
195
- }
196
- async function showSuccessToast(message, duration = 3000) {
197
- return showToast({ message, type: "success", duration });
198
- }
199
- async function showWarningToast(message, duration = 3000) {
200
- return showToast({ message, type: "warning", duration });
201
- }
202
- async function showErrorToast(message, duration = 5000) {
203
- return showToast({ message, type: "error", duration });
204
- }
205
- async function notify(title, message, type = "info") {
206
- return showAlert({ title, message, type });
207
- }
208
- function dismissAlertById(id) {
209
- const state = activeAlerts.get(id);
210
- if (state) {
211
- dismissAlert(state);
212
- }
213
- }
214
- function dismissAllAlerts() {
215
- for (const state of activeAlerts.values()) {
216
- dismissAlert(state);
217
- }
218
- }
219
- function getActiveAlertCount() {
220
- return activeAlerts.size;
221
- }
222
- var TOAST_STYLES = `
223
- .stx-toast-container {
224
- position: fixed;
225
- z-index: 10001;
226
- display: flex;
227
- flex-direction: column;
228
- gap: 8px;
229
- max-width: 400px;
230
- pointer-events: none;
231
- }
232
-
233
- .stx-toast-container.top-left { top: 16px; left: 16px; }
234
- .stx-toast-container.top-center { top: 16px; left: 50%; transform: translateX(-50%); }
235
- .stx-toast-container.top-right { top: 16px; right: 16px; }
236
- .stx-toast-container.bottom-left { bottom: 16px; left: 16px; }
237
- .stx-toast-container.bottom-center { bottom: 16px; left: 50%; transform: translateX(-50%); }
238
- .stx-toast-container.bottom-right { bottom: 16px; right: 16px; }
239
-
240
- .stx-toast {
241
- display: flex;
242
- align-items: flex-start;
243
- gap: 12px;
244
- padding: 12px 16px;
245
- background: #fff;
246
- border-radius: 8px;
247
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
248
- pointer-events: auto;
249
- cursor: pointer;
250
- opacity: 0;
251
- transform: translateY(-10px);
252
- transition: opacity 0.2s, transform 0.2s;
253
- }
254
-
255
- .stx-toast.visible {
256
- opacity: 1;
257
- transform: translateY(0);
258
- }
259
-
260
- .stx-toast.dismissing {
261
- opacity: 0;
262
- transform: translateX(100%);
263
- }
264
-
265
- @media (prefers-color-scheme: dark) {
266
- .stx-toast {
267
- background: #2d2d2d;
268
- color: #fff;
269
- }
270
- }
271
-
272
- .stx-toast-icon {
273
- font-size: 20px;
274
- flex-shrink: 0;
275
- margin-top: 2px;
276
- }
277
-
278
- .stx-toast.info .stx-toast-icon { color: #3498db; }
279
- .stx-toast.success .stx-toast-icon { color: #27ae60; }
280
- .stx-toast.warning .stx-toast-icon { color: #f39c12; }
281
- .stx-toast.error .stx-toast-icon { color: #e74c3c; }
282
-
283
- .stx-toast-content {
284
- flex: 1;
285
- min-width: 0;
286
- }
287
-
288
- .stx-toast-title {
289
- font-weight: 600;
290
- margin-bottom: 4px;
291
- }
292
-
293
- .stx-toast-message {
294
- color: #666;
295
- font-size: 14px;
296
- line-height: 1.4;
297
- }
298
-
299
- @media (prefers-color-scheme: dark) {
300
- .stx-toast-message { color: #aaa; }
301
- }
302
-
303
- .stx-toast-close {
304
- background: none;
305
- border: none;
306
- font-size: 20px;
307
- cursor: pointer;
308
- opacity: 0.5;
309
- padding: 0;
310
- line-height: 1;
311
- color: inherit;
312
- transition: opacity 0.15s;
313
- }
314
-
315
- .stx-toast-close:hover {
316
- opacity: 1;
317
- }
318
-
319
- /* Border accent for different types */
320
- .stx-toast.info { border-left: 4px solid #3498db; }
321
- .stx-toast.success { border-left: 4px solid #27ae60; }
322
- .stx-toast.warning { border-left: 4px solid #f39c12; }
323
- .stx-toast.error { border-left: 4px solid #e74c3c; }
324
- `;
325
- // src/_bridge.ts
326
- function hasBridge(ns) {
327
- if (typeof window === "undefined")
328
- return false;
329
- const c = window.craft;
330
- return !!(c && c[ns]);
331
- }
332
- function requireBridge(ns) {
333
- if (!hasBridge(ns)) {
334
- throw new Error(`craft.${ns} is not available \u2014 this API requires a Craft native window`);
335
- }
336
- return window.craft[ns];
337
- }
338
- function onCraftEvent(name, cb) {
339
- if (typeof window === "undefined")
340
- return () => {};
341
- const h = (e) => cb(e.detail ?? {});
342
- window.addEventListener(name, h);
343
- return () => window.removeEventListener(name, h);
344
- }
345
-
346
- // src/app-info.ts
347
- var DEFAULT_INFO = { name: "", version: "0.0.0" };
348
- var app = {
349
- async hideDockIcon() {
350
- if (hasBridge("app"))
351
- await window.craft.app.hideDockIcon();
352
- },
353
- async showDockIcon() {
354
- if (hasBridge("app"))
355
- await window.craft.app.showDockIcon();
356
- },
357
- async quit() {
358
- if (hasBridge("app"))
359
- await window.craft.app.quit();
360
- },
361
- async getInfo() {
362
- if (!hasBridge("app"))
363
- return DEFAULT_INFO;
364
- const r = await window.craft.app.getInfo();
365
- return { ...DEFAULT_INFO, ...r || {} };
366
- },
367
- async notify(options) {
368
- if (!options.title)
369
- throw new Error("notify: title is required");
370
- if (hasBridge("app"))
371
- await window.craft.app.notify(options);
372
- },
373
- async setBadge(count) {
374
- if (hasBridge("app"))
375
- await window.craft.app.setBadge(count);
376
- },
377
- async bounce(type = "informational") {
378
- if (hasBridge("app"))
379
- await window.craft.app.bounce(type);
380
- }
381
- };
382
- // src/apple-script.ts
383
- var appleScript = {
384
- async execute(source) {
385
- if (!source)
386
- throw new Error("appleScript.execute: source is required");
387
- if (!hasBridge("appleScript"))
388
- return { ok: false };
389
- return await window.craft.appleScript.execute(source);
390
- }
391
- };
392
- // src/audio.ts
393
- var webAudio = null;
394
- var audio = {
395
- async play(path, options) {
396
- if (hasBridge("audio")) {
397
- return await window.craft.audio.play(path, options);
398
- }
399
- if (typeof window === "undefined" || typeof Audio === "undefined")
400
- return false;
401
- if (webAudio) {
402
- webAudio.pause();
403
- webAudio = null;
404
- }
405
- webAudio = new Audio(path);
406
- if (options?.volume != null)
407
- webAudio.volume = options.volume;
408
- if (options?.loops)
409
- webAudio.loop = true;
410
- try {
411
- await webAudio.play();
412
- return true;
413
- } catch {
414
- return false;
415
- }
416
- },
417
- async playSystemSound(name) {
418
- if (hasBridge("audio"))
419
- return await window.craft.audio.playSystemSound(name);
420
- if (typeof window === "undefined" || typeof Audio === "undefined")
421
- return false;
422
- try {
423
- webAudio = new Audio(`/System/Library/Sounds/${name}.aiff`);
424
- await webAudio.play();
425
- return true;
426
- } catch {
427
- return false;
428
- }
429
- },
430
- async stop() {
431
- if (hasBridge("audio")) {
432
- await window.craft.audio.stop();
433
- return;
434
- }
435
- if (webAudio) {
436
- webAudio.pause();
437
- webAudio.currentTime = 0;
438
- webAudio = null;
439
- }
440
- },
441
- async isPlaying() {
442
- if (hasBridge("audio"))
443
- return await window.craft.audio.isPlaying();
444
- return !!(webAudio && !webAudio.paused);
445
- },
446
- async startRecording(path, options) {
447
- if (hasBridge("audio"))
448
- return await window.craft.audio.startRecording(path, options);
449
- return false;
450
- },
451
- async stopRecording() {
452
- if (hasBridge("audio"))
453
- await window.craft.audio.stopRecording();
454
- },
455
- async isRecording() {
456
- if (hasBridge("audio"))
457
- return await window.craft.audio.isRecording();
458
- return false;
459
- }
460
- };
461
- // src/battery.ts
462
- var battery = {
463
- async isCharging() {
464
- if (hasBridge("power"))
465
- return await window.craft.power.isCharging();
466
- const b = await getWebBatteryManager();
467
- return b ? !!b.charging : false;
468
- },
469
- async isPluggedIn() {
470
- if (hasBridge("power"))
471
- return await window.craft.power.isPluggedIn();
472
- const b = await getWebBatteryManager();
473
- return b ? !!b.charging || b.level >= 0.999 : false;
474
- },
475
- async isLowPowerMode() {
476
- if (hasBridge("power"))
477
- return await window.craft.power.isLowPowerMode();
478
- return false;
479
- },
480
- async level() {
481
- if (hasBridge("power")) {
482
- const v = await window.craft.power.batteryLevel();
483
- return typeof v === "number" ? v : null;
484
- }
485
- const b = await getWebBatteryManager();
486
- return b ? b.level : null;
487
- },
488
- async timeRemaining() {
489
- if (hasBridge("power")) {
490
- const r = await window.craft.power.timeRemaining();
491
- return typeof r === "number" ? r : null;
492
- }
493
- const b = await getWebBatteryManager();
494
- if (!b)
495
- return null;
496
- const sec = b.charging ? b.chargingTime : b.dischargingTime;
497
- return Number.isFinite(sec) ? Math.round(sec / 60) : null;
498
- },
499
- async thermalState() {
500
- if (hasBridge("power")) {
501
- const s = await window.craft.power.thermalState();
502
- return s || "unknown";
503
- }
504
- return "unknown";
505
- },
506
- async uptimeSeconds() {
507
- if (hasBridge("power"))
508
- return await window.craft.power.uptimeSeconds();
509
- if (typeof performance !== "undefined" && typeof performance.now === "function") {
510
- return Math.round(performance.now() / 1000);
511
- }
512
- return 0;
513
- },
514
- async preventSleep(reason = "app is busy") {
515
- if (hasBridge("power")) {
516
- await window.craft.power.preventSleep(reason);
517
- return;
518
- }
519
- if (typeof navigator === "undefined" || !navigator.wakeLock)
520
- return;
521
- const w = window;
522
- if (w.__craftWebWakeLock?.release) {
523
- try {
524
- await w.__craftWebWakeLock.release();
525
- } catch {}
526
- w.__craftWebWakeLock = null;
527
- }
528
- try {
529
- const sentinel = await navigator.wakeLock.request("screen");
530
- w.__craftWebWakeLock = sentinel;
531
- } catch {}
532
- },
533
- async allowSleep() {
534
- if (hasBridge("power")) {
535
- await window.craft.power.allowSleep();
536
- return;
537
- }
538
- const s = window.__craftWebWakeLock;
539
- if (s && typeof s.release === "function") {
540
- try {
541
- await s.release();
542
- } catch {}
543
- window.__craftWebWakeLock = null;
544
- }
545
- },
546
- onSleep(cb) {
547
- return onCraftEvent("craft:powerSleep", cb);
548
- },
549
- onWake(cb) {
550
- return onCraftEvent("craft:powerWake", cb);
551
- }
552
- };
553
- async function getWebBatteryManager() {
554
- if (typeof navigator === "undefined")
555
- return null;
556
- const nav = navigator;
557
- if (typeof nav.getBattery !== "function")
558
- return null;
559
- try {
560
- return await nav.getBattery();
561
- } catch {
562
- return null;
563
- }
564
- }
565
- // src/biometric.ts
566
- var biometric = {
567
- async isAvailable() {
568
- if (!hasBridge("biometric"))
569
- return false;
570
- return await window.craft.biometric.isAvailable();
571
- },
572
- async getBiometryType() {
573
- if (!hasBridge("biometric"))
574
- return "none";
575
- return await window.craft.biometric.getBiometryType();
576
- },
577
- async evaluate(reason, options) {
578
- if (!reason)
579
- throw new Error("biometric.evaluate: reason is required");
580
- if (!hasBridge("biometric"))
581
- return { success: false, errorCode: -1 };
582
- return await window.craft.biometric.evaluate(reason, options);
583
- }
584
- };
585
- // src/bluetooth.ts
586
- var HEX_RE = /^[\da-f]*$/i;
587
- function assertHex(label, hex) {
588
- if (typeof hex !== "string" || !HEX_RE.test(hex) || hex.length % 2 !== 0) {
589
- throw new Error(`${label}: must be a hex string with even length, got ${JSON.stringify(hex)}`);
590
- }
591
- }
592
- var bluetooth = {
593
- async isEnabled() {
594
- return hasBridge("bluetooth") ? await window.craft.bluetooth.isEnabled() : false;
595
- },
596
- async powerState() {
597
- return hasBridge("bluetooth") ? await window.craft.bluetooth.powerState() : "unknown";
598
- },
599
- async connectedDevices() {
600
- return hasBridge("bluetooth") ? await window.craft.bluetooth.connectedDevices() : [];
601
- },
602
- async pairedDevices() {
603
- return hasBridge("bluetooth") ? await window.craft.bluetooth.pairedDevices() : [];
604
- },
605
- async startDiscovery() {
606
- if (hasBridge("bluetooth"))
607
- await window.craft.bluetooth.startDiscovery();
608
- },
609
- async stopDiscovery() {
610
- if (hasBridge("bluetooth"))
611
- await window.craft.bluetooth.stopDiscovery();
612
- },
613
- async isDiscovering() {
614
- return hasBridge("bluetooth") ? await window.craft.bluetooth.isDiscovering() : false;
615
- },
616
- async connect(id) {
617
- if (hasBridge("bluetooth"))
618
- await window.craft.bluetooth.connect(id);
619
- },
620
- async disconnect(id) {
621
- if (hasBridge("bluetooth"))
622
- await window.craft.bluetooth.disconnect(id);
623
- },
624
- async openPreferences() {
625
- if (hasBridge("bluetooth"))
626
- await window.craft.bluetooth.openPreferences();
627
- },
628
- async discoverServices(deviceId) {
629
- if (!deviceId)
630
- throw new Error("bluetooth.discoverServices: deviceId is required");
631
- if (!hasBridge("bluetooth"))
632
- return [];
633
- const r = await window.craft.bluetooth.discoverServices(deviceId);
634
- return Array.isArray(r) ? r : [];
635
- },
636
- async discoverCharacteristics(deviceId, serviceUuid) {
637
- if (!deviceId || !serviceUuid)
638
- throw new Error("bluetooth.discoverCharacteristics: deviceId and serviceUuid are required");
639
- if (!hasBridge("bluetooth"))
640
- return [];
641
- const r = await window.craft.bluetooth.discoverCharacteristics(deviceId, serviceUuid);
642
- return Array.isArray(r) ? r : [];
643
- },
644
- async readCharacteristic(deviceId, serviceUuid, characteristicUuid) {
645
- if (!deviceId || !serviceUuid || !characteristicUuid) {
646
- throw new Error("bluetooth.readCharacteristic: deviceId, serviceUuid, characteristicUuid are required");
647
- }
648
- if (!hasBridge("bluetooth"))
649
- return { ok: false, reason: "bridge unavailable" };
650
- return await window.craft.bluetooth.readCharacteristic(deviceId, serviceUuid, characteristicUuid);
651
- },
652
- async writeCharacteristic(deviceId, serviceUuid, characteristicUuid, valueHex, mode = "with-response") {
653
- if (!deviceId || !serviceUuid || !characteristicUuid) {
654
- throw new Error("bluetooth.writeCharacteristic: deviceId, serviceUuid, characteristicUuid are required");
655
- }
656
- assertHex("bluetooth.writeCharacteristic.valueHex", valueHex);
657
- if (!hasBridge("bluetooth"))
658
- return { ok: false, reason: "bridge unavailable" };
659
- return await window.craft.bluetooth.writeCharacteristic(deviceId, serviceUuid, characteristicUuid, valueHex, mode);
660
- },
661
- async setCharacteristicNotify(deviceId, serviceUuid, characteristicUuid, on) {
662
- if (!deviceId || !serviceUuid || !characteristicUuid) {
663
- throw new Error("bluetooth.setCharacteristicNotify: deviceId, serviceUuid, characteristicUuid are required");
664
- }
665
- if (!hasBridge("bluetooth"))
666
- return { ok: false, reason: "bridge unavailable" };
667
- return await window.craft.bluetooth.setCharacteristicNotify(deviceId, serviceUuid, characteristicUuid, on);
668
- },
669
- onDeviceFound(cb) {
670
- return onCraftEvent("craft:bluetooth:deviceFound", cb);
671
- },
672
- onDeviceConnected(cb) {
673
- return onCraftEvent("craft:bluetooth:deviceConnected", cb);
674
- },
675
- onDeviceDisconnected(cb) {
676
- return onCraftEvent("craft:bluetooth:deviceDisconnected", cb);
677
- },
678
- onCharacteristicValue(cb) {
679
- return onCraftEvent("craft:bluetooth:characteristicValue", cb);
680
- }
681
- };
682
- // src/bonjour.ts
683
- var bonjour = {
684
- async browse(serviceType) {
685
- if (!hasBridge("bonjour"))
686
- return { started: false, reason: "bridge unavailable" };
687
- return await window.craft.bonjour.browse(serviceType);
688
- },
689
- async stop() {
690
- if (hasBridge("bonjour"))
691
- await window.craft.bonjour.stop();
692
- },
693
- onFound(cb) {
694
- return onCraftEvent("craft:bonjour:found", cb);
695
- },
696
- onLost(cb) {
697
- return onCraftEvent("craft:bonjour:lost", cb);
698
- }
699
- };
700
- // src/capabilities.ts
701
- var BRIDGE_INDEX = [
702
- { name: "fs", support: "native" },
703
- { name: "shell", support: "native" },
704
- { name: "system", support: "all" },
705
- { name: "clipboard", support: "all" },
706
- { name: "dialog", support: "all" },
707
- { name: "window", support: "native" },
708
- { name: "tray", support: "native" },
709
- { name: "menu", support: "native" },
710
- { name: "theme", support: "all" },
711
- { name: "screen", support: "all" },
712
- { name: "network", support: "all" },
713
- { name: "power", support: "all" },
714
- { name: "battery", support: "all" },
715
- { name: "notifications", support: "all" },
716
- { name: "globalShortcuts", support: "native" },
717
- { name: "autolaunch", support: "native" },
718
- { name: "appInfo", support: "all" },
719
- { name: "localServer", support: "native" },
720
- { name: "bluetooth", support: "native" },
721
- { name: "crashReporter", support: "all" },
722
- { name: "updater", support: "native" },
723
- { name: "iap", support: "macos" },
724
- { name: "keychain", support: "native" },
725
- { name: "log", support: "all" },
726
- { name: "biometric", support: "macos" },
727
- { name: "location", support: "macos" },
728
- { name: "audio", support: "macos" },
729
- { name: "deepLink", support: "native" },
730
- { name: "handoff", support: "macos" },
731
- { name: "liveActivities", support: "macos" },
732
- { name: "touchbar", support: "macos" },
733
- { name: "dragOut", support: "macos" },
734
- { name: "appleScript", support: "macos" },
735
- { name: "fileAssociations", support: "native" },
736
- { name: "tags", support: "macos" },
737
- { name: "pdf", support: "macos" },
738
- { name: "bonjour", support: "macos" },
739
- { name: "spotlight", support: "macos" },
740
- { name: "speechRecognition", support: "macos" },
741
- { name: "vision", support: "macos" },
742
- { name: "midi", support: "macos" },
743
- { name: "coreml", support: "macos" },
744
- { name: "continuityCamera", support: "macos" },
745
- { name: "serviceMenu", support: "macos" },
746
- { name: "serial", support: "native" }
747
- ];
748
- function getCapabilities() {
749
- return BRIDGE_INDEX.map(({ name, support }) => ({
750
- name,
751
- support,
752
- available: hasBridge(name)
753
- }));
754
- }
755
- function getCapability(name) {
756
- const entry = BRIDGE_INDEX.find((b) => b.name === name);
757
- if (!entry)
758
- return;
759
- return { ...entry, available: hasBridge(name) };
760
- }
761
- function isAvailable(name) {
762
- const cap = getCapability(name);
763
- return !!cap && cap.available;
764
- }
765
- // src/clipboard.ts
766
- var clipboard = {
767
- async writeText(text) {
768
- if (hasBridge("clipboard")) {
769
- await window.craft.clipboard.writeText(text);
770
- return;
771
- }
772
- if (typeof navigator !== "undefined" && navigator.clipboard) {
773
- await navigator.clipboard.writeText(text);
774
- }
775
- },
776
- async readText() {
777
- if (hasBridge("clipboard")) {
778
- const v = await window.craft.clipboard.readText();
779
- return typeof v === "string" ? v : "";
780
- }
781
- if (typeof navigator !== "undefined" && navigator.clipboard?.readText) {
782
- try {
783
- return await navigator.clipboard.readText();
784
- } catch {
785
- return "";
786
- }
787
- }
788
- return "";
789
- },
790
- async writeHTML(html) {
791
- if (hasBridge("clipboard")) {
792
- await window.craft.clipboard.writeHTML(html);
793
- return;
794
- }
795
- if (typeof navigator !== "undefined" && navigator.clipboard?.write) {
796
- try {
797
- const item = new window.ClipboardItem({
798
- "text/html": new Blob([html], { type: "text/html" }),
799
- "text/plain": new Blob([stripHtml(html)], { type: "text/plain" })
800
- });
801
- await navigator.clipboard.write([item]);
802
- } catch {}
803
- }
804
- },
805
- async readHTML() {
806
- if (hasBridge("clipboard")) {
807
- const v = await window.craft.clipboard.readHTML();
808
- return typeof v === "string" ? v : "";
809
- }
810
- if (typeof navigator !== "undefined" && navigator.clipboard?.read) {
811
- try {
812
- const items = await navigator.clipboard.read();
813
- for (const item of items) {
814
- if (item.types.includes("text/html")) {
815
- const blob = await item.getType("text/html");
816
- return await blob.text();
817
- }
818
- }
819
- } catch {}
820
- }
821
- return "";
822
- },
823
- async clear() {
824
- if (hasBridge("clipboard")) {
825
- await window.craft.clipboard.clear();
826
- return;
827
- }
828
- if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
829
- try {
830
- await navigator.clipboard.writeText("");
831
- } catch {}
832
- }
833
- },
834
- async hasText() {
835
- if (hasBridge("clipboard")) {
836
- return await window.craft.clipboard.hasText();
837
- }
838
- return (await this.readText()).length > 0;
839
- },
840
- async hasHTML() {
841
- if (hasBridge("clipboard")) {
842
- return await window.craft.clipboard.hasHTML();
843
- }
844
- return (await this.readHTML()).length > 0;
845
- },
846
- async hasImage() {
847
- if (hasBridge("clipboard")) {
848
- return await window.craft.clipboard.hasImage();
849
- }
850
- return false;
851
- }
852
- };
853
- function stripHtml(html) {
854
- return html.replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/gi, "").replace(/<\/?(br|p|div|li|h[1-6])\b[^>]*>/gi, `
855
- `).replace(/<[^>]+>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/\n{3,}/g, `
856
-
857
- `).trim();
858
- }
859
- // src/continuity-camera.ts
860
- var continuityCamera = {
861
- async listCameras() {
862
- if (!hasBridge("continuityCamera"))
863
- return [];
864
- return await window.craft.continuityCamera.listCameras();
865
- }
866
- };
867
- // src/coreml.ts
868
- var coreml = {
869
- async loadModel(id, path) {
870
- if (!id || !path)
871
- throw new Error("coreml.loadModel: id and path are required");
872
- if (!hasBridge("coreml"))
873
- return false;
874
- return await window.craft.coreml.loadModel(id, path);
875
- },
876
- async unloadModel(id) {
877
- if (!hasBridge("coreml"))
878
- return;
879
- await window.craft.coreml.unloadModel(id);
880
- },
881
- async predict(id, input) {
882
- if (!id)
883
- throw new Error("coreml.predict: id is required");
884
- if (!hasBridge("coreml"))
885
- return null;
886
- return await window.craft.coreml.predict(id, input);
887
- }
888
- };
889
- // src/crash-reporter.ts
890
- var jsQueue = [];
891
- var jsEnabled = true;
892
- var jsUserId;
893
- var jsAppVersion;
894
- var crashReporter = {
895
- async report(entry) {
896
- if (hasBridge("crashReporter")) {
897
- await window.craft.crashReporter.report(entry);
898
- return;
899
- }
900
- if (!jsEnabled)
901
- return;
902
- const normalized = entry instanceof Error ? {
903
- timestamp: Date.now(),
904
- severity: "error",
905
- message: entry.message,
906
- source: "js",
907
- stack: entry.stack || "",
908
- userId: jsUserId,
909
- appVersion: jsAppVersion
910
- } : {
911
- timestamp: Date.now(),
912
- severity: entry.severity || "error",
913
- message: entry.message || "",
914
- source: entry.source || "js",
915
- stack: entry.stack || "",
916
- userId: jsUserId,
917
- appVersion: jsAppVersion
918
- };
919
- if (jsQueue.length >= 64)
920
- jsQueue.shift();
921
- jsQueue.push(normalized);
922
- },
923
- async flush() {
924
- if (hasBridge("crashReporter"))
925
- return await window.craft.crashReporter.flush();
926
- return [...jsQueue];
927
- },
928
- async clear() {
929
- if (hasBridge("crashReporter")) {
930
- await window.craft.crashReporter.clear();
931
- return;
932
- }
933
- jsQueue.length = 0;
934
- },
935
- async setEnabled(on) {
936
- if (hasBridge("crashReporter")) {
937
- await window.craft.crashReporter.setEnabled(on);
938
- return;
939
- }
940
- jsEnabled = on;
941
- },
942
- async isEnabled() {
943
- if (hasBridge("crashReporter"))
944
- return await window.craft.crashReporter.isEnabled();
945
- return jsEnabled;
946
- },
947
- async setUser(id) {
948
- if (hasBridge("crashReporter")) {
949
- await window.craft.crashReporter.setUser(id);
950
- return;
951
- }
952
- jsUserId = id || undefined;
953
- },
954
- async setAppVersion(version) {
955
- if (hasBridge("crashReporter")) {
956
- await window.craft.crashReporter.setAppVersion(version);
957
- return;
958
- }
959
- jsAppVersion = version || undefined;
960
- },
961
- attachGlobalHandlers() {
962
- if (hasBridge("crashReporter") && window.craft.crashReporter.attachGlobalHandlers) {
963
- return window.craft.crashReporter.attachGlobalHandlers();
964
- }
965
- if (typeof window === "undefined")
966
- return () => {};
967
- const errorH = (e) => {
968
- crashReporter.report({
969
- severity: "error",
970
- message: e.message,
971
- source: "js",
972
- stack: e.error?.stack || `${e.message}
973
- at ${e.filename}:${e.lineno}:${e.colno}`
974
- }).catch(() => {});
975
- };
976
- const rejectH = (e) => {
977
- const r = e.reason;
978
- crashReporter.report({
979
- severity: "error",
980
- message: r?.message || String(r),
981
- source: "js",
982
- stack: r?.stack || ""
983
- }).catch(() => {});
984
- };
985
- window.addEventListener("error", errorH);
986
- window.addEventListener("unhandledrejection", rejectH);
987
- return () => {
988
- window.removeEventListener("error", errorH);
989
- window.removeEventListener("unhandledrejection", rejectH);
990
- };
991
- },
992
- forwardTo(options) {
993
- return startForwarder(options);
994
- }
995
- };
996
- var DEFAULT_PERSIST_KEY = "craft:crashReporter:pending";
997
- var EMAIL_RE = /[\w.+-]+@[\w-]+\.[\w.-]+/g;
998
- var IPV4_RE = /\b(?:\d{1,3}\.){3}\d{1,3}\b/g;
999
- var HOME_PATH_RE = /\/(?:Users|home)\/[^\s/'"`]+/g;
1000
- function redactPII(entry) {
1001
- const scrub = (s) => s.replace(EMAIL_RE, "<email>").replace(IPV4_RE, "<ip>").replace(HOME_PATH_RE, "/<home>");
1002
- return {
1003
- ...entry,
1004
- message: scrub(entry.message),
1005
- stack: scrub(entry.stack)
1006
- };
1007
- }
1008
- async function signPayload(secret, body) {
1009
- const enc = new TextEncoder;
1010
- const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
1011
- const sig = await crypto.subtle.sign("HMAC", key, enc.encode(body));
1012
- return [...new Uint8Array(sig)].map((b) => b.toString(16).padStart(2, "0")).join("");
1013
- }
1014
- function loadPersisted(key) {
1015
- if (!key || typeof localStorage === "undefined")
1016
- return [];
1017
- try {
1018
- const raw = localStorage.getItem(key);
1019
- if (!raw)
1020
- return [];
1021
- const parsed = JSON.parse(raw);
1022
- return Array.isArray(parsed) ? parsed : [];
1023
- } catch {
1024
- return [];
1025
- }
1026
- }
1027
- function savePersisted(key, entries) {
1028
- if (!key || typeof localStorage === "undefined")
1029
- return;
1030
- try {
1031
- if (entries.length === 0)
1032
- localStorage.removeItem(key);
1033
- else
1034
- localStorage.setItem(key, JSON.stringify(entries));
1035
- } catch {}
1036
- }
1037
- function startForwarder(options) {
1038
- const {
1039
- endpoint,
1040
- intervalMs = 60000,
1041
- signingSecret,
1042
- redact = true,
1043
- headers = {},
1044
- maxRetries = 5,
1045
- persistKey
1046
- } = options;
1047
- const storageKey = persistKey === null ? null : persistKey ?? DEFAULT_PERSIST_KEY;
1048
- let pending = loadPersisted(storageKey);
1049
- let stopped = false;
1050
- let timer = null;
1051
- let inFlight = null;
1052
- const redactor = typeof redact === "function" ? redact : redact === false ? (e) => e : redactPII;
1053
- async function postBatch(batch) {
1054
- const body = JSON.stringify({ entries: batch });
1055
- const requestHeaders = {
1056
- "Content-Type": "application/json",
1057
- ...headers
1058
- };
1059
- if (signingSecret) {
1060
- requestHeaders["X-Craft-Signature"] = await signPayload(signingSecret, body);
1061
- }
1062
- let attempt = 0;
1063
- let delay = 1000;
1064
- while (!stopped) {
1065
- try {
1066
- const res = await fetch(endpoint, { method: "POST", headers: requestHeaders, body });
1067
- if (res.ok)
1068
- return;
1069
- if (res.status >= 400 && res.status < 500)
1070
- return;
1071
- throw new Error(`HTTP ${res.status}`);
1072
- } catch (err) {
1073
- attempt += 1;
1074
- if (attempt > maxRetries) {
1075
- pending = [...batch, ...pending];
1076
- savePersisted(storageKey, pending);
1077
- throw err;
1078
- }
1079
- await new Promise((r) => {
1080
- setTimeout(r, delay);
1081
- });
1082
- delay = Math.min(delay * 2, 60000);
1083
- }
1084
- }
1085
- }
1086
- async function drain() {
1087
- if (stopped || inFlight)
1088
- return inFlight ?? undefined;
1089
- const fresh = await crashReporter.flush();
1090
- if (fresh.length > 0)
1091
- await crashReporter.clear();
1092
- pending = [...pending, ...fresh.map((e) => redactor(e))];
1093
- if (pending.length === 0)
1094
- return;
1095
- const batch = pending;
1096
- pending = [];
1097
- savePersisted(storageKey, pending);
1098
- inFlight = postBatch(batch).catch(() => {}).finally(() => {
1099
- inFlight = null;
1100
- });
1101
- return inFlight;
1102
- }
1103
- if (intervalMs > 0) {
1104
- timer = setInterval(() => {
1105
- drain().catch(() => {});
1106
- }, intervalMs);
1107
- }
1108
- return {
1109
- async flushNow() {
1110
- await drain();
1111
- },
1112
- stop() {
1113
- stopped = true;
1114
- if (timer)
1115
- clearInterval(timer);
1116
- timer = null;
1117
- },
1118
- pending() {
1119
- return [...pending];
1120
- }
1121
- };
1122
- }
1123
- // src/deep-link.ts
1124
- var deepLinks = {
1125
- onUrl(cb) {
1126
- if (!hasBridge("deepLink"))
1127
- return () => {};
1128
- return onCraftEvent("craft:deepLink", cb);
1129
- },
1130
- getInitialUrl() {
1131
- if (typeof window === "undefined")
1132
- return null;
1133
- if (hasBridge("deepLink")) {
1134
- try {
1135
- return window.craft.deepLink.getInitialUrl();
1136
- } catch {
1137
- return null;
1138
- }
1139
- }
1140
- return window.__craftPendingDeepLink || null;
1141
- },
1142
- consumeInitialUrl() {
1143
- const url = this.getInitialUrl();
1144
- if (typeof window !== "undefined")
1145
- window.__craftPendingDeepLink = undefined;
1146
- return url;
1147
- },
1148
- isAvailable() {
1149
- return hasBridge("deepLink");
1150
- }
1151
- };
1152
- // src/dialogs.ts
1153
- function isInCraftWindow() {
1154
- if (typeof window !== "undefined" && window.craft?.dialog) {
1155
- return true;
1156
- }
1157
- return false;
1158
- }
1159
- async function showOpenDialog(options = {}) {
1160
- if (isInCraftWindow()) {
1161
- const craftWindow = window;
1162
- try {
1163
- return await craftWindow.craft.dialog.showOpenDialog(options);
1164
- } catch (error) {
1165
- console.warn("[stx-dialog] Failed to show native open dialog:", error);
1166
- }
1167
- }
1168
- return new Promise((resolve) => {
1169
- if (typeof document === "undefined") {
1170
- resolve({ canceled: true, filePaths: [] });
1171
- return;
1172
- }
1173
- const input = document.createElement("input");
1174
- input.type = "file";
1175
- input.multiple = options.multiSelections ?? false;
1176
- if (options.filters?.length) {
1177
- const extensions = options.filters.flatMap((f) => f.extensions.map((e) => `.${e}`));
1178
- input.accept = extensions.join(",");
1179
- }
1180
- if (options.canChooseDirectories && !options.canChooseFiles) {
1181
- input.webkitdirectory = true;
1182
- }
1183
- input.onchange = () => {
1184
- const files = Array.from(input.files || []);
1185
- if (files.length === 0) {
1186
- resolve({ canceled: true, filePaths: [] });
1187
- } else {
1188
- const filePaths = files.map((f) => f.name);
1189
- resolve({ canceled: false, filePaths });
1190
- }
1191
- };
1192
- input.oncancel = () => {
1193
- resolve({ canceled: true, filePaths: [] });
1194
- };
1195
- input.click();
1196
- });
1197
- }
1198
- async function showSaveDialog(options = {}) {
1199
- if (isInCraftWindow()) {
1200
- const craftWindow = window;
1201
- try {
1202
- return await craftWindow.craft.dialog.showSaveDialog(options);
1203
- } catch (error) {
1204
- console.warn("[stx-dialog] Failed to show native save dialog:", error);
1205
- }
1206
- }
1207
- if (typeof window !== "undefined" && "showSaveFilePicker" in window) {
1208
- try {
1209
- const fileTypes = options.filters?.map((f) => ({
1210
- description: f.name,
1211
- accept: {
1212
- "*/*": f.extensions.map((e) => `.${e}`)
1213
- }
1214
- }));
1215
- const handle = await window.showSaveFilePicker({
1216
- suggestedName: options.defaultPath,
1217
- types: fileTypes
1218
- });
1219
- return { canceled: false, filePath: handle.name };
1220
- } catch (error) {
1221
- return { canceled: true };
1222
- }
1223
- }
1224
- console.warn("[stx-dialog] Save dialog not available, using prompt fallback");
1225
- const filename = prompt("Enter filename:", options.defaultPath || "file.txt");
1226
- if (filename) {
1227
- return { canceled: false, filePath: filename };
1228
- }
1229
- return { canceled: true };
1230
- }
1231
- async function showMessageBox(options) {
1232
- if (isInCraftWindow()) {
1233
- const craftWindow = window;
1234
- try {
1235
- return await craftWindow.craft.dialog.showMessageBox(options);
1236
- } catch (error) {
1237
- console.warn("[stx-dialog] Failed to show native message box:", error);
1238
- }
1239
- }
1240
- const buttons = options.buttons || ["OK"];
1241
- if (buttons.length === 1) {
1242
- alert(options.message);
1243
- return { response: 0 };
1244
- }
1245
- if (buttons.length === 2 && options.type === "question") {
1246
- const confirmed2 = confirm(options.message);
1247
- return { response: confirmed2 ? 1 : 0 };
1248
- }
1249
- console.warn("[stx-dialog] Complex message box not fully supported in browser, using confirm");
1250
- const confirmed = confirm(`${options.message}
1251
-
1252
- ${buttons.join(" / ")}`);
1253
- return { response: confirmed ? buttons.length - 1 : 0 };
1254
- }
1255
- async function showColorPicker(options = {}) {
1256
- if (isInCraftWindow()) {
1257
- const craftWindow = window;
1258
- try {
1259
- return await craftWindow.craft.dialog.showColorPicker(options);
1260
- } catch (error) {
1261
- console.warn("[stx-dialog] Failed to show native color picker:", error);
1262
- }
1263
- }
1264
- return new Promise((resolve) => {
1265
- if (typeof document === "undefined") {
1266
- resolve({ canceled: true });
1267
- return;
1268
- }
1269
- const input = document.createElement("input");
1270
- input.type = "color";
1271
- input.value = options.color || "#000000";
1272
- input.onchange = () => {
1273
- resolve({ canceled: false, color: input.value });
1274
- };
1275
- input.oncancel = () => {
1276
- resolve({ canceled: true });
1277
- };
1278
- input.click();
1279
- });
1280
- }
1281
- async function showAlertDialog(message, title) {
1282
- await showMessageBox({
1283
- type: "info",
1284
- title: title || "Alert",
1285
- message,
1286
- buttons: ["OK"]
1287
- });
1288
- }
1289
- async function showConfirmDialog(message, title) {
1290
- const result = await showMessageBox({
1291
- type: "question",
1292
- title: title || "Confirm",
1293
- message,
1294
- buttons: ["Cancel", "OK"],
1295
- defaultButton: 1,
1296
- cancelButton: 0
1297
- });
1298
- return result.response === 1;
1299
- }
1300
- async function showErrorDialog(message, title) {
1301
- await showMessageBox({
1302
- type: "error",
1303
- title: title || "Error",
1304
- message,
1305
- buttons: ["OK"]
1306
- });
1307
- }
1308
- async function showWarningDialog(message, title) {
1309
- await showMessageBox({
1310
- type: "warning",
1311
- title: title || "Warning",
1312
- message,
1313
- buttons: ["OK"]
1314
- });
1315
- }
1316
- function getDialogBridgeScript() {
1317
- return `
1318
- // STX Desktop Dialog Bridge
1319
- // Provides convenient wrappers around window.craft.dialog APIs
1320
- window.stxDialog = {
1321
- // File dialogs
1322
- showOpenDialog: (options) => window.craft?.dialog?.showOpenDialog(options),
1323
- showSaveDialog: (options) => window.craft?.dialog?.showSaveDialog(options),
1324
-
1325
- // Message dialogs
1326
- showMessageBox: (options) => window.craft?.dialog?.showMessageBox(options),
1327
-
1328
- // Color picker
1329
- showColorPicker: (options) => window.craft?.dialog?.showColorPicker(options),
1330
-
1331
- // Font picker
1332
- showFontPicker: (options) => window.craft?.dialog?.showFontPicker(options),
1333
-
1334
- // Convenience functions
1335
- alert: async (message, title) => {
1336
- return window.craft?.dialog?.showMessageBox({
1337
- type: 'info',
1338
- title: title || 'Alert',
1339
- message,
1340
- buttons: ['OK'],
1341
- });
1342
- },
1343
-
1344
- confirm: async (message, title) => {
1345
- const result = await window.craft?.dialog?.showMessageBox({
1346
- type: 'question',
1347
- title: title || 'Confirm',
1348
- message,
1349
- buttons: ['Cancel', 'OK'],
1350
- });
1351
- return result?.response === 1;
1352
- },
1353
-
1354
- error: async (message, title) => {
1355
- return window.craft?.dialog?.showMessageBox({
1356
- type: 'error',
1357
- title: title || 'Error',
1358
- message,
1359
- buttons: ['OK'],
1360
- });
1361
- },
1362
-
1363
- // Check if dialog is available
1364
- isAvailable: () => typeof window.craft?.dialog !== 'undefined',
1365
- };
1366
- `;
1367
- }
1368
- // src/drag-out.ts
1369
- async function dragOut(paths, options = {}) {
1370
- if (!hasBridge("dragOut")) {
1371
- throw new Error("dragOut requires a Craft native window");
1372
- }
1373
- const arr = Array.isArray(paths) ? paths : [paths];
1374
- if (arr.length === 0)
1375
- throw new Error("dragOut: at least one path required");
1376
- await window.craft.dragOut.start(arr, options);
1377
- }
1378
- function isDragOutAvailable() {
1379
- return hasBridge("dragOut");
1380
- }
1381
- // src/file-associations.ts
1382
- var fileAssociations = {
1383
- async getDefault(uti) {
1384
- if (!uti)
1385
- throw new Error("fileAssociations.getDefault: uti is required");
1386
- if (!hasBridge("fileAssociations"))
1387
- return null;
1388
- const v = await window.craft.fileAssociations.getDefault(uti);
1389
- return v ? String(v) : null;
1390
- },
1391
- async setDefault(uti, bundleId) {
1392
- if (!uti || !bundleId)
1393
- throw new Error("fileAssociations.setDefault: uti and bundleId are required");
1394
- if (!hasBridge("fileAssociations"))
1395
- return false;
1396
- return await window.craft.fileAssociations.setDefault(uti, bundleId);
1397
- }
1398
- };
1399
- // src/focus.ts
1400
- var UNSUPPORTED = { supported: false, isFocused: null, authorization: "unsupported" };
1401
- function unavailable() {
1402
- return { ok: false, error: "Focus control is only available in a Craft window on macOS" };
1403
- }
1404
- var focus = {
1405
- async getStatus() {
1406
- if (!hasBridge("focus"))
1407
- return { ...UNSUPPORTED };
1408
- return await window.craft.focus.getStatus();
1409
- },
1410
- async requestAuthorization() {
1411
- if (!hasBridge("focus"))
1412
- return "unsupported";
1413
- return await window.craft.focus.requestAuthorization();
1414
- },
1415
- async setEnabled(enabled, options = {}) {
1416
- if (!hasBridge("focus"))
1417
- return unavailable();
1418
- const name = enabled ? options.onShortcut : options.offShortcut;
1419
- if (!name) {
1420
- return {
1421
- ok: false,
1422
- error: `focus.setEnabled: no ${enabled ? "onShortcut" : "offShortcut"} configured`
1423
- };
1424
- }
1425
- return await window.craft.focus.setEnabled(enabled, options);
1426
- },
1427
- async runShortcut(name) {
1428
- if (!hasBridge("focus"))
1429
- return unavailable();
1430
- if (!name)
1431
- throw new Error("focus.runShortcut: name is required");
1432
- return await window.craft.focus.runShortcut(name);
1433
- },
1434
- async listShortcuts() {
1435
- if (!hasBridge("focus"))
1436
- return [];
1437
- return await window.craft.focus.listShortcuts();
1438
- },
1439
- async listShortcutsResult() {
1440
- if (!hasBridge("focus"))
1441
- return { canList: false, shortcuts: [] };
1442
- const r = await window.craft.focus.listShortcutsResult();
1443
- return { canList: Boolean(r?.canList), shortcuts: r?.shortcuts || [] };
1444
- }
1445
- };
1446
- async function hasFocusShortcuts(...names) {
1447
- if (names.length === 0)
1448
- return false;
1449
- const installed = new Set(await focus.listShortcuts());
1450
- return names.every((name) => installed.has(name));
1451
- }
1452
- async function focusShortcutsReady(...names) {
1453
- if (names.length === 0)
1454
- return false;
1455
- const { canList, shortcuts } = await focus.listShortcutsResult();
1456
- if (!canList)
1457
- return "unknown";
1458
- const installed = new Set(shortcuts);
1459
- return names.every((name) => installed.has(name));
1460
- }
1461
- // src/fs.ts
1462
- var fs = {
1463
- async readFile(path) {
1464
- const r = await requireBridge("fs").readFile(path);
1465
- return r && r.data || "";
1466
- },
1467
- async readBuffer(path) {
1468
- const r = await requireBridge("fs").readFile(path);
1469
- const text = r && r.data || "";
1470
- if (r && r.base64 === true) {
1471
- return base64ToBytes(text);
1472
- }
1473
- return new TextEncoder().encode(text);
1474
- },
1475
- async writeFile(path, data) {
1476
- await requireBridge("fs").writeFile(path, data);
1477
- },
1478
- async writeBuffer(path, data) {
1479
- const b64 = bytesToBase64(data);
1480
- const bridge = requireBridge("fs");
1481
- if (typeof bridge.writeFileBytes === "function") {
1482
- await bridge.writeFileBytes(path, b64);
1483
- } else {
1484
- await bridge.writeFile(path, b64);
1485
- }
1486
- },
1487
- async copy(from, to) {
1488
- if (from === to)
1489
- throw new Error("fs.copy: source and destination must differ");
1490
- await requireBridge("fs").copy(from, to);
1491
- },
1492
- async move(from, to) {
1493
- if (from === to)
1494
- throw new Error("fs.move: source and destination must differ");
1495
- await requireBridge("fs").move(from, to);
1496
- },
1497
- async appendFile(path, data) {
1498
- await requireBridge("fs").appendFile(path, data);
1499
- },
1500
- async deleteFile(path) {
1501
- await requireBridge("fs").deleteFile(path);
1502
- },
1503
- async exists(path) {
1504
- return await requireBridge("fs").exists(path);
1505
- },
1506
- async stat(path) {
1507
- return normalizeStat(await requireBridge("fs").stat(path));
1508
- },
1509
- async readDir(path) {
1510
- const r = await requireBridge("fs").readDir(path);
1511
- const raw = r && r.entries || [];
1512
- const base = path.endsWith("/") ? path.slice(0, -1) : path;
1513
- return raw.map((e) => ({
1514
- name: e.name,
1515
- path: `${base}/${e.name}`,
1516
- isDirectory: !!e.isDirectory
1517
- }));
1518
- },
1519
- async mkdir(path, opts) {
1520
- await requireBridge("fs").mkdir(path, opts);
1521
- },
1522
- async rmdir(path, opts) {
1523
- await requireBridge("fs").rmdir(path, opts);
1524
- },
1525
- async watch(path, id) {
1526
- await requireBridge("fs").watch(path, id);
1527
- },
1528
- async unwatch(id) {
1529
- await requireBridge("fs").unwatch(id);
1530
- },
1531
- onChange(cb) {
1532
- return onCraftEvent("craft:fs:change", cb);
1533
- },
1534
- async watchTree(path, options, cb) {
1535
- const id = `watch-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
1536
- const bridge = requireBridge("fs");
1537
- await bridge.watch(path, id, { recursive: !!options.recursive });
1538
- const allowed = options.kinds && options.kinds.length > 0 ? new Set(options.kinds) : null;
1539
- const coalesceMs = Math.max(0, Math.floor(options.coalesceMs ?? 0));
1540
- let buffer = [];
1541
- let flushTimer = null;
1542
- let stopped = false;
1543
- const flush = () => {
1544
- flushTimer = null;
1545
- if (buffer.length === 0)
1546
- return;
1547
- const batch = buffer;
1548
- buffer = [];
1549
- try {
1550
- cb(batch);
1551
- } catch {}
1552
- };
1553
- const off = onCraftEvent("craft:fs:change", (e) => {
1554
- if (stopped)
1555
- return;
1556
- if (e.id !== id)
1557
- return;
1558
- if (allowed && e.kind && !allowed.has(e.kind))
1559
- return;
1560
- buffer.push(e);
1561
- if (coalesceMs === 0) {
1562
- flush();
1563
- return;
1564
- }
1565
- if (flushTimer == null) {
1566
- flushTimer = setTimeout(flush, coalesceMs);
1567
- }
1568
- });
1569
- return {
1570
- id,
1571
- async stop() {
1572
- if (stopped)
1573
- return;
1574
- stopped = true;
1575
- off();
1576
- if (flushTimer) {
1577
- clearTimeout(flushTimer);
1578
- flushTimer = null;
1579
- }
1580
- if (buffer.length > 0)
1581
- flush();
1582
- try {
1583
- await bridge.unwatch(id);
1584
- } catch {}
1585
- }
1586
- };
1587
- },
1588
- async homeDir() {
1589
- return await requireBridge("fs").homeDir();
1590
- },
1591
- async tempDir() {
1592
- return await requireBridge("fs").tempDir();
1593
- },
1594
- async appDataDir() {
1595
- return await requireBridge("fs").appDataDir();
1596
- }
1597
- };
1598
- function bytesToBase64(bytes) {
1599
- let s = "";
1600
- for (let i = 0;i < bytes.length; i++)
1601
- s += String.fromCharCode(bytes[i]);
1602
- return typeof btoa === "function" ? btoa(s) : Buffer.from(bytes).toString("base64");
1603
- }
1604
- function base64ToBytes(b64) {
1605
- const bin = typeof atob === "function" ? atob(b64) : Buffer.from(b64, "base64").toString("binary");
1606
- const out = new Uint8Array(bin.length);
1607
- for (let i = 0;i < bin.length; i++)
1608
- out[i] = bin.charCodeAt(i);
1609
- return out;
1610
- }
1611
- function normalizeStat(raw) {
1612
- return {
1613
- isFile: !!raw.isFile,
1614
- isDirectory: !!raw.isDirectory,
1615
- isSymlink: !!raw.isSymlink,
1616
- size: Number(raw.size) || 0,
1617
- modifiedAt: raw.modifiedAt != null ? raw.modifiedAt < 1000000000000 ? raw.modifiedAt * 1000 : raw.modifiedAt : 0
1618
- };
1619
- }
1620
- // src/global-shortcuts.ts
1621
- var globalShortcuts = {
1622
- async register(id, accelerator, opts) {
1623
- if (!hasBridge("shortcuts"))
1624
- return;
1625
- await window.craft.shortcuts.register(id, accelerator, opts);
1626
- },
1627
- async unregister(id) {
1628
- if (!hasBridge("shortcuts"))
1629
- return;
1630
- await window.craft.shortcuts.unregister(id);
1631
- },
1632
- async unregisterAll() {
1633
- if (!hasBridge("shortcuts"))
1634
- return;
1635
- await window.craft.shortcuts.unregisterAll();
1636
- },
1637
- async enable(id) {
1638
- if (!hasBridge("shortcuts"))
1639
- return;
1640
- await window.craft.shortcuts.enable(id);
1641
- },
1642
- async disable(id) {
1643
- if (!hasBridge("shortcuts"))
1644
- return;
1645
- await window.craft.shortcuts.disable(id);
1646
- },
1647
- async isRegistered(id) {
1648
- if (!hasBridge("shortcuts"))
1649
- return false;
1650
- return await window.craft.shortcuts.isRegistered(id);
1651
- },
1652
- async list() {
1653
- if (!hasBridge("shortcuts"))
1654
- return [];
1655
- return await window.craft.shortcuts.list();
1656
- },
1657
- on(cb) {
1658
- return onCraftEvent("craft:shortcut", cb);
1659
- }
1660
- };
1661
- // src/handoff.ts
1662
- var handoff = {
1663
- async startActivity(type, options) {
1664
- if (!type)
1665
- throw new Error("handoff.startActivity: type is required");
1666
- if (!hasBridge("handoff"))
1667
- return false;
1668
- const r = await window.craft.handoff.startActivity(type, options);
1669
- return typeof r === "boolean" ? r : !!(r && r.ok);
1670
- },
1671
- async updateActivity(options) {
1672
- if (!hasBridge("handoff"))
1673
- return false;
1674
- const r = await window.craft.handoff.updateActivity(options);
1675
- return typeof r === "boolean" ? r : !!(r && r.ok);
1676
- },
1677
- async stopActivity() {
1678
- if (!hasBridge("handoff"))
1679
- return;
1680
- await window.craft.handoff.stopActivity();
1681
- },
1682
- async getCurrentActivity() {
1683
- if (!hasBridge("handoff"))
1684
- return null;
1685
- const r = await window.craft.handoff.getCurrentActivity();
1686
- return r && typeof r.type === "string" ? r : null;
1687
- },
1688
- onIncoming(cb) {
1689
- return onCraftEvent("craft:handoff:incoming", cb);
1690
- }
1691
- };
1692
- // src/hotkeys.ts
1693
- var registrations = new Map;
1694
- var nextId = 0;
1695
- var documentListenerAttached = false;
1696
- function parseShortcut(shortcut) {
1697
- const parts = shortcut.split("+").map((p) => p.trim());
1698
- const result = {
1699
- key: "",
1700
- meta: false,
1701
- ctrl: false,
1702
- shift: false,
1703
- alt: false
1704
- };
1705
- for (const part of parts) {
1706
- const lower = part.toLowerCase();
1707
- switch (lower) {
1708
- case "cmd":
1709
- case "command":
1710
- case "meta":
1711
- case "\u2318":
1712
- result.meta = true;
1713
- break;
1714
- case "ctrl":
1715
- case "control":
1716
- case "\u2303":
1717
- result.ctrl = true;
1718
- break;
1719
- case "shift":
1720
- case "\u21E7":
1721
- result.shift = true;
1722
- break;
1723
- case "alt":
1724
- case "option":
1725
- case "opt":
1726
- case "\u2325":
1727
- result.alt = true;
1728
- break;
1729
- case "cmdorctrl":
1730
- case "commandorcontrol":
1731
- if (typeof process !== "undefined" && process.platform === "darwin") {
1732
- result.meta = true;
1733
- } else {
1734
- result.ctrl = true;
1735
- }
1736
- break;
1737
- default:
1738
- result.key = lower;
1739
- }
1740
- }
1741
- return result;
1742
- }
1743
- function formatShortcut(parsed) {
1744
- const parts = [];
1745
- if (parsed.ctrl)
1746
- parts.push("\u2303");
1747
- if (parsed.alt)
1748
- parts.push("\u2325");
1749
- if (parsed.shift)
1750
- parts.push("\u21E7");
1751
- if (parsed.meta)
1752
- parts.push("\u2318");
1753
- parts.push(parsed.key.toUpperCase());
1754
- return parts.join("");
1755
- }
1756
- function matchesEvent(event, parsed) {
1757
- if (parsed.meta !== event.metaKey)
1758
- return false;
1759
- if (parsed.ctrl !== event.ctrlKey)
1760
- return false;
1761
- if (parsed.shift !== event.shiftKey)
1762
- return false;
1763
- if (parsed.alt !== event.altKey)
1764
- return false;
1765
- const eventKey = event.key.toLowerCase();
1766
- return eventKey === parsed.key || event.code.toLowerCase() === `key${parsed.key}`;
1767
- }
1768
- function handleKeyDown(event) {
1769
- for (const [, reg] of registrations) {
1770
- if (matchesEvent(event, reg.parsed)) {
1771
- event.preventDefault();
1772
- event.stopPropagation();
1773
- try {
1774
- reg.handler();
1775
- } catch {}
1776
- break;
1777
- }
1778
- }
1779
- }
1780
- function ensureDocumentListener() {
1781
- if (documentListenerAttached)
1782
- return;
1783
- if (typeof document === "undefined")
1784
- return;
1785
- document.addEventListener("keydown", handleKeyDown, true);
1786
- documentListenerAttached = true;
1787
- }
1788
- function removeDocumentListener() {
1789
- if (!documentListenerAttached)
1790
- return;
1791
- if (typeof document === "undefined")
1792
- return;
1793
- document.removeEventListener("keydown", handleKeyDown, true);
1794
- documentListenerAttached = false;
1795
- }
1796
- async function registerWithCraft(id, shortcut) {
1797
- if (typeof window === "undefined")
1798
- return false;
1799
- const craft = window.craft;
1800
- if (!craft?.hotkeys?.register)
1801
- return false;
1802
- try {
1803
- await craft.hotkeys.register(id, shortcut);
1804
- return true;
1805
- } catch {
1806
- return false;
1807
- }
1808
- }
1809
- async function unregisterWithCraft(id) {
1810
- if (typeof window === "undefined")
1811
- return;
1812
- const craft = window.craft;
1813
- if (!craft?.hotkeys?.unregister)
1814
- return;
1815
- try {
1816
- await craft.hotkeys.unregister(id);
1817
- } catch {}
1818
- }
1819
- function registerHotkey(shortcut, handler) {
1820
- const id = `hotkey_${++nextId}_${Date.now()}`;
1821
- const parsed = parseShortcut(shortcut);
1822
- registrations.set(id, { shortcut, handler, parsed });
1823
- registerWithCraft(id, shortcut);
1824
- ensureDocumentListener();
1825
- const registration = {
1826
- shortcut,
1827
- id,
1828
- unregister() {
1829
- unregisterHotkey(registration);
1830
- }
1831
- };
1832
- return registration;
1833
- }
1834
- function unregisterHotkey(registration) {
1835
- registrations.delete(registration.id);
1836
- unregisterWithCraft(registration.id);
1837
- if (registrations.size === 0) {
1838
- removeDocumentListener();
1839
- }
1840
- }
1841
- function unregisterAllHotkeys() {
1842
- for (const [id] of registrations) {
1843
- unregisterWithCraft(id);
1844
- }
1845
- registrations.clear();
1846
- removeDocumentListener();
1847
- }
1848
- function getRegisteredHotkeys() {
1849
- return Array.from(registrations.entries()).map(([id, reg]) => ({
1850
- shortcut: reg.shortcut,
1851
- id,
1852
- unregister() {
1853
- registrations.delete(id);
1854
- unregisterWithCraft(id);
1855
- if (registrations.size === 0) {
1856
- removeDocumentListener();
1857
- }
1858
- }
1859
- }));
1860
- }
1861
- // src/iap.ts
1862
- var iap = {
1863
- async isAvailable() {
1864
- if (!hasBridge("iap"))
1865
- return false;
1866
- return await window.craft.iap.isAvailable();
1867
- },
1868
- async getProducts(ids) {
1869
- if (!hasBridge("iap"))
1870
- return [];
1871
- const arr = Array.isArray(ids) ? ids : [String(ids)];
1872
- return await window.craft.iap.getProducts(arr);
1873
- },
1874
- async purchase(productId) {
1875
- if (!hasBridge("iap"))
1876
- return { queued: false, productId, reason: "IAP bridge not available" };
1877
- const r = await window.craft.iap.purchase(productId);
1878
- return { queued: !!(r && r.queued), productId: r?.productId, reason: r?.reason };
1879
- },
1880
- async restorePurchases() {
1881
- if (!hasBridge("iap"))
1882
- return { ok: false };
1883
- const r = await window.craft.iap.restorePurchases();
1884
- return { ok: !!(r && r.ok) };
1885
- },
1886
- async finishTransaction(transactionId) {
1887
- if (!hasBridge("iap"))
1888
- return;
1889
- await window.craft.iap.finishTransaction(transactionId);
1890
- },
1891
- async getReceiptData() {
1892
- if (!hasBridge("iap"))
1893
- return null;
1894
- const r = await window.craft.iap.getReceiptData();
1895
- return r ? String(r) : null;
1896
- },
1897
- onPurchased(cb) {
1898
- return onCraftEvent("craft:iap:purchased", cb);
1899
- },
1900
- onFailed(cb) {
1901
- return onCraftEvent("craft:iap:failed", cb);
1902
- },
1903
- onRestored(cb) {
1904
- return onCraftEvent("craft:iap:restored", cb);
1905
- },
1906
- onProductsLoaded(cb) {
1907
- return onCraftEvent("craft:iap:productsLoaded", (e) => cb(e.products || []));
1908
- },
1909
- onRefunded(cb) {
1910
- return onCraftEvent("craft:iap:refunded", cb);
1911
- },
1912
- onSubscriptionStatusChanged(cb) {
1913
- return onCraftEvent("craft:iap:subscriptionStatusChanged", cb);
1914
- },
1915
- async getActiveSubscriptions() {
1916
- if (!hasBridge("iap"))
1917
- return [];
1918
- const fn = window.craft.iap.getActiveSubscriptions;
1919
- if (typeof fn !== "function")
1920
- return [];
1921
- const r = await fn();
1922
- return Array.isArray(r) ? r : [];
1923
- },
1924
- async isEligibleForIntroOffer(productId) {
1925
- if (!hasBridge("iap"))
1926
- return false;
1927
- const fn = window.craft.iap.isEligibleForIntroOffer;
1928
- if (typeof fn !== "function")
1929
- return false;
1930
- return !!await fn(productId);
1931
- }
1932
- };
1933
- // src/keychain.ts
1934
- var keychain = {
1935
- async set(service, account, password) {
1936
- if (!service)
1937
- throw new Error("keychain.set: service is required");
1938
- if (!account)
1939
- throw new Error("keychain.set: account is required");
1940
- await requireBridge("keychain").set(service, account, password);
1941
- },
1942
- async get(service, account) {
1943
- if (!service)
1944
- throw new Error("keychain.get: service is required");
1945
- if (!account)
1946
- throw new Error("keychain.get: account is required");
1947
- const v = await requireBridge("keychain").get(service, account);
1948
- return typeof v === "string" ? v : null;
1949
- },
1950
- async delete(service, account) {
1951
- if (!service)
1952
- throw new Error("keychain.delete: service is required");
1953
- if (!account)
1954
- throw new Error("keychain.delete: account is required");
1955
- await requireBridge("keychain").delete(service, account);
1956
- },
1957
- async has(service, account) {
1958
- if (!service)
1959
- throw new Error("keychain.has: service is required");
1960
- if (!account)
1961
- throw new Error("keychain.has: account is required");
1962
- return await requireBridge("keychain").has(service, account);
1963
- }
1964
- };
1965
- // src/live-activities.ts
1966
- var liveActivities = {
1967
- async start(type, state) {
1968
- return handoff.startActivity(type, {
1969
- title: state?.title,
1970
- webpageURL: state?.webpageURL,
1971
- userInfo: state?.state
1972
- });
1973
- },
1974
- async update(state) {
1975
- return handoff.updateActivity({
1976
- title: state.title,
1977
- webpageURL: state.webpageURL,
1978
- userInfo: state.state
1979
- });
1980
- },
1981
- async stop() {
1982
- await handoff.stopActivity();
1983
- }
1984
- };
1985
- // src/local-server.ts
1986
- var localServer = {
1987
- async start(port = 0, host = "127.0.0.1") {
1988
- if (!hasBridge("localServer"))
1989
- return { port: 0, started: false, reason: "bridge unavailable" };
1990
- return await window.craft.localServer.start(port, host);
1991
- },
1992
- async stop() {
1993
- if (!hasBridge("localServer"))
1994
- return;
1995
- await window.craft.localServer.stop();
1996
- },
1997
- async respond(options) {
1998
- if (!hasBridge("localServer"))
1999
- return;
2000
- await window.craft.localServer.respond(options || { status: 200, body: "OK" });
2001
- },
2002
- onRequest(cb) {
2003
- return onCraftEvent("craft:localServer:request", cb);
2004
- },
2005
- async awaitOAuthCallback(options = {}) {
2006
- requireBridge("localServer");
2007
- const { port: requestedPort = 0, host = "127.0.0.1", timeoutMs = 5 * 60 * 1000, successHTML } = options;
2008
- const start = await this.start(requestedPort, host);
2009
- if (!start.started)
2010
- throw new Error(`localServer: start failed${start.reason ? ` \u2014 ${start.reason}` : ""}`);
2011
- return new Promise((resolve, reject) => {
2012
- const timer = setTimeout(() => {
2013
- off();
2014
- this.stop().catch(() => {});
2015
- reject(new Error("localServer: OAuth callback timed out"));
2016
- }, timeoutMs);
2017
- const off = this.onRequest(({ url }) => {
2018
- clearTimeout(timer);
2019
- off();
2020
- const body = successHTML ?? `<!doctype html><meta charset="utf-8"><title>Done</title>
2021
- <style>body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#f5f5f7}</style>
2022
- <div><h1>You can close this tab.</h1><p>Returning to the app\u2026</p></div>
2023
- <script>setTimeout(()=>window.close(),1500)</script>`;
2024
- this.respond({ status: 200, body, contentType: "text/html; charset=utf-8" }).catch(() => {}).finally(() => this.stop().catch(() => {}));
2025
- resolve({ url, port: start.port });
2026
- });
2027
- });
2028
- }
2029
- };
2030
- // src/location.ts
2031
- var location = {
2032
- async requestPermission(mode = "whenInUse") {
2033
- if (hasBridge("location"))
2034
- return await window.craft.location.requestPermission(mode);
2035
- if (typeof navigator !== "undefined" && navigator.geolocation) {
2036
- return "undetermined";
2037
- }
2038
- return "not-supported";
2039
- },
2040
- async getAuthorization() {
2041
- if (hasBridge("location"))
2042
- return await window.craft.location.getAuthorization();
2043
- return "unknown";
2044
- },
2045
- async getCurrentLocation() {
2046
- if (hasBridge("location"))
2047
- return await window.craft.location.getCurrentLocation();
2048
- if (typeof navigator !== "undefined" && navigator.geolocation) {
2049
- navigator.geolocation.getCurrentPosition((pos) => {
2050
- window.dispatchEvent(new CustomEvent("craft:location:update", {
2051
- detail: {
2052
- latitude: pos.coords.latitude,
2053
- longitude: pos.coords.longitude,
2054
- altitude: pos.coords.altitude,
2055
- horizontalAccuracy: pos.coords.accuracy,
2056
- verticalAccuracy: pos.coords.altitudeAccuracy,
2057
- speed: pos.coords.speed
2058
- }
2059
- }));
2060
- }, (err) => {
2061
- window.dispatchEvent(new CustomEvent("craft:location:error", {
2062
- detail: { message: err.message || String(err) }
2063
- }));
2064
- });
2065
- return { requested: true };
2066
- }
2067
- return { requested: false };
2068
- },
2069
- async startWatching(options) {
2070
- if (hasBridge("location"))
2071
- return await window.craft.location.startWatching(options);
2072
- if (typeof navigator !== "undefined" && navigator.geolocation) {
2073
- const watchId = navigator.geolocation.watchPosition((pos) => {
2074
- window.dispatchEvent(new CustomEvent("craft:location:update", {
2075
- detail: {
2076
- latitude: pos.coords.latitude,
2077
- longitude: pos.coords.longitude,
2078
- altitude: pos.coords.altitude,
2079
- horizontalAccuracy: pos.coords.accuracy,
2080
- verticalAccuracy: pos.coords.altitudeAccuracy,
2081
- speed: pos.coords.speed
2082
- }
2083
- }));
2084
- });
2085
- window.__craftWebLocationWatchId = watchId;
2086
- return true;
2087
- }
2088
- return false;
2089
- },
2090
- async stopWatching() {
2091
- if (hasBridge("location")) {
2092
- await window.craft.location.stopWatching();
2093
- return;
2094
- }
2095
- if (typeof navigator !== "undefined" && navigator.geolocation) {
2096
- const id = window.__craftWebLocationWatchId;
2097
- if (id != null) {
2098
- navigator.geolocation.clearWatch(id);
2099
- window.__craftWebLocationWatchId = null;
2100
- }
2101
- }
2102
- },
2103
- onUpdate(cb) {
2104
- return onCraftEvent("craft:location:update", cb);
2105
- },
2106
- onError(cb) {
2107
- return onCraftEvent("craft:location:error", cb);
2108
- },
2109
- onAuthChanged(cb) {
2110
- return onCraftEvent("craft:location:authChanged", cb);
2111
- }
2112
- };
2113
- // src/log.ts
2114
- var log = {
2115
- async debug(m) {
2116
- if (hasBridge("log"))
2117
- await window.craft.log.debug(m);
2118
- else
2119
- console.debug(m);
2120
- },
2121
- async info(m) {
2122
- if (hasBridge("log"))
2123
- await window.craft.log.info(m);
2124
- else
2125
- console.info(m);
2126
- },
2127
- async warn(m) {
2128
- if (hasBridge("log"))
2129
- await window.craft.log.warn(m);
2130
- else
2131
- console.warn(m);
2132
- },
2133
- async error(m) {
2134
- if (hasBridge("log"))
2135
- await window.craft.log.error(m);
2136
- else
2137
- console.error(m);
2138
- }
2139
- };
2140
- // src/menu.ts
2141
- var menu = {
2142
- async set(items) {
2143
- if (hasBridge("menu"))
2144
- await window.craft.menu.set(items);
2145
- },
2146
- async setDock(items) {
2147
- if (hasBridge("menu"))
2148
- await window.craft.menu.setDock(items);
2149
- },
2150
- async addItem(parent, item) {
2151
- if (hasBridge("menu"))
2152
- await window.craft.menu.addItem(parent, item);
2153
- },
2154
- async removeItem(id) {
2155
- if (hasBridge("menu"))
2156
- await window.craft.menu.removeItem(id);
2157
- },
2158
- async enableItem(id) {
2159
- if (hasBridge("menu"))
2160
- await window.craft.menu.enableItem(id);
2161
- },
2162
- async disableItem(id) {
2163
- if (hasBridge("menu"))
2164
- await window.craft.menu.disableItem(id);
2165
- },
2166
- async checkItem(id) {
2167
- if (hasBridge("menu"))
2168
- await window.craft.menu.checkItem(id);
2169
- },
2170
- async uncheckItem(id) {
2171
- if (hasBridge("menu"))
2172
- await window.craft.menu.uncheckItem(id);
2173
- },
2174
- async setItemLabel(id, lbl) {
2175
- if (hasBridge("menu"))
2176
- await window.craft.menu.setItemLabel(id, lbl);
2177
- },
2178
- async clearDock() {
2179
- if (hasBridge("menu"))
2180
- await window.craft.menu.clearDock();
2181
- },
2182
- onAction(cb) {
2183
- return onCraftEvent("craft:menu:action", cb);
2184
- }
2185
- };
2186
- // src/midi.ts
2187
- var midi = {
2188
- async listSources() {
2189
- if (!hasBridge("midi"))
2190
- return [];
2191
- return await window.craft.midi.listSources();
2192
- },
2193
- async listDestinations() {
2194
- if (!hasBridge("midi"))
2195
- return [];
2196
- return await window.craft.midi.listDestinations();
2197
- },
2198
- async send(destinationIndex, data) {
2199
- if (!hasBridge("midi"))
2200
- return { ok: false, reason: "bridge unavailable" };
2201
- return await window.craft.midi.send(destinationIndex, data);
2202
- },
2203
- async subscribe(sourceIndex) {
2204
- if (!hasBridge("midi"))
2205
- return { ok: false, reason: "bridge unavailable" };
2206
- return await window.craft.midi.subscribe(sourceIndex);
2207
- },
2208
- async unsubscribe(sourceIndex) {
2209
- if (!hasBridge("midi"))
2210
- return { ok: false };
2211
- return await window.craft.midi.unsubscribe(sourceIndex);
2212
- },
2213
- onMessage(cb) {
2214
- return onCraftEvent("craft:midi:message", cb);
2215
- }
2216
- };
2217
- // src/modals.ts
2218
- function hasNativeDialogSupport() {
2219
- return false;
2220
- }
2221
- function isBrowser2() {
2222
- return typeof window !== "undefined" && typeof document !== "undefined";
2223
- }
2224
- var activeModals = [];
2225
- function generateModalId() {
2226
- return `modal-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
2227
- }
2228
- function getModalIcon(type) {
2229
- switch (type) {
2230
- case "info":
2231
- return "&#x2139;";
2232
- case "warning":
2233
- return "&#x26A0;";
2234
- case "error":
2235
- return "&#x2716;";
2236
- case "success":
2237
- return "&#x2714;";
2238
- case "question":
2239
- return "&#x2753;";
2240
- default:
2241
- return "&#x2139;";
2242
- }
2243
- }
2244
- function getDefaultButtons(type) {
2245
- if (type === "question") {
2246
- return [
2247
- { label: "No", style: "default" },
2248
- { label: "Yes", style: "primary" }
2249
- ];
2250
- }
2251
- return [{ label: "OK", style: "primary" }];
2252
- }
2253
- function createModalHTML(state) {
2254
- const { options } = state;
2255
- const icon = getModalIcon(options.type);
2256
- const buttons = options.buttons || getDefaultButtons(options.type);
2257
- const typeClass = options.type || "info";
2258
- let buttonsHtml = "";
2259
- buttons.forEach((btn, index) => {
2260
- const styleClass = btn.style === "destructive" ? "destructive" : btn.style === "primary" ? "primary" : "default";
2261
- const autoFocus = index === (options.defaultButton ?? buttons.length - 1) ? "autofocus" : "";
2262
- buttonsHtml += `<button class="stx-modal-btn ${styleClass}" data-index="${index}" ${autoFocus}>${btn.label}</button>`;
2263
- });
2264
- return `
2265
- <div class="stx-modal-overlay" data-modal-id="${state.id}">
2266
- <div class="stx-modal ${typeClass}" role="dialog" aria-modal="true" aria-labelledby="${state.id}-title">
2267
- <div class="stx-modal-icon">${icon}</div>
2268
- <div class="stx-modal-content">
2269
- ${options.title ? `<h2 id="${state.id}-title" class="stx-modal-title">${escapeHtml2(options.title)}</h2>` : ""}
2270
- <p class="stx-modal-message">${escapeHtml2(options.message)}</p>
2271
- </div>
2272
- <div class="stx-modal-buttons">${buttonsHtml}</div>
2273
- </div>
2274
- </div>
2275
- `;
2276
- }
2277
- function escapeHtml2(str) {
2278
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
2279
- }
2280
- function closeModal(state, buttonIndex, cancelled = false) {
2281
- const index = activeModals.indexOf(state);
2282
- if (index > -1) {
2283
- activeModals.splice(index, 1);
2284
- }
2285
- if (state.element && isBrowser2()) {
2286
- state.element.remove();
2287
- }
2288
- const buttons = state.options.buttons || getDefaultButtons(state.options.type);
2289
- const button = buttons[buttonIndex];
2290
- if (button?.action) {
2291
- button.action();
2292
- }
2293
- state.resolve({ buttonIndex, cancelled });
2294
- }
2295
- async function showModal(options) {
2296
- const hasNative = hasNativeDialogSupport();
2297
- const id = generateModalId();
2298
- return new Promise((resolve) => {
2299
- const state = {
2300
- id,
2301
- options,
2302
- resolve
2303
- };
2304
- activeModals.push(state);
2305
- if (hasNative) {
2306
- console.log(`[stx-modal] Showing native modal: ${options.title || "Modal"}`);
2307
- setTimeout(() => {
2308
- closeModal(state, options.defaultButton ?? 0, false);
2309
- }, 0);
2310
- } else if (isBrowser2()) {
2311
- try {
2312
- const container = document.createElement("div");
2313
- container.innerHTML = createModalHTML(state);
2314
- const overlay = container.firstElementChild;
2315
- if (!overlay) {
2316
- console.log(`[stx-modal] ${options.type?.toUpperCase() || "INFO"}: ${options.title || "Modal"}`);
2317
- console.log(`[stx-modal] ${options.message}`);
2318
- setTimeout(() => {
2319
- closeModal(state, options.defaultButton ?? 0, false);
2320
- }, 0);
2321
- return;
2322
- }
2323
- state.element = overlay;
2324
- try {
2325
- document.body.appendChild(overlay);
2326
- } catch {
2327
- console.log(`[stx-modal] ${options.type?.toUpperCase() || "INFO"}: ${options.title || "Modal"}`);
2328
- console.log(`[stx-modal] ${options.message}`);
2329
- setTimeout(() => {
2330
- closeModal(state, options.defaultButton ?? 0, false);
2331
- }, 0);
2332
- return;
2333
- }
2334
- try {
2335
- overlay.querySelectorAll(".stx-modal-btn").forEach((btn) => {
2336
- btn.addEventListener("click", () => {
2337
- const index = Number.parseInt(btn.dataset.index || "0", 10);
2338
- closeModal(state, index, false);
2339
- });
2340
- });
2341
- } catch {}
2342
- try {
2343
- overlay.addEventListener("click", (e) => {
2344
- if (e.target === overlay) {
2345
- const cancelIndex = options.cancelButton ?? 0;
2346
- closeModal(state, cancelIndex, true);
2347
- }
2348
- });
2349
- } catch {}
2350
- try {
2351
- const handleKeydown = (e) => {
2352
- if (e.key === "Escape") {
2353
- const cancelIndex = options.cancelButton ?? 0;
2354
- closeModal(state, cancelIndex, true);
2355
- document.removeEventListener("keydown", handleKeydown);
2356
- } else if (e.key === "Enter") {
2357
- const defaultIndex = options.defaultButton ?? (options.buttons || getDefaultButtons(options.type)).length - 1;
2358
- closeModal(state, defaultIndex, false);
2359
- document.removeEventListener("keydown", handleKeydown);
2360
- }
2361
- };
2362
- document.addEventListener("keydown", handleKeydown);
2363
- } catch {}
2364
- try {
2365
- const firstButton = overlay.querySelector(".stx-modal-btn[autofocus]");
2366
- if (firstButton) {
2367
- firstButton.focus();
2368
- }
2369
- } catch {}
2370
- if (typeof process !== "undefined" && process.env.BUN_TEST) {
2371
- setTimeout(() => {
2372
- closeModal(state, options.defaultButton ?? 0, false);
2373
- }, 0);
2374
- }
2375
- } catch {
2376
- console.log(`[stx-modal] ${options.type?.toUpperCase() || "INFO"}: ${options.title || "Modal"}`);
2377
- console.log(`[stx-modal] ${options.message}`);
2378
- setTimeout(() => {
2379
- closeModal(state, options.defaultButton ?? 0, false);
2380
- }, 0);
2381
- }
2382
- } else {
2383
- console.log(`[stx-modal] ${options.type?.toUpperCase() || "INFO"}: ${options.title || "Modal"}`);
2384
- console.log(`[stx-modal] ${options.message}`);
2385
- const buttons = options.buttons || getDefaultButtons(options.type);
2386
- console.log(`[stx-modal] Buttons: ${buttons.map((b) => b.label).join(", ")}`);
2387
- setTimeout(() => {
2388
- closeModal(state, options.defaultButton ?? 0, false);
2389
- }, 0);
2390
- }
2391
- });
2392
- }
2393
- async function showInfoModal(title, message) {
2394
- return showModal({ title, message, type: "info" });
2395
- }
2396
- async function showWarningModal(title, message) {
2397
- return showModal({ title, message, type: "warning" });
2398
- }
2399
- async function showErrorModal(title, message) {
2400
- return showModal({ title, message, type: "error" });
2401
- }
2402
- async function showSuccessModal(title, message) {
2403
- return showModal({ title, message, type: "success" });
2404
- }
2405
- async function showQuestionModal(title, message) {
2406
- return showModal({
2407
- title,
2408
- message,
2409
- type: "question",
2410
- buttons: [
2411
- { label: "No", style: "default" },
2412
- { label: "Yes", style: "primary" }
2413
- ],
2414
- defaultButton: 1,
2415
- cancelButton: 0
2416
- });
2417
- }
2418
- async function confirm2(message, title = "Confirm") {
2419
- const result = await showQuestionModal(title, message);
2420
- return result.buttonIndex === 1;
2421
- }
2422
- async function alert2(message, title = "Alert") {
2423
- await showInfoModal(title, message);
2424
- }
2425
- async function prompt2(message, defaultValue = "", title = "Input") {
2426
- if (isBrowser2() && typeof window.prompt === "function") {
2427
- return window.prompt(message, defaultValue);
2428
- }
2429
- console.log(`[stx-modal] PROMPT: ${title}`);
2430
- console.log(`[stx-modal] ${message}`);
2431
- console.log(`[stx-modal] Default: ${defaultValue}`);
2432
- return defaultValue;
2433
- }
2434
- function getActiveModalCount() {
2435
- return activeModals.length;
2436
- }
2437
- function closeAllModals() {
2438
- while (activeModals.length > 0) {
2439
- const state = activeModals[activeModals.length - 1];
2440
- closeModal(state, 0, true);
2441
- }
2442
- }
2443
- var MODAL_STYLES = `
2444
- .stx-modal-overlay {
2445
- position: fixed;
2446
- inset: 0;
2447
- background: rgba(0, 0, 0, 0.5);
2448
- display: flex;
2449
- align-items: center;
2450
- justify-content: center;
2451
- z-index: 10000;
2452
- animation: stx-modal-fade-in 0.15s ease-out;
2453
- }
2454
-
2455
- @keyframes stx-modal-fade-in {
2456
- from { opacity: 0; }
2457
- to { opacity: 1; }
2458
- }
2459
-
2460
- .stx-modal {
2461
- background: #fff;
2462
- border-radius: 12px;
2463
- padding: 24px;
2464
- max-width: 400px;
2465
- width: 90%;
2466
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
2467
- animation: stx-modal-slide-up 0.2s ease-out;
2468
- }
2469
-
2470
- @keyframes stx-modal-slide-up {
2471
- from { transform: translateY(20px); opacity: 0; }
2472
- to { transform: translateY(0); opacity: 1; }
2473
- }
2474
-
2475
- @media (prefers-color-scheme: dark) {
2476
- .stx-modal {
2477
- background: #2d2d2d;
2478
- color: #fff;
2479
- }
2480
- }
2481
-
2482
- .stx-modal-icon {
2483
- font-size: 48px;
2484
- text-align: center;
2485
- margin-bottom: 16px;
2486
- }
2487
-
2488
- .stx-modal.info .stx-modal-icon { color: #3498db; }
2489
- .stx-modal.warning .stx-modal-icon { color: #f39c12; }
2490
- .stx-modal.error .stx-modal-icon { color: #e74c3c; }
2491
- .stx-modal.success .stx-modal-icon { color: #27ae60; }
2492
- .stx-modal.question .stx-modal-icon { color: #9b59b6; }
2493
-
2494
- .stx-modal-content {
2495
- text-align: center;
2496
- margin-bottom: 24px;
2497
- }
2498
-
2499
- .stx-modal-title {
2500
- margin: 0 0 8px;
2501
- font-size: 20px;
2502
- font-weight: 600;
2503
- }
2504
-
2505
- .stx-modal-message {
2506
- margin: 0;
2507
- color: #666;
2508
- line-height: 1.5;
2509
- }
2510
-
2511
- @media (prefers-color-scheme: dark) {
2512
- .stx-modal-message { color: #aaa; }
2513
- }
2514
-
2515
- .stx-modal-buttons {
2516
- display: flex;
2517
- gap: 8px;
2518
- justify-content: center;
2519
- }
2520
-
2521
- .stx-modal-btn {
2522
- padding: 10px 24px;
2523
- border-radius: 6px;
2524
- font-size: 14px;
2525
- font-weight: 500;
2526
- cursor: pointer;
2527
- border: none;
2528
- transition: background 0.15s, transform 0.1s;
2529
- }
2530
-
2531
- .stx-modal-btn:hover {
2532
- transform: translateY(-1px);
2533
- }
2534
-
2535
- .stx-modal-btn:active {
2536
- transform: translateY(0);
2537
- }
2538
-
2539
- .stx-modal-btn.default {
2540
- background: #e0e0e0;
2541
- color: #333;
2542
- }
2543
-
2544
- .stx-modal-btn.default:hover {
2545
- background: #d0d0d0;
2546
- }
2547
-
2548
- .stx-modal-btn.primary {
2549
- background: #3498db;
2550
- color: #fff;
2551
- }
2552
-
2553
- .stx-modal-btn.primary:hover {
2554
- background: #2980b9;
2555
- }
2556
-
2557
- .stx-modal-btn.destructive {
2558
- background: #e74c3c;
2559
- color: #fff;
2560
- }
2561
-
2562
- .stx-modal-btn.destructive:hover {
2563
- background: #c0392b;
2564
- }
2565
-
2566
- @media (prefers-color-scheme: dark) {
2567
- .stx-modal-btn.default {
2568
- background: #444;
2569
- color: #fff;
2570
- }
2571
- .stx-modal-btn.default:hover {
2572
- background: #555;
2573
- }
2574
- }
2575
- `;
2576
- // src/native-autolaunch.ts
2577
- var nativeAutoLaunch = {
2578
- async enable() {
2579
- if (!hasBridge("autoLaunch"))
2580
- return false;
2581
- return await window.craft.autoLaunch.enable();
2582
- },
2583
- async disable() {
2584
- if (!hasBridge("autoLaunch"))
2585
- return false;
2586
- return await window.craft.autoLaunch.disable();
2587
- },
2588
- async isEnabled() {
2589
- if (!hasBridge("autoLaunch"))
2590
- return false;
2591
- return await window.craft.autoLaunch.isEnabled();
2592
- }
2593
- };
2594
- // src/network.ts
2595
- var network = {
2596
- async connectionType() {
2597
- if (hasBridge("network"))
2598
- return await window.craft.network.connectionType();
2599
- return webConnectionType();
2600
- },
2601
- async wifiSSID() {
2602
- if (hasBridge("network")) {
2603
- const v = await window.craft.network.wifiSSID();
2604
- return v || undefined;
2605
- }
2606
- return;
2607
- },
2608
- async wifiSignalStrength() {
2609
- if (hasBridge("network")) {
2610
- const v = await window.craft.network.wifiSignalStrength();
2611
- return typeof v === "number" ? v : undefined;
2612
- }
2613
- return;
2614
- },
2615
- async ipAddress() {
2616
- if (hasBridge("network"))
2617
- return await window.craft.network.ipAddress();
2618
- return "";
2619
- },
2620
- async macAddress() {
2621
- if (hasBridge("network"))
2622
- return await window.craft.network.macAddress();
2623
- return "";
2624
- },
2625
- async interfaces() {
2626
- if (hasBridge("network"))
2627
- return await window.craft.network.interfaces();
2628
- return [];
2629
- },
2630
- async isVPNConnected() {
2631
- if (hasBridge("network"))
2632
- return await window.craft.network.isVPNConnected();
2633
- return false;
2634
- },
2635
- async proxySettings() {
2636
- if (hasBridge("network")) {
2637
- const r = await window.craft.network.proxySettings();
2638
- return r || {};
2639
- }
2640
- return {};
2641
- },
2642
- async openPreferences() {
2643
- if (hasBridge("network"))
2644
- await window.craft.network.openPreferences();
2645
- },
2646
- onChange(cb) {
2647
- if (hasBridge("network")) {
2648
- return onCraftEvent("craft:networkChange", cb);
2649
- }
2650
- if (typeof window === "undefined")
2651
- return () => {};
2652
- const onlineH = () => cb({ type: webConnectionType(), online: true });
2653
- const offlineH = () => cb({ type: "none", online: false });
2654
- window.addEventListener("online", onlineH);
2655
- window.addEventListener("offline", offlineH);
2656
- return () => {
2657
- window.removeEventListener("online", onlineH);
2658
- window.removeEventListener("offline", offlineH);
2659
- };
2660
- }
2661
- };
2662
- function webConnectionType() {
2663
- if (typeof navigator === "undefined")
2664
- return "unknown";
2665
- if (navigator.onLine === false)
2666
- return "none";
2667
- const conn = navigator.connection;
2668
- if (!conn)
2669
- return "unknown";
2670
- const t = String(conn.type || conn.effectiveType || "unknown").toLowerCase();
2671
- if (t === "wifi" || t === "cellular" || t === "ethernet" || t === "bluetooth" || t === "none")
2672
- return t;
2673
- return "unknown";
2674
- }
2675
- // src/notifications.ts
2676
- var notifications = {
2677
- async show(options) {
2678
- if (!options.title)
2679
- throw new Error("notification title is required");
2680
- if (hasBridge("notifications")) {
2681
- await window.craft.notifications.show(options);
2682
- return;
2683
- }
2684
- if (typeof window !== "undefined" && "Notification" in window) {
2685
- const N = window.Notification;
2686
- if (N.permission === "granted") {
2687
- new N(options.title, { body: options.body, icon: options.icon });
2688
- } else if (N.permission === "default") {
2689
- const granted = await N.requestPermission() === "granted";
2690
- if (granted)
2691
- new N(options.title, { body: options.body, icon: options.icon });
2692
- }
2693
- }
2694
- },
2695
- async schedule(options) {
2696
- if (hasBridge("notifications")) {
2697
- const o = { ...options };
2698
- if (o.triggerAt instanceof Date)
2699
- o.triggerAt = o.triggerAt.toISOString();
2700
- await window.craft.notifications.schedule(o);
2701
- return;
2702
- }
2703
- const fireAt = toEpochMs(options.triggerAt);
2704
- const delay = Math.max(0, fireAt - Date.now());
2705
- setTimeout(() => {
2706
- this.show(options).catch(() => {});
2707
- }, delay);
2708
- },
2709
- async cancel(id) {
2710
- if (hasBridge("notifications")) {
2711
- await window.craft.notifications.cancel(id);
2712
- }
2713
- },
2714
- async cancelAll() {
2715
- if (hasBridge("notifications")) {
2716
- await window.craft.notifications.cancelAll();
2717
- }
2718
- },
2719
- async setBadge(n) {
2720
- const safe = Math.max(0, Math.round(Number.isFinite(n) ? n : 0));
2721
- if (hasBridge("notifications")) {
2722
- await window.craft.notifications.setBadge(safe);
2723
- return;
2724
- }
2725
- if (typeof navigator !== "undefined" && navigator.setAppBadge) {
2726
- try {
2727
- await navigator.setAppBadge(safe);
2728
- } catch {}
2729
- }
2730
- },
2731
- async clearBadge() {
2732
- if (hasBridge("notifications")) {
2733
- await window.craft.notifications.clearBadge();
2734
- return;
2735
- }
2736
- if (typeof navigator !== "undefined" && navigator.clearAppBadge) {
2737
- try {
2738
- await navigator.clearAppBadge();
2739
- } catch {}
2740
- }
2741
- },
2742
- async requestPermission() {
2743
- if (hasBridge("notifications")) {
2744
- return await window.craft.notifications.requestPermission();
2745
- }
2746
- if (typeof window !== "undefined" && "Notification" in window) {
2747
- const N = window.Notification;
2748
- if (N.permission === "granted")
2749
- return true;
2750
- if (N.permission === "denied")
2751
- return false;
2752
- const result = await N.requestPermission();
2753
- return result === "granted";
2754
- }
2755
- return false;
2756
- },
2757
- async registerCategories(categories) {
2758
- if (!Array.isArray(categories) || categories.length === 0)
2759
- return;
2760
- if (hasBridge("notifications")) {
2761
- const fn = window.craft.notifications.registerCategories;
2762
- if (typeof fn === "function")
2763
- await fn(categories);
2764
- }
2765
- },
2766
- onActionClicked(cb) {
2767
- return onCraftEvent("craft:notification:actionClicked", cb);
2768
- },
2769
- onReply(cb) {
2770
- return onCraftEvent("craft:notification:reply", cb);
2771
- }
2772
- };
2773
- function toEpochMs(t) {
2774
- if (t == null)
2775
- return Date.now();
2776
- if (t instanceof Date)
2777
- return t.getTime();
2778
- if (typeof t === "number")
2779
- return t;
2780
- const parsed = Date.parse(t);
2781
- return Number.isNaN(parsed) ? Date.now() : parsed;
2782
- }
2783
- // src/pdf.ts
2784
- var pdf = {
2785
- async countPages(path) {
2786
- if (!path)
2787
- throw new Error("pdf.countPages: path is required");
2788
- if (!hasBridge("pdf"))
2789
- return 0;
2790
- return await window.craft.pdf.countPages(path);
2791
- },
2792
- async extractText(path) {
2793
- if (!path)
2794
- throw new Error("pdf.extractText: path is required");
2795
- if (!hasBridge("pdf"))
2796
- return "";
2797
- return await window.craft.pdf.extractText(path);
2798
- }
2799
- };
2800
- // src/permissions.ts
2801
- var permissions = {
2802
- async check(name) {
2803
- if (hasBridge("permissions"))
2804
- return await window.craft.permissions.check(name);
2805
- return await webCheck(name);
2806
- },
2807
- async request(name) {
2808
- if (hasBridge("permissions"))
2809
- return await window.craft.permissions.request(name);
2810
- return await webRequest(name);
2811
- },
2812
- async openSettings(name) {
2813
- if (hasBridge("permissions"))
2814
- await window.craft.permissions.openSettings(name);
2815
- }
2816
- };
2817
- async function webCheck(name) {
2818
- if (typeof navigator === "undefined" || !navigator.permissions?.query)
2819
- return "not-supported";
2820
- try {
2821
- const result = await navigator.permissions.query({ name });
2822
- return mapWebState(result.state);
2823
- } catch {
2824
- return "not-supported";
2825
- }
2826
- }
2827
- async function webRequest(name) {
2828
- if (name === "notifications" && typeof window !== "undefined" && "Notification" in window) {
2829
- const r = await window.Notification.requestPermission();
2830
- return r === "granted" ? "granted" : r === "denied" ? "denied" : "undetermined";
2831
- }
2832
- return await webCheck(name);
2833
- }
2834
- function mapWebState(s) {
2835
- if (s === "granted")
2836
- return "granted";
2837
- if (s === "denied")
2838
- return "denied";
2839
- if (s === "prompt")
2840
- return "undetermined";
2841
- return "undetermined";
2842
- }
2843
- // src/power.ts
2844
- var currentProcess = null;
2845
- var currentInstance = null;
2846
-
2847
- class CaffeinateInstanceImpl {
2848
- _process;
2849
- _startedAt;
2850
- _endsAt;
2851
- _options;
2852
- _expireHandlers = [];
2853
- _expireTimer = null;
2854
- _stopped = false;
2855
- constructor(process2, options) {
2856
- this._process = process2;
2857
- this._options = options;
2858
- this._startedAt = new Date;
2859
- const duration = options.duration;
2860
- if (duration && duration > 0) {
2861
- const durationMs = duration * 60 * 1000;
2862
- this._endsAt = new Date(this._startedAt.getTime() + durationMs);
2863
- this._expireTimer = setTimeout(() => {
2864
- this._stopped = true;
2865
- for (const handler of this._expireHandlers) {
2866
- try {
2867
- handler();
2868
- } catch {}
2869
- }
2870
- }, durationMs);
2871
- } else {
2872
- this._endsAt = null;
2873
- }
2874
- }
2875
- get pid() {
2876
- return this._process.pid;
2877
- }
2878
- get startedAt() {
2879
- return this._startedAt;
2880
- }
2881
- get endsAt() {
2882
- return this._endsAt;
2883
- }
2884
- get options() {
2885
- return { ...this._options };
2886
- }
2887
- get isActive() {
2888
- if (this._stopped)
2889
- return false;
2890
- return this._process.exitCode === null;
2891
- }
2892
- get remainingMs() {
2893
- if (!this._endsAt)
2894
- return null;
2895
- const remaining = this._endsAt.getTime() - Date.now();
2896
- return Math.max(0, remaining);
2897
- }
2898
- get elapsedMs() {
2899
- return Date.now() - this._startedAt.getTime();
2900
- }
2901
- stop() {
2902
- if (this._stopped)
2903
- return;
2904
- this._stopped = true;
2905
- if (this._expireTimer) {
2906
- clearTimeout(this._expireTimer);
2907
- this._expireTimer = null;
2908
- }
2909
- try {
2910
- this._process.kill();
2911
- } catch {}
2912
- }
2913
- onExpire(handler) {
2914
- this._expireHandlers.push(handler);
2915
- }
2916
- }
2917
- function caffeinate(options = {}) {
2918
- decaffeinate();
2919
- const {
2920
- duration,
2921
- preventDisplaySleep = true,
2922
- preventIdleSleep = true,
2923
- preventSystemSleep = true,
2924
- preventDiskSleep = false,
2925
- assertUserActivity = true
2926
- } = options;
2927
- const flags = [];
2928
- if (preventDisplaySleep)
2929
- flags.push("-d");
2930
- if (preventIdleSleep)
2931
- flags.push("-i");
2932
- if (preventSystemSleep)
2933
- flags.push("-s");
2934
- if (preventDiskSleep)
2935
- flags.push("-m");
2936
- if (assertUserActivity)
2937
- flags.push("-u");
2938
- const args = [...flags];
2939
- if (duration && duration > 0) {
2940
- args.push("-t", String(duration * 60));
2941
- }
2942
- const proc = Bun.spawn(["/usr/bin/caffeinate", ...args], {
2943
- stdio: ["ignore", "ignore", "ignore"]
2944
- });
2945
- const instance = new CaffeinateInstanceImpl(proc, options);
2946
- currentProcess = proc;
2947
- currentInstance = instance;
2948
- return instance;
2949
- }
2950
- function decaffeinate(instance) {
2951
- if (instance) {
2952
- instance.stop();
2953
- if (currentInstance === instance) {
2954
- currentProcess = null;
2955
- currentInstance = null;
2956
- }
2957
- return;
2958
- }
2959
- if (currentInstance) {
2960
- currentInstance.stop();
2961
- }
2962
- currentProcess = null;
2963
- currentInstance = null;
2964
- }
2965
- function isCaffeinated() {
2966
- return currentInstance !== null && currentInstance.isActive;
2967
- }
2968
- function getCaffeinateStatus() {
2969
- if (!currentInstance || !currentInstance.isActive) {
2970
- return {
2971
- active: false,
2972
- instance: null,
2973
- startedAt: null,
2974
- endsAt: null,
2975
- durationMinutes: null
2976
- };
2977
- }
2978
- const opts = currentInstance.options;
2979
- const duration = opts.duration;
2980
- return {
2981
- active: true,
2982
- instance: currentInstance,
2983
- startedAt: currentInstance.startedAt,
2984
- endsAt: currentInstance.endsAt,
2985
- durationMinutes: duration && duration > 0 ? duration : -1
2986
- };
2987
- }
2988
- function formatRemainingTime(instance) {
2989
- const inst = instance || currentInstance;
2990
- if (!inst || !inst.isActive)
2991
- return "0:00";
2992
- const remaining = inst.remainingMs;
2993
- if (remaining === null)
2994
- return "\u221E";
2995
- const totalSeconds = Math.ceil(remaining / 1000);
2996
- const hours = Math.floor(totalSeconds / 3600);
2997
- const minutes = Math.floor(totalSeconds % 3600 / 60);
2998
- const seconds = totalSeconds % 60;
2999
- if (hours > 0)
3000
- return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
3001
- return `${minutes}:${String(seconds).padStart(2, "0")}`;
3002
- }
3003
- function formatDuration(minutes) {
3004
- if (minutes <= 0 || minutes === -1)
3005
- return "Indefinitely";
3006
- if (minutes < 60)
3007
- return `${minutes} minutes`;
3008
- if (minutes === 60)
3009
- return "1 hour";
3010
- if (minutes % 60 === 0)
3011
- return `${minutes / 60} hours`;
3012
- const h = Math.floor(minutes / 60);
3013
- const m = minutes % 60;
3014
- return `${h}h ${m}m`;
3015
- }
3016
- // src/printing.ts
3017
- var printing = {
3018
- async print() {
3019
- if (hasBridge("printing")) {
3020
- await window.craft.printing.print();
3021
- return;
3022
- }
3023
- if (typeof window !== "undefined" && typeof window.print === "function") {
3024
- window.print();
3025
- }
3026
- },
3027
- async printToPDF(path) {
3028
- if (!path)
3029
- throw new Error("printToPDF: path is required");
3030
- const isPosixAbs = path.startsWith("/");
3031
- const isWinAbs = /^[a-zA-Z]:[\\/]/.test(path) || path.startsWith("\\\\");
3032
- if (!isPosixAbs && !isWinAbs) {
3033
- throw new Error("printToPDF: path must be absolute");
3034
- }
3035
- const r = await requireBridge("printing").printToPDF(path);
3036
- return { ok: !!(r && r.ok), path: r?.path };
3037
- }
3038
- };
3039
- // src/screen.ts
3040
- var screen = {
3041
- async getDisplays() {
3042
- if (hasBridge("screen"))
3043
- return await window.craft.screen.getDisplays();
3044
- return webDisplays();
3045
- },
3046
- async getPrimary() {
3047
- if (hasBridge("screen")) {
3048
- const r = await window.craft.screen.getPrimary();
3049
- return r && typeof r.width === "number" ? r : null;
3050
- }
3051
- return webDisplays()[0] ?? null;
3052
- },
3053
- onChange(cb) {
3054
- if (hasBridge("screen"))
3055
- return onCraftEvent("craft:screen:change", () => cb());
3056
- if (typeof window === "undefined")
3057
- return () => {};
3058
- const h = () => cb();
3059
- window.addEventListener("resize", h);
3060
- return () => window.removeEventListener("resize", h);
3061
- }
3062
- };
3063
- function webDisplays() {
3064
- if (typeof window === "undefined" || !window.screen)
3065
- return [];
3066
- const s = window.screen;
3067
- return [{
3068
- id: 0,
3069
- x: s.left ?? 0,
3070
- y: s.top ?? 0,
3071
- width: s.width || 0,
3072
- height: s.height || 0,
3073
- workX: s.availLeft ?? 0,
3074
- workY: s.availTop ?? 0,
3075
- workWidth: s.availWidth ?? s.width ?? 0,
3076
- workHeight: s.availHeight ?? s.height ?? 0,
3077
- scaleFactor: window.devicePixelRatio || 1
3078
- }];
3079
- }
3080
- // src/screen-capture.ts
3081
- var screenCapture = {
3082
- async captureScreen() {
3083
- if (!hasBridge("screenCapture"))
3084
- return null;
3085
- const r = await window.craft.screenCapture.captureScreen();
3086
- return r ? String(r) : null;
3087
- },
3088
- async captureWindow(id) {
3089
- if (!hasBridge("screenCapture"))
3090
- return null;
3091
- if (!Number.isFinite(id) || id <= 0)
3092
- throw new Error("captureWindow: id must be a positive number");
3093
- const r = await window.craft.screenCapture.captureWindow(id);
3094
- return r ? String(r) : null;
3095
- },
3096
- async listWindows() {
3097
- if (!hasBridge("screenCapture"))
3098
- return [];
3099
- return await window.craft.screenCapture.listWindows();
3100
- }
3101
- };
3102
- // src/screen-sharing.ts
3103
- var MIN_WATCH_INTERVAL_MS = 250;
3104
- var MAX_WATCH_INTERVAL_MS = 60000;
3105
- var DEFAULT_WATCH_INTERVAL_MS = 2000;
3106
- var IDLE = {
3107
- sharing: false,
3108
- signals: {
3109
- systemScreenShare: false,
3110
- remoteSession: false,
3111
- conferenceSharing: false,
3112
- screenRecording: false
3113
- },
3114
- sources: []
3115
- };
3116
- function idleState() {
3117
- return { ...IDLE, signals: { ...IDLE.signals }, sources: [] };
3118
- }
3119
- var screenSharing = {
3120
- async getState() {
3121
- if (!hasBridge("screenSharing"))
3122
- return idleState();
3123
- return await window.craft.screenSharing.getState();
3124
- },
3125
- async watch(intervalMs = DEFAULT_WATCH_INTERVAL_MS) {
3126
- const clamped = Math.min(MAX_WATCH_INTERVAL_MS, Math.max(MIN_WATCH_INTERVAL_MS, Math.round(intervalMs)));
3127
- if (!hasBridge("screenSharing"))
3128
- return clamped;
3129
- const r = await window.craft.screenSharing.watch(clamped);
3130
- return r && r.intervalMs || clamped;
3131
- },
3132
- async stop() {
3133
- if (!hasBridge("screenSharing"))
3134
- return;
3135
- await window.craft.screenSharing.unwatch();
3136
- },
3137
- onChange(cb) {
3138
- return onCraftEvent("craft:screenSharing:change", cb);
3139
- }
3140
- };
3141
- async function watchScreenSharing(cb, intervalMs = DEFAULT_WATCH_INTERVAL_MS) {
3142
- const off = screenSharing.onChange(cb);
3143
- await screenSharing.watch(intervalMs);
3144
- return () => {
3145
- off();
3146
- screenSharing.stop();
3147
- };
3148
- }
3149
- // src/serial.ts
3150
- var serial = {
3151
- async list() {
3152
- if (!hasBridge("serial"))
3153
- return [];
3154
- return await window.craft.serial.list();
3155
- },
3156
- async open(path, baud = 9600) {
3157
- if (!path)
3158
- throw new Error("serial.open: path is required");
3159
- if (!hasBridge("serial"))
3160
- return { ok: false, reason: "bridge unavailable" };
3161
- return await window.craft.serial.open(path, baud);
3162
- },
3163
- async write(id, data) {
3164
- if (!id)
3165
- throw new Error("serial.write: id is required");
3166
- if (!hasBridge("serial"))
3167
- return { ok: false, reason: "bridge unavailable" };
3168
- return await window.craft.serial.write(id, data);
3169
- },
3170
- async close(id) {
3171
- if (!hasBridge("serial"))
3172
- return;
3173
- await window.craft.serial.close(id);
3174
- },
3175
- onData(cb) {
3176
- return onCraftEvent("craft:serial:data", cb);
3177
- }
3178
- };
3179
- // src/service-menu.ts
3180
- var serviceMenu = {
3181
- async register(name) {
3182
- if (!name)
3183
- throw new Error("serviceMenu.register: name is required");
3184
- if (!hasBridge("serviceMenu"))
3185
- return { ok: false, reason: "bridge unavailable" };
3186
- return await window.craft.serviceMenu.register(name);
3187
- },
3188
- async unregister(name) {
3189
- if (!name)
3190
- throw new Error("serviceMenu.unregister: name is required");
3191
- if (!hasBridge("serviceMenu"))
3192
- return;
3193
- await window.craft.serviceMenu.unregister(name);
3194
- },
3195
- onInvoked(cb) {
3196
- return onCraftEvent("craft:serviceMenu:invoked", cb);
3197
- }
3198
- };
3199
- // src/shell.ts
3200
- var activeSpawnIds = new Set;
3201
- var exitListenerAttached = false;
3202
- function ensureExitListener() {
3203
- if (exitListenerAttached)
3204
- return;
3205
- if (typeof window === "undefined" || typeof window.addEventListener !== "function")
3206
- return;
3207
- window.addEventListener("craft:shell:exit", (e) => {
3208
- const detail = e.detail;
3209
- if (detail?.id)
3210
- activeSpawnIds.delete(detail.id);
3211
- });
3212
- exitListenerAttached = true;
3213
- }
3214
- var BLOCKED_SCHEMES = new Set(["javascript:", "data:", "file:", "vbscript:"]);
3215
- var shell = {
3216
- async openExternal(url) {
3217
- const lc = url.trim().toLowerCase();
3218
- for (const s of BLOCKED_SCHEMES) {
3219
- if (lc.startsWith(s)) {
3220
- throw new Error(`shell.openExternal: ${s} URLs are blocked for safety`);
3221
- }
3222
- }
3223
- if (hasBridge("shell")) {
3224
- await window.craft.shell.openExternal(url);
3225
- return;
3226
- }
3227
- if (typeof window !== "undefined" && typeof window.open === "function") {
3228
- window.open(url, "_blank", "noopener,noreferrer");
3229
- }
3230
- },
3231
- async openPath(path) {
3232
- await requireBridge("shell").openPath(path);
3233
- },
3234
- async showInFinder(path) {
3235
- await requireBridge("shell").showInFinder(path);
3236
- },
3237
- async spawn(id, command, args = [], opts = {}) {
3238
- ensureExitListener();
3239
- if (!id || typeof id !== "string")
3240
- throw new Error("shell.spawn: id must be a non-empty string");
3241
- if (!command || typeof command !== "string")
3242
- throw new Error("shell.spawn: command must be a non-empty string");
3243
- if (!Array.isArray(args))
3244
- throw new Error("shell.spawn: args must be an array");
3245
- for (const a of args) {
3246
- if (typeof a !== "string")
3247
- throw new Error("shell.spawn: args entries must be strings");
3248
- }
3249
- if (activeSpawnIds.has(id)) {
3250
- throw new Error(`shell.spawn: id "${id}" is already in use \u2014 call kill(id) first or pick a different id`);
3251
- }
3252
- activeSpawnIds.add(id);
3253
- try {
3254
- await requireBridge("shell").spawn(id, command, args, opts);
3255
- } catch (e) {
3256
- activeSpawnIds.delete(id);
3257
- throw e;
3258
- }
3259
- },
3260
- async kill(id) {
3261
- await requireBridge("shell").kill(id);
3262
- activeSpawnIds.delete(id);
3263
- },
3264
- async getEnv(name) {
3265
- if (hasBridge("shell")) {
3266
- const v = await window.craft.shell.getEnv(name);
3267
- return v == null ? undefined : String(v);
3268
- }
3269
- return;
3270
- },
3271
- async setEnv(name, value) {
3272
- await requireBridge("shell").setEnv(name, value);
3273
- },
3274
- onStdout(cb) {
3275
- return onCraftEvent("craft:shell:stdout", cb);
3276
- },
3277
- onStderr(cb) {
3278
- return onCraftEvent("craft:shell:stderr", cb);
3279
- },
3280
- onExit(cb) {
3281
- return onCraftEvent("craft:shell:exit", cb);
3282
- }
3283
- };
3284
- // src/speech.ts
3285
- var speech = {
3286
- async speak(text, options) {
3287
- if (!text)
3288
- throw new Error("speech.speak: text is required");
3289
- if (hasBridge("speech")) {
3290
- await window.craft.speech.speak(text, options);
3291
- return;
3292
- }
3293
- if (typeof window !== "undefined" && window.speechSynthesis) {
3294
- const u = new window.SpeechSynthesisUtterance(text);
3295
- if (options) {
3296
- if (options.rate != null)
3297
- u.rate = options.rate;
3298
- if (options.pitch != null)
3299
- u.pitch = options.pitch;
3300
- if (options.volume != null)
3301
- u.volume = options.volume;
3302
- if (options.voice) {
3303
- const voices = window.speechSynthesis.getVoices();
3304
- const match = voices.find((v) => v.voiceURI === options.voice || v.name === options.voice || v.lang === options.voice);
3305
- if (match)
3306
- u.voice = match;
3307
- }
3308
- }
3309
- window.speechSynthesis.speak(u);
3310
- }
3311
- },
3312
- async stop() {
3313
- if (hasBridge("speech")) {
3314
- await window.craft.speech.stop();
3315
- return;
3316
- }
3317
- if (typeof window !== "undefined" && window.speechSynthesis) {
3318
- window.speechSynthesis.cancel();
3319
- }
3320
- },
3321
- async pause() {
3322
- if (hasBridge("speech")) {
3323
- await window.craft.speech.pause();
3324
- return;
3325
- }
3326
- if (typeof window !== "undefined" && window.speechSynthesis) {
3327
- window.speechSynthesis.pause();
3328
- }
3329
- },
3330
- async resume() {
3331
- if (hasBridge("speech")) {
3332
- await window.craft.speech.resume();
3333
- return;
3334
- }
3335
- if (typeof window !== "undefined" && window.speechSynthesis) {
3336
- window.speechSynthesis.resume();
3337
- }
3338
- },
3339
- async isSpeaking() {
3340
- if (hasBridge("speech"))
3341
- return await window.craft.speech.isSpeaking();
3342
- if (typeof window !== "undefined" && window.speechSynthesis) {
3343
- return !!window.speechSynthesis.speaking;
3344
- }
3345
- return false;
3346
- },
3347
- async getVoices() {
3348
- if (hasBridge("speech"))
3349
- return await window.craft.speech.getVoices();
3350
- if (typeof window !== "undefined" && window.speechSynthesis) {
3351
- const raw = window.speechSynthesis.getVoices();
3352
- return raw.map((v) => ({
3353
- id: v.voiceURI || v.name,
3354
- name: v.name,
3355
- language: v.lang || "",
3356
- quality: "default"
3357
- }));
3358
- }
3359
- return [];
3360
- }
3361
- };
3362
- // src/speech-recognition.ts
3363
- var speechRecognition = {
3364
- async isAvailable() {
3365
- if (!hasBridge("speechRecognition"))
3366
- return false;
3367
- return await window.craft.speechRecognition.isAvailable();
3368
- },
3369
- async start(opts) {
3370
- if (!hasBridge("speechRecognition"))
3371
- return { started: false, reason: "bridge unavailable" };
3372
- return await window.craft.speechRecognition.start(opts);
3373
- },
3374
- async stop() {
3375
- if (hasBridge("speechRecognition"))
3376
- await window.craft.speechRecognition.stop();
3377
- },
3378
- onPartial(cb) {
3379
- return onCraftEvent("craft:speechRecognition:partial", cb);
3380
- },
3381
- onFinal(cb) {
3382
- return onCraftEvent("craft:speechRecognition:final", cb);
3383
- }
3384
- };
3385
- // src/spotlight.ts
3386
- var spotlight = {
3387
- async index(items) {
3388
- if (!hasBridge("spotlight"))
3389
- return { ok: false, reason: "bridge unavailable" };
3390
- return await window.craft.spotlight.index(items);
3391
- },
3392
- async remove(ids) {
3393
- if (!hasBridge("spotlight"))
3394
- return { ok: false };
3395
- return await window.craft.spotlight.remove(ids);
3396
- },
3397
- async removeAll() {
3398
- if (!hasBridge("spotlight"))
3399
- return { ok: false };
3400
- return await window.craft.spotlight.removeAll();
3401
- }
3402
- };
3403
- // src/tags.ts
3404
- var tags = {
3405
- async get(path) {
3406
- if (!path)
3407
- throw new Error("tags.get: path is required");
3408
- if (!hasBridge("tags"))
3409
- return [];
3410
- return await window.craft.tags.get(path);
3411
- },
3412
- async set(path, t) {
3413
- if (!path)
3414
- throw new Error("tags.set: path is required");
3415
- if (!hasBridge("tags"))
3416
- return false;
3417
- const arr = Array.isArray(t) ? t : [String(t)];
3418
- return await window.craft.tags.set(path, arr);
3419
- },
3420
- async clear(path) {
3421
- if (!path)
3422
- throw new Error("tags.clear: path is required");
3423
- if (!hasBridge("tags"))
3424
- return false;
3425
- return await window.craft.tags.clear(path);
3426
- }
3427
- };
3428
- // src/theme.ts
3429
- var theme = {
3430
- get() {
3431
- if (hasBridge("theme")) {
3432
- try {
3433
- return window.craft.theme.get();
3434
- } catch {}
3435
- }
3436
- return { appearance: detectWebAppearance() };
3437
- },
3438
- onChange(cb) {
3439
- cb(this.get());
3440
- if (hasBridge("theme")) {
3441
- return onCraftEvent("craft:theme", cb);
3442
- }
3443
- if (typeof window !== "undefined" && window.matchMedia) {
3444
- const mq = window.matchMedia("(prefers-color-scheme: dark)");
3445
- const handler = () => cb({ appearance: mq.matches ? "dark" : "light" });
3446
- mq.addEventListener("change", handler);
3447
- return () => mq.removeEventListener("change", handler);
3448
- }
3449
- return () => {};
3450
- },
3451
- async current() {
3452
- return this.get();
3453
- }
3454
- };
3455
- function detectWebAppearance() {
3456
- if (typeof window === "undefined" || !window.matchMedia)
3457
- return "light";
3458
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
3459
- }
3460
- // src/timer.ts
3461
- class TimerImpl {
3462
- _duration;
3463
- _tickInterval;
3464
- _remaining;
3465
- _running = false;
3466
- _paused = false;
3467
- _complete = false;
3468
- _intervalId = null;
3469
- _lastTick = 0;
3470
- _completionHandlers = new Set;
3471
- _tickHandlers = new Set;
3472
- constructor(options) {
3473
- this._duration = options.duration;
3474
- this._tickInterval = options.tickInterval || 1000;
3475
- this._remaining = options.duration;
3476
- if (options.onComplete)
3477
- this._completionHandlers.add(options.onComplete);
3478
- if (options.onTick)
3479
- this._tickHandlers.add(options.onTick);
3480
- if (options.autoStart)
3481
- this.start();
3482
- }
3483
- get isRunning() {
3484
- return this._running && !this._paused;
3485
- }
3486
- get isPaused() {
3487
- return this._paused;
3488
- }
3489
- get isComplete() {
3490
- return this._complete;
3491
- }
3492
- get remaining() {
3493
- if (this._running && !this._paused) {
3494
- const elapsed = Date.now() - this._lastTick;
3495
- return Math.max(0, this._remaining - elapsed);
3496
- }
3497
- return Math.max(0, this._remaining);
3498
- }
3499
- get elapsed() {
3500
- return this._duration - this.remaining;
3501
- }
3502
- get duration() {
3503
- return this._duration;
3504
- }
3505
- get progress() {
3506
- if (this._duration === 0)
3507
- return 1;
3508
- return Math.min(1, this.elapsed / this._duration);
3509
- }
3510
- start() {
3511
- if (this._running)
3512
- return;
3513
- this._running = true;
3514
- this._paused = false;
3515
- this._complete = false;
3516
- this._lastTick = Date.now();
3517
- this._startInterval();
3518
- }
3519
- stop() {
3520
- this._clearInterval();
3521
- this._running = false;
3522
- this._paused = false;
3523
- this._remaining = this._duration;
3524
- }
3525
- pause() {
3526
- if (!this._running || this._paused)
3527
- return;
3528
- const elapsed = Date.now() - this._lastTick;
3529
- this._remaining = Math.max(0, this._remaining - elapsed);
3530
- this._clearInterval();
3531
- this._paused = true;
3532
- }
3533
- resume() {
3534
- if (!this._paused)
3535
- return;
3536
- this._paused = false;
3537
- this._lastTick = Date.now();
3538
- this._startInterval();
3539
- }
3540
- reset() {
3541
- this._clearInterval();
3542
- this._running = false;
3543
- this._paused = false;
3544
- this._complete = false;
3545
- this._remaining = this._duration;
3546
- }
3547
- onComplete(handler) {
3548
- this._completionHandlers.add(handler);
3549
- return () => {
3550
- this._completionHandlers.delete(handler);
3551
- };
3552
- }
3553
- onTick(handler) {
3554
- this._tickHandlers.add(handler);
3555
- return () => {
3556
- this._tickHandlers.delete(handler);
3557
- };
3558
- }
3559
- _startInterval() {
3560
- this._clearInterval();
3561
- this._intervalId = setInterval(() => {
3562
- const now = Date.now();
3563
- const elapsed = now - this._lastTick;
3564
- this._lastTick = now;
3565
- this._remaining = Math.max(0, this._remaining - elapsed);
3566
- for (const handler of this._tickHandlers) {
3567
- try {
3568
- handler(this._remaining);
3569
- } catch {}
3570
- }
3571
- if (this._remaining <= 0) {
3572
- this._clearInterval();
3573
- this._running = false;
3574
- this._complete = true;
3575
- for (const handler of this._completionHandlers) {
3576
- try {
3577
- handler();
3578
- } catch {}
3579
- }
3580
- }
3581
- }, this._tickInterval);
3582
- }
3583
- _clearInterval() {
3584
- if (this._intervalId !== null) {
3585
- clearInterval(this._intervalId);
3586
- this._intervalId = null;
3587
- }
3588
- }
3589
- }
3590
-
3591
- class IntervalImpl {
3592
- _interval;
3593
- _handler;
3594
- _immediate;
3595
- _running = false;
3596
- _intervalId = null;
3597
- constructor(options) {
3598
- this._interval = options.interval;
3599
- this._handler = options.handler;
3600
- this._immediate = options.immediate !== false;
3601
- if (this._immediate)
3602
- this.start();
3603
- }
3604
- get isRunning() {
3605
- return this._running;
3606
- }
3607
- start() {
3608
- if (this._running)
3609
- return;
3610
- this._running = true;
3611
- this._intervalId = setInterval(() => {
3612
- try {
3613
- this._handler();
3614
- } catch {}
3615
- }, this._interval);
3616
- }
3617
- stop() {
3618
- if (this._intervalId !== null) {
3619
- clearInterval(this._intervalId);
3620
- this._intervalId = null;
3621
- }
3622
- this._running = false;
3623
- }
3624
- }
3625
- function createTimer(options) {
3626
- return new TimerImpl(options);
3627
- }
3628
- function createInterval(options) {
3629
- return new IntervalImpl(options);
3630
- }
3631
- function delay(ms) {
3632
- return new Promise((resolve) => setTimeout(resolve, ms));
3633
- }
3634
- function formatTime(ms) {
3635
- const totalSeconds = Math.ceil(ms / 1000);
3636
- const hours = Math.floor(totalSeconds / 3600);
3637
- const minutes = Math.floor(totalSeconds % 3600 / 60);
3638
- const seconds = totalSeconds % 60;
3639
- if (hours > 0)
3640
- return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
3641
- return `${minutes}:${String(seconds).padStart(2, "0")}`;
3642
- }
3643
- function formatCompact(ms) {
3644
- const totalSeconds = Math.ceil(ms / 1000);
3645
- const hours = Math.floor(totalSeconds / 3600);
3646
- const minutes = Math.floor(totalSeconds % 3600 / 60);
3647
- const seconds = totalSeconds % 60;
3648
- if (hours > 0) {
3649
- if (minutes > 0)
3650
- return `${hours}h ${minutes}m`;
3651
- return `${hours}h`;
3652
- }
3653
- if (minutes > 0) {
3654
- if (seconds > 0)
3655
- return `${minutes}m ${seconds}s`;
3656
- return `${minutes}m`;
3657
- }
3658
- return `${seconds}s`;
3659
- }
3660
- // src/touchbar.ts
3661
- var touchbar = {
3662
- async addItem(item) {
3663
- if (hasBridge("touchbar"))
3664
- await window.craft.touchbar.addItem(item);
3665
- },
3666
- async removeItem(id) {
3667
- if (hasBridge("touchbar"))
3668
- await window.craft.touchbar.removeItem(id);
3669
- },
3670
- async updateItem(id, props) {
3671
- if (hasBridge("touchbar"))
3672
- await window.craft.touchbar.updateItem(id, props);
3673
- },
3674
- async setLabel(id, label) {
3675
- if (hasBridge("touchbar"))
3676
- await window.craft.touchbar.setLabel(id, label);
3677
- },
3678
- async setIcon(id, icon) {
3679
- if (hasBridge("touchbar"))
3680
- await window.craft.touchbar.setIcon(id, icon);
3681
- },
3682
- async setEnabled(id, enabled) {
3683
- if (hasBridge("touchbar"))
3684
- await window.craft.touchbar.setEnabled(id, enabled);
3685
- },
3686
- async setSliderValue(id, value) {
3687
- if (hasBridge("touchbar"))
3688
- await window.craft.touchbar.setSliderValue(id, value);
3689
- },
3690
- async clear() {
3691
- if (hasBridge("touchbar"))
3692
- await window.craft.touchbar.clear();
3693
- },
3694
- async show() {
3695
- if (hasBridge("touchbar"))
3696
- await window.craft.touchbar.show();
3697
- },
3698
- async hide() {
3699
- if (hasBridge("touchbar"))
3700
- await window.craft.touchbar.hide();
3701
- },
3702
- onAction(cb) {
3703
- return onCraftEvent("craft:touchbar:action", cb);
3704
- }
3705
- };
3706
- // src/updater.ts
3707
- var updater = {
3708
- async checkForUpdates() {
3709
- if (!hasBridge("updater"))
3710
- return;
3711
- await window.craft.updater.checkForUpdates();
3712
- },
3713
- async checkInBackground() {
3714
- if (!hasBridge("updater"))
3715
- return;
3716
- await window.craft.updater.checkInBackground();
3717
- },
3718
- async setAutomaticChecks(on) {
3719
- if (!hasBridge("updater"))
3720
- return;
3721
- await window.craft.updater.setAutomaticChecks(on);
3722
- },
3723
- async setCheckInterval(seconds) {
3724
- if (!hasBridge("updater"))
3725
- return;
3726
- if (!Number.isFinite(seconds)) {
3727
- throw new Error("setCheckInterval: must be a finite number");
3728
- }
3729
- const safe = seconds <= 0 ? 0 : Math.max(60, Math.round(seconds));
3730
- await window.craft.updater.setCheckInterval(safe);
3731
- },
3732
- async setFeedURL(url) {
3733
- if (!hasBridge("updater"))
3734
- return;
3735
- await window.craft.updater.setFeedURL(url);
3736
- },
3737
- async getLastUpdateCheckDate() {
3738
- if (!hasBridge("updater"))
3739
- return null;
3740
- const v = await window.craft.updater.getLastUpdateCheckDate();
3741
- return v || null;
3742
- },
3743
- async getUpdateInfo() {
3744
- if (!hasBridge("updater"))
3745
- return null;
3746
- const v = await window.craft.updater.getUpdateInfo();
3747
- if (!v || typeof v.version !== "string" || v.version.length === 0)
3748
- return null;
3749
- return v;
3750
- },
3751
- onAvailable(cb) {
3752
- return onCraftEvent("craft:updateAvailable", cb);
3753
- },
3754
- onDownloaded(cb) {
3755
- return onCraftEvent("craft:updateDownloaded", cb);
3756
- },
3757
- async verifySignature({ payload, signatureB64, publicKeyB64 }) {
3758
- const data = toArrayBufferBytes(payload);
3759
- let publicKey;
3760
- try {
3761
- publicKey = await crypto.subtle.importKey("raw", base64ToBytes2(publicKeyB64), { name: "Ed25519" }, false, ["verify"]);
3762
- } catch {
3763
- return false;
3764
- }
3765
- try {
3766
- return await crypto.subtle.verify("Ed25519", publicKey, base64ToBytes2(signatureB64), data);
3767
- } catch {
3768
- return false;
3769
- }
3770
- },
3771
- async verifyDownload({ url, signatureB64, publicKeyB64, fetchInit }) {
3772
- let response;
3773
- try {
3774
- response = await fetch(url, fetchInit);
3775
- } catch {
3776
- return { ok: false, reason: "fetch-failed" };
3777
- }
3778
- if (!response.ok) {
3779
- return { ok: false, reason: "http-error", status: response.status };
3780
- }
3781
- const buffer = new Uint8Array(await response.arrayBuffer());
3782
- let publicKey;
3783
- try {
3784
- publicKey = await crypto.subtle.importKey("raw", base64ToBytes2(publicKeyB64), { name: "Ed25519" }, false, ["verify"]);
3785
- } catch {
3786
- return { ok: false, reason: "bad-key" };
3787
- }
3788
- let valid = false;
3789
- try {
3790
- valid = await crypto.subtle.verify("Ed25519", publicKey, base64ToBytes2(signatureB64), toArrayBufferBytes(buffer));
3791
- } catch {
3792
- valid = false;
3793
- }
3794
- if (!valid)
3795
- return { ok: false, reason: "bad-signature" };
3796
- return { ok: true, payload: buffer };
3797
- }
3798
- };
3799
- function toArrayBufferBytes(input) {
3800
- if (input instanceof ArrayBuffer)
3801
- return new Uint8Array(input);
3802
- const out = new ArrayBuffer(input.byteLength);
3803
- const view = new Uint8Array(out);
3804
- view.set(input);
3805
- return view;
3806
- }
3807
- function base64ToBytes2(b64) {
3808
- if (typeof atob === "function") {
3809
- const bin = atob(b64);
3810
- const buffer = new ArrayBuffer(bin.length);
3811
- const out = new Uint8Array(buffer);
3812
- for (let i = 0;i < bin.length; i++)
3813
- out[i] = bin.charCodeAt(i);
3814
- return out;
3815
- }
3816
- const node = Buffer.from(b64, "base64");
3817
- const buf = new ArrayBuffer(node.length);
3818
- new Uint8Array(buf).set(node);
3819
- return new Uint8Array(buf);
3820
- }
3821
- // src/vision.ts
3822
- var vision = {
3823
- async recognizeText(path) {
3824
- if (!path)
3825
- throw new Error("vision.recognizeText: path is required");
3826
- if (!hasBridge("vision"))
3827
- return [];
3828
- return await window.craft.vision.recognizeText(path);
3829
- },
3830
- async detectFaces(path) {
3831
- if (!path)
3832
- throw new Error("vision.detectFaces: path is required");
3833
- if (!hasBridge("vision"))
3834
- return [];
3835
- return await window.craft.vision.detectFaces(path);
3836
- },
3837
- async detectBarcodes(path) {
3838
- if (!path)
3839
- throw new Error("vision.detectBarcodes: path is required");
3840
- if (!hasBridge("vision"))
3841
- return [];
3842
- return await window.craft.vision.detectBarcodes(path);
3843
- }
3844
- };
3845
- // src/window-events.ts
3846
- var windowEvents = {
3847
- onFocus(cb) {
3848
- if (hasBridge("window"))
3849
- return onCraftEvent("craft:window:focus", () => cb());
3850
- return webEvent("focus", cb);
3851
- },
3852
- onBlur(cb) {
3853
- if (hasBridge("window"))
3854
- return onCraftEvent("craft:window:blur", () => cb());
3855
- return webEvent("blur", cb);
3856
- },
3857
- onResize(cb) {
3858
- if (hasBridge("window"))
3859
- return onCraftEvent("craft:window:resize", cb);
3860
- if (typeof window === "undefined")
3861
- return () => {};
3862
- const h = () => cb({ width: window.innerWidth, height: window.innerHeight });
3863
- window.addEventListener("resize", h);
3864
- return () => window.removeEventListener("resize", h);
3865
- },
3866
- onMove(cb) {
3867
- if (hasBridge("window"))
3868
- return onCraftEvent("craft:window:move", cb);
3869
- return () => {};
3870
- },
3871
- onClose(cb) {
3872
- if (hasBridge("window"))
3873
- return onCraftEvent("craft:window:close", () => cb());
3874
- return webEvent("beforeunload", cb);
3875
- },
3876
- onMinimize(cb) {
3877
- if (hasBridge("window"))
3878
- return onCraftEvent("craft:window:minimize", () => cb());
3879
- if (typeof document === "undefined")
3880
- return () => {};
3881
- const h = () => {
3882
- if (document.visibilityState === "hidden")
3883
- cb();
3884
- };
3885
- document.addEventListener("visibilitychange", h);
3886
- return () => document.removeEventListener("visibilitychange", h);
3887
- },
3888
- onRestore(cb) {
3889
- if (hasBridge("window"))
3890
- return onCraftEvent("craft:window:restore", () => cb());
3891
- if (typeof document === "undefined")
3892
- return () => {};
3893
- const h = () => {
3894
- if (document.visibilityState === "visible")
3895
- cb();
3896
- };
3897
- document.addEventListener("visibilitychange", h);
3898
- return () => document.removeEventListener("visibilitychange", h);
3899
- }
3900
- };
3901
- function webEvent(name, cb) {
3902
- if (typeof window === "undefined")
3903
- return () => {};
3904
- const h = () => cb();
3905
- window.addEventListener(name, h);
3906
- return () => window.removeEventListener(name, h);
3907
- }
2
+ import {
3
+ DEFAULT_WATCH_INTERVAL_MS,
4
+ MAX_WATCH_INTERVAL_MS,
5
+ MIN_WATCH_INTERVAL_MS,
6
+ MODAL_STYLES,
7
+ TOAST_STYLES,
8
+ alert,
9
+ app,
10
+ appleScript,
11
+ audio,
12
+ battery,
13
+ biometric,
14
+ bluetooth,
15
+ bonjour,
16
+ caffeinate,
17
+ clipboard,
18
+ closeAllModals,
19
+ confirm,
20
+ continuityCamera,
21
+ coreml,
22
+ crashReporter,
23
+ createInterval,
24
+ createTimer,
25
+ decaffeinate,
26
+ deepLinks,
27
+ delay,
28
+ dismissAlertById,
29
+ dismissAllAlerts,
30
+ dragOut,
31
+ fileAssociations,
32
+ focus,
33
+ focusShortcutsReady,
34
+ formatCompact,
35
+ formatDuration,
36
+ formatRemainingTime,
37
+ formatShortcut,
38
+ formatTime,
39
+ fs,
40
+ getActiveAlertCount,
41
+ getActiveModalCount,
42
+ getCaffeinateStatus,
43
+ getCapabilities,
44
+ getCapability,
45
+ getDialogBridgeScript,
46
+ getRegisteredHotkeys,
47
+ globalShortcuts,
48
+ handoff,
49
+ hasFocusShortcuts,
50
+ iap,
51
+ isAvailable,
52
+ isCaffeinated,
53
+ isDragOutAvailable,
54
+ keychain,
55
+ liveActivities,
56
+ localServer,
57
+ location,
58
+ log,
59
+ menu,
60
+ midi,
61
+ nativeAutoLaunch,
62
+ network,
63
+ notifications,
64
+ notify,
65
+ parseShortcut,
66
+ pdf,
67
+ permissions,
68
+ printing,
69
+ prompt,
70
+ redactPII,
71
+ registerHotkey,
72
+ requestNotificationPermission,
73
+ screen,
74
+ screenCapture,
75
+ screenSharing,
76
+ serial,
77
+ serviceMenu,
78
+ shell,
79
+ showAlert,
80
+ showAlertDialog,
81
+ showColorPicker,
82
+ showConfirmDialog,
83
+ showErrorDialog,
84
+ showErrorModal,
85
+ showErrorToast,
86
+ showInfoModal,
87
+ showInfoToast,
88
+ showMessageBox,
89
+ showModal,
90
+ showOpenDialog,
91
+ showQuestionModal,
92
+ showSaveDialog,
93
+ showSuccessModal,
94
+ showSuccessToast,
95
+ showToast,
96
+ showWarningDialog,
97
+ showWarningModal,
98
+ showWarningToast,
99
+ signPayload,
100
+ speech,
101
+ speechRecognition,
102
+ spotlight,
103
+ tags,
104
+ theme,
105
+ touchbar,
106
+ unregisterAllHotkeys,
107
+ unregisterHotkey,
108
+ updater,
109
+ vision,
110
+ watchScreenSharing,
111
+ windowEvents
112
+ } from "./chunk-ys4frvmj.js";
113
+ import"./chunk-2mx7fq49.js";
3908
114
  export {
3909
115
  windowEvents,
3910
116
  watchScreenSharing,
@@ -3948,7 +154,7 @@ export {
3948
154
  requestNotificationPermission,
3949
155
  registerHotkey,
3950
156
  redactPII,
3951
- prompt2 as prompt,
157
+ prompt,
3952
158
  printing,
3953
159
  permissions,
3954
160
  pdf,
@@ -3998,7 +204,7 @@ export {
3998
204
  crashReporter,
3999
205
  coreml,
4000
206
  continuityCamera,
4001
- confirm2 as confirm,
207
+ confirm,
4002
208
  closeAllModals,
4003
209
  clipboard,
4004
210
  caffeinate,
@@ -4009,12 +215,10 @@ export {
4009
215
  audio,
4010
216
  appleScript,
4011
217
  app,
4012
- alert2 as alert,
218
+ alert,
4013
219
  TOAST_STYLES,
4014
220
  MODAL_STYLES,
4015
221
  MIN_WATCH_INTERVAL_MS,
4016
222
  MAX_WATCH_INTERVAL_MS,
4017
223
  DEFAULT_WATCH_INTERVAL_MS
4018
224
  };
4019
-
4020
- //# debugId=2BBECC339E514CD564756E2164756E21