@glassly/engine 0.3.5 → 0.3.6-beta.851
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/build/engine.d.ts +11 -1
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +13 -1
- package/build/engine.js.map +1 -1
- package/build/facades/ai.d.ts +3 -3
- package/build/facades/ai.d.ts.map +1 -1
- package/build/facades/ai.js +3 -3
- package/build/facades/ai.js.map +1 -1
- package/build/facades/glasses.d.ts +5 -1
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +39 -30
- package/build/facades/glasses.js.map +1 -1
- package/build/facades/memory.d.ts +10 -2
- package/build/facades/memory.d.ts.map +1 -1
- package/build/facades/memory.js +10 -0
- package/build/facades/memory.js.map +1 -1
- package/build/facades/miniapps.d.ts +2 -0
- package/build/facades/miniapps.d.ts.map +1 -1
- package/build/facades/miniapps.js +2 -0
- package/build/facades/miniapps.js.map +1 -1
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +3 -2
- package/build/facades/pairing.js.map +1 -1
- package/build/facades/providerKeys.d.ts +6 -2
- package/build/facades/providerKeys.d.ts.map +1 -1
- package/build/facades/providerKeys.js +6 -2
- package/build/facades/providerKeys.js.map +1 -1
- package/build/generated/releaseMetadata.js +4 -4
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/AiTierResolution.d.ts +3 -2
- package/build/services/AiTierResolution.d.ts.map +1 -1
- package/build/services/AiTierResolution.js +7 -2
- package/build/services/AiTierResolution.js.map +1 -1
- package/build/services/AppRegistry.d.ts +4 -1
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +16 -2
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/BlobStore.js +3 -3
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/BluetoothRestoreLaunch.d.ts +8 -0
- package/build/services/BluetoothRestoreLaunch.d.ts.map +1 -0
- package/build/services/BluetoothRestoreLaunch.js +34 -0
- package/build/services/BluetoothRestoreLaunch.js.map +1 -0
- package/build/services/CloudClientService.d.ts +4 -4
- package/build/services/ForegroundLocationPermission.d.ts +1 -1
- package/build/services/ForegroundLocationPermission.d.ts.map +1 -1
- package/build/services/ForegroundLocationPermission.js.map +1 -1
- package/build/services/GestureDotsService.d.ts.map +1 -1
- package/build/services/GestureDotsService.js +2 -1
- package/build/services/GestureDotsService.js.map +1 -1
- package/build/services/GlassesTouchInput.d.ts +6 -4
- package/build/services/GlassesTouchInput.d.ts.map +1 -1
- package/build/services/GlassesTouchInput.js +81 -41
- package/build/services/GlassesTouchInput.js.map +1 -1
- package/build/services/LocalDisplayManager.d.ts +12 -4
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +21 -7
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalLlmModelManager.d.ts +3 -1
- package/build/services/LocalLlmModelManager.d.ts.map +1 -1
- package/build/services/LocalLlmModelManager.js +13 -5
- package/build/services/LocalLlmModelManager.js.map +1 -1
- package/build/services/LocalLlmPrompt.d.ts +10 -9
- package/build/services/LocalLlmPrompt.d.ts.map +1 -1
- package/build/services/LocalLlmPrompt.js +43 -32
- package/build/services/LocalLlmPrompt.js.map +1 -1
- package/build/services/LocalLlmProvider.d.ts +1 -0
- package/build/services/LocalLlmProvider.d.ts.map +1 -1
- package/build/services/LocalLlmProvider.js +62 -22
- package/build/services/LocalLlmProvider.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +28 -6
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +190 -148
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MemoryStore.d.ts +13 -1
- package/build/services/MemoryStore.d.ts.map +1 -1
- package/build/services/MemoryStore.js +98 -1
- package/build/services/MemoryStore.js.map +1 -1
- package/build/services/MiniappExitGesture.d.ts +6 -3
- package/build/services/MiniappExitGesture.d.ts.map +1 -1
- package/build/services/MiniappExitGesture.js +4 -4
- package/build/services/MiniappExitGesture.js.map +1 -1
- package/build/services/MiniappWidgetMode.d.ts +6 -4
- package/build/services/MiniappWidgetMode.d.ts.map +1 -1
- package/build/services/MiniappWidgetMode.js +7 -6
- package/build/services/MiniappWidgetMode.js.map +1 -1
- package/build/services/ProviderKeyValidation.d.ts +2 -0
- package/build/services/ProviderKeyValidation.d.ts.map +1 -1
- package/build/services/ProviderKeyValidation.js +4 -0
- package/build/services/ProviderKeyValidation.js.map +1 -1
- package/build/services/SceneRenderer.d.ts.map +1 -1
- package/build/services/SceneRenderer.js +3 -1
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/services/ScreenshotFixture.d.ts +56 -0
- package/build/services/ScreenshotFixture.d.ts.map +1 -0
- package/build/services/ScreenshotFixture.js +72 -0
- package/build/services/ScreenshotFixture.js.map +1 -0
- package/build/services/actionInvoke.d.ts +37 -0
- package/build/services/actionInvoke.d.ts.map +1 -0
- package/build/services/actionInvoke.js +89 -0
- package/build/services/actionInvoke.js.map +1 -0
- package/build/services/appleWatch/AppleWatchLink.d.ts +42 -0
- package/build/services/appleWatch/AppleWatchLink.d.ts.map +1 -0
- package/build/services/appleWatch/AppleWatchLink.js +259 -0
- package/build/services/appleWatch/AppleWatchLink.js.map +1 -0
- package/build/services/controllers.d.ts.map +1 -1
- package/build/services/controllers.js +16 -0
- package/build/services/controllers.js.map +1 -1
- package/build/services/manifestActions.d.ts +4 -2
- package/build/services/manifestActions.d.ts.map +1 -1
- package/build/services/manifestActions.js +9 -3
- package/build/services/manifestActions.js.map +1 -1
- package/build/services/memoryBackupFile.d.ts +9 -0
- package/build/services/memoryBackupFile.d.ts.map +1 -0
- package/build/services/memoryBackupFile.js +47 -0
- package/build/services/memoryBackupFile.js.map +1 -0
- package/build/services/settingsBridge.d.ts +4 -4
- package/build/services/settingsBridge.d.ts.map +1 -1
- package/build/services/settingsBridge.js +9 -4
- package/build/services/settingsBridge.js.map +1 -1
- package/build/services/widgetPlacements.d.ts +8 -7
- package/build/services/widgetPlacements.d.ts.map +1 -1
- package/build/services/widgetPlacements.js +7 -6
- package/build/services/widgetPlacements.js.map +1 -1
- package/build/stores/apps.d.ts +6 -0
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +29 -12
- package/build/stores/apps.js.map +1 -1
- package/build/stores/bluetoothSettingKeys.d.ts +2 -0
- package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
- package/build/stores/bluetoothSettingKeys.js +17 -0
- package/build/stores/bluetoothSettingKeys.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +42 -2
- package/build/stores/settings.js.map +1 -1
- package/build/types/applet.d.ts +5 -1
- package/build/types/applet.d.ts.map +1 -1
- package/build/types/applet.js.map +1 -1
- package/build/types/capabilities/rayneo-io.d.ts.map +1 -1
- package/build/types/capabilities/rayneo-io.js +2 -0
- package/build/types/capabilities/rayneo-io.js.map +1 -1
- package/build/types/enums.d.ts +2 -1
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +1 -0
- package/build/types/enums.js.map +1 -1
- package/build/utils/cloudClient/RnUdpAdapter.d.ts.map +1 -1
- package/build/utils/cloudClient/RnUdpAdapter.js +13 -2
- package/build/utils/cloudClient/RnUdpAdapter.js.map +1 -1
- package/build/utils/display/scene/index.d.ts +1 -1
- package/build/utils/display/scene/index.d.ts.map +1 -1
- package/build/utils/display/scene/index.js.map +1 -1
- package/build/utils/display/scene/process.d.ts.map +1 -1
- package/build/utils/display/scene/process.js +20 -1
- package/build/utils/display/scene/process.js.map +1 -1
- package/build/utils/display/scene/types.d.ts +1 -1
- package/build/utils/display/scene/types.d.ts.map +1 -1
- package/build/utils/display/scene/types.js.map +1 -1
- package/build/utils/miniappGlobals.d.ts +6 -0
- package/build/utils/miniappGlobals.d.ts.map +1 -1
- package/build/utils/miniappGlobals.js +28 -4
- package/build/utils/miniappGlobals.js.map +1 -1
- package/package.json +9 -9
- package/src/engine.ts +13 -1
- package/src/facades/ai.ts +3 -3
- package/src/facades/glasses.ts +65 -42
- package/src/facades/memory.ts +11 -2
- package/src/facades/miniapps.ts +3 -0
- package/src/facades/pairing.ts +4 -2
- package/src/facades/providerKeys.ts +6 -1
- package/src/generated/releaseMetadata.ts +4 -4
- package/src/services/AiTierResolution.ts +9 -3
- package/src/services/AppRegistry.ts +18 -3
- package/src/services/BlobStore.ts +3 -3
- package/src/services/BluetoothRestoreLaunch.ts +37 -0
- package/src/services/ForegroundLocationPermission.ts +1 -1
- package/src/services/GestureDotsService.ts +2 -1
- package/src/services/GlassesTouchInput.ts +86 -38
- package/src/services/LocalDisplayManager.ts +22 -8
- package/src/services/LocalLlmModelManager.ts +16 -6
- package/src/services/LocalLlmPrompt.ts +56 -37
- package/src/services/LocalLlmProvider.ts +74 -24
- package/src/services/LocalMiniappRuntime.ts +228 -147
- package/src/services/MemoryStore.ts +111 -2
- package/src/services/MiniappExitGesture.ts +8 -5
- package/src/services/MiniappWidgetMode.ts +9 -7
- package/src/services/ProviderKeyValidation.ts +5 -0
- package/src/services/SceneRenderer.ts +3 -1
- package/src/services/ScreenshotFixture.ts +113 -0
- package/src/services/actionInvoke.ts +105 -0
- package/src/services/appleWatch/AppleWatchLink.ts +297 -0
- package/src/services/controllers.ts +17 -0
- package/src/services/manifestActions.ts +16 -4
- package/src/services/memoryBackupFile.ts +50 -0
- package/src/services/settingsBridge.ts +9 -4
- package/src/services/widgetPlacements.ts +23 -9
- package/src/stores/apps.ts +35 -11
- package/src/stores/bluetoothSettingKeys.ts +20 -0
- package/src/stores/settings.ts +44 -2
- package/src/types/applet.ts +5 -1
- package/src/types/capabilities/rayneo-io.ts +2 -0
- package/src/types/enums.ts +1 -0
- package/src/utils/cloudClient/RnUdpAdapter.ts +13 -1
- package/src/utils/display/scene/index.ts +1 -0
- package/src/utils/display/scene/process.ts +17 -1
- package/src/utils/display/scene/types.ts +1 -0
- package/src/utils/miniappGlobals.ts +30 -4
|
@@ -17,7 +17,14 @@
|
|
|
17
17
|
* user-scoped keys so existing history survives the move.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import type {
|
|
20
|
+
import type {
|
|
21
|
+
MemoryBackup,
|
|
22
|
+
MemoryEntry,
|
|
23
|
+
MemoryImportResult,
|
|
24
|
+
MemoryNoteSource,
|
|
25
|
+
MemorySession,
|
|
26
|
+
MemorySummary,
|
|
27
|
+
} from "@glassly/miniapp"
|
|
21
28
|
|
|
22
29
|
import {searchMemoryEntries} from "../utils/memory/search"
|
|
23
30
|
|
|
@@ -91,6 +98,14 @@ export interface MemoryStats {
|
|
|
91
98
|
oldestAt: number | null
|
|
92
99
|
}
|
|
93
100
|
|
|
101
|
+
/** Thrown by importBackup for anything that isn't a backup this build can read. */
|
|
102
|
+
export class MemoryBackupError extends Error {
|
|
103
|
+
constructor(message: string) {
|
|
104
|
+
super(message)
|
|
105
|
+
this.name = "MemoryBackupError"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
94
109
|
export function hourStartOf(ms: number): number {
|
|
95
110
|
return Math.floor(ms / HOUR_MS) * HOUR_MS
|
|
96
111
|
}
|
|
@@ -359,6 +374,65 @@ export class MemoryStore {
|
|
|
359
374
|
return Math.max(...hours)
|
|
360
375
|
}
|
|
361
376
|
|
|
377
|
+
/** The whole timeline as a backup file body (Settings > Memory, session.memory.exportFile). */
|
|
378
|
+
async exportBackup(): Promise<MemoryBackup> {
|
|
379
|
+
await this.ensureLoaded()
|
|
380
|
+
return {
|
|
381
|
+
format: "glassly-memory",
|
|
382
|
+
version: 1,
|
|
383
|
+
exportedAt: Date.now(),
|
|
384
|
+
entries: [...this.entries],
|
|
385
|
+
summaries: [...this.summaries].sort((a, b) => a.hourStart - b.hourStart),
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Merge a backup into the timeline. Skips entries whose id is stored, notes
|
|
391
|
+
* whose text is already in the same hour, and hours that already have a
|
|
392
|
+
* summary. Throws MemoryBackupError when `data` isn't a memory backup.
|
|
393
|
+
*/
|
|
394
|
+
async importBackup(data: unknown): Promise<MemoryImportResult> {
|
|
395
|
+
const backup = parseBackup(data)
|
|
396
|
+
await this.ensureLoaded()
|
|
397
|
+
const result: MemoryImportResult = {entries: 0, summaries: 0, skipped: 0}
|
|
398
|
+
|
|
399
|
+
const ids = new Set(this.entries.map((e) => e.id))
|
|
400
|
+
const noteKeys = new Set(this.entries.filter((e) => e.kind === "note").map(noteKey))
|
|
401
|
+
for (const entry of backup.entries) {
|
|
402
|
+
const key = entry.kind === "note" ? noteKey(entry) : null
|
|
403
|
+
if (ids.has(entry.id) || (key != null && noteKeys.has(key))) {
|
|
404
|
+
result.skipped++
|
|
405
|
+
continue
|
|
406
|
+
}
|
|
407
|
+
ids.add(entry.id)
|
|
408
|
+
if (key != null) noteKeys.add(key)
|
|
409
|
+
this.entries.push(entry)
|
|
410
|
+
result.entries++
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const byHour = new Map(this.summaries.map((s) => [s.hourStart, s]))
|
|
414
|
+
for (const summary of backup.summaries) {
|
|
415
|
+
if (byHour.get(summary.hourStart)?.text.trim()) {
|
|
416
|
+
result.skipped++
|
|
417
|
+
continue
|
|
418
|
+
}
|
|
419
|
+
byHour.set(summary.hourStart, summary)
|
|
420
|
+
result.summaries++
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (result.entries > 0) {
|
|
424
|
+
this.entries.sort((a, b) => a.at - b.at)
|
|
425
|
+
this.prune()
|
|
426
|
+
await this.persistLog()
|
|
427
|
+
}
|
|
428
|
+
if (result.summaries > 0) {
|
|
429
|
+
this.summaries = [...byHour.values()].sort((a, b) => a.hourStart - b.hourStart).slice(-MEMORY_BOUNDS.maxSummaries)
|
|
430
|
+
await this.persistSummaries()
|
|
431
|
+
}
|
|
432
|
+
if (result.entries > 0 || result.summaries > 0) this.emitChanged()
|
|
433
|
+
return result
|
|
434
|
+
}
|
|
435
|
+
|
|
362
436
|
/** Plain-text export of a scope, for the OS share sheet. */
|
|
363
437
|
async exportText(scope: "session" | "day" | "all", at?: number): Promise<string> {
|
|
364
438
|
const sessions = (await this.sessionList()).filter((s) => {
|
|
@@ -563,10 +637,45 @@ function parseArray(raw: unknown): unknown[] {
|
|
|
563
637
|
function normalize(text: string): string {
|
|
564
638
|
return text
|
|
565
639
|
.toLowerCase()
|
|
566
|
-
.replace(/[
|
|
640
|
+
.replace(/[^\p{L}\p{N}]+/gu, " ")
|
|
567
641
|
.trim()
|
|
568
642
|
}
|
|
569
643
|
|
|
644
|
+
function noteKey(entry: MemoryEntry): string {
|
|
645
|
+
return `${hourStartOf(entry.at)}:${normalize(entry.text)}`
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function parseBackup(data: unknown): {entries: MemoryEntry[]; summaries: MemorySummary[]} {
|
|
649
|
+
const raw = data as Record<string, unknown> | null
|
|
650
|
+
if (!raw || typeof raw !== "object" || raw.format !== "glassly-memory" || !Array.isArray(raw.entries)) {
|
|
651
|
+
throw new MemoryBackupError("Not a Glassly memory backup")
|
|
652
|
+
}
|
|
653
|
+
if (typeof raw.version !== "number" || raw.version > 1) {
|
|
654
|
+
throw new MemoryBackupError(`Unsupported memory backup version ${String(raw.version)}`)
|
|
655
|
+
}
|
|
656
|
+
const entries: MemoryEntry[] = []
|
|
657
|
+
for (const v of raw.entries) {
|
|
658
|
+
if (!isEntry(v) || !v.id || !v.text.trim() || !Number.isFinite(v.at)) continue
|
|
659
|
+
entries.push({
|
|
660
|
+
id: v.id,
|
|
661
|
+
kind: v.kind,
|
|
662
|
+
text: v.text.trim(),
|
|
663
|
+
at: v.at,
|
|
664
|
+
speakerId: typeof v.speakerId === "string" ? v.speakerId : null,
|
|
665
|
+
...(typeof v.resolvedSpeakerName === "string" && v.resolvedSpeakerName
|
|
666
|
+
? {resolvedSpeakerName: v.resolvedSpeakerName}
|
|
667
|
+
: {}),
|
|
668
|
+
language: typeof v.language === "string" ? v.language : null,
|
|
669
|
+
...(v.kind === "note" ? {source: v.source === "voice" || v.source === "auto" ? v.source : "ui"} : {}),
|
|
670
|
+
})
|
|
671
|
+
}
|
|
672
|
+
const summaries = (Array.isArray(raw.summaries) ? raw.summaries : [])
|
|
673
|
+
.filter(isSummary)
|
|
674
|
+
.filter((s) => s.text.trim() && Number.isFinite(s.hourStart))
|
|
675
|
+
.map((s) => ({hourStart: s.hourStart, text: s.text, createdAt: s.createdAt}))
|
|
676
|
+
return {entries, summaries}
|
|
677
|
+
}
|
|
678
|
+
|
|
570
679
|
function isEntry(v: unknown): v is MemoryEntry {
|
|
571
680
|
if (!v || typeof v !== "object") return false
|
|
572
681
|
const e = v as Record<string, unknown>
|
|
@@ -4,6 +4,8 @@ interface ExitGestureApp {
|
|
|
4
4
|
packageName: string
|
|
5
5
|
name: string
|
|
6
6
|
running: boolean
|
|
7
|
+
/** Running only for a placed widget: the wearer is not in it, so there is nothing to leave. */
|
|
8
|
+
widgetOnly?: boolean
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
interface ExitGestureState {
|
|
@@ -15,7 +17,8 @@ interface ExitGestureState {
|
|
|
15
17
|
|
|
16
18
|
export interface ExitGestureDependencies {
|
|
17
19
|
getState: () => ExitGestureState
|
|
18
|
-
|
|
20
|
+
/** The miniapp a double tap leaves right now, or null when the tap is the system's. */
|
|
21
|
+
getTargetPackageName: () => string | null
|
|
19
22
|
beginExitNotice: (packageName: string, text: string) => boolean
|
|
20
23
|
finishExitNotice: (packageName: string) => void
|
|
21
24
|
cancelExitNotice: (packageName: string) => void
|
|
@@ -29,16 +32,16 @@ export class MiniappExitGesture {
|
|
|
29
32
|
|
|
30
33
|
public constructor(private readonly deps: ExitGestureDependencies) {}
|
|
31
34
|
|
|
32
|
-
/** Returns true when the system gesture was consumed by a
|
|
33
|
-
public
|
|
35
|
+
/** Returns true when the system gesture was consumed by a miniapp exit. */
|
|
36
|
+
public tryExitMiniapp(): boolean {
|
|
34
37
|
// Swallow repeat taps while the notice is already counting down.
|
|
35
38
|
if (this.pending) return true
|
|
36
39
|
|
|
37
|
-
const packageName = this.deps.
|
|
40
|
+
const packageName = this.deps.getTargetPackageName()
|
|
38
41
|
if (!packageName) return false
|
|
39
42
|
|
|
40
43
|
const app = this.deps.getState().apps.find((candidate) => candidate.packageName === packageName)
|
|
41
|
-
if (!app?.running) return false
|
|
44
|
+
if (!app?.running || app.widgetOnly) return false
|
|
42
45
|
|
|
43
46
|
const name = app.name.trim() || packageName
|
|
44
47
|
if (!this.deps.beginExitNotice(packageName, `Exiting ${name}…`)) return false
|
|
@@ -2,25 +2,27 @@
|
|
|
2
2
|
* Widget mode: a miniapp whose background context runs only because a host
|
|
3
3
|
* (the dashboard) placed one of its widgets. It is running, but it has no
|
|
4
4
|
* card in the app switcher, draws nothing of its own on the glasses and
|
|
5
|
-
* hears no touch input; the Widgets pill lists it.
|
|
6
|
-
*
|
|
7
|
-
* tray demotes it back here instead of
|
|
5
|
+
* hears no touch input; the Widgets pill lists it. Foregrounding the app on
|
|
6
|
+
* the phone promotes it to a normal run (the store's start/setForeground);
|
|
7
|
+
* closing a placed app from the tray demotes it back here instead of
|
|
8
|
+
* stopping it.
|
|
8
9
|
*
|
|
9
10
|
* The runtime is the writer (placements come and go through it); the apps
|
|
10
11
|
* store projects `widgetOnly` from here beside `running`.
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
/** Called with the app whose mode changed and whether it is now widget-only. */
|
|
15
|
+
type Listener = (packageName: string, widgetOnly: boolean) => void
|
|
14
16
|
|
|
15
17
|
const widgetOnly = new Set<string>()
|
|
16
18
|
/** Providers with at least one widget placed by some host. */
|
|
17
19
|
const placed = new Set<string>()
|
|
18
20
|
const listeners = new Set<Listener>()
|
|
19
21
|
|
|
20
|
-
function notify(): void {
|
|
22
|
+
function notify(packageName: string, on: boolean): void {
|
|
21
23
|
for (const fn of listeners) {
|
|
22
24
|
try {
|
|
23
|
-
fn()
|
|
25
|
+
fn(packageName, on)
|
|
24
26
|
} catch (e) {
|
|
25
27
|
console.warn("MiniappWidgetMode: listener threw", e)
|
|
26
28
|
}
|
|
@@ -32,7 +34,7 @@ export const miniappWidgetMode = {
|
|
|
32
34
|
setWidgetOnly(packageName: string, on: boolean): void {
|
|
33
35
|
const changed = on ? !widgetOnly.has(packageName) : widgetOnly.delete(packageName)
|
|
34
36
|
if (on) widgetOnly.add(packageName)
|
|
35
|
-
if (changed) notify()
|
|
37
|
+
if (changed) notify(packageName, on)
|
|
36
38
|
},
|
|
37
39
|
|
|
38
40
|
isWidgetOnly(packageName: string): boolean {
|
|
@@ -51,6 +51,11 @@ export function validateOpenAiApiKey(apiKey: string): Promise<KeyValidationResul
|
|
|
51
51
|
return probe("https://api.openai.com/v1/models", {Authorization: `Bearer ${apiKey.trim()}`}, [401, 403])
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/** GET /models with the candidate key; DeepSeek answers 401 for bad keys. */
|
|
55
|
+
export function validateDeepSeekApiKey(apiKey: string): Promise<KeyValidationResult> {
|
|
56
|
+
return probe("https://api.deepseek.com/models", {Authorization: `Bearer ${apiKey.trim()}`}, [401, 403])
|
|
57
|
+
}
|
|
58
|
+
|
|
54
59
|
/** GET /v1/user with the candidate key; ElevenLabs answers 401 for bad keys. */
|
|
55
60
|
export function validateElevenLabsApiKey(apiKey: string): Promise<KeyValidationResult> {
|
|
56
61
|
return probe("https://api.elevenlabs.io/v1/user", {"xi-api-key": apiKey.trim()}, [401, 403])
|
|
@@ -66,7 +66,9 @@ const MAX_SETTLE_MS = 255 * 33
|
|
|
66
66
|
function settleTimeOf(elements: FrameElement[]): number {
|
|
67
67
|
let ms = 0
|
|
68
68
|
for (const el of elements) {
|
|
69
|
-
|
|
69
|
+
// Entering and leaving elements tween too, from or to their offset.
|
|
70
|
+
const edge = el.transition?.exit !== undefined || (el.change === "created" && el.transition?.enter !== undefined)
|
|
71
|
+
if (el.change !== "updated" && el.change !== "moved" && !edge) continue
|
|
70
72
|
const t = el.transition?.durationMs ?? 0
|
|
71
73
|
// Rotation has its own millisecond clock, beyond the geometry frame limit.
|
|
72
74
|
const duration = Math.min(t, el.rotation !== undefined ? 65535 : MAX_SETTLE_MS)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screenshot fixture — dresses Phone Mode up as a paired Even Realities G2
|
|
3
|
+
* with a Meta Neural Band and an R1 ring, for the store-screenshot build only
|
|
4
|
+
* (EXPO_PUBLIC_SCREENSHOT_MODE=1, set by scripts/maestro-screenshots-*.mjs
|
|
5
|
+
* for its own Release build and never by a shipped one).
|
|
6
|
+
*
|
|
7
|
+
* The fixture is applied where hosts READ — engine.pairing.identity and
|
|
8
|
+
* engine.glasses.status/info/controllers.* — not where native writes: the
|
|
9
|
+
* Simulated driver keeps running underneath (so the mirror has frames),
|
|
10
|
+
* nothing is persisted, and native never learns of the fixture, so no
|
|
11
|
+
* reconnect, demotion or driver path can act on it. It is active only while
|
|
12
|
+
* the underlying identity is the simulated device: home enters Phone Mode as
|
|
13
|
+
* usual (identity `none` passes through untouched), the simulated pairing
|
|
14
|
+
* then flips the read-models to the fixture, and real glasses on a fixture
|
|
15
|
+
* build stay real.
|
|
16
|
+
*/
|
|
17
|
+
import type {ControllerStatus, PairedController} from "@glassly/bluetooth-sdk"
|
|
18
|
+
|
|
19
|
+
import {ControllerTypes, DeviceTypes} from "../types/enums"
|
|
20
|
+
import type {IdentitySnapshot} from "./PairingIdentity"
|
|
21
|
+
|
|
22
|
+
export function isScreenshotMode(): boolean {
|
|
23
|
+
return process.env.EXPO_PUBLIC_SCREENSHOT_MODE === "1"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const FIXTURE_GLASSES = {
|
|
27
|
+
model: DeviceTypes.G2,
|
|
28
|
+
name: "Even G2_7F3A",
|
|
29
|
+
battery: 87,
|
|
30
|
+
caseBattery: 100,
|
|
31
|
+
signal: -58,
|
|
32
|
+
} as const
|
|
33
|
+
|
|
34
|
+
interface FixtureController extends PairedController {
|
|
35
|
+
batteryLevel: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The R1's id is its ring id (from the BLE name); the band's is an opaque
|
|
39
|
+
// peripheral id. Both are stable fixtures so flows can target
|
|
40
|
+
// `controller-tile-<id>`.
|
|
41
|
+
export const FIXTURE_CONTROLLERS: readonly FixtureController[] = [
|
|
42
|
+
{id: "7C1A2B9E", model: ControllerTypes.NEURAL_BAND, name: "Meta Neural Band", batteryLevel: 73},
|
|
43
|
+
{id: "CEC5BA", model: ControllerTypes.R1, name: "CEC5BA", batteryLevel: 58},
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
/** The fixture dresses Phone Mode only. */
|
|
47
|
+
export function fixtureActive(identity: IdentitySnapshot): boolean {
|
|
48
|
+
return isScreenshotMode() && identity.kind === "paired" && identity.model.includes(DeviceTypes.SIMULATED)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function fixtureIdentity(identity: IdentitySnapshot): IdentitySnapshot {
|
|
52
|
+
if (!fixtureActive(identity)) return identity
|
|
53
|
+
return {kind: "paired", model: FIXTURE_GLASSES.model, name: FIXTURE_GLASSES.name}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface StatusLike {
|
|
57
|
+
battery: number
|
|
58
|
+
charging: boolean
|
|
59
|
+
case: {battery: number; charging: boolean; open: boolean; removed: boolean}
|
|
60
|
+
signal: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function fixtureStatus<T extends StatusLike>(status: T, identity: IdentitySnapshot): T {
|
|
64
|
+
if (!fixtureActive(identity)) return status
|
|
65
|
+
return {
|
|
66
|
+
...status,
|
|
67
|
+
battery: FIXTURE_GLASSES.battery,
|
|
68
|
+
charging: false,
|
|
69
|
+
case: {battery: FIXTURE_GLASSES.caseBattery, charging: false, open: false, removed: false},
|
|
70
|
+
signal: FIXTURE_GLASSES.signal,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface InfoLike {
|
|
75
|
+
model: string
|
|
76
|
+
firmwareUpdateAvailable: boolean
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function fixtureInfo<T extends InfoLike>(info: T, identity: IdentitySnapshot): T {
|
|
80
|
+
if (!fixtureActive(identity)) return info
|
|
81
|
+
return {...info, model: FIXTURE_GLASSES.model, firmwareUpdateAvailable: false}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Fixture controllers first, then any real records that are not the fixture's. */
|
|
85
|
+
export function fixtureControllerList(list: PairedController[], identity: IdentitySnapshot): PairedController[] {
|
|
86
|
+
if (!fixtureActive(identity)) return list
|
|
87
|
+
const fixture = FIXTURE_CONTROLLERS.map(({id, model, name}) => ({id, model, name}))
|
|
88
|
+
const ids = new Set(fixture.map((record) => record.id))
|
|
89
|
+
return [...fixture, ...list.filter((record) => !ids.has(record.id))]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function fixtureControllerStatuses(
|
|
93
|
+
statuses: Record<string, ControllerStatus>,
|
|
94
|
+
identity: IdentitySnapshot,
|
|
95
|
+
): Record<string, ControllerStatus> {
|
|
96
|
+
if (!fixtureActive(identity)) return statuses
|
|
97
|
+
const result = {...statuses}
|
|
98
|
+
for (const {id, model, name, batteryLevel} of FIXTURE_CONTROLLERS) {
|
|
99
|
+
result[id] = {
|
|
100
|
+
id,
|
|
101
|
+
model,
|
|
102
|
+
name,
|
|
103
|
+
connected: true,
|
|
104
|
+
fullyBooted: true,
|
|
105
|
+
searching: false,
|
|
106
|
+
batteryLevel,
|
|
107
|
+
charging: false,
|
|
108
|
+
macAddress: "",
|
|
109
|
+
signalStrength: -62,
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result
|
|
113
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {MiniappErrorCode} from "@glassly/miniapp"
|
|
2
|
+
|
|
3
|
+
import type {ClientApp} from "../types/applet"
|
|
4
|
+
|
|
5
|
+
export type ActionError = {code: string; message: string} & Record<string, unknown>
|
|
6
|
+
export type ActionOutcome = {ok: true; data: unknown} | {ok: false; error: ActionError}
|
|
7
|
+
|
|
8
|
+
export const actionFailure = (code: string, message: string): ActionOutcome => ({ok: false, error: {code, message}})
|
|
9
|
+
|
|
10
|
+
/** Installed, compatible, and declares the action. */
|
|
11
|
+
export function appDeclaresAction(app: ClientApp | undefined, actionId: string): boolean {
|
|
12
|
+
if (!app) return false
|
|
13
|
+
if (app.compatibility && app.compatibility.isCompatible === false) return false
|
|
14
|
+
return (app.actions ?? []).some((a) => a.id === actionId)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Gates shared by miniapp->miniapp invoke and host invoke. Declared actions are
|
|
19
|
+
* public by default; `visibility: "system"` ones need a system caller.
|
|
20
|
+
*/
|
|
21
|
+
export function checkActionAccess(
|
|
22
|
+
app: ClientApp | undefined,
|
|
23
|
+
target: string | undefined,
|
|
24
|
+
actionId: string | undefined,
|
|
25
|
+
callerIsSystem: boolean,
|
|
26
|
+
): ActionError | null {
|
|
27
|
+
if (!target || !app) {
|
|
28
|
+
return {
|
|
29
|
+
code: MiniappErrorCode.APP_NOT_FOUND,
|
|
30
|
+
message: `Miniapp not found: ${target ?? "(missing targetPackageName)"}`,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!actionId) return {code: MiniappErrorCode.ACTION_NOT_FOUND, message: "invoke requires an actionId"}
|
|
34
|
+
if (app.compatibility && app.compatibility.isCompatible === false) {
|
|
35
|
+
return {
|
|
36
|
+
code: MiniappErrorCode.APP_NOT_COMPATIBLE,
|
|
37
|
+
message: `${target} is not compatible with the connected glasses`,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const declared = (app.actions ?? []).find((a) => a.id === actionId)
|
|
41
|
+
if (!declared) {
|
|
42
|
+
return {code: MiniappErrorCode.ACTION_NOT_FOUND, message: `${target} does not declare action "${actionId}"`}
|
|
43
|
+
}
|
|
44
|
+
if (declared.visibility === "system" && !callerIsSystem) {
|
|
45
|
+
return {
|
|
46
|
+
code: MiniappErrorCode.NOT_PERMITTED,
|
|
47
|
+
message: `Action "${actionId}" on ${target} is restricted to system apps`,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** BgTimer's shape; passed in so this module stays loadable without the native timer. */
|
|
54
|
+
export interface ActionTimers {
|
|
55
|
+
setTimeout: (cb: () => void, ms: number) => number
|
|
56
|
+
clearTimeout: (id: number) => void
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Outstanding ACTION_CALLs awaiting an ACTION_RESULT, keyed by host-generated callId. */
|
|
60
|
+
export class PendingActionCalls {
|
|
61
|
+
private readonly calls = new Map<string, {target: string; settle: (o: ActionOutcome) => void; timer: number}>()
|
|
62
|
+
|
|
63
|
+
constructor(
|
|
64
|
+
private readonly timers: ActionTimers,
|
|
65
|
+
private readonly payloadTooLarge: (value: unknown) => boolean,
|
|
66
|
+
) {}
|
|
67
|
+
|
|
68
|
+
/** Register a call; resolves with its outcome, or ACTION_TIMEOUT. */
|
|
69
|
+
register(callId: string, target: string, actionId: string, timeoutMs: number): Promise<ActionOutcome> {
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
const timer = this.timers.setTimeout(() => {
|
|
72
|
+
this.calls.delete(callId)
|
|
73
|
+
resolve(actionFailure(MiniappErrorCode.ACTION_TIMEOUT, `action "${actionId}" timed out after ${timeoutMs}ms`))
|
|
74
|
+
}, timeoutMs)
|
|
75
|
+
this.calls.set(callId, {target, settle: resolve, timer})
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Settle from a target's ACTION_RESULT payload. Only the invoked target may resolve a call. */
|
|
80
|
+
handleResult(targetPackageName: string, payload: Record<string, unknown>): void {
|
|
81
|
+
const callId = payload.callId as string | undefined
|
|
82
|
+
if (!callId) return
|
|
83
|
+
const pending = this.calls.get(callId)
|
|
84
|
+
// callIds are guessable (timestamp + seq): a different miniapp must not spoof someone else's result.
|
|
85
|
+
if (!pending || pending.target !== targetPackageName) return
|
|
86
|
+
this.calls.delete(callId)
|
|
87
|
+
this.timers.clearTimeout(pending.timer)
|
|
88
|
+
|
|
89
|
+
if (payload.ok === true) {
|
|
90
|
+
if (this.payloadTooLarge(payload.result)) {
|
|
91
|
+
pending.settle(actionFailure(MiniappErrorCode.PAYLOAD_TOO_LARGE, "action result exceeded the 256 KB cap"))
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
pending.settle({ok: true, data: payload.result ?? null})
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
pending.settle({
|
|
98
|
+
ok: false,
|
|
99
|
+
error: (payload.error as ActionError | undefined) ?? {
|
|
100
|
+
code: MiniappErrorCode.INTERNAL,
|
|
101
|
+
message: "action handler error",
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
}
|