@glassly/engine 0.3.5 → 0.3.6-beta.851
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/engine.d.ts +11 -1
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +13 -1
- package/build/engine.js.map +1 -1
- package/build/facades/ai.d.ts +3 -3
- package/build/facades/ai.d.ts.map +1 -1
- package/build/facades/ai.js +3 -3
- package/build/facades/ai.js.map +1 -1
- package/build/facades/glasses.d.ts +5 -1
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +39 -30
- package/build/facades/glasses.js.map +1 -1
- package/build/facades/memory.d.ts +10 -2
- package/build/facades/memory.d.ts.map +1 -1
- package/build/facades/memory.js +10 -0
- package/build/facades/memory.js.map +1 -1
- package/build/facades/miniapps.d.ts +2 -0
- package/build/facades/miniapps.d.ts.map +1 -1
- package/build/facades/miniapps.js +2 -0
- package/build/facades/miniapps.js.map +1 -1
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +3 -2
- package/build/facades/pairing.js.map +1 -1
- package/build/facades/providerKeys.d.ts +6 -2
- package/build/facades/providerKeys.d.ts.map +1 -1
- package/build/facades/providerKeys.js +6 -2
- package/build/facades/providerKeys.js.map +1 -1
- package/build/generated/releaseMetadata.js +4 -4
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/AiTierResolution.d.ts +3 -2
- package/build/services/AiTierResolution.d.ts.map +1 -1
- package/build/services/AiTierResolution.js +7 -2
- package/build/services/AiTierResolution.js.map +1 -1
- package/build/services/AppRegistry.d.ts +4 -1
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +16 -2
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/BlobStore.js +3 -3
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/BluetoothRestoreLaunch.d.ts +8 -0
- package/build/services/BluetoothRestoreLaunch.d.ts.map +1 -0
- package/build/services/BluetoothRestoreLaunch.js +34 -0
- package/build/services/BluetoothRestoreLaunch.js.map +1 -0
- package/build/services/CloudClientService.d.ts +4 -4
- package/build/services/ForegroundLocationPermission.d.ts +1 -1
- package/build/services/ForegroundLocationPermission.d.ts.map +1 -1
- package/build/services/ForegroundLocationPermission.js.map +1 -1
- package/build/services/GestureDotsService.d.ts.map +1 -1
- package/build/services/GestureDotsService.js +2 -1
- package/build/services/GestureDotsService.js.map +1 -1
- package/build/services/GlassesTouchInput.d.ts +6 -4
- package/build/services/GlassesTouchInput.d.ts.map +1 -1
- package/build/services/GlassesTouchInput.js +81 -41
- package/build/services/GlassesTouchInput.js.map +1 -1
- package/build/services/LocalDisplayManager.d.ts +12 -4
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +21 -7
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalLlmModelManager.d.ts +3 -1
- package/build/services/LocalLlmModelManager.d.ts.map +1 -1
- package/build/services/LocalLlmModelManager.js +13 -5
- package/build/services/LocalLlmModelManager.js.map +1 -1
- package/build/services/LocalLlmPrompt.d.ts +10 -9
- package/build/services/LocalLlmPrompt.d.ts.map +1 -1
- package/build/services/LocalLlmPrompt.js +43 -32
- package/build/services/LocalLlmPrompt.js.map +1 -1
- package/build/services/LocalLlmProvider.d.ts +1 -0
- package/build/services/LocalLlmProvider.d.ts.map +1 -1
- package/build/services/LocalLlmProvider.js +62 -22
- package/build/services/LocalLlmProvider.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +28 -6
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +190 -148
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MemoryStore.d.ts +13 -1
- package/build/services/MemoryStore.d.ts.map +1 -1
- package/build/services/MemoryStore.js +98 -1
- package/build/services/MemoryStore.js.map +1 -1
- package/build/services/MiniappExitGesture.d.ts +6 -3
- package/build/services/MiniappExitGesture.d.ts.map +1 -1
- package/build/services/MiniappExitGesture.js +4 -4
- package/build/services/MiniappExitGesture.js.map +1 -1
- package/build/services/MiniappWidgetMode.d.ts +6 -4
- package/build/services/MiniappWidgetMode.d.ts.map +1 -1
- package/build/services/MiniappWidgetMode.js +7 -6
- package/build/services/MiniappWidgetMode.js.map +1 -1
- package/build/services/ProviderKeyValidation.d.ts +2 -0
- package/build/services/ProviderKeyValidation.d.ts.map +1 -1
- package/build/services/ProviderKeyValidation.js +4 -0
- package/build/services/ProviderKeyValidation.js.map +1 -1
- package/build/services/SceneRenderer.d.ts.map +1 -1
- package/build/services/SceneRenderer.js +3 -1
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/services/ScreenshotFixture.d.ts +56 -0
- package/build/services/ScreenshotFixture.d.ts.map +1 -0
- package/build/services/ScreenshotFixture.js +72 -0
- package/build/services/ScreenshotFixture.js.map +1 -0
- package/build/services/actionInvoke.d.ts +37 -0
- package/build/services/actionInvoke.d.ts.map +1 -0
- package/build/services/actionInvoke.js +89 -0
- package/build/services/actionInvoke.js.map +1 -0
- package/build/services/appleWatch/AppleWatchLink.d.ts +42 -0
- package/build/services/appleWatch/AppleWatchLink.d.ts.map +1 -0
- package/build/services/appleWatch/AppleWatchLink.js +259 -0
- package/build/services/appleWatch/AppleWatchLink.js.map +1 -0
- package/build/services/controllers.d.ts.map +1 -1
- package/build/services/controllers.js +16 -0
- package/build/services/controllers.js.map +1 -1
- package/build/services/manifestActions.d.ts +4 -2
- package/build/services/manifestActions.d.ts.map +1 -1
- package/build/services/manifestActions.js +9 -3
- package/build/services/manifestActions.js.map +1 -1
- package/build/services/memoryBackupFile.d.ts +9 -0
- package/build/services/memoryBackupFile.d.ts.map +1 -0
- package/build/services/memoryBackupFile.js +47 -0
- package/build/services/memoryBackupFile.js.map +1 -0
- package/build/services/settingsBridge.d.ts +4 -4
- package/build/services/settingsBridge.d.ts.map +1 -1
- package/build/services/settingsBridge.js +9 -4
- package/build/services/settingsBridge.js.map +1 -1
- package/build/services/widgetPlacements.d.ts +8 -7
- package/build/services/widgetPlacements.d.ts.map +1 -1
- package/build/services/widgetPlacements.js +7 -6
- package/build/services/widgetPlacements.js.map +1 -1
- package/build/stores/apps.d.ts +6 -0
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +29 -12
- package/build/stores/apps.js.map +1 -1
- package/build/stores/bluetoothSettingKeys.d.ts +2 -0
- package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
- package/build/stores/bluetoothSettingKeys.js +17 -0
- package/build/stores/bluetoothSettingKeys.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +42 -2
- package/build/stores/settings.js.map +1 -1
- package/build/types/applet.d.ts +5 -1
- package/build/types/applet.d.ts.map +1 -1
- package/build/types/applet.js.map +1 -1
- package/build/types/capabilities/rayneo-io.d.ts.map +1 -1
- package/build/types/capabilities/rayneo-io.js +2 -0
- package/build/types/capabilities/rayneo-io.js.map +1 -1
- package/build/types/enums.d.ts +2 -1
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +1 -0
- package/build/types/enums.js.map +1 -1
- package/build/utils/cloudClient/RnUdpAdapter.d.ts.map +1 -1
- package/build/utils/cloudClient/RnUdpAdapter.js +13 -2
- package/build/utils/cloudClient/RnUdpAdapter.js.map +1 -1
- package/build/utils/display/scene/index.d.ts +1 -1
- package/build/utils/display/scene/index.d.ts.map +1 -1
- package/build/utils/display/scene/index.js.map +1 -1
- package/build/utils/display/scene/process.d.ts.map +1 -1
- package/build/utils/display/scene/process.js +20 -1
- package/build/utils/display/scene/process.js.map +1 -1
- package/build/utils/display/scene/types.d.ts +1 -1
- package/build/utils/display/scene/types.d.ts.map +1 -1
- package/build/utils/display/scene/types.js.map +1 -1
- package/build/utils/miniappGlobals.d.ts +6 -0
- package/build/utils/miniappGlobals.d.ts.map +1 -1
- package/build/utils/miniappGlobals.js +28 -4
- package/build/utils/miniappGlobals.js.map +1 -1
- package/package.json +9 -9
- package/src/engine.ts +13 -1
- package/src/facades/ai.ts +3 -3
- package/src/facades/glasses.ts +65 -42
- package/src/facades/memory.ts +11 -2
- package/src/facades/miniapps.ts +3 -0
- package/src/facades/pairing.ts +4 -2
- package/src/facades/providerKeys.ts +6 -1
- package/src/generated/releaseMetadata.ts +4 -4
- package/src/services/AiTierResolution.ts +9 -3
- package/src/services/AppRegistry.ts +18 -3
- package/src/services/BlobStore.ts +3 -3
- package/src/services/BluetoothRestoreLaunch.ts +37 -0
- package/src/services/ForegroundLocationPermission.ts +1 -1
- package/src/services/GestureDotsService.ts +2 -1
- package/src/services/GlassesTouchInput.ts +86 -38
- package/src/services/LocalDisplayManager.ts +22 -8
- package/src/services/LocalLlmModelManager.ts +16 -6
- package/src/services/LocalLlmPrompt.ts +56 -37
- package/src/services/LocalLlmProvider.ts +74 -24
- package/src/services/LocalMiniappRuntime.ts +228 -147
- package/src/services/MemoryStore.ts +111 -2
- package/src/services/MiniappExitGesture.ts +8 -5
- package/src/services/MiniappWidgetMode.ts +9 -7
- package/src/services/ProviderKeyValidation.ts +5 -0
- package/src/services/SceneRenderer.ts +3 -1
- package/src/services/ScreenshotFixture.ts +113 -0
- package/src/services/actionInvoke.ts +105 -0
- package/src/services/appleWatch/AppleWatchLink.ts +297 -0
- package/src/services/controllers.ts +17 -0
- package/src/services/manifestActions.ts +16 -4
- package/src/services/memoryBackupFile.ts +50 -0
- package/src/services/settingsBridge.ts +9 -4
- package/src/services/widgetPlacements.ts +23 -9
- package/src/stores/apps.ts +35 -11
- package/src/stores/bluetoothSettingKeys.ts +20 -0
- package/src/stores/settings.ts +44 -2
- package/src/types/applet.ts +5 -1
- package/src/types/capabilities/rayneo-io.ts +2 -0
- package/src/types/enums.ts +1 -0
- package/src/utils/cloudClient/RnUdpAdapter.ts +13 -1
- package/src/utils/display/scene/index.ts +1 -0
- package/src/utils/display/scene/process.ts +17 -1
- package/src/utils/display/scene/types.ts +1 -0
- package/src/utils/miniappGlobals.ts +30 -4
|
@@ -32,7 +32,7 @@ import { useGlassesStore } from "../stores/glasses";
|
|
|
32
32
|
import { SETTINGS, useSettingsStore } from "../stores/settings";
|
|
33
33
|
import { markTranscriptSourceRecent, useTranscriptionDebugStore } from "../stores/transcriptionDebug";
|
|
34
34
|
import { resolveMapboxRenderToken } from "./MapboxTokenService";
|
|
35
|
-
import { createAnthropicProvider, createGeminiProvider, createOpenAiProvider, } from "@glassly/llm-providers";
|
|
35
|
+
import { createAnthropicProvider, createDeepSeekProvider, createGeminiProvider, createOpenAiProvider, } from "@glassly/llm-providers";
|
|
36
36
|
import { normalizeAiTier, resolveAiTier } from "./AiTierResolution";
|
|
37
37
|
import { decideLlmRoute, localLlmReason } from "./LlmRouting";
|
|
38
38
|
import localLlmModelManager from "./LocalLlmModelManager";
|
|
@@ -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";
|
|
@@ -95,6 +96,8 @@ import { listPhoneCalendarEvents, PhoneCalendarError } from "./PhoneCalendarServ
|
|
|
95
96
|
import { LocalMiniappStorage } from "./LocalMiniappStorage";
|
|
96
97
|
import { captureDecisionFor } from "./MemoryCapture";
|
|
97
98
|
import { memoryStore } from "./memoryStoreInstance";
|
|
99
|
+
import { exportMemoryFile, importMemoryFile } from "./memoryBackupFile";
|
|
100
|
+
import { MemoryBackupError } from "./MemoryStore";
|
|
98
101
|
import { speakerProfileStore } from "./speakerProfileStoreInstance";
|
|
99
102
|
/** Streams that carry speech text and so have a cloud route and an on-device route. */
|
|
100
103
|
const isSpeechTextStream = (stream) => stream.startsWith("transcription:") || stream.startsWith("translation:");
|
|
@@ -247,6 +250,7 @@ const MINIAPP_READABLE_API_KEYS = {
|
|
|
247
250
|
anthropic: SETTINGS.anthropic_api_key.key,
|
|
248
251
|
gemini: SETTINGS.gemini_api_key.key,
|
|
249
252
|
openai: SETTINGS.openai_api_key.key,
|
|
253
|
+
deepseek: SETTINGS.deepseek_api_key.key,
|
|
250
254
|
elevenlabs: SETTINGS.elevenlabs_api_key.key,
|
|
251
255
|
mapbox: SETTINGS.mapbox_api_key.key,
|
|
252
256
|
};
|
|
@@ -267,7 +271,9 @@ function directLlmProvider(name) {
|
|
|
267
271
|
? createGeminiProvider({ logger })
|
|
268
272
|
: name === "openai"
|
|
269
273
|
? createOpenAiProvider({ logger })
|
|
270
|
-
:
|
|
274
|
+
: name === "deepseek"
|
|
275
|
+
? createDeepSeekProvider({ logger })
|
|
276
|
+
: createAnthropicProvider({ logger });
|
|
271
277
|
directLlmProviders.set(name, built);
|
|
272
278
|
return built;
|
|
273
279
|
}
|
|
@@ -283,6 +289,7 @@ function savedLlmKeys() {
|
|
|
283
289
|
anthropic: userLlmKey("anthropic").length > 0,
|
|
284
290
|
gemini: userLlmKey("gemini").length > 0,
|
|
285
291
|
openai: userLlmKey("openai").length > 0,
|
|
292
|
+
deepseek: userLlmKey("deepseek").length > 0,
|
|
286
293
|
};
|
|
287
294
|
}
|
|
288
295
|
/** The stored Settings > AI Models dropdown value for a tier ("auto" default). */
|
|
@@ -438,6 +445,10 @@ class LocalMiniappRuntime {
|
|
|
438
445
|
connectedApps = new Map();
|
|
439
446
|
/** Claimed gestures (CLAIM_GESTURES): a stack per gesture, the most recent claimer holding it. */
|
|
440
447
|
gestureClaims = emptyGestureClaims();
|
|
448
|
+
// The last main-view frame each app asked to render. Widget mode refuses
|
|
449
|
+
// frames and demotion clears them, and an app that has drawn once may not
|
|
450
|
+
// draw again on its own, so promotion replays this.
|
|
451
|
+
retainedRenders = new Map();
|
|
441
452
|
/**
|
|
442
453
|
* Packages that have completed their CONNECT handshake (sent
|
|
443
454
|
* `miniapp_connect`, got `CONNECT_ACK`). Distinct from `connectedApps`,
|
|
@@ -490,7 +501,14 @@ class LocalMiniappRuntime {
|
|
|
490
501
|
// secret never leaves the device.
|
|
491
502
|
localSecret = `miniapp_${Date.now()}_${Math.random().toString(36).slice(2, 14)}`;
|
|
492
503
|
usedTokens = new Set();
|
|
493
|
-
constructor() {
|
|
504
|
+
constructor() {
|
|
505
|
+
miniappWidgetMode.subscribe((packageName, widgetOnly) => {
|
|
506
|
+
// Promotion happens inside the apps store's start(); the core-app slot
|
|
507
|
+
// settles with that store write, so replay once it has.
|
|
508
|
+
if (!widgetOnly)
|
|
509
|
+
queueMicrotask(() => this.replayRetainedRender(packageName));
|
|
510
|
+
});
|
|
511
|
+
}
|
|
494
512
|
/** Snapshot of miniapps actively subscribed to hardware button events. */
|
|
495
513
|
getButtonPressSubscribers() {
|
|
496
514
|
return [...(this.streamSubscribers.get(MiniappStreamType.BUTTON_PRESS) ?? [])].sort();
|
|
@@ -874,7 +892,11 @@ class LocalMiniappRuntime {
|
|
|
874
892
|
// window ends early once this app pushes its first display (see
|
|
875
893
|
// LocalDisplayManager.request), or after ~1.5s. Fires once per spawn —
|
|
876
894
|
// including a crash-respawn, since each spawn is a fresh "app is starting".
|
|
877
|
-
|
|
895
|
+
// A spawn for a dashboard widget is not the wearer starting the app: it draws
|
|
896
|
+
// nothing of its own, so it shows no boot text either.
|
|
897
|
+
if (!miniappWidgetMode.isWidgetOnly(packageName)) {
|
|
898
|
+
localDisplayManager.onMount(packageName, installedManifest?.name ?? packageName);
|
|
899
|
+
}
|
|
878
900
|
}
|
|
879
901
|
/**
|
|
880
902
|
* Resolve once `packageName` has completed its CONNECT handshake. If it has
|
|
@@ -996,6 +1018,7 @@ class LocalMiniappRuntime {
|
|
|
996
1018
|
}
|
|
997
1019
|
// Its gesture claims go back to whoever claimed them before.
|
|
998
1020
|
this.applyGestureClaimChanges(dropGestureClaims(this.gestureClaims, packageName));
|
|
1021
|
+
this.retainedRenders.delete(packageName);
|
|
999
1022
|
// Warm-up is a request-owned camera lease. Cancel it even if the app record was already
|
|
1000
1023
|
// removed so a close racing the camera-open promise cannot leave the sensor running.
|
|
1001
1024
|
// Keep camera teardown ordered: restoring FOV can restart the HAL, so the warm-up lease must
|
|
@@ -1300,6 +1323,8 @@ class LocalMiniappRuntime {
|
|
|
1300
1323
|
case MiniappRequestType.MEMORY_QUERY:
|
|
1301
1324
|
case MiniappRequestType.MEMORY_WINDOW:
|
|
1302
1325
|
case MiniappRequestType.MEMORY_EXPORT_TEXT:
|
|
1326
|
+
case MiniappRequestType.MEMORY_EXPORT_FILE:
|
|
1327
|
+
case MiniappRequestType.MEMORY_IMPORT_FILE:
|
|
1303
1328
|
case MiniappRequestType.MEMORY_SUMMARY_ADD:
|
|
1304
1329
|
case MiniappRequestType.MEMORY_SUMMARY_LIST:
|
|
1305
1330
|
case MiniappRequestType.MEMORY_PENDING_SUMMARY_HOUR:
|
|
@@ -1491,6 +1516,9 @@ class LocalMiniappRuntime {
|
|
|
1491
1516
|
case MiniappRequestType.WIDGET_RESULT:
|
|
1492
1517
|
this.handleWidgetResult(packageName, payload);
|
|
1493
1518
|
break;
|
|
1519
|
+
case MiniappRequestType.WIDGET_EXPANDABLE:
|
|
1520
|
+
this.handleWidgetExpandable(packageName, payload, requestId);
|
|
1521
|
+
break;
|
|
1494
1522
|
case MiniappRequestType.SYSTEM_EXIT:
|
|
1495
1523
|
void this.handleSystemExit(packageName, requestId);
|
|
1496
1524
|
break;
|
|
@@ -2124,11 +2152,6 @@ class LocalMiniappRuntime {
|
|
|
2124
2152
|
this.sendResult(packageName, requestId, true, { status: "blocked", reason: "app stopped" });
|
|
2125
2153
|
return;
|
|
2126
2154
|
}
|
|
2127
|
-
// A widget-only app draws through its widget alone.
|
|
2128
|
-
if (miniappWidgetMode.isWidgetOnly(packageName)) {
|
|
2129
|
-
this.sendResult(packageName, requestId, true, { status: "blocked", reason: "widget mode" });
|
|
2130
|
-
return;
|
|
2131
|
-
}
|
|
2132
2155
|
if (!Array.isArray(payload.elements)) {
|
|
2133
2156
|
this.sendResult(packageName, requestId, false, undefined, {
|
|
2134
2157
|
code: MiniappErrorCode.INTERNAL,
|
|
@@ -2136,6 +2159,17 @@ class LocalMiniappRuntime {
|
|
|
2136
2159
|
});
|
|
2137
2160
|
return;
|
|
2138
2161
|
}
|
|
2162
|
+
if (payload.view !== "dashboard") {
|
|
2163
|
+
if (payload.elements.length === 0)
|
|
2164
|
+
this.retainedRenders.delete(packageName);
|
|
2165
|
+
else
|
|
2166
|
+
this.retainedRenders.set(packageName, payload);
|
|
2167
|
+
}
|
|
2168
|
+
// A widget-only app draws through its widget alone; the frame waits for promotion.
|
|
2169
|
+
if (miniappWidgetMode.isWidgetOnly(packageName)) {
|
|
2170
|
+
this.sendResult(packageName, requestId, true, { status: "blocked", reason: "widget mode" });
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2139
2173
|
// The dashboard view is the overlay app's alone; anyone else's frame
|
|
2140
2174
|
// is dropped, reported rather than silently lost.
|
|
2141
2175
|
if (payload.view === "dashboard" && !isOverlayApp(packageName)) {
|
|
@@ -2160,6 +2194,13 @@ class LocalMiniappRuntime {
|
|
|
2160
2194
|
});
|
|
2161
2195
|
}
|
|
2162
2196
|
}
|
|
2197
|
+
/** Put a promoted app's last main-view frame on the glasses; widget mode had refused it. */
|
|
2198
|
+
replayRetainedRender(packageName) {
|
|
2199
|
+
const payload = this.retainedRenders.get(packageName);
|
|
2200
|
+
if (!payload || !this.connectedApps.has(packageName) || miniappWidgetMode.isWidgetOnly(packageName))
|
|
2201
|
+
return;
|
|
2202
|
+
this.handleRender(packageName, payload);
|
|
2203
|
+
}
|
|
2163
2204
|
/**
|
|
2164
2205
|
* display.showView: the active dashboard takes or releases the overlay
|
|
2165
2206
|
* slot. Refused (NOT_PERMITTED) for an app that is not a dashboard;
|
|
@@ -3433,6 +3474,14 @@ class LocalMiniappRuntime {
|
|
|
3433
3474
|
this.sendResult(packageName, requestId, true, { text: await memoryStore.exportText(scope, num(payload.at)) });
|
|
3434
3475
|
return;
|
|
3435
3476
|
}
|
|
3477
|
+
case MiniappRequestType.MEMORY_EXPORT_FILE: {
|
|
3478
|
+
this.sendResult(packageName, requestId, true, await exportMemoryFile());
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3481
|
+
case MiniappRequestType.MEMORY_IMPORT_FILE: {
|
|
3482
|
+
this.sendResult(packageName, requestId, true, { imported: await importMemoryFile() });
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3436
3485
|
case MiniappRequestType.MEMORY_SUMMARY_ADD: {
|
|
3437
3486
|
const hourStart = num(payload.hourStart);
|
|
3438
3487
|
if (hourStart == null || typeof payload.text !== "string") {
|
|
@@ -3469,6 +3518,13 @@ class LocalMiniappRuntime {
|
|
|
3469
3518
|
}
|
|
3470
3519
|
}
|
|
3471
3520
|
catch (err) {
|
|
3521
|
+
if (err instanceof MemoryBackupError) {
|
|
3522
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
3523
|
+
code: MiniappErrorCode.INVALID_ARGUMENT,
|
|
3524
|
+
message: err.message,
|
|
3525
|
+
});
|
|
3526
|
+
return;
|
|
3527
|
+
}
|
|
3472
3528
|
console.error(`${LOG_TAG}: ${requestType} error:`, err);
|
|
3473
3529
|
this.sendResult(packageName, requestId, false, undefined, {
|
|
3474
3530
|
code: MiniappErrorCode.INTERNAL,
|
|
@@ -3985,7 +4041,7 @@ class LocalMiniappRuntime {
|
|
|
3985
4041
|
if (base64) {
|
|
3986
4042
|
// File share via base64 — write to temp file then share
|
|
3987
4043
|
const tempFile = new File(Paths.cache, filename || "shared_file");
|
|
3988
|
-
tempFile.
|
|
4044
|
+
tempFile.writeSync(base64, { encoding: "base64" });
|
|
3989
4045
|
await Share.open({
|
|
3990
4046
|
url: tempFile.uri,
|
|
3991
4047
|
type: mimeType || "application/octet-stream",
|
|
@@ -4055,7 +4111,7 @@ class LocalMiniappRuntime {
|
|
|
4055
4111
|
let file;
|
|
4056
4112
|
if (base64) {
|
|
4057
4113
|
file = new File(Paths.cache, name);
|
|
4058
|
-
file.
|
|
4114
|
+
file.writeSync(base64, { encoding: "base64" });
|
|
4059
4115
|
}
|
|
4060
4116
|
else if (url) {
|
|
4061
4117
|
file = await File.downloadFileAsync(url, new File(Paths.cache, name), { idempotent: true });
|
|
@@ -5224,7 +5280,7 @@ class LocalMiniappRuntime {
|
|
|
5224
5280
|
/** Max serialized size of an action call's params / result (256 KB). */
|
|
5225
5281
|
static ACTION_PAYLOAD_CAP = 256 * 1024;
|
|
5226
5282
|
/** Outstanding action invocations, keyed by host-generated callId. */
|
|
5227
|
-
actionCalls = new
|
|
5283
|
+
actionCalls = new PendingActionCalls(BgTimer, (value) => this.actionPayloadTooLarge(value));
|
|
5228
5284
|
actionCallSeq = 0;
|
|
5229
5285
|
interopApps() {
|
|
5230
5286
|
return useAppStatusStore.getState().apps;
|
|
@@ -5238,6 +5294,9 @@ class LocalMiniappRuntime {
|
|
|
5238
5294
|
const app = this.interopApps().find((a) => a.packageName === packageName);
|
|
5239
5295
|
if (!app)
|
|
5240
5296
|
return false;
|
|
5297
|
+
// Running only for its widget: start() promotes it to a normal run, as opening it on the phone does.
|
|
5298
|
+
if (app.widgetOnly)
|
|
5299
|
+
return useAppStatusStore.getState().start(app, { skipNavigation: true });
|
|
5241
5300
|
if (app.local) {
|
|
5242
5301
|
await miniappLauncher.ensureConnected(packageName);
|
|
5243
5302
|
return true;
|
|
@@ -5291,7 +5350,8 @@ class LocalMiniappRuntime {
|
|
|
5291
5350
|
packageName: app.packageName,
|
|
5292
5351
|
name: app.name,
|
|
5293
5352
|
version: app.version ?? "",
|
|
5294
|
-
running
|
|
5353
|
+
// A provider running only for a placed widget is not a run the wearer started.
|
|
5354
|
+
running: app.running && !app.widgetOnly,
|
|
5295
5355
|
hidden: app.hidden === true,
|
|
5296
5356
|
system: isSystemMiniapp(app),
|
|
5297
5357
|
compatibility,
|
|
@@ -5463,6 +5523,8 @@ class LocalMiniappRuntime {
|
|
|
5463
5523
|
widgetCallSeq = 0;
|
|
5464
5524
|
/** Elements per widget frame; a card is small, and the link is slow. */
|
|
5465
5525
|
static WIDGET_FRAME_ELEMENT_CAP = 64;
|
|
5526
|
+
/** Elements per expanded widget frame: the full screen, on the same link. */
|
|
5527
|
+
static WIDGET_EXPANDED_FRAME_ELEMENT_CAP = 128;
|
|
5466
5528
|
requireWidgetHost(packageName, requestId, op) {
|
|
5467
5529
|
return this.requirePermission(packageName, requestId, "HOST_WIDGETS", `to ${op} widgets`);
|
|
5468
5530
|
}
|
|
@@ -5505,6 +5567,7 @@ class LocalMiniappRuntime {
|
|
|
5505
5567
|
name: widget.name,
|
|
5506
5568
|
sizes: widget.sizes,
|
|
5507
5569
|
defaultSize: widget.defaultSize,
|
|
5570
|
+
expandable: widget.expandOnTap,
|
|
5508
5571
|
running: app.running,
|
|
5509
5572
|
compatible: app.compatibility?.isCompatible !== false,
|
|
5510
5573
|
});
|
|
@@ -5516,7 +5579,7 @@ class LocalMiniappRuntime {
|
|
|
5516
5579
|
const box = raw;
|
|
5517
5580
|
if (!box || typeof box !== "object")
|
|
5518
5581
|
return null;
|
|
5519
|
-
if (box.size !== "XS" && box.size !== "SM" && box.size !== "MD" && box.size !== "LG")
|
|
5582
|
+
if (box.size !== "XS" && box.size !== "XSM" && box.size !== "SM" && box.size !== "MD" && box.size !== "LG")
|
|
5520
5583
|
return null;
|
|
5521
5584
|
const width = Number(box.width);
|
|
5522
5585
|
const height = Number(box.height);
|
|
@@ -5538,7 +5601,7 @@ class LocalMiniappRuntime {
|
|
|
5538
5601
|
/**
|
|
5539
5602
|
* session.widgets.place: record the host's box, wake the provider in widget
|
|
5540
5603
|
* mode when it is not running, hand it the box, and answer with the last
|
|
5541
|
-
*
|
|
5604
|
+
* card and expanded frames the provider pushed for the widget.
|
|
5542
5605
|
*/
|
|
5543
5606
|
async handleWidgetPlace(hostPackageName, payload, requestId) {
|
|
5544
5607
|
if (!this.requireWidgetHost(hostPackageName, requestId, "place"))
|
|
@@ -5564,10 +5627,12 @@ class LocalMiniappRuntime {
|
|
|
5564
5627
|
}
|
|
5565
5628
|
const provider = app.packageName;
|
|
5566
5629
|
// Record the placement before the wake so the spawn lands in widget mode
|
|
5567
|
-
// and an unplace racing it cannot stop the provider early.
|
|
5630
|
+
// and an unplace racing it cannot stop the provider early. A provider the
|
|
5631
|
+
// store already runs (a wearer's launch still spawning, say) stays a
|
|
5632
|
+
// normal run; the registry alone lags that launch.
|
|
5568
5633
|
this.widgetPlacements = placeWidget(this.widgetPlacements, { hostPackageName, providerPackageName: provider, widgetId, box });
|
|
5569
5634
|
miniappWidgetMode.setPlaced(provider, true);
|
|
5570
|
-
if (!miniappRunningRegistry.has(provider))
|
|
5635
|
+
if (!app.running && !miniappRunningRegistry.has(provider))
|
|
5571
5636
|
miniappWidgetMode.setWidgetOnly(provider, true);
|
|
5572
5637
|
try {
|
|
5573
5638
|
await this.wakeInteropApp(provider);
|
|
@@ -5585,7 +5650,10 @@ class LocalMiniappRuntime {
|
|
|
5585
5650
|
if (placementOf(this.widgetPlacements, hostPackageName, provider, widgetId) && this.connectedApps.has(provider)) {
|
|
5586
5651
|
this.sendWidgetCall(provider, widgetId, { kind: "box", box });
|
|
5587
5652
|
}
|
|
5588
|
-
this.sendResult(hostPackageName, requestId, true, {
|
|
5653
|
+
this.sendResult(hostPackageName, requestId, true, {
|
|
5654
|
+
frame: widgetFrameOf(this.widgetPlacements, provider, widgetId),
|
|
5655
|
+
expandedFrame: widgetFrameOf(this.widgetPlacements, provider, widgetId, "expanded"),
|
|
5656
|
+
});
|
|
5589
5657
|
}
|
|
5590
5658
|
async handleWidgetUnplace(hostPackageName, payload, requestId) {
|
|
5591
5659
|
if (!this.requireWidgetHost(hostPackageName, requestId, "unplace"))
|
|
@@ -5702,22 +5770,54 @@ class LocalMiniappRuntime {
|
|
|
5702
5770
|
this.sendResult(pending.hostPackageName, pending.hostRequestId, false, undefined, error);
|
|
5703
5771
|
}
|
|
5704
5772
|
}
|
|
5705
|
-
/** Provider
|
|
5773
|
+
/** Provider: read, or set with `expandable`, whether a tap expands one of its own `EX` widgets. */
|
|
5774
|
+
handleWidgetExpandable(packageName, payload, requestId) {
|
|
5775
|
+
const widgetId = payload.widgetId;
|
|
5776
|
+
const app = this.interopApps().find((a) => a.packageName === packageName);
|
|
5777
|
+
const widget = app && widgetId ? this.declaredWidget(app, widgetId) : undefined;
|
|
5778
|
+
if (!widget?.expandable) {
|
|
5779
|
+
this.sendResult(packageName, requestId, false, undefined, {
|
|
5780
|
+
code: MiniappErrorCode.WIDGET_NOT_FOUND,
|
|
5781
|
+
message: `${packageName} has no widget "${widgetId ?? "(missing widgetId)"}" that lists EX`,
|
|
5782
|
+
});
|
|
5783
|
+
return;
|
|
5784
|
+
}
|
|
5785
|
+
const expandable = typeof payload.expandable === "boolean"
|
|
5786
|
+
? useAppStatusStore.getState().setWidgetExpand(packageName, widget.id, payload.expandable)
|
|
5787
|
+
: widget.expandOnTap;
|
|
5788
|
+
this.sendResult(packageName, requestId, true, { expandable });
|
|
5789
|
+
}
|
|
5790
|
+
/** Provider → host, one-shot: cache the frame for its view and push it to every host holding the widget. */
|
|
5706
5791
|
handleWidgetUpdate(providerPackageName, payload) {
|
|
5707
5792
|
const widgetId = payload.widgetId;
|
|
5708
5793
|
const elements = payload.elements;
|
|
5709
5794
|
if (!widgetId || !Array.isArray(elements))
|
|
5710
5795
|
return;
|
|
5711
5796
|
const app = this.interopApps().find((a) => a.packageName === providerPackageName);
|
|
5712
|
-
|
|
5797
|
+
const widget = app ? this.declaredWidget(app, widgetId) : undefined;
|
|
5798
|
+
if (!widget)
|
|
5713
5799
|
return;
|
|
5714
|
-
|
|
5715
|
-
|
|
5800
|
+
const view = payload.view === "expanded" ? "expanded" : "card";
|
|
5801
|
+
if (view === "expanded" && !widget.expandable) {
|
|
5802
|
+
console.warn(`${LOG_TAG}: dropping expanded frame from ${providerPackageName}/${widgetId}: it does not list EX`);
|
|
5716
5803
|
return;
|
|
5717
5804
|
}
|
|
5718
|
-
|
|
5805
|
+
const cap = view === "expanded"
|
|
5806
|
+
? LocalMiniappRuntime.WIDGET_EXPANDED_FRAME_ELEMENT_CAP
|
|
5807
|
+
: LocalMiniappRuntime.WIDGET_FRAME_ELEMENT_CAP;
|
|
5808
|
+
if (elements.length > cap || this.actionPayloadTooLarge(elements)) {
|
|
5809
|
+
console.warn(`${LOG_TAG}: dropping oversized ${view} widget frame from ${providerPackageName}/${widgetId}`);
|
|
5810
|
+
return;
|
|
5811
|
+
}
|
|
5812
|
+
this.widgetPlacements = setWidgetFrame(this.widgetPlacements, providerPackageName, widgetId, elements, view);
|
|
5719
5813
|
for (const host of widgetHostsOf(this.widgetPlacements, providerPackageName, widgetId)) {
|
|
5720
|
-
this.sendToMiniapp(host, {
|
|
5814
|
+
this.sendToMiniapp(host, {
|
|
5815
|
+
type: MiniappResponseType.WIDGET_FRAME,
|
|
5816
|
+
packageName: providerPackageName,
|
|
5817
|
+
widgetId,
|
|
5818
|
+
elements,
|
|
5819
|
+
...(view === "expanded" ? { view } : {}),
|
|
5820
|
+
});
|
|
5721
5821
|
}
|
|
5722
5822
|
}
|
|
5723
5823
|
/** A miniapp disconnected: as a host its placements go, as a provider its boxes are forgotten and its frames kept. */
|
|
@@ -5754,104 +5854,82 @@ class LocalMiniappRuntime {
|
|
|
5754
5854
|
return false;
|
|
5755
5855
|
}
|
|
5756
5856
|
}
|
|
5857
|
+
/** 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. */
|
|
5858
|
+
static clampActionTimeout(raw) {
|
|
5859
|
+
return Math.min(Math.max(Number(raw) || 30_000, 6_000), 120_000);
|
|
5860
|
+
}
|
|
5861
|
+
/** Caller identity the host presents for host-initiated actions (a system caller). */
|
|
5862
|
+
static HOST_ACTION_CALLER = "host";
|
|
5863
|
+
/** Installed, compatible, and declares the action — the same gates `invokeActionFromHost` applies up front. */
|
|
5864
|
+
hostActionAvailable(targetPackageName, actionId) {
|
|
5865
|
+
return appDeclaresAction(this.interopApps().find((a) => a.packageName === targetPackageName), actionId);
|
|
5866
|
+
}
|
|
5757
5867
|
/**
|
|
5758
|
-
*
|
|
5759
|
-
* and
|
|
5868
|
+
* Host-initiated action call (e.g. the watch remote): same gates, headless
|
|
5869
|
+
* wake and ACTION_CALL/ACTION_RESULT correlation as a miniapp's
|
|
5870
|
+
* session.actions.invoke, with the host as a system caller.
|
|
5871
|
+
* Rejects with an Error whose `code` is a MiniappErrorCode.
|
|
5760
5872
|
*/
|
|
5873
|
+
async invokeActionFromHost(targetPackageName, actionId, params = {}, timeoutMs) {
|
|
5874
|
+
const outcome = await this.runActionInvoke(LocalMiniappRuntime.HOST_ACTION_CALLER, true, targetPackageName, actionId, params, LocalMiniappRuntime.clampActionTimeout(timeoutMs));
|
|
5875
|
+
if (outcome.ok)
|
|
5876
|
+
return outcome.data;
|
|
5877
|
+
throw Object.assign(new Error(outcome.error.message), { code: outcome.error.code });
|
|
5878
|
+
}
|
|
5879
|
+
/** session.actions.invoke → run the call, then relay the outcome to the caller's pending request. */
|
|
5761
5880
|
async handleActionInvoke(callerPackageName, payload, requestId) {
|
|
5762
|
-
const
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5881
|
+
const outcome = await this.runActionInvoke(callerPackageName, this.isSystemPackage(callerPackageName), payload.targetPackageName, payload.actionId, payload.params ?? {}, LocalMiniappRuntime.clampActionTimeout(payload.timeoutMs));
|
|
5882
|
+
if (outcome.ok)
|
|
5883
|
+
this.sendResult(callerPackageName, requestId, true, outcome.data);
|
|
5884
|
+
else
|
|
5885
|
+
this.sendResult(callerPackageName, requestId, false, undefined, outcome.error);
|
|
5886
|
+
}
|
|
5887
|
+
/**
|
|
5888
|
+
* Gate, wake the target headlessly, deliver an ACTION_CALL, and resolve when
|
|
5889
|
+
* its ACTION_RESULT (or the timeout) arrives. Never rejects.
|
|
5890
|
+
*/
|
|
5891
|
+
async runActionInvoke(callerPackageName, callerIsSystem, target, actionId, params, timeoutMs) {
|
|
5892
|
+
const fail = actionFailure;
|
|
5773
5893
|
if (this.actionPayloadTooLarge(params)) {
|
|
5774
|
-
|
|
5775
|
-
code: MiniappErrorCode.PAYLOAD_TOO_LARGE,
|
|
5776
|
-
message: "action params exceeded the 256 KB cap",
|
|
5777
|
-
});
|
|
5778
|
-
return;
|
|
5894
|
+
return fail(MiniappErrorCode.PAYLOAD_TOO_LARGE, "action params exceeded the 256 KB cap");
|
|
5779
5895
|
}
|
|
5780
5896
|
const app = target ? this.interopApps().find((a) => a.packageName === target) : undefined;
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
reason: `${target} is not compatible with the connected glasses`,
|
|
5804
|
-
metadata: { missingRequired: app.compatibility.missingRequired ?? [], via: "miniapps.invoke" },
|
|
5805
|
-
timestamp: Date.now(),
|
|
5806
|
-
});
|
|
5807
|
-
return;
|
|
5808
|
-
}
|
|
5809
|
-
// Declared-action gate: the target must declare the action in its manifest.
|
|
5810
|
-
const declared = (app.actions ?? []).find((a) => a.id === actionId);
|
|
5811
|
-
if (!declared) {
|
|
5812
|
-
this.sendResult(callerPackageName, requestId, false, undefined, {
|
|
5813
|
-
code: MiniappErrorCode.ACTION_NOT_FOUND,
|
|
5814
|
-
message: `${target} does not declare action "${actionId}"`,
|
|
5815
|
-
});
|
|
5816
|
-
return;
|
|
5817
|
-
}
|
|
5818
|
-
// Visibility gate: declared actions are public by default; ones marked
|
|
5819
|
-
// `visibility: "system"` stay restricted to system/dev callers.
|
|
5820
|
-
if (declared.visibility === "system" && !this.isSystemPackage(callerPackageName)) {
|
|
5821
|
-
this.auditInterop({
|
|
5822
|
-
caller: callerPackageName,
|
|
5823
|
-
op: "invoke",
|
|
5824
|
-
target,
|
|
5825
|
-
actionId,
|
|
5826
|
-
ok: false,
|
|
5827
|
-
errorCode: MiniappErrorCode.NOT_PERMITTED,
|
|
5828
|
-
});
|
|
5829
|
-
this.sendResult(callerPackageName, requestId, false, undefined, {
|
|
5830
|
-
code: MiniappErrorCode.NOT_PERMITTED,
|
|
5831
|
-
message: `Action "${actionId}" on ${target} is restricted to system apps`,
|
|
5832
|
-
});
|
|
5833
|
-
return;
|
|
5897
|
+
const denied = checkActionAccess(app, target, actionId, callerIsSystem);
|
|
5898
|
+
if (denied) {
|
|
5899
|
+
if (denied.code === MiniappErrorCode.APP_NOT_COMPATIBLE && target && app) {
|
|
5900
|
+
islandNotifications.emit({
|
|
5901
|
+
kind: "version_incompatible",
|
|
5902
|
+
packageName: target,
|
|
5903
|
+
reason: denied.message,
|
|
5904
|
+
metadata: { missingRequired: app.compatibility?.missingRequired ?? [], via: "miniapps.invoke" },
|
|
5905
|
+
timestamp: Date.now(),
|
|
5906
|
+
});
|
|
5907
|
+
}
|
|
5908
|
+
else if (denied.code === MiniappErrorCode.NOT_PERMITTED) {
|
|
5909
|
+
this.auditInterop({
|
|
5910
|
+
caller: callerPackageName,
|
|
5911
|
+
op: "invoke",
|
|
5912
|
+
target,
|
|
5913
|
+
actionId,
|
|
5914
|
+
ok: false,
|
|
5915
|
+
errorCode: MiniappErrorCode.NOT_PERMITTED,
|
|
5916
|
+
});
|
|
5917
|
+
}
|
|
5918
|
+
return { ok: false, error: denied };
|
|
5834
5919
|
}
|
|
5920
|
+
if (!target || !actionId)
|
|
5921
|
+
return fail(MiniappErrorCode.INTERNAL, "unreachable");
|
|
5835
5922
|
// Headless wake + wait for CONNECT (idempotent / fast if already connected).
|
|
5836
5923
|
try {
|
|
5837
5924
|
await this.wakeInteropApp(target);
|
|
5838
5925
|
}
|
|
5839
5926
|
catch (e) {
|
|
5840
|
-
|
|
5841
|
-
code: MiniappErrorCode.WAKE_FAILED,
|
|
5842
|
-
message: e?.message ?? `failed to wake ${target}`,
|
|
5843
|
-
});
|
|
5844
|
-
return;
|
|
5927
|
+
return fail(MiniappErrorCode.WAKE_FAILED, e?.message ?? `failed to wake ${target}`);
|
|
5845
5928
|
}
|
|
5846
5929
|
// The wake waited for CONNECT, but the target could have dropped in the gap
|
|
5847
5930
|
// before delivery — fail fast rather than arming a timer for a call that
|
|
5848
|
-
// sendToMiniapp would silently drop
|
|
5849
|
-
// full invoke timeout for an ACTION_CALL that was never delivered).
|
|
5931
|
+
// sendToMiniapp would silently drop.
|
|
5850
5932
|
if (!this.connectedApps.has(target)) {
|
|
5851
|
-
this.sendResult(callerPackageName, requestId, false, undefined, {
|
|
5852
|
-
code: MiniappErrorCode.WAKE_FAILED,
|
|
5853
|
-
message: `${target} disconnected before the action could be delivered`,
|
|
5854
|
-
});
|
|
5855
5933
|
this.auditInterop({
|
|
5856
5934
|
caller: callerPackageName,
|
|
5857
5935
|
op: "invoke",
|
|
@@ -5860,18 +5938,11 @@ class LocalMiniappRuntime {
|
|
|
5860
5938
|
ok: false,
|
|
5861
5939
|
errorCode: MiniappErrorCode.WAKE_FAILED,
|
|
5862
5940
|
});
|
|
5863
|
-
return;
|
|
5941
|
+
return fail(MiniappErrorCode.WAKE_FAILED, `${target} disconnected before the action could be delivered`);
|
|
5864
5942
|
}
|
|
5865
|
-
// Deliver the call and arm the handler timeout; ACTION_RESULT
|
|
5943
|
+
// Deliver the call and arm the handler timeout; ACTION_RESULT settles it.
|
|
5866
5944
|
const callId = `act-${Date.now().toString(36)}-${this.actionCallSeq++}`;
|
|
5867
|
-
const
|
|
5868
|
-
this.actionCalls.delete(callId);
|
|
5869
|
-
this.sendResult(callerPackageName, requestId, false, undefined, {
|
|
5870
|
-
code: MiniappErrorCode.ACTION_TIMEOUT,
|
|
5871
|
-
message: `action "${actionId}" timed out after ${timeoutMs}ms`,
|
|
5872
|
-
});
|
|
5873
|
-
}, timeoutMs);
|
|
5874
|
-
this.actionCalls.set(callId, { callerPackageName, targetPackageName: target, callerRequestId: requestId, timer });
|
|
5945
|
+
const outcome = this.actionCalls.register(callId, target, actionId, timeoutMs);
|
|
5875
5946
|
this.sendToMiniapp(target, {
|
|
5876
5947
|
type: MiniappResponseType.ACTION_CALL,
|
|
5877
5948
|
callId,
|
|
@@ -5880,40 +5951,11 @@ class LocalMiniappRuntime {
|
|
|
5880
5951
|
callerPackageName,
|
|
5881
5952
|
});
|
|
5882
5953
|
this.auditInterop({ caller: callerPackageName, op: "invoke", target, actionId, ok: true });
|
|
5954
|
+
return outcome;
|
|
5883
5955
|
}
|
|
5884
|
-
/** Target → host:
|
|
5956
|
+
/** Target → host: settle the pending invoke for this ACTION_RESULT. */
|
|
5885
5957
|
handleActionResult(targetPackageName, payload) {
|
|
5886
|
-
|
|
5887
|
-
if (!callId)
|
|
5888
|
-
return;
|
|
5889
|
-
const pending = this.actionCalls.get(callId);
|
|
5890
|
-
if (!pending)
|
|
5891
|
-
return;
|
|
5892
|
-
// Only the invoked target may resolve this call. callIds are guessable
|
|
5893
|
-
// (timestamp + seq), so a different connected miniapp must not be able to
|
|
5894
|
-
// spoof a result/error for someone else's invoke.
|
|
5895
|
-
if (pending.targetPackageName !== targetPackageName)
|
|
5896
|
-
return;
|
|
5897
|
-
this.actionCalls.delete(callId);
|
|
5898
|
-
BgTimer.clearTimeout(pending.timer);
|
|
5899
|
-
if (payload.ok === true) {
|
|
5900
|
-
const result = payload.result;
|
|
5901
|
-
if (this.actionPayloadTooLarge(result)) {
|
|
5902
|
-
this.sendResult(pending.callerPackageName, pending.callerRequestId, false, undefined, {
|
|
5903
|
-
code: MiniappErrorCode.PAYLOAD_TOO_LARGE,
|
|
5904
|
-
message: "action result exceeded the 256 KB cap",
|
|
5905
|
-
});
|
|
5906
|
-
return;
|
|
5907
|
-
}
|
|
5908
|
-
this.sendResult(pending.callerPackageName, pending.callerRequestId, true, result ?? null);
|
|
5909
|
-
}
|
|
5910
|
-
else {
|
|
5911
|
-
const error = payload.error ?? {
|
|
5912
|
-
code: MiniappErrorCode.INTERNAL,
|
|
5913
|
-
message: "action handler error",
|
|
5914
|
-
};
|
|
5915
|
-
this.sendResult(pending.callerPackageName, pending.callerRequestId, false, undefined, error);
|
|
5916
|
-
}
|
|
5958
|
+
this.actionCalls.handleResult(targetPackageName, payload);
|
|
5917
5959
|
}
|
|
5918
5960
|
sendResult(packageName, requestId, ok, data, error) {
|
|
5919
5961
|
if (!requestId)
|