@mentra/engine 3.2.0-dev.261 → 3.2.0-dev.262
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/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/AcsMeetingService.d.ts +25 -1
- package/build/services/AcsMeetingService.d.ts.map +1 -1
- package/build/services/AcsMeetingService.js +41 -3
- package/build/services/AcsMeetingService.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +32 -3
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +284 -30
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MiniappLiveness.d.ts +11 -0
- package/build/services/MiniappLiveness.d.ts.map +1 -1
- package/build/services/MiniappLiveness.js +11 -0
- package/build/services/MiniappLiveness.js.map +1 -1
- package/build/services/PhoneStreamCoordinator.d.ts +13 -1
- package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
- package/build/services/PhoneStreamCoordinator.js +27 -4
- package/build/services/PhoneStreamCoordinator.js.map +1 -1
- package/build/services/SoftapCallTransport.d.ts +87 -7
- package/build/services/SoftapCallTransport.d.ts.map +1 -1
- package/build/services/SoftapCallTransport.js +229 -28
- package/build/services/SoftapCallTransport.js.map +1 -1
- package/build/utils/miniappGlobals.d.ts.map +1 -1
- package/build/utils/miniappGlobals.js +13 -11
- package/build/utils/miniappGlobals.js.map +1 -1
- package/package.json +8 -8
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/services/AcsMeetingService.ts +51 -3
- package/src/services/LocalMiniappRuntime.ts +296 -30
- package/src/services/MiniappLiveness.ts +18 -0
- package/src/services/PhoneStreamCoordinator.ts +27 -5
- package/src/services/SoftapCallTransport.ts +274 -32
- package/src/utils/miniappGlobals.ts +13 -11
|
@@ -423,6 +423,7 @@ declare class LocalMiniappRuntime {
|
|
|
423
423
|
private checkpointSoftapAttempt;
|
|
424
424
|
/** Publishes a checklist snapshot, unless this attempt has already been superseded. */
|
|
425
425
|
private emitSoftapProgress;
|
|
426
|
+
private softapRecoveryFields;
|
|
426
427
|
/**
|
|
427
428
|
* Narrates preflight work on the first row while it is still pending.
|
|
428
429
|
*
|
|
@@ -433,6 +434,23 @@ declare class LocalMiniappRuntime {
|
|
|
433
434
|
*/
|
|
434
435
|
private narrateSoftapPreflight;
|
|
435
436
|
private runSoftapAttempt;
|
|
437
|
+
/**
|
|
438
|
+
* SoftAP loss is a media outage. Keep ACS, destroy generation N, wait for the glasses,
|
|
439
|
+
* then build generation N+1. Duplicate losses do not renew the deadline.
|
|
440
|
+
*/
|
|
441
|
+
private beginSoftapRecovery;
|
|
442
|
+
/**
|
|
443
|
+
* Wait for a link the hotspot commands can actually use.
|
|
444
|
+
*
|
|
445
|
+
* The hotspot goes away a couple of seconds before the phone declares the BLE link dead, so the
|
|
446
|
+
* snapshot at loss time still reads "ready". Taking that at face value sent `set_hotspot_state`
|
|
447
|
+
* into a dying link twice, and each one burned its full timeout before the wearer was even back.
|
|
448
|
+
* Requiring the link to hold steady turns that into waiting, which is what the budget is for.
|
|
449
|
+
*/
|
|
450
|
+
private waitForGlassesReturn;
|
|
451
|
+
private awaitGlassesReady;
|
|
452
|
+
/** True when the link stays ready for [ms]; false the moment it drops again. */
|
|
453
|
+
private glassesLinkHoldsSteady;
|
|
436
454
|
/**
|
|
437
455
|
* Cancels a SoftAP attempt and does not return until everything it owns has been released.
|
|
438
456
|
*
|
|
@@ -457,6 +475,17 @@ declare class LocalMiniappRuntime {
|
|
|
457
475
|
* is what lets the next call start on leaked state.
|
|
458
476
|
*/
|
|
459
477
|
private teardownSoftapAttempt;
|
|
478
|
+
/**
|
|
479
|
+
* Send one hotspot command, re-sending it once if a session refresh cancelled it.
|
|
480
|
+
*
|
|
481
|
+
* The retry lives inside the unit chained onto [glassesHotspotCommand] rather than around it,
|
|
482
|
+
* which is what holds the invariant: one logical hotspot operation issues at most two native
|
|
483
|
+
* commands, and no later operation runs between them. A retry outside the queue would let an
|
|
484
|
+
* `on` land between the two halves of an `off` and leave the glasses in the opposite state.
|
|
485
|
+
*
|
|
486
|
+
* Re-sending is free because ASG compares against current state first and answers a same-state
|
|
487
|
+
* command with its current status instead of cycling the AP.
|
|
488
|
+
*/
|
|
460
489
|
private setGlassesHotspotState;
|
|
461
490
|
/**
|
|
462
491
|
* The last two gates, and the point where a timeout stops being treated as consent.
|
|
@@ -468,9 +497,9 @@ declare class LocalMiniappRuntime {
|
|
|
468
497
|
* failure — the listener was still inside its 3s tombstone and the glasses AP was still up.
|
|
469
498
|
*
|
|
470
499
|
* A bound that expires is not success. It means the resource is still held by a call nobody is
|
|
471
|
-
* in any more, so it is taken by force and re-verified.
|
|
472
|
-
*
|
|
473
|
-
*
|
|
500
|
+
* in any more, so it is taken by force and re-verified. An ingest port that is still held
|
|
501
|
+
* refuses the next join. A glasses hotspot that missed its off-ack does not — the next join
|
|
502
|
+
* wants the AP up and will raise it again.
|
|
474
503
|
*
|
|
475
504
|
* @returns failures to record; a non-empty list refuses the next join.
|
|
476
505
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalMiniappRuntime.d.ts","sourceRoot":"","sources":["../../src/services/LocalMiniappRuntime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAuEH,OAAO,EAA+B,KAAK,gCAAgC,EAAC,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"LocalMiniappRuntime.d.ts","sourceRoot":"","sources":["../../src/services/LocalMiniappRuntime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAuEH,OAAO,EAA+B,KAAK,gCAAgC,EAAC,MAAM,6BAA6B,CAAA;AAqC/G,MAAM,WAAW,wBAAwB;IACvC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;4EACwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAC,CAAA;IAC1C,WAAW,CAAC,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAA;IAC7E,oBAAoB,CAAC,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAA;IACjF,OAAO,CAAC,EAAE,KAAK,CAAC;QAAC,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAA;KAAC,CAAC,CAAA;CACrG;AAgDD,KAAK,wBAAwB,GAAG,OAAO,GAAG,OAAO,CAAA;AAuUjD,cAAM,mBAAmB;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAmC;IAE1D;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa,CAA6B;IAClD;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB,CAAI;IAC5B;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB,CAAsB;IAChD;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB,CAAsC;IAEnE,+CAA+C;IAC/C,OAAO,CAAC,aAAa,CAA2C;IAEhE;;;;;OAKG;IACH,OAAO,CAAC,aAAa,CAAyB;IAE9C,sEAAsE;IACtE,OAAO,CAAC,cAAc,CAAqD;IAE3E,sEAAsE;IACtE,OAAO,CAAC,iBAAiB,CAAsC;IAE/D,gEAAgE;IAChE,OAAO,CAAC,8BAA8B,CAA8C;IAEpF,0EAA0E;IAC1E,OAAO,CAAC,iBAAiB,CAAQ;IACjC,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,0BAA0B,CAAmC;IAErE;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB,CAA8B;IAE7D,4BAA4B;IAC5B,OAAO,CAAC,cAAc,CAAsB;IAC5C,OAAO,CAAC,qBAAqB,CAAiC;IAE9D;;;;;;;OAOG;IACI,iBAAiB,EAAE,CAAC,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAO;IAEvE,OAAO,CAAC,UAAU,CAA+B;IAKjD,OAAO,CAAC,WAAW,CAAqE;IACxF,OAAO,CAAC,UAAU,CAAoB;IAEtC,OAAO;IAEP,0EAA0E;IACnE,yBAAyB,IAAI,MAAM,EAAE;IAI5C,oDAAoD;IAC7C,+BAA+B,CAAC,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,IAAI,GAAG,MAAM,IAAI;IAK9F,+DAA+D;IACxD,qBAAqB,CAAC,KAAK,SAAa,GAAG,gCAAgC;IA8ClF,OAAO,CAAC,wBAAwB;IAmChC;;;;OAIG;IACI,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM;IAYtE;;OAEG;IACI,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,GAAG,IAAI;IAoCtF;;;;OAIG;IACH,OAAO,CAAC,QAAQ;WAoBF,WAAW,IAAI,mBAAmB;IAOhD,OAAO,CAAC,WAAW,CAAQ;IAE3B;;;OAGG;IACI,UAAU,IAAI,IAAI;IAWlB,WAAW,CAChB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAC7B,iBAAiB,CAAC,EAAE,wBAAwB,GAC3C,IAAI;IAiFP;;;;;;;OAOG;IACI,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB5E,+EAA+E;IAC/E,OAAO,CAAC,mBAAmB;IAO3B;;;;;;OAMG;IACI,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAKhD;;;;OAIG;IACI,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,iBAAiB,EAAE,wBAAwB,GAAG,IAAI;IAcnG;;;;;;;;OAQG;IACU,uBAAuB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,SAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB3F,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IA4GxC,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;YAiXjD,aAAa;YA8Db,kBAAkB;YAYlB,iBAAiB;YAuBjB,kBAAkB;IAWhC,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,0BAA0B;IAiBlC,OAAO,CAAC,uBAAuB;IAQ/B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,yBAAyB;IA4BjC,OAAO,CAAC,+BAA+B;IAavC,OAAO,CAAC,6BAA6B;IAOrC;;;;;;OAMG;IACH,OAAO,CAAC,0BAA0B;IAWlC;;;;;OAKG;IACH,OAAO,CAAC,yBAAyB;IAuCjC,OAAO,CAAC,eAAe;YAgIT,wBAAwB;IA+BtC;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAgF5B;;;;OAIG;YACW,kBAAkB;IAsBhC,OAAO,CAAC,aAAa;IAoCrB;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAiCpB,OAAO,CAAC,eAAe;IAiDvB,OAAO,CAAC,eAAe;IASvB,4EAA4E;IAC5E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,2BAA2B,CAAiC;IACpF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,+BAA+B,CAAkC;IACzF,OAAO,CAAC,oBAAoB,CAAI;YAElB,uBAAuB;IAiGrC,qFAAqF;IACrF,OAAO,CAAC,mBAAmB;YAOb,wBAAwB;YAuCxB,wBAAwB;YA0BxB,wBAAwB;YAsBxB,WAAW;IAmSzB,OAAO,CAAC,YAAY;YASN,YAAY;YA6BZ,kBAAkB;IAoDhC;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAGlC;IAED;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAIxB;IACF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAmC;IAErE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAG5B;IAEF;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IA0C1B,OAAO,CAAC,sBAAsB;IAQ9B;;;OAGG;IACH,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,4BAA4B;IAapC;;;;;OAKG;IACH,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,wBAAwB;IAOhC;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB,CAA4B;IAE3D;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,qBAAqB;YAuBf,gBAAgB;YAyBhB,gBAAgB;YAyBhB,mBAAmB;YAyBnB,iBAAiB;YAiBjB,kBAAkB;YAalB,gBAAgB;YAyBhB,mBAAmB;YAanB,wBAAwB;YA6BxB,qBAAqB;YA8BrB,eAAe;IAmC7B;;;;;;;;;OASG;IACH,OAAO,CAAC,mBAAmB;IAgB3B;;;;OAIG;YACW,sBAAsB;IAwCpC;;;;OAIG;YACW,+BAA+B;IA6C7C;;;OAGG;YACW,YAAY;YAmBZ,WAAW;YAqCX,aAAa;YAkBb,mBAAmB;YAsBnB,cAAc;IA4C5B;;;;OAIG;IACH,OAAO,CAAC,yBAAyB;IAmBjC;;;;;;;OAOG;YACW,mBAAmB;YAiBnB,WAAW;YA6CX,kBAAkB;YAuClB,yBAAyB;YAoCzB,wBAAwB;IAmBtC;;;;;OAKG;YACW,iBAAiB;YA6CjB,gBAAgB;YAwBhB,wBAAwB;YAiDxB,uBAAuB;IAQrC,OAAO,CAAC,wBAAwB;IAgBhC,0FAA0F;YAC5E,kBAAkB;YASlB,iBAAiB;IAsI/B;;;;;;;;OAQG;YACW,iBAAiB;IAiC/B,OAAO,CAAC,mBAAmB;IAuC3B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,uBAAuB;IAe/B,uFAAuF;IACvF,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,oBAAoB;IAiB5B;;;;;;;OAOG;IACH,OAAO,CAAC,sBAAsB;YAQhB,gBAAgB;IAiN9B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IA4F3B;;;;;;;OAOG;YACW,oBAAoB;IAclC,OAAO,CAAC,iBAAiB;IAazB,gFAAgF;IAChF,OAAO,CAAC,sBAAsB;IAyB9B;;;;;;;;;;OAUG;YACW,mBAAmB;IAyDjC;;;;;;;;;;OAUG;YACW,qBAAqB;IAkKnC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,sBAAsB;IAoB9B;;;;;;;;;;;;;;;OAeG;YACW,oBAAoB;IAsDlC;;;;;OAKG;YACW,kBAAkB;YAkDlB,kBAAkB;IA4ChC;;;;;OAKG;YACW,gBAAgB;YAqChB,qBAAqB;YA+BrB,8BAA8B;YAyB9B,qBAAqB;IAYnC;;;;OAIG;YACW,sBAAsB;IAwBpC;;;;OAIG;IACI,yBAAyB,IAAI,IAAI;IAqBxC;;;;;OAKG;IACI,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAC;IAsCvF,OAAO,CAAC,qBAAqB;IAU7B,OAAO,CAAC,wBAAwB;IAehC;;;;;;OAMG;IACH,OAAO,CAAC,wBAAwB;IAuDhC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,4BAA4B;IA+BpC,OAAO,CAAC,oBAAoB;IAoB5B;;;;;;OAMG;IACH,OAAO,CAAC,uBAAuB;IAsD/B,OAAO,CAAC,sBAAsB;IAuB9B,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,wBAAwB;IAYhC;;;;;;OAMG;IACI,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,mBAAmB,CAAC,EAAE,wBAAwB,GAAG,IAAI;IA+H5G,OAAO,CAAC,qCAAqC;IAc7C,OAAO,CAAC,8BAA8B;IAkBtC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA0B3B;;OAEG;IACH;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IA4BvB,OAAO,CAAC,aAAa;IA0BrB;;OAEG;IAKH,wEAAwE;IACxE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAa;IAEvD,sEAAsE;IACtE,OAAO,CAAC,WAAW,CAGhB;IACH,OAAO,CAAC,aAAa,CAAI;IAEzB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,eAAe;YAYT,eAAe;IAU7B,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,cAAc;IAItB,oFAAoF;IACpF,OAAO,CAAC,YAAY;IAepB,+EAA+E;IAC/E,OAAO,CAAC,mBAAmB;IAe3B,sFAAsF;IACtF,OAAO,CAAC,gBAAgB;IAiBxB,OAAO,CAAC,kBAAkB;YAgBZ,mBAAmB;YAgFnB,kBAAkB;IA0BhC,sEAAsE;IACtE,OAAO,CAAC,qBAAqB;IAS7B;;;OAGG;YACW,kBAAkB;IAsHhC,mFAAmF;IACnF,OAAO,CAAC,kBAAkB;IA+B1B,OAAO,CAAC,UAAU;IAsBlB;;OAEG;IACI,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,GAAG,YAAY,GAAG,IAAI;IAO/F;;;;;;OAMG;IACI,uBAAuB,CAC5B,WAAW,EAAE,MAAM,EACnB,MAAM,SAAoB,EAC1B,SAAS,SAAuC,GAC/C,IAAI;IA4BP,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,WAAW;IAiCnB;;;OAGG;IACI,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAS5C,OAAO,CAAC,oBAAoB;IAWrB,OAAO,IAAI,IAAI;IA+BtB;;OAEG;IACH,IAAW,iBAAiB,IAAI,MAAM,CAErC;CACF;AAED,QAAA,MAAM,mBAAmB,qBAAoC,CAAA;AAC7D,eAAe,mBAAmB,CAAA"}
|
|
@@ -23,7 +23,7 @@ import { storage as mmkvStorage } from "../utils/storage/storage";
|
|
|
23
23
|
import { BgTimer } from "../utils/timers";
|
|
24
24
|
import devServerBridge from "./DevServerBridge";
|
|
25
25
|
import { islandNotifications } from "./NotificationsEmitter";
|
|
26
|
-
import { isGlassesConnected } from "./GlassesReadiness";
|
|
26
|
+
import { isGlassesConnected, isGlassesReady, waitForGlassesReady } from "./GlassesReadiness";
|
|
27
27
|
import { toMiniappConnectionData } from "./GlassesStatusProjection";
|
|
28
28
|
import audioPlaybackService from "./AudioPlaybackService";
|
|
29
29
|
import { phoneLocationService } from "./PhoneLocationService";
|
|
@@ -56,11 +56,11 @@ import { NavigationHandlers } from "./NavigationHandlers";
|
|
|
56
56
|
import { useAppStatusStore } from "../stores/apps";
|
|
57
57
|
import appRegistry, { getDevAppAttestation, getDevAppSourcePackage } from "./AppRegistry";
|
|
58
58
|
import { resolveForegroundLocationPermission } from "./ForegroundLocationPermission";
|
|
59
|
-
import { advanceMiniappPingLiveness } from "./MiniappLiveness";
|
|
59
|
+
import { advanceMiniappPingLiveness, shouldHoldMiniappPingLiveness } from "./MiniappLiveness";
|
|
60
60
|
import { listPhoneCalendarEvents, PhoneCalendarError } from "./PhoneCalendarService";
|
|
61
61
|
import { LocalMiniappStorage } from "./LocalMiniappStorage";
|
|
62
62
|
import acsMeetingService, { parseAcsCallOrigin, parseAcsOutgoingVideo, parseAcsVideoSource, resolveAcsAudioSource, } from "./AcsMeetingService";
|
|
63
|
-
import { createSoftapCallDeps, SoftapCallError, SoftapCallTransport, SOFTAP_STEPS, } from "./SoftapCallTransport";
|
|
63
|
+
import { createSoftapCallDeps, SoftapCallError, SoftapCallTransport, RETURN_DEADLINE_MS, SOFTAP_STEPS, } from "./SoftapCallTransport";
|
|
64
64
|
import { awaitCleanupBarrier } from "./SoftapCleanupBarrier";
|
|
65
65
|
import { softapTrace, softapTraceFailure } from "../utils/softapTrace";
|
|
66
66
|
import { PermissionFeatures, permissions } from "../facades/permissions";
|
|
@@ -120,6 +120,11 @@ const FOREGROUND_LIVENESS_PROBE_TIMEOUT_MS = 2_500;
|
|
|
120
120
|
* rather than sit in "connecting" while the wearer waits.
|
|
121
121
|
*/
|
|
122
122
|
const SOFTAP_FIRST_FRAME_MS = 20_000;
|
|
123
|
+
/**
|
|
124
|
+
* How long the BLE link must stay up before SoftAP recovery trusts it. Covers the gap between
|
|
125
|
+
* the hotspot dying and the phone noticing the link died with it.
|
|
126
|
+
*/
|
|
127
|
+
const GLASSES_LINK_SETTLE_MS = 4_000;
|
|
123
128
|
const REQUEST_WIFI_SETUP_TYPE = "miniapp_request_wifi_setup";
|
|
124
129
|
// Unregister after this many missed pongs. Generous on purpose: a busy
|
|
125
130
|
// context (heavy interim translation traffic) or OS scheduling while idle can
|
|
@@ -265,6 +270,25 @@ const SOFTAP_INGEST_CLOSE_WAIT_MS = 3_500;
|
|
|
265
270
|
const SOFTAP_HOTSPOT_OFF_ACK_MS = 5_000;
|
|
266
271
|
/** After a forced close there is nothing left to wait for, so this only catches a real failure. */
|
|
267
272
|
const SOFTAP_FORCED_VERIFY_MS = 500;
|
|
273
|
+
/**
|
|
274
|
+
* Rejection codes meaning the glasses replaced their Wi-Fi protocol session, not that the
|
|
275
|
+
* command failed.
|
|
276
|
+
*
|
|
277
|
+
* The glasses emit `wifi_protocol_session_ready` on every `glasses_ready`, which fires on BLE
|
|
278
|
+
* reconnects and ASG restarts — routine during a SoftAP teardown. The SDK cancels every pending
|
|
279
|
+
* Wi-Fi request when that lands, so a hotspot command in flight is lost even though nothing is
|
|
280
|
+
* actually wrong. `wifi_session_disconnected` is deliberately absent: that one is raised from the
|
|
281
|
+
* store update where the glasses went away, so there is nothing left to re-send to.
|
|
282
|
+
*/
|
|
283
|
+
const HOTSPOT_SESSION_REFRESH_CODES = new Set(["wifi_session_restarted", "wifi_session_changed"]);
|
|
284
|
+
/**
|
|
285
|
+
* Let the restarted ASG command path settle before re-sending.
|
|
286
|
+
*
|
|
287
|
+
* `handleSetHotspotState` returns false when its network manager is not up yet, which drops the
|
|
288
|
+
* command silently, so an immediate re-send would just race the same initialization. Mirrors the
|
|
289
|
+
* retry spacing `disableHotspotWithRetry` uses for the sibling ASG-replacement case.
|
|
290
|
+
*/
|
|
291
|
+
const HOTSPOT_SESSION_RETRY_DELAY_MS = 500;
|
|
268
292
|
/** Reject with [reason] if [work] has not settled in [ms]. The work itself is not cancellable. */
|
|
269
293
|
function withTimeout(work, ms, reason) {
|
|
270
294
|
return new Promise((resolve, reject) => {
|
|
@@ -3097,6 +3121,7 @@ class LocalMiniappRuntime {
|
|
|
3097
3121
|
this.sendToMiniapp(owner, {
|
|
3098
3122
|
type: MiniappResponseType.MEETING_STATE,
|
|
3099
3123
|
...state,
|
|
3124
|
+
...this.softapRecoveryFields(attempt?.packageName === owner ? attempt : null),
|
|
3100
3125
|
});
|
|
3101
3126
|
if (attempt?.ownsResources && attempt.transport?.shouldRepublish(state.mediaSource)) {
|
|
3102
3127
|
void attempt.transport.republish(state.mediaSourceReason ?? "mediaSource failed");
|
|
@@ -3282,8 +3307,12 @@ class LocalMiniappRuntime {
|
|
|
3282
3307
|
phase: "starting",
|
|
3283
3308
|
steps: SOFTAP_STEPS.map((step) => ({ step, status: "pending" })),
|
|
3284
3309
|
elapsedMs: 0,
|
|
3310
|
+
mediaGeneration: 0,
|
|
3285
3311
|
},
|
|
3286
3312
|
scopedLostUnsub: null,
|
|
3313
|
+
recovery: null,
|
|
3314
|
+
recoveryActive: false,
|
|
3315
|
+
recoveryDeadlineAt: null,
|
|
3287
3316
|
body: null,
|
|
3288
3317
|
teardown: null,
|
|
3289
3318
|
settled,
|
|
@@ -3348,8 +3377,21 @@ class LocalMiniappRuntime {
|
|
|
3348
3377
|
// join *is* in progress, and `idle` would make the miniapp think the call ended.
|
|
3349
3378
|
state: current.state === "idle" ? "connecting" : current.state,
|
|
3350
3379
|
softap: progress,
|
|
3380
|
+
...this.softapRecoveryFields(attempt),
|
|
3351
3381
|
});
|
|
3352
3382
|
}
|
|
3383
|
+
softapRecoveryFields(attempt) {
|
|
3384
|
+
if (!attempt?.recoveryDeadlineAt)
|
|
3385
|
+
return {};
|
|
3386
|
+
return {
|
|
3387
|
+
recovery: {
|
|
3388
|
+
active: attempt.recoveryActive,
|
|
3389
|
+
generation: attempt.transport?.currentMediaGeneration(),
|
|
3390
|
+
deadlineAt: attempt.recoveryDeadlineAt,
|
|
3391
|
+
phase: attempt.transport?.currentPhase(),
|
|
3392
|
+
},
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3353
3395
|
/**
|
|
3354
3396
|
* Narrates preflight work on the first row while it is still pending.
|
|
3355
3397
|
*
|
|
@@ -3388,10 +3430,12 @@ class LocalMiniappRuntime {
|
|
|
3388
3430
|
this.checkpointSoftapAttempt(attempt, "cleanup barrier");
|
|
3389
3431
|
const cleanupError = this.softapCleanupError;
|
|
3390
3432
|
if (cleanupError) {
|
|
3391
|
-
//
|
|
3433
|
+
// Cleared on read, so this refuses at most once and the next attempt always gets through —
|
|
3434
|
+
// which is why the wearer is told to retry rather than to go power-cycle anything. The
|
|
3435
|
+
// native reason stays in the trace: it names a leaked port, not something they can act on.
|
|
3392
3436
|
this.softapCleanupError = null;
|
|
3393
3437
|
softapTraceFailure("softap_join_refused", { packageName, reason: cleanupError });
|
|
3394
|
-
throw new Error(
|
|
3438
|
+
throw new Error("Previous call cleanup has not finished yet. Please try joining again.");
|
|
3395
3439
|
}
|
|
3396
3440
|
attempt.releaseHotspot = acquireGlassesHotspot();
|
|
3397
3441
|
attempt.ownsResources = true;
|
|
@@ -3420,7 +3464,7 @@ class LocalMiniappRuntime {
|
|
|
3420
3464
|
token: args.token,
|
|
3421
3465
|
displayName: args.displayName,
|
|
3422
3466
|
video: args.video,
|
|
3423
|
-
awaitFirstFrame: () => acsMeetingService.waitForFirstFrame(SOFTAP_FIRST_FRAME_MS),
|
|
3467
|
+
awaitFirstFrame: (_report, options) => acsMeetingService.waitForFirstFrame(SOFTAP_FIRST_FRAME_MS, options),
|
|
3424
3468
|
waitUntilLive: (timeoutMs) => acsMeetingService.waitUntilMediaLive(timeoutMs),
|
|
3425
3469
|
subsystems: {
|
|
3426
3470
|
setHotspotState: async (enabled) => {
|
|
@@ -3484,6 +3528,15 @@ class LocalMiniappRuntime {
|
|
|
3484
3528
|
glassesLc3Uplink: () => acsMeetingService.glassesLc3UplinkActive(),
|
|
3485
3529
|
startPublishing: (pkg, options) => phoneStreamCoordinator.startUnmanaged(pkg, options),
|
|
3486
3530
|
stopPublishing: (pkg) => phoneStreamCoordinator.stop(pkg),
|
|
3531
|
+
discardPendingBleStop: () => phoneStreamCoordinator.discardPendingBleStop(),
|
|
3532
|
+
rebindIngest: async (report) => {
|
|
3533
|
+
report?.("Rebinding the glasses video receiver on the hotspot");
|
|
3534
|
+
const ingestUrl = await acsMeetingService.rebindSoftApIngest();
|
|
3535
|
+
if (!ingestUrl)
|
|
3536
|
+
throw new Error("the rebound meeting reported no ingest URL");
|
|
3537
|
+
report?.(`Receiver rebound at ${ingestUrl}`);
|
|
3538
|
+
return { ingestUrl };
|
|
3539
|
+
},
|
|
3487
3540
|
},
|
|
3488
3541
|
}));
|
|
3489
3542
|
attempt.transport = transport;
|
|
@@ -3515,14 +3568,7 @@ class LocalMiniappRuntime {
|
|
|
3515
3568
|
reason: error.message,
|
|
3516
3569
|
attemptAgeMs: Date.now() - attempt.startedAt,
|
|
3517
3570
|
});
|
|
3518
|
-
|
|
3519
|
-
this.sendToMiniapp(packageName, {
|
|
3520
|
-
type: MiniappResponseType.MEETING_STATE,
|
|
3521
|
-
...current,
|
|
3522
|
-
state: "error",
|
|
3523
|
-
error: `SOFTAP_NETWORK_LOST: ${error.message}`,
|
|
3524
|
-
softap: transport.progress(),
|
|
3525
|
-
});
|
|
3571
|
+
this.beginSoftapRecovery(attempt, transport, error);
|
|
3526
3572
|
});
|
|
3527
3573
|
// Sign in on the phone's current internet *before* the glasses hotspot takes DNS. On device,
|
|
3528
3574
|
// createCallAgent after the scoped join timed out at 20s and only finished once SoftAP was
|
|
@@ -3561,6 +3607,163 @@ class LocalMiniappRuntime {
|
|
|
3561
3607
|
});
|
|
3562
3608
|
return state;
|
|
3563
3609
|
}
|
|
3610
|
+
/**
|
|
3611
|
+
* SoftAP loss is a media outage. Keep ACS, destroy generation N, wait for the glasses,
|
|
3612
|
+
* then build generation N+1. Duplicate losses do not renew the deadline.
|
|
3613
|
+
*/
|
|
3614
|
+
beginSoftapRecovery(attempt, transport, error) {
|
|
3615
|
+
// Gate on the live flag, not the promise: a settled Promise is still truthy, so a second
|
|
3616
|
+
// walk-away after a successful re-arm was dropped as "in-flight" and the glasses kept
|
|
3617
|
+
// POSTing WHIP at a hotspot the phone had already left.
|
|
3618
|
+
if (attempt.recoveryActive) {
|
|
3619
|
+
softapTrace("softap_recovery_dropped", { attempt: attempt.id, cause: "in-flight" });
|
|
3620
|
+
return;
|
|
3621
|
+
}
|
|
3622
|
+
if (attempt.cancelled || this.softapAttempt !== attempt) {
|
|
3623
|
+
softapTrace("softap_recovery_dropped", {
|
|
3624
|
+
attempt: attempt.id,
|
|
3625
|
+
cause: attempt.cancelled ? "retired" : "superseded",
|
|
3626
|
+
});
|
|
3627
|
+
return;
|
|
3628
|
+
}
|
|
3629
|
+
const lostAt = Date.now();
|
|
3630
|
+
attempt.recoveryDeadlineAt = lostAt + RETURN_DEADLINE_MS;
|
|
3631
|
+
attempt.recoveryActive = true;
|
|
3632
|
+
this.emitSoftapProgress(attempt, transport.progress());
|
|
3633
|
+
softapTrace("softap_recovery_begin", {
|
|
3634
|
+
attempt: attempt.id,
|
|
3635
|
+
deadlineAt: attempt.recoveryDeadlineAt,
|
|
3636
|
+
mediaGeneration: transport.currentMediaGeneration(),
|
|
3637
|
+
reason: error.message,
|
|
3638
|
+
});
|
|
3639
|
+
let run;
|
|
3640
|
+
run = (async () => {
|
|
3641
|
+
try {
|
|
3642
|
+
await transport.recover(`${error.code}: ${error.message}`, {
|
|
3643
|
+
wait: async () => {
|
|
3644
|
+
const remaining = (attempt.recoveryDeadlineAt ?? 0) - Date.now();
|
|
3645
|
+
if (remaining <= 0) {
|
|
3646
|
+
throw new SoftapCallError("hotspot", "RETURN_DEADLINE", "The glasses did not return in time");
|
|
3647
|
+
}
|
|
3648
|
+
const ready = await this.waitForGlassesReturn(remaining, () => attempt.cancelled);
|
|
3649
|
+
if (attempt.cancelled || this.softapAttempt !== attempt) {
|
|
3650
|
+
throw new SoftapCallError("hotspot", "CANCELLED", "SoftAP recovery was cancelled");
|
|
3651
|
+
}
|
|
3652
|
+
if (!ready) {
|
|
3653
|
+
throw new SoftapCallError("hotspot", "RETURN_DEADLINE", "The glasses did not return in time");
|
|
3654
|
+
}
|
|
3655
|
+
},
|
|
3656
|
+
});
|
|
3657
|
+
if (attempt.cancelled || this.softapAttempt !== attempt)
|
|
3658
|
+
return;
|
|
3659
|
+
this.emitSoftapProgress(attempt, transport.progress());
|
|
3660
|
+
const current = acsMeetingService.getState();
|
|
3661
|
+
this.sendToMiniapp(attempt.packageName, {
|
|
3662
|
+
type: MiniappResponseType.MEETING_STATE,
|
|
3663
|
+
...current,
|
|
3664
|
+
softap: transport.progress(),
|
|
3665
|
+
...this.softapRecoveryFields(attempt),
|
|
3666
|
+
});
|
|
3667
|
+
softapTrace("softap_recovery_live", {
|
|
3668
|
+
attempt: attempt.id,
|
|
3669
|
+
mediaGeneration: transport.currentMediaGeneration(),
|
|
3670
|
+
elapsedMs: Date.now() - lostAt,
|
|
3671
|
+
});
|
|
3672
|
+
}
|
|
3673
|
+
catch (recoveryError) {
|
|
3674
|
+
if (attempt.cancelled || this.softapAttempt !== attempt)
|
|
3675
|
+
return;
|
|
3676
|
+
const message = recoveryError instanceof Error ? recoveryError.message : String(recoveryError);
|
|
3677
|
+
softapTraceFailure("softap_recovery_failed", {
|
|
3678
|
+
attempt: attempt.id,
|
|
3679
|
+
reason: message,
|
|
3680
|
+
elapsedMs: Date.now() - lostAt,
|
|
3681
|
+
});
|
|
3682
|
+
const current = acsMeetingService.getState();
|
|
3683
|
+
this.sendToMiniapp(attempt.packageName, {
|
|
3684
|
+
type: MiniappResponseType.MEETING_STATE,
|
|
3685
|
+
...current,
|
|
3686
|
+
state: "error",
|
|
3687
|
+
error: `SOFTAP_NETWORK_LOST: ${error.message}`,
|
|
3688
|
+
softap: transport.progress(),
|
|
3689
|
+
recovery: {
|
|
3690
|
+
active: false,
|
|
3691
|
+
generation: transport.currentMediaGeneration(),
|
|
3692
|
+
deadlineAt: attempt.recoveryDeadlineAt ?? undefined,
|
|
3693
|
+
phase: transport.currentPhase(),
|
|
3694
|
+
},
|
|
3695
|
+
});
|
|
3696
|
+
}
|
|
3697
|
+
finally {
|
|
3698
|
+
attempt.recoveryActive = false;
|
|
3699
|
+
if (attempt.recovery === run)
|
|
3700
|
+
attempt.recovery = null;
|
|
3701
|
+
}
|
|
3702
|
+
})();
|
|
3703
|
+
attempt.recovery = run;
|
|
3704
|
+
}
|
|
3705
|
+
/**
|
|
3706
|
+
* Wait for a link the hotspot commands can actually use.
|
|
3707
|
+
*
|
|
3708
|
+
* The hotspot goes away a couple of seconds before the phone declares the BLE link dead, so the
|
|
3709
|
+
* snapshot at loss time still reads "ready". Taking that at face value sent `set_hotspot_state`
|
|
3710
|
+
* into a dying link twice, and each one burned its full timeout before the wearer was even back.
|
|
3711
|
+
* Requiring the link to hold steady turns that into waiting, which is what the budget is for.
|
|
3712
|
+
*/
|
|
3713
|
+
async waitForGlassesReturn(timeoutMs, cancelled) {
|
|
3714
|
+
const deadlineAt = Date.now() + timeoutMs;
|
|
3715
|
+
while (!cancelled()) {
|
|
3716
|
+
const remaining = deadlineAt - Date.now();
|
|
3717
|
+
if (remaining <= 0)
|
|
3718
|
+
return false;
|
|
3719
|
+
if (!isGlassesReady(useGlassesStore.getState().connection) && !(await this.awaitGlassesReady(remaining, cancelled))) {
|
|
3720
|
+
return false;
|
|
3721
|
+
}
|
|
3722
|
+
const settleMs = Math.min(GLASSES_LINK_SETTLE_MS, Math.max(0, deadlineAt - Date.now()));
|
|
3723
|
+
if (await this.glassesLinkHoldsSteady(settleMs, cancelled))
|
|
3724
|
+
return true;
|
|
3725
|
+
}
|
|
3726
|
+
return false;
|
|
3727
|
+
}
|
|
3728
|
+
awaitGlassesReady(timeoutMs, cancelled) {
|
|
3729
|
+
const controller = new AbortController();
|
|
3730
|
+
const poll = setInterval(() => {
|
|
3731
|
+
if (cancelled())
|
|
3732
|
+
controller.abort();
|
|
3733
|
+
}, 250);
|
|
3734
|
+
return waitForGlassesReady({
|
|
3735
|
+
getConnection: () => useGlassesStore.getState().connection,
|
|
3736
|
+
subscribe: (listener) => useGlassesStore.subscribe((state) => state.connection, listener),
|
|
3737
|
+
timeoutMs,
|
|
3738
|
+
signal: controller.signal,
|
|
3739
|
+
}).finally(() => clearInterval(poll));
|
|
3740
|
+
}
|
|
3741
|
+
/** True when the link stays ready for [ms]; false the moment it drops again. */
|
|
3742
|
+
glassesLinkHoldsSteady(ms, cancelled) {
|
|
3743
|
+
if (ms <= 0)
|
|
3744
|
+
return Promise.resolve(isGlassesReady(useGlassesStore.getState().connection));
|
|
3745
|
+
return new Promise((resolve) => {
|
|
3746
|
+
let done = false;
|
|
3747
|
+
const settle = (steady) => {
|
|
3748
|
+
if (done)
|
|
3749
|
+
return;
|
|
3750
|
+
done = true;
|
|
3751
|
+
clearTimeout(timer);
|
|
3752
|
+
clearInterval(poll);
|
|
3753
|
+
unsubscribe();
|
|
3754
|
+
resolve(steady);
|
|
3755
|
+
};
|
|
3756
|
+
const unsubscribe = useGlassesStore.subscribe((state) => state.connection, (connection) => {
|
|
3757
|
+
if (!isGlassesReady(connection))
|
|
3758
|
+
settle(false);
|
|
3759
|
+
});
|
|
3760
|
+
const poll = setInterval(() => {
|
|
3761
|
+
if (cancelled())
|
|
3762
|
+
settle(false);
|
|
3763
|
+
}, 250);
|
|
3764
|
+
const timer = setTimeout(() => settle(true), ms);
|
|
3765
|
+
});
|
|
3766
|
+
}
|
|
3564
3767
|
/**
|
|
3565
3768
|
* Cancels a SoftAP attempt and does not return until everything it owns has been released.
|
|
3566
3769
|
*
|
|
@@ -3603,7 +3806,11 @@ class LocalMiniappRuntime {
|
|
|
3603
3806
|
afterMs: SOFTAP_CLEANUP_STALL_LOG_MS,
|
|
3604
3807
|
});
|
|
3605
3808
|
}, SOFTAP_CLEANUP_STALL_LOG_MS);
|
|
3606
|
-
void Promise.allSettled([
|
|
3809
|
+
void Promise.allSettled([
|
|
3810
|
+
attempt.teardown,
|
|
3811
|
+
attempt.body ?? Promise.resolve(),
|
|
3812
|
+
attempt.recovery ?? Promise.resolve(),
|
|
3813
|
+
]).then(() => {
|
|
3607
3814
|
clearTimeout(stallWatchdog);
|
|
3608
3815
|
attempt.markSettled();
|
|
3609
3816
|
if (this.softapAttempt === attempt)
|
|
@@ -3690,8 +3897,17 @@ class LocalMiniappRuntime {
|
|
|
3690
3897
|
// so this is the caller's business, not a reason to block the next call.
|
|
3691
3898
|
endFailure = await releaseStep("transport.stop", () => transport.stop(mode ? { mode } : {}));
|
|
3692
3899
|
const undoFailures = transport.lastTeardownFailures();
|
|
3693
|
-
|
|
3694
|
-
|
|
3900
|
+
// A hotspot that did not ACK off is leftover ON — the next join wants it on and will
|
|
3901
|
+
// call setHotspotState(true). Refusing that join is how we got hotspot_off_ack_timeout
|
|
3902
|
+
// as a wearer-facing "power-cycle the glasses" dead end.
|
|
3903
|
+
const blockingUndos = undoFailures.filter((step) => step !== "hotspot");
|
|
3904
|
+
if (blockingUndos.length > 0)
|
|
3905
|
+
failures.push(`could not release ${blockingUndos.join(", ")}`);
|
|
3906
|
+
if (undoFailures.includes("hotspot")) {
|
|
3907
|
+
console.warn("[LocalMiniappRuntime] glasses hotspot undo timed out; next join will raise it again", {
|
|
3908
|
+
attempt: attempt.id,
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3695
3911
|
}
|
|
3696
3912
|
const scopedFailure = await releaseStep("leaveScopedNetwork", () => acsMeetingService.leaveScopedNetwork());
|
|
3697
3913
|
if (scopedFailure) {
|
|
@@ -3791,8 +4007,32 @@ class LocalMiniappRuntime {
|
|
|
3791
4007
|
if (endFailure)
|
|
3792
4008
|
throw endFailure;
|
|
3793
4009
|
}
|
|
4010
|
+
/**
|
|
4011
|
+
* Send one hotspot command, re-sending it once if a session refresh cancelled it.
|
|
4012
|
+
*
|
|
4013
|
+
* The retry lives inside the unit chained onto [glassesHotspotCommand] rather than around it,
|
|
4014
|
+
* which is what holds the invariant: one logical hotspot operation issues at most two native
|
|
4015
|
+
* commands, and no later operation runs between them. A retry outside the queue would let an
|
|
4016
|
+
* `on` land between the two halves of an `off` and leave the glasses in the opposite state.
|
|
4017
|
+
*
|
|
4018
|
+
* Re-sending is free because ASG compares against current state first and answers a same-state
|
|
4019
|
+
* command with its current status instead of cycling the AP.
|
|
4020
|
+
*/
|
|
3794
4021
|
setGlassesHotspotState(enabled) {
|
|
3795
|
-
const
|
|
4022
|
+
const send = async () => {
|
|
4023
|
+
try {
|
|
4024
|
+
return await BluetoothSdk.setHotspotState(enabled);
|
|
4025
|
+
}
|
|
4026
|
+
catch (error) {
|
|
4027
|
+
const code = error?.code;
|
|
4028
|
+
if (!code || !HOTSPOT_SESSION_REFRESH_CODES.has(code))
|
|
4029
|
+
throw error;
|
|
4030
|
+
softapTrace("softap_hotspot_session_retry", { enabled, code });
|
|
4031
|
+
await new Promise((resolve) => setTimeout(resolve, HOTSPOT_SESSION_RETRY_DELAY_MS));
|
|
4032
|
+
return await BluetoothSdk.setHotspotState(enabled);
|
|
4033
|
+
}
|
|
4034
|
+
};
|
|
4035
|
+
const run = this.glassesHotspotCommand.then(send, send);
|
|
3796
4036
|
this.glassesHotspotCommand = run.then(() => undefined, () => undefined);
|
|
3797
4037
|
return run;
|
|
3798
4038
|
}
|
|
@@ -3806,9 +4046,9 @@ class LocalMiniappRuntime {
|
|
|
3806
4046
|
* failure — the listener was still inside its 3s tombstone and the glasses AP was still up.
|
|
3807
4047
|
*
|
|
3808
4048
|
* A bound that expires is not success. It means the resource is still held by a call nobody is
|
|
3809
|
-
* in any more, so it is taken by force and re-verified.
|
|
3810
|
-
*
|
|
3811
|
-
*
|
|
4049
|
+
* in any more, so it is taken by force and re-verified. An ingest port that is still held
|
|
4050
|
+
* refuses the next join. A glasses hotspot that missed its off-ack does not — the next join
|
|
4051
|
+
* wants the AP up and will raise it again.
|
|
3812
4052
|
*
|
|
3813
4053
|
* @returns failures to record; a non-empty list refuses the next join.
|
|
3814
4054
|
*/
|
|
@@ -3888,11 +4128,18 @@ class LocalMiniappRuntime {
|
|
|
3888
4128
|
if (!gates.hotspotOff) {
|
|
3889
4129
|
try {
|
|
3890
4130
|
const status = await withTimeout(this.setGlassesHotspotState(false), SOFTAP_HOTSPOT_OFF_ACK_MS, "hotspot_off_ack_timeout");
|
|
3891
|
-
if (status.state !== "disabled")
|
|
3892
|
-
|
|
4131
|
+
if (status.state !== "disabled") {
|
|
4132
|
+
console.warn("[LocalMiniappRuntime] glasses hotspot still reported after disable; next join will raise it", {
|
|
4133
|
+
attempt: attempt.id,
|
|
4134
|
+
state: status.state,
|
|
4135
|
+
});
|
|
4136
|
+
}
|
|
3893
4137
|
}
|
|
3894
4138
|
catch (error) {
|
|
3895
|
-
|
|
4139
|
+
console.warn("[LocalMiniappRuntime] glasses hotspot off ack missed; next join will raise it", {
|
|
4140
|
+
attempt: attempt.id,
|
|
4141
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
4142
|
+
});
|
|
3896
4143
|
}
|
|
3897
4144
|
}
|
|
3898
4145
|
softapTrace("softap_teardown_forced_done", {
|
|
@@ -5032,13 +5279,20 @@ class LocalMiniappRuntime {
|
|
|
5032
5279
|
doPingRound() {
|
|
5033
5280
|
const toRemove = [];
|
|
5034
5281
|
for (const [packageName, app] of this.connectedApps) {
|
|
5035
|
-
const
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5282
|
+
const holdPingLiveness = shouldHoldMiniappPingLiveness({
|
|
5283
|
+
packageName,
|
|
5284
|
+
softapPackageName: this.softapAttempt?.packageName,
|
|
5285
|
+
softapCancelled: this.softapAttempt?.cancelled,
|
|
5286
|
+
});
|
|
5287
|
+
if (!holdPingLiveness) {
|
|
5288
|
+
const liveness = advanceMiniappPingLiveness(app.unansweredPingRounds, PING_TIMEOUT_THRESHOLD);
|
|
5289
|
+
if (liveness.shouldUnregister) {
|
|
5290
|
+
console.warn(`${LOG_TAG}: ${packageName} missed ${PING_TIMEOUT_THRESHOLD} pings, unregistering`);
|
|
5291
|
+
toRemove.push(packageName);
|
|
5292
|
+
continue;
|
|
5293
|
+
}
|
|
5294
|
+
app.unansweredPingRounds = liveness.unansweredPingRounds;
|
|
5040
5295
|
}
|
|
5041
|
-
app.unansweredPingRounds = liveness.unansweredPingRounds;
|
|
5042
5296
|
// Send PING — SDK auto-replies with PONG
|
|
5043
5297
|
this.sendToMiniapp(packageName, {
|
|
5044
5298
|
type: MiniappRequestType.PING,
|