@glassly/engine 0.3.5-dev.745 → 0.3.5-dev.746

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.
Files changed (42) hide show
  1. package/build/engine.d.ts +3 -0
  2. package/build/engine.d.ts.map +1 -1
  3. package/build/engine.js +4 -0
  4. package/build/engine.js.map +1 -1
  5. package/build/facades/glasses.d.ts +5 -1
  6. package/build/facades/glasses.d.ts.map +1 -1
  7. package/build/facades/glasses.js +5 -0
  8. package/build/facades/glasses.js.map +1 -1
  9. package/build/generated/releaseMetadata.js +3 -3
  10. package/build/generated/releaseMetadata.js.map +1 -1
  11. package/build/services/LocalMiniappRuntime.d.ts +19 -4
  12. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  13. package/build/services/LocalMiniappRuntime.js +68 -125
  14. package/build/services/LocalMiniappRuntime.js.map +1 -1
  15. package/build/services/actionInvoke.d.ts +36 -0
  16. package/build/services/actionInvoke.d.ts.map +1 -0
  17. package/build/services/actionInvoke.js +89 -0
  18. package/build/services/actionInvoke.js.map +1 -0
  19. package/build/services/appleWatch/AppleWatchLink.d.ts +42 -0
  20. package/build/services/appleWatch/AppleWatchLink.d.ts.map +1 -0
  21. package/build/services/appleWatch/AppleWatchLink.js +259 -0
  22. package/build/services/appleWatch/AppleWatchLink.js.map +1 -0
  23. package/build/services/controllers.d.ts.map +1 -1
  24. package/build/services/controllers.js +16 -0
  25. package/build/services/controllers.js.map +1 -1
  26. package/build/stores/settings.d.ts.map +1 -1
  27. package/build/stores/settings.js +9 -0
  28. package/build/stores/settings.js.map +1 -1
  29. package/build/types/enums.d.ts +2 -1
  30. package/build/types/enums.d.ts.map +1 -1
  31. package/build/types/enums.js +1 -0
  32. package/build/types/enums.js.map +1 -1
  33. package/package.json +7 -7
  34. package/src/engine.ts +4 -0
  35. package/src/facades/glasses.ts +7 -0
  36. package/src/generated/releaseMetadata.ts +3 -3
  37. package/src/services/LocalMiniappRuntime.ts +103 -125
  38. package/src/services/actionInvoke.ts +104 -0
  39. package/src/services/appleWatch/AppleWatchLink.ts +297 -0
  40. package/src/services/controllers.ts +17 -0
  41. package/src/stores/settings.ts +9 -0
  42. package/src/types/enums.ts +1 -0
@@ -82,6 +82,7 @@ import { invokeScanQrSeam } from "../runtime/scanQrSeam";
82
82
  import { invokeWebLoginSeam } from "../runtime/webLoginSeam";
83
83
  import { normalizeStreamAudioConfig, normalizeStreamVideoConfig } from "../runtime/streamConfig";
84
84
  import { visibleActionRows } from "./manifestActions";
85
+ import { actionFailure, appDeclaresAction, checkActionAccess, PendingActionCalls, } from "./actionInvoke";
85
86
  import { toLanguageHint } from "@glassly/cloud-protocol/languages";
86
87
  import { buildMiniappManifestSnapshot } from "../utils/miniappDiagnostics";
87
88
  import ttsModelManager from "./TTSModelManager";
