@glassly/engine 0.3.2-dev.564 → 0.3.2-dev.573
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 +1 -1
- package/build/engine.d.ts +1 -1
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +3 -3
- package/build/engine.js.map +1 -1
- package/build/facades/dev.d.ts +2 -2
- package/build/facades/dev.d.ts.map +1 -1
- package/build/facades/dev.js +2 -2
- package/build/facades/dev.js.map +1 -1
- package/build/facades/reports.js +2 -2
- package/build/facades/reports.js.map +1 -1
- package/build/facades/session.d.ts +1 -1
- package/build/facades/session.js +2 -2
- package/build/facades/session.js.map +1 -1
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/runtime/bootstrap.d.ts +2 -2
- package/build/runtime/bootstrap.d.ts.map +1 -1
- package/build/runtime/bootstrap.js.map +1 -1
- package/build/services/CloudAudioSubscriptionSync.d.ts +2 -2
- package/build/services/CloudAudioSubscriptionSync.js +2 -2
- package/build/services/CloudAudioSubscriptionSync.js.map +1 -1
- package/build/services/CloudClientService.d.ts +2 -2
- package/build/services/CloudClientService.d.ts.map +1 -1
- package/build/services/CloudClientService.js +4 -4
- package/build/services/CloudClientService.js.map +1 -1
- package/build/services/CloudTranscriptE2EMetrics.d.ts +2 -2
- package/build/services/CloudTranscriptE2EMetrics.d.ts.map +1 -1
- package/build/services/CloudTranscriptE2EMetrics.js +3 -3
- package/build/services/CloudTranscriptE2EMetrics.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +1 -1
- package/build/services/LocalMiniappRuntime.js +3 -3
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/PhonePhotoCoordinator.d.ts +1 -1
- package/build/services/PhonePhotoCoordinator.js +4 -4
- package/build/services/PhonePhotoCoordinator.js.map +1 -1
- package/build/services/PhoneStreamCoordinator.d.ts +1 -1
- package/build/services/PhoneStreamCoordinator.js +1 -1
- package/build/services/PhoneStreamCoordinator.js.map +1 -1
- package/build/services/SupportProfileSync.d.ts +1 -1
- package/build/services/SupportProfileSync.js +2 -2
- package/build/services/SupportProfileSync.js.map +1 -1
- package/build/services/cloudStreamApi.d.ts +2 -2
- package/build/services/cloudStreamApi.js +2 -2
- package/build/services/cloudStreamApi.js.map +1 -1
- package/build/services/soniox/SonioxRealtimeClient.d.ts +2 -2
- package/build/services/soniox/SonioxRealtimeClient.js +2 -2
- package/build/services/soniox/SonioxRealtimeClient.js.map +1 -1
- package/build/services/soniox/SonioxTokenAssembler.d.ts +1 -1
- package/build/services/soniox/SonioxTokenAssembler.js +1 -1
- package/build/services/soniox/SonioxTokenAssembler.js.map +1 -1
- package/build/stores/cloudClientStatus.js +1 -1
- package/build/stores/cloudClientStatus.js.map +1 -1
- package/build/stores/settings.js +3 -3
- package/build/stores/settings.js.map +1 -1
- package/build/utils/cloudClient/cloudSecureStore.js +2 -2
- package/build/utils/cloudClient/cloudSecureStore.js.map +1 -1
- package/package.json +7 -7
- package/src/engine.ts +3 -3
- package/src/facades/dev.ts +2 -2
- package/src/facades/reports.ts +2 -2
- package/src/facades/session.ts +2 -2
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/index.ts +1 -1
- package/src/runtime/bootstrap.ts +2 -2
- package/src/services/CloudAudioSubscriptionSync.ts +2 -2
- package/src/services/CloudClientService.ts +4 -4
- package/src/services/CloudTranscriptE2EMetrics.ts +3 -3
- package/src/services/LocalMiniappRuntime.ts +3 -3
- package/src/services/PhonePhotoCoordinator.ts +4 -4
- package/src/services/PhoneStreamCoordinator.ts +1 -1
- package/src/services/SupportProfileSync.ts +2 -2
- package/src/services/cloudStreamApi.ts +3 -3
- package/src/services/soniox/SonioxRealtimeClient.ts +2 -2
- package/src/services/soniox/SonioxTokenAssembler.ts +1 -1
- package/src/stores/cloudClientStatus.ts +1 -1
- package/src/stores/settings.ts +3 -3
- package/src/utils/cloudClient/cloudSecureStore.ts +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SupportProfileSync.js","sourceRoot":"","sources":["../../src/services/SupportProfileSync.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,gBAAgB,CAAA;AACtC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,EAAC,QAAQ,EAAC,MAAM,cAAc,CAAA;AAGrC,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAA;AAEvD,MAAM,cAAc,GAAI,OAAO,CAAC,oBAAoB,CAAwB,CAAC,OAAO,CAAA;AACpF,MAAM,qBAAqB,GAAI,OAAO,CAAC,qCAAqC,CAAwB,CAAC,OAAO,CAAA;AAC5G,MAAM,WAAW,GAAG,KAAK,CAAA;AACzB,MAAM,QAAQ,GAAG,MAAM,CAAA;AACvB,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,MAAM,CAAA;AAEpC,IAAI,WAAW,GAAwB,IAAI,CAAA;AAC3C,IAAI,aAAa,GAAkB,IAAI,CAAA;AACvC,IAAI,UAAU,GAAkB,IAAI,CAAA;AACpC,IAAI,cAAc,GAAkB,IAAI,CAAA;AACxC,IAAI,mBAAmB,GAAkB,IAAI,CAAA;AAC7C,IAAI,iBAAiB,GAAkB,IAAI,CAAA;AAC3C,IAAI,iBAAiB,GAAkB,IAAI,CAAA;AAC3C,IAAI,cAAc,GAAG,CAAC,CAAA;AACtB,IAAI,uBAAuB,GAAG,CAAC,CAAA;AAC/B,IAAI,WAAW,GAAyC,IAAI,CAAA;AAC5D,IAAI,mBAAmB,GAAkB,IAAI,CAAA;AAE7C;;;;GAIG;AACH,MAAM,UAAU,uBAAuB;IACrC,IAAI,WAAW;QAAE,OAAM;IACvB,MAAM,UAAU,GAAG,EAAE,cAAc,CAAA;IACnC,mBAAmB,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,KAAK,CAAA;IACjE,WAAW,GAAG,eAAe,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAC,CAAA;IACnF,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,mBAAmB,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,YAAY,CAAC,CAAA;IACpG,KAAK,mBAAmB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;AAC5C,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,cAAc,IAAI,CAAC,CAAA;IACnB,WAAW,EAAE,EAAE,CAAA;IACf,WAAW,GAAG,IAAI,CAAA;IAClB,IAAI,aAAa,KAAK,IAAI;QAAE,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC,CAAA;IAC/D,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAA;IACzD,IAAI,cAAc,KAAK,IAAI;QAAE,OAAO,CAAC,aAAa,CAAC,cAAc,CAAC,CAAA;IAClE,aAAa,GAAG,IAAI,CAAA;IACpB,UAAU,GAAG,IAAI,CAAA;IACjB,cAAc,GAAG,IAAI,CAAA;IACrB,mBAAmB,GAAG,IAAI,CAAA;IAC1B,iBAAiB,GAAG,IAAI,CAAA;IACxB,iBAAiB,GAAG,IAAI,CAAA;IACxB,uBAAuB,GAAG,CAAC,CAAA;IAC3B,WAAW,GAAG,IAAI,CAAA;IAClB,mBAAmB,GAAG,IAAI,CAAA;AAC5B,CAAC;AAED,SAAS,wBAAwB,CAAC,UAAkB;IAClD,IAAI,CAAC,WAAW,IAAI,UAAU,KAAK,cAAc;QAAE,OAAM;IACzD,MAAM,eAAe,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,KAAK,CAAA;IACnE,IAAI,eAAe,KAAK,WAAW,IAAI,mBAAmB,KAAK,WAAW;QAAE,WAAW,GAAG,IAAI,CAAA;IAC9F,mBAAmB,GAAG,eAAe,CAAA;IACrC,wBAAwB,CAAC,UAAU,CAAC,CAAA;AACtC,CAAC;AAED,SAAS,wBAAwB,CAAC,UAAU,GAAG,cAAc;IAC3D,IAAI,CAAC,WAAW,IAAI,UAAU,KAAK,cAAc;QAAE,OAAM;IACzD,MAAM,WAAW,GAAG,mBAAmB,CAAC,aAAa,EAAE,CAAC,CAAA;IACxD,IAAI,WAAW,KAAK,mBAAmB;QAAE,OAAM;IAC/C,iBAAiB,GAAG,WAAW,CAAA;IAC/B,WAAW,CAAC,UAAU,CAAC,CAAA;AACzB,CAAC;AAED,SAAS,WAAW,CAAC,UAAkB;IACrC,IAAI,aAAa,KAAK,IAAI;QAAE,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC,CAAA;IAC/D,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE;QACtC,aAAa,GAAG,IAAI,CAAA;QACpB,KAAK,mBAAmB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAA;IAC7C,CAAC,EAAE,WAAW,CAAC,CAAA;AACjB,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,KAAc,EAAE,UAAU,GAAG,cAAc;IAC5E,IAAI,CAAC,WAAW,IAAI,UAAU,KAAK,cAAc;QAAE,OAAM;IACzD,IAAI,iBAAiB,KAAK,UAAU,EAAE,CAAC;QACrC,MAAM,WAAW,GAAG,mBAAmB,CAAC,aAAa,EAAE,CAAC,CAAA;QACxD,IAAI,WAAW,KAAK,mBAAmB;YAAE,iBAAiB,GAAG,WAAW,CAAA;QACxE,OAAM;IACR,CAAC;IACD,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAA;IAChC,MAAM,WAAW,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IACjD,IAAI,CAAC,KAAK,IAAI,WAAW,KAAK,mBAAmB;QAAE,OAAM;IAEzD,iBAAiB,GAAG,UAAU,CAAA;IAC9B,IAAI,YAAY,GAAkB,IAAI,CAAA;IACtC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC5E,IAAI,UAAU,KAAK,cAAc,IAAI,CAAC,WAAW;YAAE,OAAM;QACzD,uBAAuB,GAAG,CAAC,CAAA;QAC3B,YAAY,GAAG,iCAAiC,CAAC,MAAM,CAAC,CAAA;QACxD,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;YAC1B,mBAAmB,GAAG,WAAW,CAAA;YACjC,IAAI,iBAAiB,KAAK,WAAW;gBAAE,iBAAiB,GAAG,IAAI,CAAA;YAC/D,IAAI,UAAU,KAAK,IAAI;gBAAE,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAA;YACzD,UAAU,GAAG,IAAI,CAAA;QACnB,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,UAAU,KAAK,cAAc,IAAI,CAAC,WAAW;YAAE,OAAM;QACzD,uBAAuB,IAAI,CAAC,CAAA;QAC5B,YAAY,GAAG,wBAAwB,CAAC,uBAAuB,CAAC,CAAA;QAChE,OAAO,CAAC,IAAI,CACV,oDAAoD,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,KAAK,EACzF,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAC/C,CAAA;IACH,CAAC;YAAS,CAAC;QACT,MAAM,kBAAkB,GAAG,UAAU,KAAK,cAAc,IAAI,iBAAiB,KAAK,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC,CAAA;QACpH,IAAI,kBAAkB,EAAE,CAAC;YACvB,iBAAiB,GAAG,IAAI,CAAA;YACxB,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;gBAC1B,IAAI,UAAU,KAAK,IAAI;oBAAE,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAA;gBACzD,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE;oBACnC,UAAU,GAAG,IAAI,CAAA;oBACjB,KAAK,mBAAmB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;gBAC5C,CAAC,EAAE,YAAY,CAAC,CAAA;YAClB,CAAC;iBAAM,CAAC;gBACN,MAAM,kBAAkB,GAAG,mBAAmB,CAAC,aAAa,EAAE,CAAC,CAAA;gBAC/D,IAAI,kBAAkB,KAAK,mBAAmB,EAAE,CAAC;oBAC/C,iBAAiB,GAAG,kBAAkB,CAAA;oBACtC,IAAI,UAAU,KAAK,IAAI;wBAAE,WAAW,CAAC,UAAU,CAAC,CAAA;gBAClD,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iCAAiC,CAAC,MAEjD;IACC,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,CAAC,MAAM,KAAK,cAAc;QAAE,OAAO,IAAI,CAAA;IACjF,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,mBAA2B;IAClE,OAAO,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;AACnG,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE;IACnD,MAAM,OAAO,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAA;IAC1C,MAAM,MAAM,GAAG;QACb,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC;QAC1C,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC;QACpC,cAAc,EAAE,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC;QAChD,eAAe,EAAE,QAAQ,CAAC,OAAO,CAAC,eAAe,CAAC;QAClD,kBAAkB,EAAE,QAAQ,CAAC,OAAO,CAAC,kBAAkB,CAAC;QACxD,kBAAkB,EAAE,QAAQ,CAAC,OAAO,CAAC,kBAAkB,CAAC;QACxD,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC;QACxC,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC;KAC3C,CAAA;IACD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACrD,MAAM,SAAS,GAAG,SAGjB,CAAA;IACD,OAAO;QACL,UAAU,EAAE,UAAU,CAAC,WAAW,EAAE;QACpC,IAAI,EAAE;YACJ,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,gBAAgB,IAAI,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC;YACjF,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,kBAAkB,CAAC;YAChD,aAAa,EAAE,QAAQ,CAAC,cAAc,CAAC;YACvC,mBAAmB,EAAE,QAAQ,CAAC,qBAAqB,CAAC;YACpD,aAAa,EAAE,QAAQ,CAAC,EAAE,KAAK,KAAK,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;YAC3F,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;YACtC,cAAc,EAAE,QAAQ,CACtB,QAAQ,CAAC,EAAE,KAAK,SAAS;gBACvB,CAAC,CAAC,MAAM,CAAE,QAAQ,CAAC,SAA4C,EAAE,OAAO,IAAI,QAAQ,CAAC,OAAO,CAAC;gBAC7F,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAC7B;YACD,eAAe,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK;YACzC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAC,WAAW,EAAE,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,CAAC,KAAK,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SACzF;QACD,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAC,MAAM,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/B,CAAA;AACH,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,qCAAqC,CAAC,KAAc,EAAE,KAAa;IACjF,MAAM,OAAO,GACX,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,CAAE,KAA0B,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC7G,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAC5F,MAAM,IAAI,GAAG,wBAAwB,CAAC,IAAI,CAAC,OAAO,CAAC;QACjD,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE;QACvB,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;YAC9B,CAAC,CAAC,mBAAmB;YACrB,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;gBAC7F,CAAC,CAAC,uBAAuB;gBACzB,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;oBAC/D,CAAC,CAAC,kBAAkB;oBACpB,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;wBAC3B,CAAC,CAAC,iBAAiB;wBACnB,CAAC,CAAC,gBAAgB,CAAA;IAC5B,WAAW,GAAG,EAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAC,CAAA;IAChF,wBAAwB,EAAE,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAA2B;IAC7D,OAAO,IAAI,CAAC,SAAS,CAAC,EAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAC,CAAC,CAAA;AACvE,CAAC;AAED,SAAS,QAAQ,CAAC,KAAgC;IAChD,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,CAAA;IAC7B,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AACpD,CAAC","sourcesContent":["import Constants from \"expo-constants\"\nimport * as Device from \"expo-device\"\nimport {Platform} from \"react-native\"\nimport type {SupportStateInput} from \"@glassly/cloud-client\"\n\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {BgTimer} from \"../utils/timers\"\nimport {cloudClientService} from \"./CloudClientService\"\n\nconst ENGINE_VERSION = (require(\"../../package.json\") as {version?: string}).version\nconst BLUETOOTH_SDK_VERSION = (require(\"@glassly/bluetooth-sdk/package.json\") as {version?: string}).version\nconst DEBOUNCE_MS = 2_000\nconst RETRY_MS = 30_000\nconst HEARTBEAT_MS = 6 * 60 * 60_000\n\nlet unsubscribe: (() => void) | null = null\nlet debounceTimer: number | null = null\nlet retryTimer: number | null = null\nlet heartbeatTimer: number | null = null\nlet lastSentFingerprint: string | null = null\nlet queuedFingerprint: string | null = null\nlet sendingGeneration: number | null = null\nlet syncGeneration = 0\nlet consecutiveSendFailures = 0\nlet lastFailure: {code: string; stage: string} | null = null\nlet lastConnectionState: string | null = null\n\n/**\n * Keep Cloud V2's canonical support snapshot current without creating an event\n * stream from noisy store updates. Only the explicitly selected fields below\n * leave the phone; Wi-Fi, Bluetooth addresses, location and diagnostics do not.\n */\nexport function startSupportProfileSync(): void {\n if (unsubscribe) return\n const generation = ++syncGeneration\n lastConnectionState = useGlassesStore.getState().connection.state\n unsubscribe = useGlassesStore.subscribe(() => handleGlassesStoreChange(generation))\n heartbeatTimer = BgTimer.setInterval(() => void sendCurrentSnapshot(true, generation), HEARTBEAT_MS)\n void sendCurrentSnapshot(true, generation)\n}\n\nexport function stopSupportProfileSync(): void {\n syncGeneration += 1\n unsubscribe?.()\n unsubscribe = null\n if (debounceTimer !== null) BgTimer.clearTimeout(debounceTimer)\n if (retryTimer !== null) BgTimer.clearTimeout(retryTimer)\n if (heartbeatTimer !== null) BgTimer.clearInterval(heartbeatTimer)\n debounceTimer = null\n retryTimer = null\n heartbeatTimer = null\n lastSentFingerprint = null\n queuedFingerprint = null\n sendingGeneration = null\n consecutiveSendFailures = 0\n lastFailure = null\n lastConnectionState = null\n}\n\nfunction handleGlassesStoreChange(generation: number): void {\n if (!unsubscribe || generation !== syncGeneration) return\n const connectionState = useGlassesStore.getState().connection.state\n if (connectionState === \"connected\" && lastConnectionState !== \"connected\") lastFailure = null\n lastConnectionState = connectionState\n scheduleMeaningfulUpdate(generation)\n}\n\nfunction scheduleMeaningfulUpdate(generation = syncGeneration): void {\n if (!unsubscribe || generation !== syncGeneration) return\n const fingerprint = snapshotFingerprint(buildSnapshot())\n if (fingerprint === lastSentFingerprint) return\n queuedFingerprint = fingerprint\n armDebounce(generation)\n}\n\nfunction armDebounce(generation: number): void {\n if (debounceTimer !== null) BgTimer.clearTimeout(debounceTimer)\n debounceTimer = BgTimer.setTimeout(() => {\n debounceTimer = null\n void sendCurrentSnapshot(false, generation)\n }, DEBOUNCE_MS)\n}\n\nasync function sendCurrentSnapshot(force: boolean, generation = syncGeneration): Promise<void> {\n if (!unsubscribe || generation !== syncGeneration) return\n if (sendingGeneration === generation) {\n const fingerprint = snapshotFingerprint(buildSnapshot())\n if (fingerprint !== lastSentFingerprint) queuedFingerprint = fingerprint\n return\n }\n const snapshot = buildSnapshot()\n const fingerprint = snapshotFingerprint(snapshot)\n if (!force && fingerprint === lastSentFingerprint) return\n\n sendingGeneration = generation\n let retryDelayMs: number | null = null\n try {\n const result = await cloudClientService.core.supportProfile.update(snapshot)\n if (generation !== syncGeneration || !unsubscribe) return\n consecutiveSendFailures = 0\n retryDelayMs = retryDelayForSupportProfileResult(result)\n if (retryDelayMs === null) {\n lastSentFingerprint = fingerprint\n if (queuedFingerprint === fingerprint) queuedFingerprint = null\n if (retryTimer !== null) BgTimer.clearTimeout(retryTimer)\n retryTimer = null\n }\n } catch (error) {\n if (generation !== syncGeneration || !unsubscribe) return\n consecutiveSendFailures += 1\n retryDelayMs = retryDelayForSendFailure(consecutiveSendFailures)\n console.warn(\n `supportProfile: Cloud V2 update failed (retry in ${Math.round(retryDelayMs / 1_000)}s):`,\n error instanceof Error ? error.message : error,\n )\n } finally {\n const generationIsActive = generation === syncGeneration && sendingGeneration === generation && Boolean(unsubscribe)\n if (generationIsActive) {\n sendingGeneration = null\n if (retryDelayMs !== null) {\n if (retryTimer !== null) BgTimer.clearTimeout(retryTimer)\n retryTimer = BgTimer.setTimeout(() => {\n retryTimer = null\n void sendCurrentSnapshot(true, generation)\n }, retryDelayMs)\n } else {\n const currentFingerprint = snapshotFingerprint(buildSnapshot())\n if (currentFingerprint !== lastSentFingerprint) {\n queuedFingerprint = currentFingerprint\n if (retryTimer === null) armDebounce(generation)\n }\n }\n }\n }\n}\n\nexport function retryDelayForSupportProfileResult(result: {\n status: \"accepted\" | \"deduplicated\" | \"stale\"\n}): number | null {\n if (result.status === \"accepted\" || result.status === \"deduplicated\") return null\n return RETRY_MS\n}\n\n/**\n * Support freshness is not worth an aggressive retry loop. A snapshot the\n * server keeps rejecting (skewed clock, deleted account, signed-out host)\n * backs off exponentially until it converges on the heartbeat cadence.\n */\nexport function retryDelayForSendFailure(consecutiveFailures: number): number {\n return Math.min(HEARTBEAT_MS, RETRY_MS * 2 ** Math.min(Math.max(consecutiveFailures, 1) - 1, 10))\n}\n\nexport function buildSnapshot(observedAt = new Date()): SupportStateInput {\n const glasses = useGlassesStore.getState()\n const device = {\n hardwareId: optional(glasses.serialNumber),\n model: optional(glasses.deviceModel),\n androidVersion: optional(glasses.androidVersion),\n firmwareVersion: optional(glasses.firmwareVersion),\n mtkFirmwareVersion: optional(glasses.mtkFirmwareVersion),\n besFirmwareVersion: optional(glasses.besFirmwareVersion),\n appVersion: optional(glasses.appVersion),\n buildNumber: optional(glasses.buildNumber),\n }\n const hasDevice = Object.values(device).some(Boolean)\n const constants = Constants as typeof Constants & {\n nativeAppVersion?: string | null\n nativeBuildVersion?: string | null\n }\n return {\n observedAt: observedAt.toISOString(),\n host: {\n appVersion: optional(constants.nativeAppVersion ?? Constants.expoConfig?.version),\n appBuild: optional(constants.nativeBuildVersion),\n engineVersion: optional(ENGINE_VERSION),\n bluetoothSdkVersion: optional(BLUETOOTH_SDK_VERSION),\n phonePlatform: Platform.OS === \"ios\" || Platform.OS === \"android\" ? Platform.OS : \"unknown\",\n phoneModel: optional(Device.modelName),\n phoneOsVersion: optional(\n Platform.OS === \"android\"\n ? String((Platform.constants as {Release?: string} | undefined)?.Release ?? Platform.Version)\n : String(Platform.Version),\n ),\n connectionState: glasses.connection.state,\n ...(lastFailure ? {failureCode: lastFailure.code, failureStage: lastFailure.stage} : {}),\n },\n ...(hasDevice ? {device} : {}),\n }\n}\n\n/** Record only normalized connection failures; arbitrary exception messages never leave the phone. */\nexport function recordSupportProfileConnectionFailure(error: unknown, stage: string): void {\n const rawCode =\n typeof error === \"object\" && error && \"code\" in error ? String((error as {code?: unknown}).code ?? \"\") : \"\"\n const message = error instanceof Error ? `${error.name} ${error.message}`.toLowerCase() : \"\"\n const code = /^[a-zA-Z0-9_.-]{1,64}$/.test(rawCode)\n ? rawCode.toLowerCase()\n : message.includes(\"permission\")\n ? \"permission_denied\"\n : message.includes(\"bluetooth\") && (message.includes(\"off\") || message.includes(\"unavailable\"))\n ? \"bluetooth_unavailable\"\n : message.includes(\"not found\") || message.includes(\"no default\")\n ? \"device_not_found\"\n : message.includes(\"timeout\")\n ? \"connect_timeout\"\n : \"connect_failed\"\n lastFailure = {code, stage: stage.replace(/[^a-zA-Z0-9_.-]/g, \"_\").slice(0, 64)}\n scheduleMeaningfulUpdate()\n}\n\nexport function snapshotFingerprint(snapshot: SupportStateInput): string {\n return JSON.stringify({host: snapshot.host, device: snapshot.device})\n}\n\nfunction optional(value: string | null | undefined): string | undefined {\n const trimmed = value?.trim()\n return trimmed ? trimmed.slice(0, 128) : undefined\n}\n"]}
|
|
1
|
+
{"version":3,"file":"SupportProfileSync.js","sourceRoot":"","sources":["../../src/services/SupportProfileSync.ts"],"names":[],"mappings":"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","sourcesContent":["import Constants from \"expo-constants\"\nimport * as Device from \"expo-device\"\nimport {Platform} from \"react-native\"\nimport type {SupportStateInput} from \"@glassly/cloud-client\"\n\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {BgTimer} from \"../utils/timers\"\nimport {cloudClientService} from \"./CloudClientService\"\n\nconst ENGINE_VERSION = (require(\"../../package.json\") as {version?: string}).version\nconst BLUETOOTH_SDK_VERSION = (require(\"@glassly/bluetooth-sdk/package.json\") as {version?: string}).version\nconst DEBOUNCE_MS = 2_000\nconst RETRY_MS = 30_000\nconst HEARTBEAT_MS = 6 * 60 * 60_000\n\nlet unsubscribe: (() => void) | null = null\nlet debounceTimer: number | null = null\nlet retryTimer: number | null = null\nlet heartbeatTimer: number | null = null\nlet lastSentFingerprint: string | null = null\nlet queuedFingerprint: string | null = null\nlet sendingGeneration: number | null = null\nlet syncGeneration = 0\nlet consecutiveSendFailures = 0\nlet lastFailure: {code: string; stage: string} | null = null\nlet lastConnectionState: string | null = null\n\n/**\n * Keep Cloud's canonical support snapshot current without creating an event\n * stream from noisy store updates. Only the explicitly selected fields below\n * leave the phone; Wi-Fi, Bluetooth addresses, location and diagnostics do not.\n */\nexport function startSupportProfileSync(): void {\n if (unsubscribe) return\n const generation = ++syncGeneration\n lastConnectionState = useGlassesStore.getState().connection.state\n unsubscribe = useGlassesStore.subscribe(() => handleGlassesStoreChange(generation))\n heartbeatTimer = BgTimer.setInterval(() => void sendCurrentSnapshot(true, generation), HEARTBEAT_MS)\n void sendCurrentSnapshot(true, generation)\n}\n\nexport function stopSupportProfileSync(): void {\n syncGeneration += 1\n unsubscribe?.()\n unsubscribe = null\n if (debounceTimer !== null) BgTimer.clearTimeout(debounceTimer)\n if (retryTimer !== null) BgTimer.clearTimeout(retryTimer)\n if (heartbeatTimer !== null) BgTimer.clearInterval(heartbeatTimer)\n debounceTimer = null\n retryTimer = null\n heartbeatTimer = null\n lastSentFingerprint = null\n queuedFingerprint = null\n sendingGeneration = null\n consecutiveSendFailures = 0\n lastFailure = null\n lastConnectionState = null\n}\n\nfunction handleGlassesStoreChange(generation: number): void {\n if (!unsubscribe || generation !== syncGeneration) return\n const connectionState = useGlassesStore.getState().connection.state\n if (connectionState === \"connected\" && lastConnectionState !== \"connected\") lastFailure = null\n lastConnectionState = connectionState\n scheduleMeaningfulUpdate(generation)\n}\n\nfunction scheduleMeaningfulUpdate(generation = syncGeneration): void {\n if (!unsubscribe || generation !== syncGeneration) return\n const fingerprint = snapshotFingerprint(buildSnapshot())\n if (fingerprint === lastSentFingerprint) return\n queuedFingerprint = fingerprint\n armDebounce(generation)\n}\n\nfunction armDebounce(generation: number): void {\n if (debounceTimer !== null) BgTimer.clearTimeout(debounceTimer)\n debounceTimer = BgTimer.setTimeout(() => {\n debounceTimer = null\n void sendCurrentSnapshot(false, generation)\n }, DEBOUNCE_MS)\n}\n\nasync function sendCurrentSnapshot(force: boolean, generation = syncGeneration): Promise<void> {\n if (!unsubscribe || generation !== syncGeneration) return\n if (sendingGeneration === generation) {\n const fingerprint = snapshotFingerprint(buildSnapshot())\n if (fingerprint !== lastSentFingerprint) queuedFingerprint = fingerprint\n return\n }\n const snapshot = buildSnapshot()\n const fingerprint = snapshotFingerprint(snapshot)\n if (!force && fingerprint === lastSentFingerprint) return\n\n sendingGeneration = generation\n let retryDelayMs: number | null = null\n try {\n const result = await cloudClientService.core.supportProfile.update(snapshot)\n if (generation !== syncGeneration || !unsubscribe) return\n consecutiveSendFailures = 0\n retryDelayMs = retryDelayForSupportProfileResult(result)\n if (retryDelayMs === null) {\n lastSentFingerprint = fingerprint\n if (queuedFingerprint === fingerprint) queuedFingerprint = null\n if (retryTimer !== null) BgTimer.clearTimeout(retryTimer)\n retryTimer = null\n }\n } catch (error) {\n if (generation !== syncGeneration || !unsubscribe) return\n consecutiveSendFailures += 1\n retryDelayMs = retryDelayForSendFailure(consecutiveSendFailures)\n console.warn(\n `supportProfile: Cloud update failed (retry in ${Math.round(retryDelayMs / 1_000)}s):`,\n error instanceof Error ? error.message : error,\n )\n } finally {\n const generationIsActive = generation === syncGeneration && sendingGeneration === generation && Boolean(unsubscribe)\n if (generationIsActive) {\n sendingGeneration = null\n if (retryDelayMs !== null) {\n if (retryTimer !== null) BgTimer.clearTimeout(retryTimer)\n retryTimer = BgTimer.setTimeout(() => {\n retryTimer = null\n void sendCurrentSnapshot(true, generation)\n }, retryDelayMs)\n } else {\n const currentFingerprint = snapshotFingerprint(buildSnapshot())\n if (currentFingerprint !== lastSentFingerprint) {\n queuedFingerprint = currentFingerprint\n if (retryTimer === null) armDebounce(generation)\n }\n }\n }\n }\n}\n\nexport function retryDelayForSupportProfileResult(result: {\n status: \"accepted\" | \"deduplicated\" | \"stale\"\n}): number | null {\n if (result.status === \"accepted\" || result.status === \"deduplicated\") return null\n return RETRY_MS\n}\n\n/**\n * Support freshness is not worth an aggressive retry loop. A snapshot the\n * server keeps rejecting (skewed clock, deleted account, signed-out host)\n * backs off exponentially until it converges on the heartbeat cadence.\n */\nexport function retryDelayForSendFailure(consecutiveFailures: number): number {\n return Math.min(HEARTBEAT_MS, RETRY_MS * 2 ** Math.min(Math.max(consecutiveFailures, 1) - 1, 10))\n}\n\nexport function buildSnapshot(observedAt = new Date()): SupportStateInput {\n const glasses = useGlassesStore.getState()\n const device = {\n hardwareId: optional(glasses.serialNumber),\n model: optional(glasses.deviceModel),\n androidVersion: optional(glasses.androidVersion),\n firmwareVersion: optional(glasses.firmwareVersion),\n mtkFirmwareVersion: optional(glasses.mtkFirmwareVersion),\n besFirmwareVersion: optional(glasses.besFirmwareVersion),\n appVersion: optional(glasses.appVersion),\n buildNumber: optional(glasses.buildNumber),\n }\n const hasDevice = Object.values(device).some(Boolean)\n const constants = Constants as typeof Constants & {\n nativeAppVersion?: string | null\n nativeBuildVersion?: string | null\n }\n return {\n observedAt: observedAt.toISOString(),\n host: {\n appVersion: optional(constants.nativeAppVersion ?? Constants.expoConfig?.version),\n appBuild: optional(constants.nativeBuildVersion),\n engineVersion: optional(ENGINE_VERSION),\n bluetoothSdkVersion: optional(BLUETOOTH_SDK_VERSION),\n phonePlatform: Platform.OS === \"ios\" || Platform.OS === \"android\" ? Platform.OS : \"unknown\",\n phoneModel: optional(Device.modelName),\n phoneOsVersion: optional(\n Platform.OS === \"android\"\n ? String((Platform.constants as {Release?: string} | undefined)?.Release ?? Platform.Version)\n : String(Platform.Version),\n ),\n connectionState: glasses.connection.state,\n ...(lastFailure ? {failureCode: lastFailure.code, failureStage: lastFailure.stage} : {}),\n },\n ...(hasDevice ? {device} : {}),\n }\n}\n\n/** Record only normalized connection failures; arbitrary exception messages never leave the phone. */\nexport function recordSupportProfileConnectionFailure(error: unknown, stage: string): void {\n const rawCode =\n typeof error === \"object\" && error && \"code\" in error ? String((error as {code?: unknown}).code ?? \"\") : \"\"\n const message = error instanceof Error ? `${error.name} ${error.message}`.toLowerCase() : \"\"\n const code = /^[a-zA-Z0-9_.-]{1,64}$/.test(rawCode)\n ? rawCode.toLowerCase()\n : message.includes(\"permission\")\n ? \"permission_denied\"\n : message.includes(\"bluetooth\") && (message.includes(\"off\") || message.includes(\"unavailable\"))\n ? \"bluetooth_unavailable\"\n : message.includes(\"not found\") || message.includes(\"no default\")\n ? \"device_not_found\"\n : message.includes(\"timeout\")\n ? \"connect_timeout\"\n : \"connect_failed\"\n lastFailure = {code, stage: stage.replace(/[^a-zA-Z0-9_.-]/g, \"_\").slice(0, 64)}\n scheduleMeaningfulUpdate()\n}\n\nexport function snapshotFingerprint(snapshot: SupportStateInput): string {\n return JSON.stringify({host: snapshot.host, device: snapshot.device})\n}\n\nfunction optional(value: string | null | undefined): string | undefined {\n const trimmed = value?.trim()\n return trimmed ? trimmed.slice(0, 128) : undefined\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Managed-stream provisioning over the cloud
|
|
2
|
+
* Managed-stream provisioning over the cloud runtime (cloud-client REST):
|
|
3
3
|
*
|
|
4
4
|
* POST /api/camera/stream -> provision (Cloudflare live input)
|
|
5
5
|
* GET /api/camera/stream/:id -> provider's ingest status
|
|
@@ -48,7 +48,7 @@ export declare class StreamApiError extends Error {
|
|
|
48
48
|
constructor(code: string, message: string, stage: StreamApiStage);
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
-
* Provision a managed stream on the cloud
|
|
51
|
+
* Provision a managed stream on the cloud runtime. Returns ready-to-publish
|
|
52
52
|
* ingest URLs (credentials embedded) plus the playback URLs.
|
|
53
53
|
*/
|
|
54
54
|
export declare function provisionManagedStream(restreamDestinations?: RestreamDestinationInput[]): Promise<ProvisionResult>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Managed-stream provisioning over the cloud
|
|
2
|
+
* Managed-stream provisioning over the cloud runtime (cloud-client REST):
|
|
3
3
|
*
|
|
4
4
|
* POST /api/camera/stream -> provision (Cloudflare live input)
|
|
5
5
|
* GET /api/camera/stream/:id -> provider's ingest status
|
|
@@ -26,7 +26,7 @@ export class StreamApiError extends Error {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
* Provision a managed stream on the cloud
|
|
29
|
+
* Provision a managed stream on the cloud runtime. Returns ready-to-publish
|
|
30
30
|
* ingest URLs (credentials embedded) plus the playback URLs.
|
|
31
31
|
*/
|
|
32
32
|
export async function provisionManagedStream(restreamDestinations) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudStreamApi.js","sourceRoot":"","sources":["../../src/services/cloudStreamApi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Managed-stream provisioning over the cloud
|
|
1
|
+
{"version":3,"file":"cloudStreamApi.js","sourceRoot":"","sources":["../../src/services/cloudStreamApi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Managed-stream provisioning over the cloud runtime (cloud-client REST):\n *\n * POST /api/camera/stream -> provision (Cloudflare live input)\n * GET /api/camera/stream/:id -> provider's ingest status\n * DELETE /api/camera/stream/:id -> teardown\n *\n * Drop-in replacement for the legacy `v2StreamApi` (which hit the v1 cloud's\n * /api/v2/client/streams/managed on backend_url): same exported shapes, so\n * PhoneStreamCoordinator is unchanged apart from the import. Local miniapps\n * use ONLY this path — no legacy V1 transport.\n *\n * Errors are tagged with stage \"provision\"/\"status\"/\"teardown\" and transport\n * \"cloud-rest\" so a miniapp sees exactly where and on which leg it broke.\n */\n\nimport {cloudClientService as cloudClient} from \"./CloudClientService\"\n\n/**\n * Restream destination. The full stream key is part of the URL — Cloudflare\n * appends nothing on the publish side. `name` is a human label that surfaces\n * in Cloudflare's dashboard but doesn't affect routing.\n */\nexport interface RestreamDestination {\n url: string\n name?: string\n}\n\n/** Caller-facing shape: either a bare URL string or the full object. */\nexport type RestreamDestinationInput = string | RestreamDestination\n\nexport interface ProvisionResult {\n liveInputId: string\n rtmpUrl: string\n srtUrl?: string\n hlsUrl: string\n dashUrl: string\n webrtcUrl?: string\n webrtcPublishUrl?: string\n}\n\nexport interface CloudflareStatus {\n isConnected: boolean\n state?: string | null\n connectedAt?: string\n lastSeenAt?: string\n reason?: string\n}\n\nexport type StreamApiStage = \"provision\" | \"status\" | \"teardown\"\n\nexport class StreamApiError extends Error {\n readonly transport = \"cloud-rest\" as const\n constructor(\n public readonly code: string,\n message: string,\n public readonly stage: StreamApiStage,\n ) {\n super(message)\n this.name = \"StreamApiError\"\n }\n}\n\n/** The cloud runtime's ManagedStream wire shape (open records, see protocol). */\ninterface ManagedStreamWire {\n streamId: string\n ingest: {\n url?: string\n streamKey?: string\n rtmpUrl?: string\n srtUrl?: string\n webrtcPublishUrl?: string\n }\n playback: {\n hls?: string\n dash?: string\n webrtc?: string\n }\n}\n\n/**\n * Provision a managed stream on the cloud runtime. Returns ready-to-publish\n * ingest URLs (credentials embedded) plus the playback URLs.\n */\nexport async function provisionManagedStream(\n restreamDestinations?: RestreamDestinationInput[],\n): Promise<ProvisionResult> {\n let wire: ManagedStreamWire\n try {\n wire = (await cloudClient.startManagedStream(\n restreamDestinations && restreamDestinations.length > 0 ? {restreamDestinations} : {},\n )) as ManagedStreamWire\n } catch (err) {\n throw new StreamApiError(\"STREAM_PROVISION_FAILED\", err instanceof Error ? err.message : String(err), \"provision\")\n }\n\n const ingest = wire.ingest ?? {}\n const playback = wire.playback ?? {}\n const rtmpUrl = ingest.rtmpUrl ?? (ingest.url && ingest.streamKey ? `${ingest.url}${ingest.streamKey}` : \"\")\n if (!playback.hls || !playback.dash) {\n throw new StreamApiError(\n \"STREAM_PROVISION_INCOMPLETE\",\n `runtime returned no playback URLs for stream ${wire.streamId}`,\n \"provision\",\n )\n }\n\n return {\n liveInputId: wire.streamId,\n rtmpUrl,\n srtUrl: ingest.srtUrl,\n hlsUrl: playback.hls,\n dashUrl: playback.dash,\n webrtcUrl: playback.webrtc,\n webrtcPublishUrl: ingest.webrtcPublishUrl,\n }\n}\n\n/** The provider's view of the ingest: is the device's push arriving? */\nexport async function getManagedStreamStatus(liveInputId: string): Promise<CloudflareStatus> {\n try {\n const s = await cloudClient.getManagedStreamStatus(liveInputId)\n return {\n isConnected: s.isConnected,\n state: s.state,\n connectedAt: s.connectedAt,\n lastSeenAt: s.lastSeenAt,\n reason: s.reason,\n }\n } catch (err) {\n throw new StreamApiError(\"STREAM_STATUS_FAILED\", err instanceof Error ? err.message : String(err), \"status\")\n }\n}\n\n/** Tear down the provider stream. */\nexport async function teardownManagedStream(liveInputId: string): Promise<void> {\n try {\n await cloudClient.stopManagedStream(liveInputId)\n } catch (err) {\n throw new StreamApiError(\"STREAM_TEARDOWN_FAILED\", err instanceof Error ? err.message : String(err), \"teardown\")\n }\n}\n"]}
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* machinery) or `@soniox/web` (MediaStream capture): the wire protocol is one
|
|
11
11
|
* JSON config message, then binary PCM frames up / JSON token results down.
|
|
12
12
|
* Protocol shape mirrors the vendored SDK's buildConfigMessage /
|
|
13
|
-
* parseResultMessage (cloud
|
|
14
|
-
* implementation: cloud
|
|
13
|
+
* parseResultMessage (cloud runtime's provider is the reference
|
|
14
|
+
* implementation: cloud/packages/runtime/src/services/audio/providers/soniox.ts).
|
|
15
15
|
*/
|
|
16
16
|
export declare const SONIOX_STT_WS_URL = "wss://stt-rt.soniox.com/transcribe-websocket";
|
|
17
17
|
export declare const SONIOX_MODEL = "stt-rt-v4";
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* machinery) or `@soniox/web` (MediaStream capture): the wire protocol is one
|
|
11
11
|
* JSON config message, then binary PCM frames up / JSON token results down.
|
|
12
12
|
* Protocol shape mirrors the vendored SDK's buildConfigMessage /
|
|
13
|
-
* parseResultMessage (cloud
|
|
14
|
-
* implementation: cloud
|
|
13
|
+
* parseResultMessage (cloud runtime's provider is the reference
|
|
14
|
+
* implementation: cloud/packages/runtime/src/services/audio/providers/soniox.ts).
|
|
15
15
|
*/
|
|
16
16
|
import { BgTimer } from "../../utils/timers";
|
|
17
17
|
export const SONIOX_STT_WS_URL = "wss://stt-rt.soniox.com/transcribe-websocket";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SonioxRealtimeClient.js","sourceRoot":"","sources":["../../../src/services/soniox/SonioxRealtimeClient.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SonioxRealtimeClient\n *\n * Minimal React Native WebSocket client for Soniox realtime STT\n * (wss://stt-rt.soniox.com/transcribe-websocket). Used by SonioxDirectService\n * when the user supplies their own Soniox API key: audio streams from the\n * phone straight to Soniox, bypassing the Glassly cloud.\n *\n * Deliberately hand-rolled rather than depending on `@soniox/node` (node HTTP\n * machinery) or `@soniox/web` (MediaStream capture): the wire protocol is one\n * JSON config message, then binary PCM frames up / JSON token results down.\n * Protocol shape mirrors the vendored SDK's buildConfigMessage /\n * parseResultMessage (cloud-v2 runtime's provider is the reference\n * implementation: cloud-v2/packages/runtime/src/services/audio/providers/soniox.ts).\n */\n\nimport {BgTimer} from \"../../utils/timers\"\n\nexport const SONIOX_STT_WS_URL = \"wss://stt-rt.soniox.com/transcribe-websocket\"\nexport const SONIOX_MODEL = \"stt-rt-v4\"\n/** REST endpoint used only for cheap key validation (Save & Test). */\nexport const SONIOX_MODELS_URL = \"https://api.soniox.com/v1/models\"\n\nconst CONNECT_TIMEOUT_MS = 20_000\nconst KEEPALIVE_INTERVAL_MS = 5_000\n\nexport interface SonioxToken {\n text: string\n start_ms?: number\n end_ms?: number\n confidence?: number\n is_final: boolean\n speaker?: string\n language?: string\n translation_status?: \"none\" | \"original\" | \"translation\"\n source_language?: string\n}\n\nexport interface SonioxResult {\n tokens: SonioxToken[]\n finished?: boolean\n}\n\nexport class SonioxClientError extends Error {\n readonly code: number | string | undefined\n /**\n * Billing/auth failures (bad key, quota exhausted) are not healed by\n * reconnecting — Soniox accepts the socket then rejects the session.\n * Mirrors the cloud provider's isNonTransientProviderError: 401/402/403\n * plus the quota codes (the raw wire uses 429 for quota).\n */\n readonly nonTransient: boolean\n\n constructor(message: string, code?: number | string) {\n super(message)\n this.name = \"SonioxClientError\"\n this.code = code\n this.nonTransient = code === 401 || code === 402 || code === 403 || code === 429 || code === \"quota_exceeded\"\n }\n}\n\nexport interface SonioxSessionOptions {\n apiKey: string\n /** Subscription language (\"auto\", \"en-US\", ...). Hints are derived from it. */\n language?: string\n /** Detection hints for auto-mode subscriptions (already bare or BCP-47; normalized here). */\n languageHints?: string[]\n /** When set, configure Soniox one-way translation to this target (BCP-47 ok; normalized). */\n targetLanguage?: string\n onOpen?: () => void\n onResult: (result: SonioxResult) => void\n onEndpoint: () => void\n onFinalized: () => void\n onError: (err: SonioxClientError) => void\n onClose?: () => void\n /** Test seam: WebSocket constructor override. */\n webSocketFactory?: (url: string) => WebSocket\n}\n\n/**\n * Build Soniox `language_hints`. A specific language is its own hint (region\n * stripped: \"en-US\" -> \"en\"). For \"auto\" the miniapp's detection hints are\n * reduced to bare codes and deduped. Passing a BCP-47 tag or unknown code\n * makes Soniox reject the session (\"Invalid language hint\"), so everything is\n * normalized here. Port of the cloud provider's sonioxLanguageHints.\n */\nexport function sonioxLanguageHints(language: string | undefined, hints: string[] | undefined): string[] | undefined {\n const toBareCode = (code: string) => code.split(\"-\")[0].toLowerCase()\n if (language && language !== \"auto\") {\n return [toBareCode(language)]\n }\n if (!hints || hints.length === 0) return undefined\n const bare = [...new Set(hints.map(toBareCode).filter(Boolean))]\n return bare.length > 0 ? bare : undefined\n}\n\n/**\n * Soniox one-way translation target takes a bare ISO 639-1 code — a BCP-47\n * tag (\"es-ES\") kills the session. Subscription keys keep the full tag; only\n * the wire config is reduced. Port of the cloud provider's sonioxTranslationTarget.\n */\nexport function sonioxTranslationTarget(target: string): string {\n return target.split(\"-\")[0].toLowerCase()\n}\n\n/** The first (and only) config message sent after the socket opens. */\nexport function buildSonioxConfigMessage(opts: {\n apiKey: string\n language?: string\n languageHints?: string[]\n targetLanguage?: string\n}): Record<string, unknown> {\n return {\n api_key: opts.apiKey,\n model: SONIOX_MODEL,\n audio_format: \"pcm_s16le\",\n sample_rate: 16_000,\n num_channels: 1,\n enable_language_identification: true,\n enable_endpoint_detection: true,\n enable_speaker_diarization: true,\n max_endpoint_delay_ms: 2_000,\n language_hints: sonioxLanguageHints(opts.language, opts.languageHints),\n context: {\n terms: [\"Glassly\", \"Hey Glassly\"],\n text: \"Glassly, Hey Glassly (an AI assistant)\",\n },\n translation: opts.targetLanguage\n ? {type: \"one_way\", target_language: sonioxTranslationTarget(opts.targetLanguage)}\n : undefined,\n }\n}\n\n/**\n * One realtime session: connect, stream audio, receive tokens. A dead session\n * is not reusable — SonioxDirectService replaces it on reconnect.\n */\nexport class SonioxRealtimeClient {\n private readonly opts: SonioxSessionOptions\n private ws: WebSocket | null = null\n private opened = false\n private closed = false\n private connectTimer: number | null = null\n private keepaliveTimer: number | null = null\n private lastAudioSentAt = 0\n\n constructor(opts: SonioxSessionOptions) {\n this.opts = opts\n }\n\n isOpen(): boolean {\n return this.opened && !this.closed\n }\n\n connect(): void {\n if (this.ws) return\n const factory = this.opts.webSocketFactory ?? ((url: string) => new WebSocket(url))\n let ws: WebSocket\n try {\n ws = factory(SONIOX_STT_WS_URL)\n } catch (err) {\n this.opts.onError(new SonioxClientError(`websocket construction failed: ${(err as Error)?.message ?? err}`))\n return\n }\n this.ws = ws\n ws.binaryType = \"arraybuffer\"\n\n this.connectTimer = BgTimer.setTimeout(() => {\n this.connectTimer = null\n if (!this.opened && !this.closed) {\n this.opts.onError(new SonioxClientError(\"connect timeout\", 503))\n this.close()\n }\n }, CONNECT_TIMEOUT_MS)\n\n ws.onopen = () => {\n if (this.closed) return\n this.clearConnectTimer()\n this.opened = true\n const config = buildSonioxConfigMessage({\n apiKey: this.opts.apiKey,\n language: this.opts.language,\n languageHints: this.opts.languageHints,\n targetLanguage: this.opts.targetLanguage,\n })\n try {\n ws.send(JSON.stringify(config))\n } catch (err) {\n this.opts.onError(new SonioxClientError(`config send failed: ${(err as Error)?.message ?? err}`))\n this.close()\n return\n }\n this.startKeepalive()\n this.opts.onOpen?.()\n }\n\n ws.onmessage = (event) => {\n if (this.closed || typeof event.data !== \"string\") return\n this.handleMessage(event.data)\n }\n\n ws.onerror = () => {\n if (this.closed) return\n // RN's error event carries no useful detail; the close event follows.\n this.opts.onError(new SonioxClientError(\"websocket error\", 503))\n }\n\n ws.onclose = () => {\n const wasClosed = this.closed\n this.teardown()\n if (!wasClosed) this.opts.onClose?.()\n }\n }\n\n /** Binary PCM (s16le 16kHz mono) frame. Dropped when the socket isn't open. */\n sendAudio(pcm: ArrayBuffer): void {\n if (!this.isOpen() || !this.ws) return\n this.lastAudioSentAt = Date.now()\n try {\n this.ws.send(pcm)\n } catch (err) {\n this.opts.onError(new SonioxClientError(`audio send failed: ${(err as Error)?.message ?? err}`))\n }\n }\n\n finalize(): void {\n this.sendControl({type: \"finalize\"})\n }\n\n close(): void {\n if (this.closed) {\n return\n }\n this.closed = true\n this.clearConnectTimer()\n this.stopKeepalive()\n const ws = this.ws\n this.ws = null\n this.opened = false\n if (ws) {\n try {\n ws.close()\n } catch {\n /* best-effort */\n }\n }\n }\n\n private handleMessage(data: string): void {\n let raw: Record<string, unknown>\n try {\n raw = JSON.parse(data) as Record<string, unknown>\n } catch {\n return\n }\n if (\"error_code\" in raw || \"error_message\" in raw) {\n const code = raw.error_code as number | string | undefined\n const message = typeof raw.error_message === \"string\" ? raw.error_message : \"soniox error\"\n this.opts.onError(new SonioxClientError(message, code))\n return\n }\n\n const allTokens = Array.isArray(raw.tokens) ? (raw.tokens as SonioxToken[]) : []\n let sawEndpoint = false\n let sawFinalized = false\n const tokens: SonioxToken[] = []\n for (const t of allTokens) {\n if (typeof t?.text !== \"string\") continue\n if (t.text === \"<end>\") {\n sawEndpoint = true\n } else if (t.text === \"<fin>\") {\n sawFinalized = true\n } else {\n tokens.push(t)\n }\n }\n\n if (tokens.length > 0) {\n this.opts.onResult({tokens, finished: raw.finished === true})\n }\n // Order matters: tokens in the same message belong to the utterance the\n // endpoint closes, so deliver them before the boundary signal.\n if (sawEndpoint) this.opts.onEndpoint()\n if (sawFinalized) this.opts.onFinalized()\n }\n\n private sendControl(msg: {type: string}): void {\n if (!this.isOpen() || !this.ws) return\n try {\n this.ws.send(JSON.stringify(msg))\n } catch {\n /* best-effort */\n }\n }\n\n private startKeepalive(): void {\n this.stopKeepalive()\n this.keepaliveTimer = BgTimer.setInterval(() => {\n // Only needed when no audio has been sent recently — Soniox times out\n // idle sessions. Continuous audio makes this a safety net.\n if (Date.now() - this.lastAudioSentAt >= KEEPALIVE_INTERVAL_MS) {\n this.sendControl({type: \"keepalive\"})\n }\n }, KEEPALIVE_INTERVAL_MS)\n }\n\n private stopKeepalive(): void {\n if (this.keepaliveTimer !== null) {\n BgTimer.clearInterval(this.keepaliveTimer)\n this.keepaliveTimer = null\n }\n }\n\n private clearConnectTimer(): void {\n if (this.connectTimer !== null) {\n BgTimer.clearTimeout(this.connectTimer)\n this.connectTimer = null\n }\n }\n\n private teardown(): void {\n this.closed = true\n this.opened = false\n this.clearConnectTimer()\n this.stopKeepalive()\n this.ws = null\n }\n}\n\n/**\n * Cheap key validation for the Keys screen: GET /v1/models with the key as a\n * Bearer token. 401/403 → invalid key; network failure → indeterminate (the\n * caller may offer \"save anyway\").\n */\nexport async function validateSonioxApiKey(\n apiKey: string,\n): Promise<{ok: true} | {ok: false; reason: \"invalid_key\" | \"network\"}> {\n try {\n const res = await fetch(SONIOX_MODELS_URL, {\n method: \"GET\",\n headers: {Authorization: `Bearer ${apiKey}`},\n })\n if (res.ok) return {ok: true}\n if (res.status === 401 || res.status === 403) return {ok: false, reason: \"invalid_key\"}\n return {ok: false, reason: \"network\"}\n } catch {\n return {ok: false, reason: \"network\"}\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"SonioxRealtimeClient.js","sourceRoot":"","sources":["../../../src/services/soniox/SonioxRealtimeClient.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SonioxRealtimeClient\n *\n * Minimal React Native WebSocket client for Soniox realtime STT\n * (wss://stt-rt.soniox.com/transcribe-websocket). Used by SonioxDirectService\n * when the user supplies their own Soniox API key: audio streams from the\n * phone straight to Soniox, bypassing the Glassly cloud.\n *\n * Deliberately hand-rolled rather than depending on `@soniox/node` (node HTTP\n * machinery) or `@soniox/web` (MediaStream capture): the wire protocol is one\n * JSON config message, then binary PCM frames up / JSON token results down.\n * Protocol shape mirrors the vendored SDK's buildConfigMessage /\n * parseResultMessage (cloud runtime's provider is the reference\n * implementation: cloud/packages/runtime/src/services/audio/providers/soniox.ts).\n */\n\nimport {BgTimer} from \"../../utils/timers\"\n\nexport const SONIOX_STT_WS_URL = \"wss://stt-rt.soniox.com/transcribe-websocket\"\nexport const SONIOX_MODEL = \"stt-rt-v4\"\n/** REST endpoint used only for cheap key validation (Save & Test). */\nexport const SONIOX_MODELS_URL = \"https://api.soniox.com/v1/models\"\n\nconst CONNECT_TIMEOUT_MS = 20_000\nconst KEEPALIVE_INTERVAL_MS = 5_000\n\nexport interface SonioxToken {\n text: string\n start_ms?: number\n end_ms?: number\n confidence?: number\n is_final: boolean\n speaker?: string\n language?: string\n translation_status?: \"none\" | \"original\" | \"translation\"\n source_language?: string\n}\n\nexport interface SonioxResult {\n tokens: SonioxToken[]\n finished?: boolean\n}\n\nexport class SonioxClientError extends Error {\n readonly code: number | string | undefined\n /**\n * Billing/auth failures (bad key, quota exhausted) are not healed by\n * reconnecting — Soniox accepts the socket then rejects the session.\n * Mirrors the cloud provider's isNonTransientProviderError: 401/402/403\n * plus the quota codes (the raw wire uses 429 for quota).\n */\n readonly nonTransient: boolean\n\n constructor(message: string, code?: number | string) {\n super(message)\n this.name = \"SonioxClientError\"\n this.code = code\n this.nonTransient = code === 401 || code === 402 || code === 403 || code === 429 || code === \"quota_exceeded\"\n }\n}\n\nexport interface SonioxSessionOptions {\n apiKey: string\n /** Subscription language (\"auto\", \"en-US\", ...). Hints are derived from it. */\n language?: string\n /** Detection hints for auto-mode subscriptions (already bare or BCP-47; normalized here). */\n languageHints?: string[]\n /** When set, configure Soniox one-way translation to this target (BCP-47 ok; normalized). */\n targetLanguage?: string\n onOpen?: () => void\n onResult: (result: SonioxResult) => void\n onEndpoint: () => void\n onFinalized: () => void\n onError: (err: SonioxClientError) => void\n onClose?: () => void\n /** Test seam: WebSocket constructor override. */\n webSocketFactory?: (url: string) => WebSocket\n}\n\n/**\n * Build Soniox `language_hints`. A specific language is its own hint (region\n * stripped: \"en-US\" -> \"en\"). For \"auto\" the miniapp's detection hints are\n * reduced to bare codes and deduped. Passing a BCP-47 tag or unknown code\n * makes Soniox reject the session (\"Invalid language hint\"), so everything is\n * normalized here. Port of the cloud provider's sonioxLanguageHints.\n */\nexport function sonioxLanguageHints(language: string | undefined, hints: string[] | undefined): string[] | undefined {\n const toBareCode = (code: string) => code.split(\"-\")[0].toLowerCase()\n if (language && language !== \"auto\") {\n return [toBareCode(language)]\n }\n if (!hints || hints.length === 0) return undefined\n const bare = [...new Set(hints.map(toBareCode).filter(Boolean))]\n return bare.length > 0 ? bare : undefined\n}\n\n/**\n * Soniox one-way translation target takes a bare ISO 639-1 code — a BCP-47\n * tag (\"es-ES\") kills the session. Subscription keys keep the full tag; only\n * the wire config is reduced. Port of the cloud provider's sonioxTranslationTarget.\n */\nexport function sonioxTranslationTarget(target: string): string {\n return target.split(\"-\")[0].toLowerCase()\n}\n\n/** The first (and only) config message sent after the socket opens. */\nexport function buildSonioxConfigMessage(opts: {\n apiKey: string\n language?: string\n languageHints?: string[]\n targetLanguage?: string\n}): Record<string, unknown> {\n return {\n api_key: opts.apiKey,\n model: SONIOX_MODEL,\n audio_format: \"pcm_s16le\",\n sample_rate: 16_000,\n num_channels: 1,\n enable_language_identification: true,\n enable_endpoint_detection: true,\n enable_speaker_diarization: true,\n max_endpoint_delay_ms: 2_000,\n language_hints: sonioxLanguageHints(opts.language, opts.languageHints),\n context: {\n terms: [\"Glassly\", \"Hey Glassly\"],\n text: \"Glassly, Hey Glassly (an AI assistant)\",\n },\n translation: opts.targetLanguage\n ? {type: \"one_way\", target_language: sonioxTranslationTarget(opts.targetLanguage)}\n : undefined,\n }\n}\n\n/**\n * One realtime session: connect, stream audio, receive tokens. A dead session\n * is not reusable — SonioxDirectService replaces it on reconnect.\n */\nexport class SonioxRealtimeClient {\n private readonly opts: SonioxSessionOptions\n private ws: WebSocket | null = null\n private opened = false\n private closed = false\n private connectTimer: number | null = null\n private keepaliveTimer: number | null = null\n private lastAudioSentAt = 0\n\n constructor(opts: SonioxSessionOptions) {\n this.opts = opts\n }\n\n isOpen(): boolean {\n return this.opened && !this.closed\n }\n\n connect(): void {\n if (this.ws) return\n const factory = this.opts.webSocketFactory ?? ((url: string) => new WebSocket(url))\n let ws: WebSocket\n try {\n ws = factory(SONIOX_STT_WS_URL)\n } catch (err) {\n this.opts.onError(new SonioxClientError(`websocket construction failed: ${(err as Error)?.message ?? err}`))\n return\n }\n this.ws = ws\n ws.binaryType = \"arraybuffer\"\n\n this.connectTimer = BgTimer.setTimeout(() => {\n this.connectTimer = null\n if (!this.opened && !this.closed) {\n this.opts.onError(new SonioxClientError(\"connect timeout\", 503))\n this.close()\n }\n }, CONNECT_TIMEOUT_MS)\n\n ws.onopen = () => {\n if (this.closed) return\n this.clearConnectTimer()\n this.opened = true\n const config = buildSonioxConfigMessage({\n apiKey: this.opts.apiKey,\n language: this.opts.language,\n languageHints: this.opts.languageHints,\n targetLanguage: this.opts.targetLanguage,\n })\n try {\n ws.send(JSON.stringify(config))\n } catch (err) {\n this.opts.onError(new SonioxClientError(`config send failed: ${(err as Error)?.message ?? err}`))\n this.close()\n return\n }\n this.startKeepalive()\n this.opts.onOpen?.()\n }\n\n ws.onmessage = (event) => {\n if (this.closed || typeof event.data !== \"string\") return\n this.handleMessage(event.data)\n }\n\n ws.onerror = () => {\n if (this.closed) return\n // RN's error event carries no useful detail; the close event follows.\n this.opts.onError(new SonioxClientError(\"websocket error\", 503))\n }\n\n ws.onclose = () => {\n const wasClosed = this.closed\n this.teardown()\n if (!wasClosed) this.opts.onClose?.()\n }\n }\n\n /** Binary PCM (s16le 16kHz mono) frame. Dropped when the socket isn't open. */\n sendAudio(pcm: ArrayBuffer): void {\n if (!this.isOpen() || !this.ws) return\n this.lastAudioSentAt = Date.now()\n try {\n this.ws.send(pcm)\n } catch (err) {\n this.opts.onError(new SonioxClientError(`audio send failed: ${(err as Error)?.message ?? err}`))\n }\n }\n\n finalize(): void {\n this.sendControl({type: \"finalize\"})\n }\n\n close(): void {\n if (this.closed) {\n return\n }\n this.closed = true\n this.clearConnectTimer()\n this.stopKeepalive()\n const ws = this.ws\n this.ws = null\n this.opened = false\n if (ws) {\n try {\n ws.close()\n } catch {\n /* best-effort */\n }\n }\n }\n\n private handleMessage(data: string): void {\n let raw: Record<string, unknown>\n try {\n raw = JSON.parse(data) as Record<string, unknown>\n } catch {\n return\n }\n if (\"error_code\" in raw || \"error_message\" in raw) {\n const code = raw.error_code as number | string | undefined\n const message = typeof raw.error_message === \"string\" ? raw.error_message : \"soniox error\"\n this.opts.onError(new SonioxClientError(message, code))\n return\n }\n\n const allTokens = Array.isArray(raw.tokens) ? (raw.tokens as SonioxToken[]) : []\n let sawEndpoint = false\n let sawFinalized = false\n const tokens: SonioxToken[] = []\n for (const t of allTokens) {\n if (typeof t?.text !== \"string\") continue\n if (t.text === \"<end>\") {\n sawEndpoint = true\n } else if (t.text === \"<fin>\") {\n sawFinalized = true\n } else {\n tokens.push(t)\n }\n }\n\n if (tokens.length > 0) {\n this.opts.onResult({tokens, finished: raw.finished === true})\n }\n // Order matters: tokens in the same message belong to the utterance the\n // endpoint closes, so deliver them before the boundary signal.\n if (sawEndpoint) this.opts.onEndpoint()\n if (sawFinalized) this.opts.onFinalized()\n }\n\n private sendControl(msg: {type: string}): void {\n if (!this.isOpen() || !this.ws) return\n try {\n this.ws.send(JSON.stringify(msg))\n } catch {\n /* best-effort */\n }\n }\n\n private startKeepalive(): void {\n this.stopKeepalive()\n this.keepaliveTimer = BgTimer.setInterval(() => {\n // Only needed when no audio has been sent recently — Soniox times out\n // idle sessions. Continuous audio makes this a safety net.\n if (Date.now() - this.lastAudioSentAt >= KEEPALIVE_INTERVAL_MS) {\n this.sendControl({type: \"keepalive\"})\n }\n }, KEEPALIVE_INTERVAL_MS)\n }\n\n private stopKeepalive(): void {\n if (this.keepaliveTimer !== null) {\n BgTimer.clearInterval(this.keepaliveTimer)\n this.keepaliveTimer = null\n }\n }\n\n private clearConnectTimer(): void {\n if (this.connectTimer !== null) {\n BgTimer.clearTimeout(this.connectTimer)\n this.connectTimer = null\n }\n }\n\n private teardown(): void {\n this.closed = true\n this.opened = false\n this.clearConnectTimer()\n this.stopKeepalive()\n this.ws = null\n }\n}\n\n/**\n * Cheap key validation for the Keys screen: GET /v1/models with the key as a\n * Bearer token. 401/403 → invalid key; network failure → indeterminate (the\n * caller may offer \"save anyway\").\n */\nexport async function validateSonioxApiKey(\n apiKey: string,\n): Promise<{ok: true} | {ok: false; reason: \"invalid_key\" | \"network\"}> {\n try {\n const res = await fetch(SONIOX_MODELS_URL, {\n method: \"GET\",\n headers: {Authorization: `Bearer ${apiKey}`},\n })\n if (res.ok) return {ok: true}\n if (res.status === 401 || res.status === 403) return {ok: false, reason: \"invalid_key\"}\n return {ok: false, reason: \"network\"}\n } catch {\n return {ok: false, reason: \"network\"}\n }\n}\n"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* SonioxTokenAssembler
|
|
3
3
|
*
|
|
4
4
|
* Pure port of the cloud Soniox provider's utterance assembly
|
|
5
|
-
* (cloud
|
|
5
|
+
* (cloud/packages/runtime/src/services/audio/providers/soniox.ts). Soniox
|
|
6
6
|
* emits a rolling token window that is re-finalized/compacted across results;
|
|
7
7
|
* this class turns that stream into stable interim/final utterances:
|
|
8
8
|
*
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* SonioxTokenAssembler
|
|
3
3
|
*
|
|
4
4
|
* Pure port of the cloud Soniox provider's utterance assembly
|
|
5
|
-
* (cloud
|
|
5
|
+
* (cloud/packages/runtime/src/services/audio/providers/soniox.ts). Soniox
|
|
6
6
|
* emits a rolling token window that is re-finalized/compacted across results;
|
|
7
7
|
* this class turns that stream into stable interim/final utterances:
|
|
8
8
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SonioxTokenAssembler.js","sourceRoot":"","sources":["../../../src/services/soniox/SonioxTokenAssembler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAA;AAG1C,MAAM,0BAA0B,GAAG,CAAC,CAAA;AACpC,MAAM,yBAAyB,GAAG,EAAE,CAAA;AACpC,MAAM,2BAA2B,GAAG,EAAE,CAAA;AACtC,MAAM,4BAA4B,GAAG,GAAG,CAAA;AA4BxC,MAAM,OAAO,oBAAoB;IACd,IAAI,CAA6B;IACjC,kBAAkB,CAAQ;IAC1B,YAAY,CAAwC;IACpD,cAAc,CAAsB;IAErD,8BAA8B;IACtB,YAAY,GAAG,EAAE,CAAA;IACjB,YAAY,GAAG,CAAC,CAAA;IAChB,eAAe,GAAG,EAAE,CAAA;IAE5B,4EAA4E;IACpE,oBAAoB,GAAG,EAAE,CAAA;IACzB,oBAAoB,GAAG,CAAC,CAAA;IACxB,gBAAgB,GAAG,EAAE,CAAA;IACrB,qBAAqB,GAAuB,SAAS,CAAA;IAErD,kBAAkB,GAAkB,IAAI,CAAA;IACxC,gBAAgB,GAAuB,SAAS,CAAA;IAChD,eAAe,GAAuB,SAAS,CAAA;IAEvD,sCAAsC;IAC9B,gBAAgB,GAAG,EAAE,CAAA;IACrB,uBAAuB,GAAkB,IAAI,CAAA;IAC7C,qBAAqB,GAAuB,SAAS,CAAA;IACrD,oBAAoB,GAAuB,SAAS,CAAA;IACpD,wBAAwB,GAAG,EAAE,CAAA;IAC7B,0BAA0B,GAAuB,SAAS,CAAA;IAC1D,mBAAmB,GAAuB,SAAS,CAAA;IACnD,iBAAiB,GAAuB,SAAS,CAAA;IACjD,iBAAiB,GAAkB,IAAI,CAAA;IAE/C,6EAA6E;IACrE,YAAY,GAAG,CAAC,CAAA;IACP,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IAErE,YAAY,IAAiC;QAC3C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,kBAAkB,IAAI,4BAA4B,CAAA;QACjF,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;QACjF,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAA;IACjF,CAAC;IAEO,eAAe;QACrB,IAAI,CAAC,YAAY,IAAI,CAAC,CAAA;QACtB,OAAO,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,YAAY,EAAE,CAAA;IACzE,CAAC;IAEO,iBAAiB,CAAC,SAAkB,EAAE,QAAiB;QAC7D,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,eAAe,EAAE,CAAA;QAChD,IAAI,CAAC,gBAAgB,GAAG,SAAS,CAAA;QACjC,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAA;QAC/B,IAAI,CAAC,YAAY,GAAG,EAAE,CAAA;QACtB,IAAI,CAAC,YAAY,GAAG,CAAC,CAAA;QACrB,IAAI,CAAC,eAAe,GAAG,EAAE,CAAA;QACzB,IAAI,CAAC,oBAAoB,GAAG,EAAE,CAAA;QAC9B,IAAI,CAAC,oBAAoB,GAAG,CAAC,CAAA;QAC7B,IAAI,CAAC,gBAAgB,GAAG,EAAE,CAAA;QAC1B,IAAI,CAAC,qBAAqB,GAAG,SAAS,CAAA;IACxC,CAAC;IAEO,oBAAoB;QAC1B,IAAI,CAAC,YAAY,GAAG,EAAE,CAAA;QACtB,IAAI,CAAC,YAAY,GAAG,CAAC,CAAA;QACrB,IAAI,CAAC,eAAe,GAAG,EAAE,CAAA;QACzB,IAAI,CAAC,oBAAoB,GAAG,EAAE,CAAA;QAC9B,IAAI,CAAC,oBAAoB,GAAG,CAAC,CAAA;QAC7B,IAAI,CAAC,gBAAgB,GAAG,EAAE,CAAA;QAC1B,IAAI,CAAC,qBAAqB,GAAG,SAAS,CAAA;QACtC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAA;QAC9B,IAAI,CAAC,gBAAgB,GAAG,SAAS,CAAA;QACjC,IAAI,CAAC,eAAe,GAAG,SAAS,CAAA;IAClC,CAAC;IAEO,iBAAiB;QACvB,IAAI,IAAI,CAAC,iBAAiB,KAAK,IAAI,EAAE,CAAC;YACpC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;YAC3C,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAA;QAC/B,CAAC;QACD,IAAI,CAAC,gBAAgB,GAAG,EAAE,CAAA;QAC1B,IAAI,CAAC,uBAAuB,GAAG,IAAI,CAAA;QACnC,IAAI,CAAC,qBAAqB,GAAG,SAAS,CAAA;QACtC,IAAI,CAAC,oBAAoB,GAAG,SAAS,CAAA;QACrC,IAAI,CAAC,wBAAwB,GAAG,EAAE,CAAA;QAClC,IAAI,CAAC,0BAA0B,GAAG,SAAS,CAAA;QAC3C,IAAI,CAAC,mBAAmB,GAAG,SAAS,CAAA;QACpC,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAA;IACpC,CAAC;IAEO,eAAe,CAAC,CAAS,EAAE,CAAS;QAC1C,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;QACrB,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;QACtB,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QAChC,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QACzD,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAElE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;QACtD,KAAK,IAAI,GAAG,GAAG,UAAU,EAAE,GAAG,IAAI,0BAA0B,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;YACvE,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;gBACvC,OAAO,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAChC,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAEO,wBAAwB,CAAC,IAAY,EAAE,QAAgB;QAC7D,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;QAClC,MAAM,kBAAkB,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAA;QAC1C,IAAI,CAAC,cAAc,IAAI,CAAC,kBAAkB;YAAE,OAAO,KAAK,CAAA;QACxD,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,GAAG,cAAc,CAAC,MAAM,CAAA;QAC9D,IAAI,IAAI,GAAG,yBAAyB;YAAE,OAAO,KAAK,CAAA;QAElD,IAAI,YAAY,GAAG,CAAC,CAAA;QACpB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,MAAM,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAA;QAC5E,OAAO,YAAY,GAAG,SAAS,IAAI,cAAc,CAAC,YAAY,CAAC,KAAK,kBAAkB,CAAC,YAAY,CAAC,EAAE,CAAC;YACrG,YAAY,IAAI,CAAC,CAAA;QACnB,CAAC;QAED,OAAO,YAAY,IAAI,IAAI,CAAC,GAAG,CAAC,2BAA2B,EAAE,cAAc,CAAC,MAAM,CAAC,CAAA;IACrF,CAAC;IAEO,cAAc,CAAC,KAStB;QACC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC;YACrB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,SAAS;YAC7C,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,KAAK,EAAE,KAAK,CAAC,KAAK;SACnB,CAAC,CAAA;IACJ,CAAC;IAEO,kBAAkB;QACxB,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,IAAI,CAAC,iBAAiB,EAAE,CAAA;YACxB,OAAM;QACR,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,gBAAgB,CAAA;QAC3C,MAAM,oBAAoB,GAAG,IAAI,CAAC,uBAAuB,CAAA;QACzD,IAAI,CAAC,cAAc,CAAC;YAClB,IAAI,EAAE,aAAa;YACnB,WAAW,EAAE,oBAAoB,IAAI,SAAS;YAC9C,SAAS,EAAE,IAAI,CAAC,qBAAqB;YACrC,QAAQ,EAAE,IAAI,CAAC,oBAAoB;YACnC,YAAY,EAAE,IAAI,CAAC,wBAAwB;YAC3C,cAAc,EAAE,IAAI,CAAC,0BAA0B;YAC/C,OAAO,EAAE,IAAI,CAAC,mBAAmB;YACjC,KAAK,EAAE,IAAI,CAAC,iBAAiB;SAC9B,CAAC,CAAA;QACF,IACE,oBAAoB;YACpB,IAAI,CAAC,kBAAkB,KAAK,oBAAoB;YAChD,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,aAAa,EAC7C,CAAC;YACD,IAAI,CAAC,oBAAoB,EAAE,CAAA;QAC7B,CAAC;QACD,IAAI,CAAC,iBAAiB,EAAE,CAAA;IAC1B,CAAC;IAEO,0BAA0B;QAChC,IAAI,IAAI,CAAC,iBAAiB,KAAK,IAAI;YAAE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;QAChF,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE;YAC9C,IAAI,CAAC,kBAAkB,EAAE,CAAA;QAC3B,CAAC,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAA;IAC7B,CAAC;IAEO,yBAAyB,CAAC,IAAY;QAC5C,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAA;QAC5B,IAAI,CAAC,uBAAuB,GAAG,IAAI,CAAC,kBAAkB,CAAA;QACtD,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,gBAAgB,CAAA;QAClD,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC,eAAe,CAAA;QAChD,IAAI,CAAC,wBAAwB,GAAG,IAAI,CAAC,gBAAgB,CAAA;QACrD,IAAI,CAAC,0BAA0B,GAAG,IAAI,CAAC,qBAAqB,CAAA;IAC9D,CAAC;IAED;;;OAGG;IACK,SAAS,CAAC,OAAgB,EAAE,KAAc;QAChD,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAA;QACxC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,CAAC,oBAAoB,EAAE,CAAA;YAC3B,OAAM;QACR,CAAC;QACD,IAAI,CAAC,cAAc,CAAC;YAClB,IAAI;YACJ,WAAW,EAAE,IAAI,CAAC,kBAAkB,IAAI,SAAS;YACjD,SAAS,EAAE,IAAI,CAAC,gBAAgB;YAChC,QAAQ,EAAE,IAAI,CAAC,eAAe;YAC9B,YAAY,EAAE,IAAI,CAAC,gBAAgB,IAAI,SAAS;YAChD,cAAc,EAAE,IAAI,CAAC,qBAAqB;YAC1C,OAAO;YACP,KAAK;SACN,CAAC,CAAA;QACF,IAAI,CAAC,oBAAoB,EAAE,CAAA;IAC7B,CAAC;IAED,YAAY,CAAC,MAAoB;QAC/B,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAA;QAErC,wEAAwE;QACxE,qEAAqE;QACrE,0EAA0E;QAC1E,2EAA2E;QAC3E,iDAAiD;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,kBAAkB,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QACjH,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,kBAAkB,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAElH,IAAI,WAAW,GAAG,EAAE,CAAA;QACpB,IAAI,QAA4B,CAAA;QAChC,IAAI,OAA2B,CAAA;QAC/B,IAAI,KAAyB,CAAA;QAC7B,IAAI,gBAAgB,GAAG,EAAE,CAAA;QAEzB,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,+DAA+D;YAC/D,wEAAwE;YACxE,uEAAuE;YACvE,qEAAqE;YACrE,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC7B,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;YAC7E,CAAC;YAED,IAAI,CAAC,CAAC,OAAO;gBAAE,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC,OAAO,CAAA;YAChD,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;gBACf,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAA;gBACrB,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC,QAAQ,CAAA;gBACjC,iEAAiE;gBACjE,sDAAsD;gBACtD,IAAI,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,CAAC,kBAAkB,IAAI,IAAI,EAAE,CAAC;oBAC5D,IAAI,CAAC,qBAAqB,GAAG,CAAC,CAAC,QAAQ,CAAA;gBACzC,CAAC;YACH,CAAC;YAED,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;gBACf,gBAAgB,IAAI,CAAC,CAAC,IAAI,CAAA;gBAC1B,IAAI,CAAC,CAAC,QAAQ,IAAI,IAAI,IAAI,OAAO,IAAI,IAAI;oBAAE,OAAO,GAAG,CAAC,CAAC,QAAQ,CAAA;gBAC/D,IAAI,CAAC,CAAC,MAAM,IAAI,IAAI;oBAAE,KAAK,GAAG,CAAC,CAAC,MAAM,CAAA;YACxC,CAAC;iBAAM,CAAC;gBACN,WAAW,IAAI,CAAC,CAAC,IAAI,CAAA;YACvB,CAAC;QACH,CAAC;QAED,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,IAAI,eAAe,GAAG,EAAE,CAAA;YACxB,IAAI,aAAa,GAAG,EAAE,CAAA;YACtB,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;gBAC/B,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;oBAC7B,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;gBAC9C,CAAC;gBACD,IAAI,CAAC,CAAC,QAAQ;oBAAE,IAAI,CAAC,qBAAqB,GAAG,CAAC,CAAC,QAAQ,CAAA;gBACvD,IAAI,CAAC,CAAC,QAAQ;oBAAE,aAAa,IAAI,CAAC,CAAC,IAAI,CAAA;;oBAClC,eAAe,IAAI,CAAC,CAAC,IAAI,CAAA;YAChC,CAAC;YACD,IAAI,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC,oBAAoB,EAAE,CAAC;gBACrD,IAAI,CAAC,oBAAoB,IAAI,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;YAC7E,CAAC;YACD,IAAI,CAAC,oBAAoB,GAAG,aAAa,CAAC,MAAM,CAAA;YAChD,MAAM,iBAAiB,GAAG,IAAI,CAAC,oBAAoB,GAAG,eAAe,CAAA;YACrE,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC;gBAAE,IAAI,CAAC,gBAAgB,GAAG,iBAAiB,CAAA;QAC7E,CAAC;QAED,0EAA0E;QAC1E,wDAAwD;QACxD,IAAI,gBAAgB,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YAChD,IAAI,CAAC,YAAY,IAAI,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QAChE,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,gBAAgB,CAAC,MAAM,CAAA;QAE3C,MAAM,aAAa,GAAG,CAAC,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC,CAAC,IAAI,EAAE,CAAA;QAC9D,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;YAAE,OAAM;QAEtC,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,iBAAiB,KAAK,IAAI,EAAE,CAAC;YAC7D,MAAM,aAAa,GAAG,IAAI,CAAC,gBAAgB,CAAA;YAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAA;YACzE,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBACpB,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,uBAAuB,IAAI,IAAI,CAAC,kBAAkB,CAAA;gBACjF,IAAI,IAAI,CAAC,gBAAgB;oBAAE,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,gBAAgB,CAAA;;oBACxE,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,qBAAqB,CAAA;gBACvD,IAAI,IAAI,CAAC,eAAe;oBAAE,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC,eAAe,CAAA;;oBACrE,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,oBAAoB,CAAA;gBACrD,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;oBAC1B,IAAI,CAAC,wBAAwB;wBAC3B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,wBAAwB,EAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,IAAI,CAAC,gBAAgB,CAAA;gBACvG,CAAC;gBACD,IAAI,IAAI,CAAC,qBAAqB,EAAE,CAAC;oBAC/B,IAAI,CAAC,0BAA0B,GAAG,IAAI,CAAC,qBAAqB,CAAA;gBAC9D,CAAC;gBAED,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAA;gBAC9B,IAAI,CAAC,eAAe,GAAG,MAAM,CAAA;gBAE7B,IAAI,MAAM,KAAK,aAAa,EAAE,CAAC;oBAC7B,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC;wBACrB,IAAI,EAAE,MAAM;wBACZ,OAAO,EAAE,KAAK;wBACd,WAAW,EAAE,IAAI,CAAC,kBAAkB,IAAI,SAAS;wBACjD,SAAS,EAAE,IAAI,CAAC,gBAAgB;wBAChC,QAAQ;wBACR,YAAY,EAAE,IAAI,CAAC,wBAAwB,IAAI,SAAS;wBACxD,cAAc,EAAE,IAAI,CAAC,0BAA0B;wBAC/C,OAAO;wBACP,KAAK;qBACN,CAAC,CAAA;oBACF,IAAI,CAAC,0BAA0B,EAAE,CAAA;gBACnC,CAAC;gBACD,OAAM;YACR,CAAC;YAED,IAAI,CAAC,kBAAkB,EAAE,CAAA;QAC3B,CAAC;QAED,0EAA0E;QAC1E,IAAI,IAAI,CAAC,wBAAwB,CAAC,aAAa,EAAE,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;YACvE,OAAM;QACR,CAAC;QAED,IAAI,aAAa,KAAK,IAAI,CAAC,eAAe,EAAE,CAAC;YAC3C,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC;gBACrB,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,KAAK;gBACd,WAAW,EAAE,IAAI,CAAC,kBAAkB,IAAI,SAAS;gBACjD,SAAS,EAAE,IAAI,CAAC,gBAAgB;gBAChC,QAAQ;gBACR,YAAY,EAAE,IAAI,CAAC,gBAAgB,IAAI,SAAS;gBAChD,cAAc,EAAE,IAAI,CAAC,qBAAqB;gBAC1C,OAAO;gBACP,KAAK;aACN,CAAC,CAAA;YACF,IAAI,CAAC,eAAe,GAAG,aAAa,CAAA;QACtC,CAAC;IACH,CAAC;IAED,cAAc;QACZ,0EAA0E;QAC1E,gDAAgD;QAChD,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAA;QAC7C,IAAI,SAAS,EAAE,CAAC;YACd,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,iBAAiB,KAAK,IAAI,EAAE,CAAC;gBAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAA;gBACrE,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;oBACpB,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAA;oBAC9B,IAAI,IAAI,CAAC,gBAAgB;wBAAE,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,gBAAgB,CAAA;oBAC7E,IAAI,IAAI,CAAC,eAAe;wBAAE,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC,eAAe,CAAA;oBAC1E,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;wBAC1B,IAAI,CAAC,wBAAwB;4BAC3B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,wBAAwB,EAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,IAAI,CAAC,gBAAgB,CAAA;oBACvG,CAAC;oBACD,IAAI,IAAI,CAAC,qBAAqB,EAAE,CAAC;wBAC/B,IAAI,CAAC,0BAA0B,GAAG,IAAI,CAAC,qBAAqB,CAAA;oBAC9D,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,kBAAkB,EAAE,CAAA;oBACzB,IAAI,CAAC,yBAAyB,CAAC,SAAS,CAAC,CAAA;gBAC3C,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,yBAAyB,CAAC,SAAS,CAAC,CAAA;YAC3C,CAAC;YACD,IAAI,CAAC,0BAA0B,EAAE,CAAA;QACnC,CAAC;QACD,IAAI,CAAC,oBAAoB,EAAE,CAAA;IAC7B,CAAC;IAED,qEAAqE;IACrE,eAAe;QACb,IAAI,CAAC,kBAAkB,EAAE,CAAA;QACzB,IAAI,CAAC,SAAS,EAAE,CAAA;IAClB,CAAC;IAED,yEAAyE;IACzE,KAAK;QACH,IAAI,CAAC,kBAAkB,EAAE,CAAA;QACzB,IAAI,CAAC,SAAS,EAAE,CAAA;IAClB,CAAC;IAED,+DAA+D;IAC/D,OAAO;QACL,IAAI,CAAC,iBAAiB,EAAE,CAAA;QACxB,IAAI,CAAC,oBAAoB,EAAE,CAAA;IAC7B,CAAC;CACF","sourcesContent":["/**\n * SonioxTokenAssembler\n *\n * Pure port of the cloud Soniox provider's utterance assembly\n * (cloud-v2/packages/runtime/src/services/audio/providers/soniox.ts). Soniox\n * emits a rolling token window that is re-finalized/compacted across results;\n * this class turns that stream into stable interim/final utterances:\n *\n * - finalized text accumulates into a stable prefix so interims never shrink\n * - a single FINAL per utterance, committed at the endpoint boundary\n * - the endpoint is a soft signal: the candidate final is held briefly\n * (debounce) and merged back if the next rolling window overlaps\n * - speaker/language are tracked per token but never split an utterance\n * (Soniox re-diarizes its window; per-token labels flicker)\n * - translation sessions split the window: translation-status tokens are the\n * emitted text, original-status tokens ride along as `originalText`, and\n * untagged tokens are Soniox's same-language passthrough\n *\n * Timers are injected for testability; production uses BgTimer so the debounce\n * survives app backgrounding.\n */\n\nimport {BgTimer} from \"../../utils/timers\"\nimport type {SonioxResult} from \"./SonioxRealtimeClient\"\n\nconst MIN_ENDPOINT_OVERLAP_CHARS = 3\nconst MIN_REGRESSION_DROP_CHARS = 12\nconst MIN_REGRESSION_PREFIX_CHARS = 12\nconst DEFAULT_ENDPOINT_DEBOUNCE_MS = 500\n\nexport interface AssembledTranscript {\n text: string\n isFinal: boolean\n utteranceId?: string\n speakerId?: string\n language?: string\n originalText?: string\n sourceLanguage?: string\n startMs?: number\n endMs?: number\n}\n\nexport interface SonioxTokenAssemblerOptions {\n /** Stable id used in minted utterance ids (readable per subscription). */\n scope: string\n /** True for translation sessions: split original/translation token halves. */\n isTranslation: boolean\n /** Subscription language, used when a token carries no detected language. */\n fallbackLanguage?: string\n onTranscript: (t: AssembledTranscript) => void\n endpointDebounceMs?: number\n /** Test seams; default to background-safe timers. */\n setTimeoutFn?: (cb: () => void, ms: number) => number\n clearTimeoutFn?: (id: number) => void\n}\n\nexport class SonioxTokenAssembler {\n private readonly opts: SonioxTokenAssemblerOptions\n private readonly endpointDebounceMs: number\n private readonly setTimeoutFn: (cb: () => void, ms: number) => number\n private readonly clearTimeoutFn: (id: number) => void\n\n // === Per-utterance state ===\n private stablePrefix = \"\"\n private prevFinalLen = 0\n private lastSentInterim = \"\"\n\n // Translation subs only: parallel accumulator for original-language tokens.\n private originalStablePrefix = \"\"\n private originalPrevFinalLen = 0\n private lastOriginalText = \"\"\n private currentSourceLanguage: string | undefined = undefined\n\n private currentUtteranceId: string | null = null\n private currentSpeakerId: string | undefined = undefined\n private currentLanguage: string | undefined = undefined\n\n // Deferred final (endpoint debounce).\n private pendingFinalText = \"\"\n private pendingFinalUtteranceId: string | null = null\n private pendingFinalSpeakerId: string | undefined = undefined\n private pendingFinalLanguage: string | undefined = undefined\n private pendingFinalOriginalText = \"\"\n private pendingFinalSourceLanguage: string | undefined = undefined\n private pendingFinalStartMs: number | undefined = undefined\n private pendingFinalEndMs: number | undefined = undefined\n private pendingFinalTimer: number | null = null\n\n // Id minting: an instance nonce prevents collisions across service restarts.\n private utteranceSeq = 0\n private readonly instanceId = Math.random().toString(36).slice(2, 10)\n\n constructor(opts: SonioxTokenAssemblerOptions) {\n this.opts = opts\n this.endpointDebounceMs = opts.endpointDebounceMs ?? DEFAULT_ENDPOINT_DEBOUNCE_MS\n this.setTimeoutFn = opts.setTimeoutFn ?? ((cb, ms) => BgTimer.setTimeout(cb, ms))\n this.clearTimeoutFn = opts.clearTimeoutFn ?? ((id) => BgTimer.clearTimeout(id))\n }\n\n private mintUtteranceId(): string {\n this.utteranceSeq += 1\n return `utt_${this.opts.scope}_${this.instanceId}_${this.utteranceSeq}`\n }\n\n private startNewUtterance(speakerId?: string, language?: string): void {\n this.currentUtteranceId = this.mintUtteranceId()\n this.currentSpeakerId = speakerId\n this.currentLanguage = language\n this.stablePrefix = \"\"\n this.prevFinalLen = 0\n this.lastSentInterim = \"\"\n this.originalStablePrefix = \"\"\n this.originalPrevFinalLen = 0\n this.lastOriginalText = \"\"\n this.currentSourceLanguage = undefined\n }\n\n private resetActiveUtterance(): void {\n this.stablePrefix = \"\"\n this.prevFinalLen = 0\n this.lastSentInterim = \"\"\n this.originalStablePrefix = \"\"\n this.originalPrevFinalLen = 0\n this.lastOriginalText = \"\"\n this.currentSourceLanguage = undefined\n this.currentUtteranceId = null\n this.currentSpeakerId = undefined\n this.currentLanguage = undefined\n }\n\n private clearPendingFinal(): void {\n if (this.pendingFinalTimer !== null) {\n this.clearTimeoutFn(this.pendingFinalTimer)\n this.pendingFinalTimer = null\n }\n this.pendingFinalText = \"\"\n this.pendingFinalUtteranceId = null\n this.pendingFinalSpeakerId = undefined\n this.pendingFinalLanguage = undefined\n this.pendingFinalOriginalText = \"\"\n this.pendingFinalSourceLanguage = undefined\n this.pendingFinalStartMs = undefined\n this.pendingFinalEndMs = undefined\n }\n\n private tryMergeOverlap(a: string, b: string): string | null {\n const left = a.trim()\n const right = b.trim()\n if (!left || !right) return null\n if (left === right || left.startsWith(right)) return left\n if (right.startsWith(left)) return left + right.slice(left.length)\n\n const maxOverlap = Math.min(left.length, right.length)\n for (let len = maxOverlap; len >= MIN_ENDPOINT_OVERLAP_CHARS; len -= 1) {\n if (left.endsWith(right.slice(0, len))) {\n return left + right.slice(len)\n }\n }\n return null\n }\n\n private isLikelyWindowRegression(next: string, previous: string): boolean {\n const normalizedNext = next.trim()\n const normalizedPrevious = previous.trim()\n if (!normalizedNext || !normalizedPrevious) return false\n const drop = normalizedPrevious.length - normalizedNext.length\n if (drop < MIN_REGRESSION_DROP_CHARS) return false\n\n let sharedPrefix = 0\n const maxPrefix = Math.min(normalizedNext.length, normalizedPrevious.length)\n while (sharedPrefix < maxPrefix && normalizedNext[sharedPrefix] === normalizedPrevious[sharedPrefix]) {\n sharedPrefix += 1\n }\n\n return sharedPrefix >= Math.min(MIN_REGRESSION_PREFIX_CHARS, normalizedNext.length)\n }\n\n private emitFinalEvent(event: {\n text: string\n utteranceId?: string\n speakerId?: string\n language?: string\n originalText?: string\n sourceLanguage?: string\n startMs?: number\n endMs?: number\n }): void {\n this.opts.onTranscript({\n text: event.text,\n isFinal: true,\n utteranceId: event.utteranceId,\n speakerId: event.speakerId,\n language: event.language,\n originalText: event.originalText || undefined,\n sourceLanguage: event.sourceLanguage,\n startMs: event.startMs,\n endMs: event.endMs,\n })\n }\n\n private commitPendingFinal(): void {\n if (!this.pendingFinalText) {\n this.clearPendingFinal()\n return\n }\n\n const committedText = this.pendingFinalText\n const committedUtteranceId = this.pendingFinalUtteranceId\n this.emitFinalEvent({\n text: committedText,\n utteranceId: committedUtteranceId ?? undefined,\n speakerId: this.pendingFinalSpeakerId,\n language: this.pendingFinalLanguage,\n originalText: this.pendingFinalOriginalText,\n sourceLanguage: this.pendingFinalSourceLanguage,\n startMs: this.pendingFinalStartMs,\n endMs: this.pendingFinalEndMs,\n })\n if (\n committedUtteranceId &&\n this.currentUtteranceId === committedUtteranceId &&\n this.lastSentInterim.trim() === committedText\n ) {\n this.resetActiveUtterance()\n }\n this.clearPendingFinal()\n }\n\n private schedulePendingFinalCommit(): void {\n if (this.pendingFinalTimer !== null) this.clearTimeoutFn(this.pendingFinalTimer)\n this.pendingFinalTimer = this.setTimeoutFn(() => {\n this.commitPendingFinal()\n }, this.endpointDebounceMs)\n }\n\n private setPendingFinalFromActive(text: string): void {\n this.pendingFinalText = text\n this.pendingFinalUtteranceId = this.currentUtteranceId\n this.pendingFinalSpeakerId = this.currentSpeakerId\n this.pendingFinalLanguage = this.currentLanguage\n this.pendingFinalOriginalText = this.lastOriginalText\n this.pendingFinalSourceLanguage = this.currentSourceLanguage\n }\n\n /**\n * Commit the current utterance as a single FINAL and reset. Called only at a\n * real boundary: endpoint (via the debounce path), finalize ack, or close.\n */\n private emitFinal(startMs?: number, endMs?: number): void {\n const text = this.lastSentInterim.trim()\n if (!text) {\n this.resetActiveUtterance()\n return\n }\n this.emitFinalEvent({\n text,\n utteranceId: this.currentUtteranceId ?? undefined,\n speakerId: this.currentSpeakerId,\n language: this.currentLanguage,\n originalText: this.lastOriginalText || undefined,\n sourceLanguage: this.currentSourceLanguage,\n startMs,\n endMs,\n })\n this.resetActiveUtterance()\n }\n\n handleResult(result: SonioxResult): void {\n const allTokens = result.tokens ?? []\n\n // Translation sessions: translation-status tokens are the emitted text;\n // original-status tokens feed the parallel originalText accumulator.\n // Untagged tokens are Soniox's SAME-LANGUAGE PASSTHROUGH (spoken language\n // already equals the target): keep them as emitted text so target-language\n // speech surfaces instead of silently vanishing.\n const tokens = this.opts.isTranslation ? allTokens.filter((t) => t.translation_status !== \"original\") : allTokens\n const originalTokens = this.opts.isTranslation ? allTokens.filter((t) => t.translation_status === \"original\") : []\n\n let interimText = \"\"\n let language: string | undefined\n let startMs: number | undefined\n let endMs: number | undefined\n let currentFinalText = \"\"\n\n for (const t of tokens) {\n // First token of the stream (or first after a commit): open an\n // utterance. Deliberately no split on per-token speaker change — Soniox\n // re-diarizes its rolling window, so speaker labels flicker within one\n // real utterance; the single FINAL comes from the endpoint boundary.\n if (!this.currentUtteranceId) {\n this.startNewUtterance(t.speaker, t.language ?? this.opts.fallbackLanguage)\n }\n\n if (t.speaker) this.currentSpeakerId = t.speaker\n if (t.language) {\n language = t.language\n this.currentLanguage = t.language\n // Untagged tokens on a translation session are the same-language\n // passthrough: their language IS the detected source.\n if (this.opts.isTranslation && t.translation_status == null) {\n this.currentSourceLanguage = t.language\n }\n }\n\n if (t.is_final) {\n currentFinalText += t.text\n if (t.start_ms != null && startMs == null) startMs = t.start_ms\n if (t.end_ms != null) endMs = t.end_ms\n } else {\n interimText += t.text\n }\n }\n\n if (originalTokens.length > 0) {\n let originalInterim = \"\"\n let originalFinal = \"\"\n for (const t of originalTokens) {\n if (!this.currentUtteranceId) {\n this.startNewUtterance(t.speaker, undefined)\n }\n if (t.language) this.currentSourceLanguage = t.language\n if (t.is_final) originalFinal += t.text\n else originalInterim += t.text\n }\n if (originalFinal.length > this.originalPrevFinalLen) {\n this.originalStablePrefix += originalFinal.slice(this.originalPrevFinalLen)\n }\n this.originalPrevFinalLen = originalFinal.length\n const originalComposite = this.originalStablePrefix + originalInterim\n if (originalComposite.length > 0) this.lastOriginalText = originalComposite\n }\n\n // Accumulate finalized text: on window growth append the delta; on shrink\n // (compaction) keep stablePrefix and reset the tracker.\n if (currentFinalText.length > this.prevFinalLen) {\n this.stablePrefix += currentFinalText.slice(this.prevFinalLen)\n }\n this.prevFinalLen = currentFinalText.length\n\n const compositeText = (this.stablePrefix + interimText).trim()\n if (compositeText.length === 0) return\n\n if (this.pendingFinalText && this.pendingFinalTimer !== null) {\n const pendingBefore = this.pendingFinalText\n const merged = this.tryMergeOverlap(this.pendingFinalText, compositeText)\n if (merged !== null) {\n this.currentUtteranceId = this.pendingFinalUtteranceId ?? this.currentUtteranceId\n if (this.currentSpeakerId) this.pendingFinalSpeakerId = this.currentSpeakerId\n else this.currentSpeakerId = this.pendingFinalSpeakerId\n if (this.currentLanguage) this.pendingFinalLanguage = this.currentLanguage\n else this.currentLanguage = this.pendingFinalLanguage\n if (this.lastOriginalText) {\n this.pendingFinalOriginalText =\n this.tryMergeOverlap(this.pendingFinalOriginalText, this.lastOriginalText) ?? this.lastOriginalText\n }\n if (this.currentSourceLanguage) {\n this.pendingFinalSourceLanguage = this.currentSourceLanguage\n }\n\n this.pendingFinalText = merged\n this.lastSentInterim = merged\n\n if (merged !== pendingBefore) {\n this.opts.onTranscript({\n text: merged,\n isFinal: false,\n utteranceId: this.currentUtteranceId ?? undefined,\n speakerId: this.currentSpeakerId,\n language,\n originalText: this.pendingFinalOriginalText || undefined,\n sourceLanguage: this.pendingFinalSourceLanguage,\n startMs,\n endMs,\n })\n this.schedulePendingFinalCommit()\n }\n return\n }\n\n this.commitPendingFinal()\n }\n\n // Emit an INTERIM only; the single final comes at the utterance boundary.\n if (this.isLikelyWindowRegression(compositeText, this.lastSentInterim)) {\n return\n }\n\n if (compositeText !== this.lastSentInterim) {\n this.opts.onTranscript({\n text: compositeText,\n isFinal: false,\n utteranceId: this.currentUtteranceId ?? undefined,\n speakerId: this.currentSpeakerId,\n language,\n originalText: this.lastOriginalText || undefined,\n sourceLanguage: this.currentSourceLanguage,\n startMs,\n endMs,\n })\n this.lastSentInterim = compositeText\n }\n }\n\n handleEndpoint(): void {\n // Soft boundary: defer the FINAL briefly; if Soniox's next rolling window\n // overlaps, it merges back into this utterance.\n const candidate = this.lastSentInterim.trim()\n if (candidate) {\n if (this.pendingFinalText && this.pendingFinalTimer !== null) {\n const merged = this.tryMergeOverlap(this.pendingFinalText, candidate)\n if (merged !== null) {\n this.pendingFinalText = merged\n if (this.currentSpeakerId) this.pendingFinalSpeakerId = this.currentSpeakerId\n if (this.currentLanguage) this.pendingFinalLanguage = this.currentLanguage\n if (this.lastOriginalText) {\n this.pendingFinalOriginalText =\n this.tryMergeOverlap(this.pendingFinalOriginalText, this.lastOriginalText) ?? this.lastOriginalText\n }\n if (this.currentSourceLanguage) {\n this.pendingFinalSourceLanguage = this.currentSourceLanguage\n }\n } else {\n this.commitPendingFinal()\n this.setPendingFinalFromActive(candidate)\n }\n } else {\n this.setPendingFinalFromActive(candidate)\n }\n this.schedulePendingFinalCommit()\n }\n this.resetActiveUtterance()\n }\n\n /** Finalize ack (`<fin>`): trusted \"speech segment ended\" signal. */\n handleFinalized(): void {\n this.commitPendingFinal()\n this.emitFinal()\n }\n\n /** Flush any in-flight utterance as a final (session close/teardown). */\n flush(): void {\n this.commitPendingFinal()\n this.emitFinal()\n }\n\n /** Drop all state without emitting (e.g. audio-gap resume). */\n discard(): void {\n this.clearPendingFinal()\n this.resetActiveUtterance()\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"SonioxTokenAssembler.js","sourceRoot":"","sources":["../../../src/services/soniox/SonioxTokenAssembler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SonioxTokenAssembler\n *\n * Pure port of the cloud Soniox provider's utterance assembly\n * (cloud/packages/runtime/src/services/audio/providers/soniox.ts). Soniox\n * emits a rolling token window that is re-finalized/compacted across results;\n * this class turns that stream into stable interim/final utterances:\n *\n * - finalized text accumulates into a stable prefix so interims never shrink\n * - a single FINAL per utterance, committed at the endpoint boundary\n * - the endpoint is a soft signal: the candidate final is held briefly\n * (debounce) and merged back if the next rolling window overlaps\n * - speaker/language are tracked per token but never split an utterance\n * (Soniox re-diarizes its window; per-token labels flicker)\n * - translation sessions split the window: translation-status tokens are the\n * emitted text, original-status tokens ride along as `originalText`, and\n * untagged tokens are Soniox's same-language passthrough\n *\n * Timers are injected for testability; production uses BgTimer so the debounce\n * survives app backgrounding.\n */\n\nimport {BgTimer} from \"../../utils/timers\"\nimport type {SonioxResult} from \"./SonioxRealtimeClient\"\n\nconst MIN_ENDPOINT_OVERLAP_CHARS = 3\nconst MIN_REGRESSION_DROP_CHARS = 12\nconst MIN_REGRESSION_PREFIX_CHARS = 12\nconst DEFAULT_ENDPOINT_DEBOUNCE_MS = 500\n\nexport interface AssembledTranscript {\n text: string\n isFinal: boolean\n utteranceId?: string\n speakerId?: string\n language?: string\n originalText?: string\n sourceLanguage?: string\n startMs?: number\n endMs?: number\n}\n\nexport interface SonioxTokenAssemblerOptions {\n /** Stable id used in minted utterance ids (readable per subscription). */\n scope: string\n /** True for translation sessions: split original/translation token halves. */\n isTranslation: boolean\n /** Subscription language, used when a token carries no detected language. */\n fallbackLanguage?: string\n onTranscript: (t: AssembledTranscript) => void\n endpointDebounceMs?: number\n /** Test seams; default to background-safe timers. */\n setTimeoutFn?: (cb: () => void, ms: number) => number\n clearTimeoutFn?: (id: number) => void\n}\n\nexport class SonioxTokenAssembler {\n private readonly opts: SonioxTokenAssemblerOptions\n private readonly endpointDebounceMs: number\n private readonly setTimeoutFn: (cb: () => void, ms: number) => number\n private readonly clearTimeoutFn: (id: number) => void\n\n // === Per-utterance state ===\n private stablePrefix = \"\"\n private prevFinalLen = 0\n private lastSentInterim = \"\"\n\n // Translation subs only: parallel accumulator for original-language tokens.\n private originalStablePrefix = \"\"\n private originalPrevFinalLen = 0\n private lastOriginalText = \"\"\n private currentSourceLanguage: string | undefined = undefined\n\n private currentUtteranceId: string | null = null\n private currentSpeakerId: string | undefined = undefined\n private currentLanguage: string | undefined = undefined\n\n // Deferred final (endpoint debounce).\n private pendingFinalText = \"\"\n private pendingFinalUtteranceId: string | null = null\n private pendingFinalSpeakerId: string | undefined = undefined\n private pendingFinalLanguage: string | undefined = undefined\n private pendingFinalOriginalText = \"\"\n private pendingFinalSourceLanguage: string | undefined = undefined\n private pendingFinalStartMs: number | undefined = undefined\n private pendingFinalEndMs: number | undefined = undefined\n private pendingFinalTimer: number | null = null\n\n // Id minting: an instance nonce prevents collisions across service restarts.\n private utteranceSeq = 0\n private readonly instanceId = Math.random().toString(36).slice(2, 10)\n\n constructor(opts: SonioxTokenAssemblerOptions) {\n this.opts = opts\n this.endpointDebounceMs = opts.endpointDebounceMs ?? DEFAULT_ENDPOINT_DEBOUNCE_MS\n this.setTimeoutFn = opts.setTimeoutFn ?? ((cb, ms) => BgTimer.setTimeout(cb, ms))\n this.clearTimeoutFn = opts.clearTimeoutFn ?? ((id) => BgTimer.clearTimeout(id))\n }\n\n private mintUtteranceId(): string {\n this.utteranceSeq += 1\n return `utt_${this.opts.scope}_${this.instanceId}_${this.utteranceSeq}`\n }\n\n private startNewUtterance(speakerId?: string, language?: string): void {\n this.currentUtteranceId = this.mintUtteranceId()\n this.currentSpeakerId = speakerId\n this.currentLanguage = language\n this.stablePrefix = \"\"\n this.prevFinalLen = 0\n this.lastSentInterim = \"\"\n this.originalStablePrefix = \"\"\n this.originalPrevFinalLen = 0\n this.lastOriginalText = \"\"\n this.currentSourceLanguage = undefined\n }\n\n private resetActiveUtterance(): void {\n this.stablePrefix = \"\"\n this.prevFinalLen = 0\n this.lastSentInterim = \"\"\n this.originalStablePrefix = \"\"\n this.originalPrevFinalLen = 0\n this.lastOriginalText = \"\"\n this.currentSourceLanguage = undefined\n this.currentUtteranceId = null\n this.currentSpeakerId = undefined\n this.currentLanguage = undefined\n }\n\n private clearPendingFinal(): void {\n if (this.pendingFinalTimer !== null) {\n this.clearTimeoutFn(this.pendingFinalTimer)\n this.pendingFinalTimer = null\n }\n this.pendingFinalText = \"\"\n this.pendingFinalUtteranceId = null\n this.pendingFinalSpeakerId = undefined\n this.pendingFinalLanguage = undefined\n this.pendingFinalOriginalText = \"\"\n this.pendingFinalSourceLanguage = undefined\n this.pendingFinalStartMs = undefined\n this.pendingFinalEndMs = undefined\n }\n\n private tryMergeOverlap(a: string, b: string): string | null {\n const left = a.trim()\n const right = b.trim()\n if (!left || !right) return null\n if (left === right || left.startsWith(right)) return left\n if (right.startsWith(left)) return left + right.slice(left.length)\n\n const maxOverlap = Math.min(left.length, right.length)\n for (let len = maxOverlap; len >= MIN_ENDPOINT_OVERLAP_CHARS; len -= 1) {\n if (left.endsWith(right.slice(0, len))) {\n return left + right.slice(len)\n }\n }\n return null\n }\n\n private isLikelyWindowRegression(next: string, previous: string): boolean {\n const normalizedNext = next.trim()\n const normalizedPrevious = previous.trim()\n if (!normalizedNext || !normalizedPrevious) return false\n const drop = normalizedPrevious.length - normalizedNext.length\n if (drop < MIN_REGRESSION_DROP_CHARS) return false\n\n let sharedPrefix = 0\n const maxPrefix = Math.min(normalizedNext.length, normalizedPrevious.length)\n while (sharedPrefix < maxPrefix && normalizedNext[sharedPrefix] === normalizedPrevious[sharedPrefix]) {\n sharedPrefix += 1\n }\n\n return sharedPrefix >= Math.min(MIN_REGRESSION_PREFIX_CHARS, normalizedNext.length)\n }\n\n private emitFinalEvent(event: {\n text: string\n utteranceId?: string\n speakerId?: string\n language?: string\n originalText?: string\n sourceLanguage?: string\n startMs?: number\n endMs?: number\n }): void {\n this.opts.onTranscript({\n text: event.text,\n isFinal: true,\n utteranceId: event.utteranceId,\n speakerId: event.speakerId,\n language: event.language,\n originalText: event.originalText || undefined,\n sourceLanguage: event.sourceLanguage,\n startMs: event.startMs,\n endMs: event.endMs,\n })\n }\n\n private commitPendingFinal(): void {\n if (!this.pendingFinalText) {\n this.clearPendingFinal()\n return\n }\n\n const committedText = this.pendingFinalText\n const committedUtteranceId = this.pendingFinalUtteranceId\n this.emitFinalEvent({\n text: committedText,\n utteranceId: committedUtteranceId ?? undefined,\n speakerId: this.pendingFinalSpeakerId,\n language: this.pendingFinalLanguage,\n originalText: this.pendingFinalOriginalText,\n sourceLanguage: this.pendingFinalSourceLanguage,\n startMs: this.pendingFinalStartMs,\n endMs: this.pendingFinalEndMs,\n })\n if (\n committedUtteranceId &&\n this.currentUtteranceId === committedUtteranceId &&\n this.lastSentInterim.trim() === committedText\n ) {\n this.resetActiveUtterance()\n }\n this.clearPendingFinal()\n }\n\n private schedulePendingFinalCommit(): void {\n if (this.pendingFinalTimer !== null) this.clearTimeoutFn(this.pendingFinalTimer)\n this.pendingFinalTimer = this.setTimeoutFn(() => {\n this.commitPendingFinal()\n }, this.endpointDebounceMs)\n }\n\n private setPendingFinalFromActive(text: string): void {\n this.pendingFinalText = text\n this.pendingFinalUtteranceId = this.currentUtteranceId\n this.pendingFinalSpeakerId = this.currentSpeakerId\n this.pendingFinalLanguage = this.currentLanguage\n this.pendingFinalOriginalText = this.lastOriginalText\n this.pendingFinalSourceLanguage = this.currentSourceLanguage\n }\n\n /**\n * Commit the current utterance as a single FINAL and reset. Called only at a\n * real boundary: endpoint (via the debounce path), finalize ack, or close.\n */\n private emitFinal(startMs?: number, endMs?: number): void {\n const text = this.lastSentInterim.trim()\n if (!text) {\n this.resetActiveUtterance()\n return\n }\n this.emitFinalEvent({\n text,\n utteranceId: this.currentUtteranceId ?? undefined,\n speakerId: this.currentSpeakerId,\n language: this.currentLanguage,\n originalText: this.lastOriginalText || undefined,\n sourceLanguage: this.currentSourceLanguage,\n startMs,\n endMs,\n })\n this.resetActiveUtterance()\n }\n\n handleResult(result: SonioxResult): void {\n const allTokens = result.tokens ?? []\n\n // Translation sessions: translation-status tokens are the emitted text;\n // original-status tokens feed the parallel originalText accumulator.\n // Untagged tokens are Soniox's SAME-LANGUAGE PASSTHROUGH (spoken language\n // already equals the target): keep them as emitted text so target-language\n // speech surfaces instead of silently vanishing.\n const tokens = this.opts.isTranslation ? allTokens.filter((t) => t.translation_status !== \"original\") : allTokens\n const originalTokens = this.opts.isTranslation ? allTokens.filter((t) => t.translation_status === \"original\") : []\n\n let interimText = \"\"\n let language: string | undefined\n let startMs: number | undefined\n let endMs: number | undefined\n let currentFinalText = \"\"\n\n for (const t of tokens) {\n // First token of the stream (or first after a commit): open an\n // utterance. Deliberately no split on per-token speaker change — Soniox\n // re-diarizes its rolling window, so speaker labels flicker within one\n // real utterance; the single FINAL comes from the endpoint boundary.\n if (!this.currentUtteranceId) {\n this.startNewUtterance(t.speaker, t.language ?? this.opts.fallbackLanguage)\n }\n\n if (t.speaker) this.currentSpeakerId = t.speaker\n if (t.language) {\n language = t.language\n this.currentLanguage = t.language\n // Untagged tokens on a translation session are the same-language\n // passthrough: their language IS the detected source.\n if (this.opts.isTranslation && t.translation_status == null) {\n this.currentSourceLanguage = t.language\n }\n }\n\n if (t.is_final) {\n currentFinalText += t.text\n if (t.start_ms != null && startMs == null) startMs = t.start_ms\n if (t.end_ms != null) endMs = t.end_ms\n } else {\n interimText += t.text\n }\n }\n\n if (originalTokens.length > 0) {\n let originalInterim = \"\"\n let originalFinal = \"\"\n for (const t of originalTokens) {\n if (!this.currentUtteranceId) {\n this.startNewUtterance(t.speaker, undefined)\n }\n if (t.language) this.currentSourceLanguage = t.language\n if (t.is_final) originalFinal += t.text\n else originalInterim += t.text\n }\n if (originalFinal.length > this.originalPrevFinalLen) {\n this.originalStablePrefix += originalFinal.slice(this.originalPrevFinalLen)\n }\n this.originalPrevFinalLen = originalFinal.length\n const originalComposite = this.originalStablePrefix + originalInterim\n if (originalComposite.length > 0) this.lastOriginalText = originalComposite\n }\n\n // Accumulate finalized text: on window growth append the delta; on shrink\n // (compaction) keep stablePrefix and reset the tracker.\n if (currentFinalText.length > this.prevFinalLen) {\n this.stablePrefix += currentFinalText.slice(this.prevFinalLen)\n }\n this.prevFinalLen = currentFinalText.length\n\n const compositeText = (this.stablePrefix + interimText).trim()\n if (compositeText.length === 0) return\n\n if (this.pendingFinalText && this.pendingFinalTimer !== null) {\n const pendingBefore = this.pendingFinalText\n const merged = this.tryMergeOverlap(this.pendingFinalText, compositeText)\n if (merged !== null) {\n this.currentUtteranceId = this.pendingFinalUtteranceId ?? this.currentUtteranceId\n if (this.currentSpeakerId) this.pendingFinalSpeakerId = this.currentSpeakerId\n else this.currentSpeakerId = this.pendingFinalSpeakerId\n if (this.currentLanguage) this.pendingFinalLanguage = this.currentLanguage\n else this.currentLanguage = this.pendingFinalLanguage\n if (this.lastOriginalText) {\n this.pendingFinalOriginalText =\n this.tryMergeOverlap(this.pendingFinalOriginalText, this.lastOriginalText) ?? this.lastOriginalText\n }\n if (this.currentSourceLanguage) {\n this.pendingFinalSourceLanguage = this.currentSourceLanguage\n }\n\n this.pendingFinalText = merged\n this.lastSentInterim = merged\n\n if (merged !== pendingBefore) {\n this.opts.onTranscript({\n text: merged,\n isFinal: false,\n utteranceId: this.currentUtteranceId ?? undefined,\n speakerId: this.currentSpeakerId,\n language,\n originalText: this.pendingFinalOriginalText || undefined,\n sourceLanguage: this.pendingFinalSourceLanguage,\n startMs,\n endMs,\n })\n this.schedulePendingFinalCommit()\n }\n return\n }\n\n this.commitPendingFinal()\n }\n\n // Emit an INTERIM only; the single final comes at the utterance boundary.\n if (this.isLikelyWindowRegression(compositeText, this.lastSentInterim)) {\n return\n }\n\n if (compositeText !== this.lastSentInterim) {\n this.opts.onTranscript({\n text: compositeText,\n isFinal: false,\n utteranceId: this.currentUtteranceId ?? undefined,\n speakerId: this.currentSpeakerId,\n language,\n originalText: this.lastOriginalText || undefined,\n sourceLanguage: this.currentSourceLanguage,\n startMs,\n endMs,\n })\n this.lastSentInterim = compositeText\n }\n }\n\n handleEndpoint(): void {\n // Soft boundary: defer the FINAL briefly; if Soniox's next rolling window\n // overlaps, it merges back into this utterance.\n const candidate = this.lastSentInterim.trim()\n if (candidate) {\n if (this.pendingFinalText && this.pendingFinalTimer !== null) {\n const merged = this.tryMergeOverlap(this.pendingFinalText, candidate)\n if (merged !== null) {\n this.pendingFinalText = merged\n if (this.currentSpeakerId) this.pendingFinalSpeakerId = this.currentSpeakerId\n if (this.currentLanguage) this.pendingFinalLanguage = this.currentLanguage\n if (this.lastOriginalText) {\n this.pendingFinalOriginalText =\n this.tryMergeOverlap(this.pendingFinalOriginalText, this.lastOriginalText) ?? this.lastOriginalText\n }\n if (this.currentSourceLanguage) {\n this.pendingFinalSourceLanguage = this.currentSourceLanguage\n }\n } else {\n this.commitPendingFinal()\n this.setPendingFinalFromActive(candidate)\n }\n } else {\n this.setPendingFinalFromActive(candidate)\n }\n this.schedulePendingFinalCommit()\n }\n this.resetActiveUtterance()\n }\n\n /** Finalize ack (`<fin>`): trusted \"speech segment ended\" signal. */\n handleFinalized(): void {\n this.commitPendingFinal()\n this.emitFinal()\n }\n\n /** Flush any in-flight utterance as a final (session close/teardown). */\n flush(): void {\n this.commitPendingFinal()\n this.emitFinal()\n }\n\n /** Drop all state without emitting (e.g. audio-gap resume). */\n discard(): void {\n this.clearPendingFinal()\n this.resetActiveUtterance()\n }\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cloud-client runtime status store — moved into engine so engine owns the
|
|
3
|
-
* cloud
|
|
3
|
+
* cloud runtime status (connection status + audio transport). Re-exported
|
|
4
4
|
* through the host's `@/stores/cloudClientStatus` shim so the app keeps its
|
|
5
5
|
* imports, and surfaced as `engine.stores.cloudClientStatus`.
|
|
6
6
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudClientStatus.js","sourceRoot":"","sources":["../../src/stores/cloudClientStatus.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAC,MAAM,EAAC,MAAM,SAAS,CAAA;AAI9B,MAAM,eAAe,GAAoB;IACvC,MAAM,EAAE,cAAc;IACtB,cAAc,EAAE,MAAM;CACvB,CAAA;AAQD,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAyB,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAChF,GAAG,eAAe;IAClB,aAAa,EAAE,IAAI;IACnB,WAAW,EAAE,CAAC,QAAQ,EAAE,EAAE,CACxB,GAAG,CAAC;QACF,GAAG,QAAQ;QACX,aAAa,EAAE,IAAI,IAAI,EAAE;KAC1B,CAAC;IACJ,KAAK,EAAE,GAAG,EAAE,CACV,GAAG,CAAC;QACF,GAAG,eAAe;QAClB,aAAa,EAAE,IAAI;KACpB,CAAC;CACL,CAAC,CAAC,CAAA","sourcesContent":["/**\n * Cloud-client runtime status store — moved into engine so engine owns the\n * cloud
|
|
1
|
+
{"version":3,"file":"cloudClientStatus.js","sourceRoot":"","sources":["../../src/stores/cloudClientStatus.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAC,MAAM,EAAC,MAAM,SAAS,CAAA;AAI9B,MAAM,eAAe,GAAoB;IACvC,MAAM,EAAE,cAAc;IACtB,cAAc,EAAE,MAAM;CACvB,CAAA;AAQD,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAyB,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAChF,GAAG,eAAe;IAClB,aAAa,EAAE,IAAI;IACnB,WAAW,EAAE,CAAC,QAAQ,EAAE,EAAE,CACxB,GAAG,CAAC;QACF,GAAG,QAAQ;QACX,aAAa,EAAE,IAAI,IAAI,EAAE;KAC1B,CAAC;IACJ,KAAK,EAAE,GAAG,EAAE,CACV,GAAG,CAAC;QACF,GAAG,eAAe;QAClB,aAAa,EAAE,IAAI;KACpB,CAAC;CACL,CAAC,CAAC,CAAA","sourcesContent":["/**\n * Cloud-client runtime status store — moved into engine so engine owns the\n * cloud runtime status (connection status + audio transport). Re-exported\n * through the host's `@/stores/cloudClientStatus` shim so the app keeps its\n * imports, and surfaced as `engine.stores.cloudClientStatus`.\n */\nimport {create} from \"zustand\"\n\nimport type {RuntimeAudioTransport, RuntimeSnapshot, RuntimeStatus} from \"@glassly/cloud-client/react-native\"\n\nconst initialSnapshot: RuntimeSnapshot = {\n status: \"disconnected\",\n audioTransport: \"none\",\n}\n\nexport interface CloudClientStatusState extends RuntimeSnapshot {\n lastChangedAt: Date | null\n setSnapshot: (snapshot: RuntimeSnapshot) => void\n reset: () => void\n}\n\nexport const useCloudClientStatusStore = create<CloudClientStatusState>((set) => ({\n ...initialSnapshot,\n lastChangedAt: null,\n setSnapshot: (snapshot) =>\n set({\n ...snapshot,\n lastChangedAt: new Date(),\n }),\n reset: () =>\n set({\n ...initialSnapshot,\n lastChangedAt: null,\n }),\n}))\n\nexport type {RuntimeAudioTransport, RuntimeSnapshot, RuntimeStatus}\n"]}
|
package/build/stores/settings.js
CHANGED
|
@@ -219,7 +219,7 @@ export const SETTINGS = {
|
|
|
219
219
|
saveOnServer: false,
|
|
220
220
|
persist: true,
|
|
221
221
|
},
|
|
222
|
-
// Cloud
|
|
222
|
+
// Cloud endpoint OVERRIDES. Empty = no override; cloudClient's resolveUrl
|
|
223
223
|
// owns the full precedence (override -> env -> Metro-derived dev default).
|
|
224
224
|
// The value may be an explicit URL or the METRO_AUTO sentinel ("my dev
|
|
225
225
|
// laptop", resolved live from Metro so it survives network changes). Never
|
|
@@ -241,7 +241,7 @@ export const SETTINGS = {
|
|
|
241
241
|
persist: true,
|
|
242
242
|
resetOnBuildEnvChange: true,
|
|
243
243
|
},
|
|
244
|
-
// Bookmarked Cloud
|
|
244
|
+
// Bookmarked Cloud endpoint pairs. Each entry is {label, coreUrl,
|
|
245
245
|
// runtimeUrl} — core + runtime are saved together because they are always
|
|
246
246
|
// applied as a matched set (presets fill both; Save & Test verifies both).
|
|
247
247
|
saved_cloud_url_pairs: {
|
|
@@ -368,7 +368,7 @@ export const SETTINGS = {
|
|
|
368
368
|
persist: true,
|
|
369
369
|
},
|
|
370
370
|
location_tier: { key: "location_tier", defaultValue: () => "", writable: true, saveOnServer: true, persist: true },
|
|
371
|
-
// Volatile bearer synced to Bluetooth for glasses-side Cloud
|
|
371
|
+
// Volatile bearer synced to Bluetooth for glasses-side Cloud calls. The
|
|
372
372
|
// key name is kept for BLE compatibility, but the value must never be loaded
|
|
373
373
|
// from or saved to the legacy Cloud V1 settings store.
|
|
374
374
|
core_token: { key: "core_token", defaultValue: () => "", writable: true, saveOnServer: false, persist: false },
|