@glassly/engine 0.3.7-dev.964 → 0.3.7-dev.969
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/JSRouter.d.ts +9 -3
- package/build/services/JSRouter.d.ts.map +1 -1
- package/build/services/JSRouter.js +21 -5
- package/build/services/JSRouter.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +7 -0
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +27 -18
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +2 -0
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/pages/pageEligibility.js +1 -1
- package/build/services/pages/pageEligibility.js.map +1 -1
- package/build/services/settingsBridge.d.ts.map +1 -1
- package/build/services/settingsBridge.js +1 -0
- package/build/services/settingsBridge.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +7 -0
- package/build/stores/settings.js.map +1 -1
- package/package.json +7 -7
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/services/JSRouter.ts +23 -5
- package/src/services/LocalMiniappRuntime.ts +26 -19
- package/src/services/MiniappLauncher.ts +2 -0
- package/src/services/pages/pageEligibility.ts +1 -1
- package/src/services/settingsBridge.ts +1 -0
- package/src/stores/settings.ts +7 -0
package/src/services/JSRouter.ts
CHANGED
|
@@ -146,6 +146,9 @@ export class JSRouter {
|
|
|
146
146
|
*/
|
|
147
147
|
uiRouter: UIRouter | null = null
|
|
148
148
|
|
|
149
|
+
/** Teardowns in progress, so a stop and a racing start or stop see the same one. */
|
|
150
|
+
private readonly unregistering = new Map<string, Promise<void>>()
|
|
151
|
+
|
|
149
152
|
constructor(
|
|
150
153
|
private readonly runtime: LocalMiniappRuntime,
|
|
151
154
|
private readonly crust: JSCrustBinding,
|
|
@@ -351,12 +354,26 @@ export class JSRouter {
|
|
|
351
354
|
}
|
|
352
355
|
|
|
353
356
|
/**
|
|
354
|
-
* Tear down a JSContext and unregister from the runtime.
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
+
* Tear down a JSContext and unregister from the runtime. The session's
|
|
358
|
+
* `onBeforeDisconnect` handlers get a short grace window first, so a
|
|
359
|
+
* miniapp can clear what it drew and end its own work while its messages
|
|
360
|
+
* still reach the host. Concurrent calls share one teardown.
|
|
357
361
|
*/
|
|
358
|
-
|
|
359
|
-
if (!this.registered.has(packageName)) return
|
|
362
|
+
unregister(packageName: string): Promise<void> {
|
|
363
|
+
if (!this.registered.has(packageName)) return Promise.resolve()
|
|
364
|
+
const inFlight = this.unregistering.get(packageName)
|
|
365
|
+
if (inFlight) return inFlight
|
|
366
|
+
const teardown = this.teardown(packageName).finally(() => this.unregistering.delete(packageName))
|
|
367
|
+
this.unregistering.set(packageName, teardown)
|
|
368
|
+
return teardown
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Resolves once an in-flight {@link unregister} of this package has finished. */
|
|
372
|
+
async whenUnregistered(packageName: string): Promise<void> {
|
|
373
|
+
await this.unregistering.get(packageName)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private async teardown(packageName: string): Promise<void> {
|
|
360
377
|
// Cancel any pending crash-respawn before tearing down — we don't
|
|
361
378
|
// want a stale timer to re-spawn a miniapp the user just disabled.
|
|
362
379
|
const pending = this.respawnTimers.get(packageName)
|
|
@@ -364,6 +381,7 @@ export class JSRouter {
|
|
|
364
381
|
BgTimer.clearTimeout(pending)
|
|
365
382
|
this.respawnTimers.delete(packageName)
|
|
366
383
|
}
|
|
384
|
+
await this.runtime.notifyWillDisconnect(packageName, "stopped")
|
|
367
385
|
this.spawnCache.delete(packageName)
|
|
368
386
|
this.crashController?.onKill(packageName)
|
|
369
387
|
// User-initiated close/disable: end any active nav trip so the glasses
|
|
@@ -340,6 +340,8 @@ const SYSTEM_MINIAPP_PACKAGES = new Set([
|
|
|
340
340
|
"com.glassly.miniappdev",
|
|
341
341
|
])
|
|
342
342
|
const PING_INTERVAL_MS = 5_000
|
|
343
|
+
/** How long a stopping miniapp's `beforeDisconnect` handlers get before teardown. */
|
|
344
|
+
const WILL_DISCONNECT_GRACE_MS = 100
|
|
343
345
|
// Min gap between "transcription limit reached" notices on the glasses. A
|
|
344
346
|
// debounce, not a cooldown: the cloud re-announces the block per refused
|
|
345
347
|
// subscribe, and miniapps retry in bursts — this only coalesces those bursts
|
|
@@ -1249,21 +1251,26 @@ class LocalMiniappRuntime {
|
|
|
1249
1251
|
* undeliverable anyway.
|
|
1250
1252
|
*/
|
|
1251
1253
|
public async gracefullyUnregisterApp(packageName: string, reason = "unregistering"): Promise<void> {
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
console.log(`${LOG_TAG}: gracefullyUnregisterApp(${packageName}) — sending WILL_DISCONNECT`)
|
|
1256
|
-
this.sendToMiniapp(packageName, {
|
|
1257
|
-
type: MiniappResponseType.WILL_DISCONNECT,
|
|
1258
|
-
reason,
|
|
1259
|
-
})
|
|
1260
|
-
// 50 ms: imperceptible to the user, plenty of time for the SDK's
|
|
1261
|
-
// synchronous `beforeDisconnect` handlers to flush a final
|
|
1262
|
-
// sendOneShot through the still-open transport.
|
|
1263
|
-
await new Promise<void>((resolve) => BgTimer.setTimeout(resolve, 50))
|
|
1254
|
+
if (!this.connectedApps.has(packageName)) return
|
|
1255
|
+
await this.notifyWillDisconnect(packageName, reason)
|
|
1264
1256
|
this.unregisterApp(packageName)
|
|
1265
1257
|
}
|
|
1266
1258
|
|
|
1259
|
+
/**
|
|
1260
|
+
* Send `WILL_DISCONNECT` and wait out the grace window, so the session's
|
|
1261
|
+
* `beforeDisconnect` handlers run (Navigation clears its HUD and ends its
|
|
1262
|
+
* trip) and their last messages land before teardown. No-op unless the
|
|
1263
|
+
* app finished its handshake.
|
|
1264
|
+
*/
|
|
1265
|
+
public async notifyWillDisconnect(packageName: string, reason: string): Promise<void> {
|
|
1266
|
+
if (!this.connectedApps.has(packageName) || !this.handshookApps.has(packageName)) return
|
|
1267
|
+
console.log(`${LOG_TAG}: ${packageName} — sending WILL_DISCONNECT (${reason})`)
|
|
1268
|
+
this.sendToMiniapp(packageName, {type: MiniappResponseType.WILL_DISCONNECT, reason})
|
|
1269
|
+
// The message and the replies it triggers each cross a JSContext hop;
|
|
1270
|
+
// short enough not to slow an app switch noticeably.
|
|
1271
|
+
await new Promise<void>((resolve) => BgTimer.setTimeout(resolve, WILL_DISCONNECT_GRACE_MS))
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1267
1274
|
public unregisterApp(packageName: string, opts?: {endNavigation?: boolean}): void {
|
|
1268
1275
|
console.log(`${LOG_TAG}: unregisterApp(${packageName})`)
|
|
1269
1276
|
const releasedMicGateOverride = micStateCoordinator.clearMiniappGateOverrides(packageName)
|
|
@@ -6087,14 +6094,14 @@ class LocalMiniappRuntime {
|
|
|
6087
6094
|
if (!app) return false
|
|
6088
6095
|
// session.miniapps.start comes from the glasses (the dashboard's Apps menu,
|
|
6089
6096
|
// an Assist voice command), unlike a headless action wake, which carries no source.
|
|
6097
|
+
// The full start, as a phone open runs it: the standard peer stops so this
|
|
6098
|
+
// app owns the display, and an app already running hears the new launch.
|
|
6090
6099
|
const launchSource = "glasses" as const
|
|
6091
|
-
|
|
6092
|
-
if (
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
}
|
|
6097
|
-
return useAppStatusStore.getState().start(app, {skipNavigation: true, launchSource})
|
|
6100
|
+
const started = await useAppStatusStore.getState().start(app, {skipNavigation: true, launchSource})
|
|
6101
|
+
if (!started) return false
|
|
6102
|
+
// The caller may message the app next; start() resolves at spawn.
|
|
6103
|
+
if (app.local) await miniappLauncher.ensureConnected(packageName, undefined, {launchSource})
|
|
6104
|
+
return true
|
|
6098
6105
|
}
|
|
6099
6106
|
|
|
6100
6107
|
private stopInteropApp(packageName: string): Promise<void> {
|
|
@@ -233,6 +233,8 @@ class MiniappLauncher {
|
|
|
233
233
|
*/
|
|
234
234
|
async ensureRunning(packageName: string, hints?: LaunchHints): Promise<LaunchResult> {
|
|
235
235
|
const router = this.requireRouter()
|
|
236
|
+
// A context still tearing down from a stop is not one to reuse.
|
|
237
|
+
await router.whenUnregistered(packageName)
|
|
236
238
|
|
|
237
239
|
// Already spawned: best-effort resolve for the UI entry, never throw —
|
|
238
240
|
// a headless caller that doesn't need UI gets {null, null} fast even if
|
|
@@ -29,7 +29,7 @@ export function isCustomDashboardEligible(info: {model?: string | null; firmware
|
|
|
29
29
|
if (info.model === DeviceTypes.MEMOMIND_ONE) {
|
|
30
30
|
const match = /^GLASSLYCFW\/([0-9]+)$/.exec(info.firmwareVersion ?? "")
|
|
31
31
|
const revision = match ? Number(match[1]) : 0
|
|
32
|
-
return Number.isSafeInteger(revision) && revision >=
|
|
32
|
+
return Number.isSafeInteger(revision) && revision >= 7
|
|
33
33
|
}
|
|
34
34
|
return (
|
|
35
35
|
getModelCapabilities(info.model as DeviceTypes).hasCustomFirmware === true &&
|
|
@@ -38,6 +38,7 @@ export const SETTINGS_BRIDGE_KEYS: Readonly<Record<string, SettingsBridgeAccess>
|
|
|
38
38
|
notifications_enabled: "write",
|
|
39
39
|
notifications_popups: "write",
|
|
40
40
|
notifications_popup_seconds: "write",
|
|
41
|
+
notifications_batch_seconds: "write",
|
|
41
42
|
notifications_hold_during_apps: "write",
|
|
42
43
|
notifications_ignore_while_active: "write",
|
|
43
44
|
notifications_blocklist: "write",
|
package/src/stores/settings.ts
CHANGED
|
@@ -1147,6 +1147,13 @@ export const SETTINGS: Record<string, Setting> = {
|
|
|
1147
1147
|
saveOnServer: true,
|
|
1148
1148
|
persist: true,
|
|
1149
1149
|
},
|
|
1150
|
+
notifications_batch_seconds: {
|
|
1151
|
+
key: "notifications_batch_seconds",
|
|
1152
|
+
defaultValue: () => 0,
|
|
1153
|
+
writable: true,
|
|
1154
|
+
saveOnServer: true,
|
|
1155
|
+
persist: true,
|
|
1156
|
+
},
|
|
1150
1157
|
// Hold popups while another glasses feature (navigation, a miniapp) is on screen.
|
|
1151
1158
|
notifications_hold_during_apps: {
|
|
1152
1159
|
key: "notifications_hold_during_apps",
|