@@ -5253,7 +5254,7 @@ class LocalMiniappRuntime {
5253
5254
  /** Max serialized size of an action call's params / result (256 KB). */
5254
5255
  static ACTION_PAYLOAD_CAP = 256 * 1024;
5255
5256
  /** Outstanding action invocations, keyed by host-generated callId. */
5256
- actionCalls = new Map();
5257
+ actionCalls = new PendingActionCalls(BgTimer, (value) => this.actionPayloadTooLarge(value));
5257
5258
  actionCallSeq = 0;
5258
5259
  interopApps() {
5259
5260
  return useAppStatusStore.getState().apps;
@@ -5785,104 +5786,82 @@ class LocalMiniappRuntime {
5785
5786
  return false;
5786
5787
  }
5787
5788
  }
5789
+ /** Floored at 6s: a freshly-woken target buffers an undelivered ACTION_CALL for 5s (HANDLER_WAIT_MS in @glassly/miniapp's actions module) + 1s for the reply. Keep in sync. */
5790
+ static clampActionTimeout(raw) {
5791
+ return Math.min(Math.max(Number(raw) || 30_000, 6_000), 120_000);
5792
+ }
5793
+ /** Caller identity the host presents for host-initiated actions (a system caller). */
5794
+ static HOST_ACTION_CALLER = "host";
5795
+ /** Installed, compatible, and declares the action — the same gates `invokeActionFromHost` applies up front. */
5796
+ hostActionAvailable(targetPackageName, actionId) {
5797
+ return appDeclaresAction(this.interopApps().find((a) => a.packageName === targetPackageName), actionId);
5798
+ }
5788
5799
  /**
5789
- * session.actions.invoke → wake the target headlessly, deliver an ACTION_CALL,
5790
- * and correlate its ACTION_RESULT back to the caller's pending request.
5800
+ * Host-initiated action call (e.g. the watch remote): same gates, headless
5801
+ * wake and ACTION_CALL/ACTION_RESULT correlation as a miniapp's
5802
+ * session.actions.invoke, with the host as a system caller.
5803
+ * Rejects with an Error whose `code` is a MiniappErrorCode.
5791
5804
  */
5805
+ async invokeActionFromHost(targetPackageName, actionId, params = {}, timeoutMs) {
5806
+ const outcome = await this.runActionInvoke(LocalMiniappRuntime.HOST_ACTION_CALLER, true, targetPackageName, actionId, params, LocalMiniappRuntime.clampActionTimeout(timeoutMs));
5807
+ if (outcome.ok)
5808
+ return outcome.data;
5809
+ throw Object.assign(new Error(outcome.error.message), { code: outcome.error.code });
5810
+ }
5811
+ /** session.actions.invoke → run the call, then relay the outcome to the caller's pending request. */
5792
5812
  async handleActionInvoke(callerPackageName, payload, requestId) {
5793
- const target = payload.targetPackageName;
5794
- const actionId = payload.actionId;
5795
- const params = payload.params ?? {};
5796
- // Floor the timeout at 6s so the host never rejects with ACTION_TIMEOUT before
5797
- // the target SDK's handler-registration window closes. A freshly-woken miniapp
5798
- // buffers an undelivered ACTION_CALL for HANDLER_WAIT_MS (5s in @glassly/miniapp's
5799
- // actions module) waiting for session.actions.handle; a shorter host timeout
5800
- // could fire while the target is still registering, then the action would run
5801
- // with no caller left to receive its result. 6s = that 5s buffer + 1s for the
5802
- // ACTION_RESULT/NO_ACTION_HANDLER reply to travel back. Keep these in sync.
5803
- const timeoutMs = Math.min(Math.max(Number(payload.timeoutMs) || 30_000, 6_000), 120_000);
5813
+ const outcome = await this.runActionInvoke(callerPackageName, this.isSystemPackage(callerPackageName), payload.targetPackageName, payload.actionId, payload.params ?? {}, LocalMiniappRuntime.clampActionTimeout(payload.timeoutMs));
5814
+ if (outcome.ok)
5815
+ this.sendResult(callerPackageName, requestId, true, outcome.data);
5816
+ else
5817
+ this.sendResult(callerPackageName, requestId, false, undefined, outcome.error);
5818
+ }
5819
+ /**
5820
+ * Gate, wake the target headlessly, deliver an ACTION_CALL, and resolve when
5821
+ * its ACTION_RESULT (or the timeout) arrives. Never rejects.
5822
+ */
5823
+ async runActionInvoke(callerPackageName, callerIsSystem, target, actionId, params, timeoutMs) {
5824
+ const fail = actionFailure;
5804
5825
  if (this.actionPayloadTooLarge(params)) {
5805
- this.sendResult(callerPackageName, requestId, false, undefined, {
5806
- code: MiniappErrorCode.PAYLOAD_TOO_LARGE,
5807
- message: "action params exceeded the 256 KB cap",
5808
- });
5809
- return;
5826
+ return fail(MiniappErrorCode.PAYLOAD_TOO_LARGE, "action params exceeded the 256 KB cap");
5810
5827
  }
5811
5828
  const app = target ? this.interopApps().find((a) => a.packageName === target) : undefined;
5812
- if (!target || !app) {
5813
- this.sendResult(callerPackageName, requestId, false, undefined, {
5814
- code: MiniappErrorCode.APP_NOT_FOUND,
5815
- message: `Miniapp not found: ${target ?? "(missing targetPackageName)"}`,
5816
- });
5817
- return;
5818
- }
5819
- if (!actionId) {
5820
- this.sendResult(callerPackageName, requestId, false, undefined, {
5821
- code: MiniappErrorCode.ACTION_NOT_FOUND,
5822
- message: "invoke requires an actionId",
5823
- });
5824
- return;
5825
- }
5826
- if (app.compatibility && app.compatibility.isCompatible === false) {
5827
- this.sendResult(callerPackageName, requestId, false, undefined, {
5828
- code: MiniappErrorCode.APP_NOT_COMPATIBLE,
5829
- message: `${target} is not compatible with the connected glasses`,
5830
- });
5831
- islandNotifications.emit({
5832
- kind: "version_incompatible",
5833
- packageName: target,
5834
- reason: `${target} is not compatible with the connected glasses`,
5835
- metadata: { missingRequired: app.compatibility.missingRequired ?? [], via: "miniapps.invoke" },
5836
- timestamp: Date.now(),
5837
- });
5838
- return;
5839
- }
5840
- // Declared-action gate: the target must declare the action in its manifest.
5841
- const declared = (app.actions ?? []).find((a) => a.id === actionId);
5842
- if (!declared) {
5843
- this.sendResult(callerPackageName, requestId, false, undefined, {
5844
- code: MiniappErrorCode.ACTION_NOT_FOUND,
5845
- message: `${target} does not declare action "${actionId}"`,
5846
- });
5847
- return;
5848
- }
5849
- // Visibility gate: declared actions are public by default; ones marked
5850
- // `visibility: "system"` stay restricted to system/dev callers.
5851
- if (declared.visibility === "system" && !this.isSystemPackage(callerPackageName)) {
5852
- this.auditInterop({
5853
- caller: callerPackageName,
5854
- op: "invoke",
5855
- target,
5856
- actionId,
5857
- ok: false,
5858
- errorCode: MiniappErrorCode.NOT_PERMITTED,
5859
- });
5860
- this.sendResult(callerPackageName, requestId, false, undefined, {
5861
- code: MiniappErrorCode.NOT_PERMITTED,
5862
- message: `Action "${actionId}" on ${target} is restricted to system apps`,
5863
- });
5864
- return;
5829
+ const denied = checkActionAccess(app, target, actionId, callerIsSystem);
5830
+ if (denied) {
5831
+ if (denied.code === MiniappErrorCode.APP_NOT_COMPATIBLE && target && app) {
5832
+ islandNotifications.emit({
5833
+ kind: "version_incompatible",
5834
+ packageName: target,
5835
+ reason: denied.message,
5836
+ metadata: { missingRequired: app.compatibility?.missingRequired ?? [], via: "miniapps.invoke" },
5837
+ timestamp: Date.now(),
5838
+ });
5839
+ }
5840
+ else if (denied.code === MiniappErrorCode.NOT_PERMITTED) {
5841
+ this.auditInterop({
5842
+ caller: callerPackageName,
5843
+ op: "invoke",
5844
+ target,
5845
+ actionId,
5846
+ ok: false,
5847
+ errorCode: MiniappErrorCode.NOT_PERMITTED,
5848
+ });
5849
+ }
5850
+ return { ok: false, error: denied };
5865
5851
  }
5852
+ if (!target || !actionId)
5853
+ return fail(MiniappErrorCode.INTERNAL, "unreachable");
5866
5854
  // Headless wake + wait for CONNECT (idempotent / fast if already connected).
5867
5855
  try {
5868
5856
  await this.wakeInteropApp(target);
5869
5857
  }
5870
5858
  catch (e) {
5871
- this.sendResult(callerPackageName, requestId, false, undefined, {
5872
- code: MiniappErrorCode.WAKE_FAILED,
5873
- message: e?.message ?? `failed to wake ${target}`,
5874
- });
5875
- return;
5859
+ return fail(MiniappErrorCode.WAKE_FAILED, e?.message ?? `failed to wake ${target}`);
5876
5860
  }
5877
5861
  // The wake waited for CONNECT, but the target could have dropped in the gap
5878
5862
  // before delivery — fail fast rather than arming a timer for a call that
5879
- // sendToMiniapp would silently drop (the caller would otherwise wait the
5880
- // full invoke timeout for an ACTION_CALL that was never delivered).
5863
+ // sendToMiniapp would silently drop.
5881
5864
  if (!this.connectedApps.has(target)) {
5882
- this.sendResult(callerPackageName, requestId, false, undefined, {
5883
- code: MiniappErrorCode.WAKE_FAILED,
5884
- message: `${target} disconnected before the action could be delivered`,
5885
- });
5886
5865
  this.auditInterop({
5887
5866
  caller: callerPackageName,
5888
5867
  op: "invoke",
@@ -5891,18 +5870,11 @@ class LocalMiniappRuntime {
5891
5870
  ok: false,
5892
5871
  errorCode: MiniappErrorCode.WAKE_FAILED,
5893
5872
  });
5894
- return;
5873
+ return fail(MiniappErrorCode.WAKE_FAILED, `${target} disconnected before the action could be delivered`);
5895
5874
  }
5896
- // Deliver the call and arm the handler timeout; ACTION_RESULT resolves it.
5875
+ // Deliver the call and arm the handler timeout; ACTION_RESULT settles it.
5897
5876
  const callId = `act-${Date.now().toString(36)}-${this.actionCallSeq++}`;
5898
- const timer = BgTimer.setTimeout(() => {
5899
- this.actionCalls.delete(callId);
5900
- this.sendResult(callerPackageName, requestId, false, undefined, {
5901
- code: MiniappErrorCode.ACTION_TIMEOUT,
5902
- message: `action "${actionId}" timed out after ${timeoutMs}ms`,
5903
- });
5904
- }, timeoutMs);
5905
- this.actionCalls.set(callId, { callerPackageName, targetPackageName: target, callerRequestId: requestId, timer });
5877
+ const outcome = this.actionCalls.register(callId, target, actionId, timeoutMs);
5906
5878
  this.sendToMiniapp(target, {
5907
5879
  type: MiniappResponseType.ACTION_CALL,
5908
5880
  callId,
@@ -5911,40 +5883,11 @@ class LocalMiniappRuntime {
5911
5883
  callerPackageName,
5912
5884
  });
5913
5885
  this.auditInterop({ caller: callerPackageName, op: "invoke", target, actionId, ok: true });
5886
+ return outcome;
5914
5887
  }
5915
- /** Target → host: forward an ACTION_RESULT back to the caller's pending invoke. */
5888
+ /** Target → host: settle the pending invoke for this ACTION_RESULT. */
5916
5889
  handleActionResult(targetPackageName, payload) {
5917
- const callId = payload.callId;
5918
- if (!callId)
5919
- return;
5920
- const pending = this.actionCalls.get(callId);
5921
- if (!pending)
5922
- return;
5923
- // Only the invoked target may resolve this call. callIds are guessable
5924
- // (timestamp + seq), so a different connected miniapp must not be able to
5925
- // spoof a result/error for someone else's invoke.
5926
- if (pending.targetPackageName !== targetPackageName)
5927
- return;
5928
- this.actionCalls.delete(callId);
5929
- BgTimer.clearTimeout(pending.timer);
5930
- if (payload.ok === true) {
5931
- const result = payload.result;
5932
- if (this.actionPayloadTooLarge(result)) {
5933
- this.sendResult(pending.callerPackageName, pending.callerRequestId, false, undefined, {
5934
- code: MiniappErrorCode.PAYLOAD_TOO_LARGE,
5935
- message: "action result exceeded the 256 KB cap",
5936
- });
5937
- return;
5938
- }
5939
- this.sendResult(pending.callerPackageName, pending.callerRequestId, true, result ?? null);
5940
- }
5941
- else {
5942
- const error = payload.error ?? {
5943
- code: MiniappErrorCode.INTERNAL,
5944
- message: "action handler error",
5945
- };
5946
- this.sendResult(pending.callerPackageName, pending.callerRequestId, false, undefined, error);
5947
- }
5890
+ this.actionCalls.handleResult(targetPackageName, payload);
5948
5891
  }
5949
5892
  sendResult(packageName, requestId, ok, data, error) {
5950
5893
  if (!requestId)