@glassly/engine 0.3.4-dev.704 → 0.3.5-dev.706
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/generated/releaseMetadata.js +4 -4
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/services/AppRegistry.d.ts +3 -1
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +5 -2
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +32 -0
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +332 -1
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +2 -0
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/MiniappWidgetMode.d.ts +24 -0
- package/build/services/MiniappWidgetMode.d.ts.map +1 -0
- package/build/services/MiniappWidgetMode.js +58 -0
- package/build/services/MiniappWidgetMode.js.map +1 -0
- package/build/services/manifestActions.d.ts +8 -1
- package/build/services/manifestActions.d.ts.map +1 -1
- package/build/services/manifestActions.js +29 -0
- package/build/services/manifestActions.js.map +1 -1
- package/build/services/widgetPlacements.d.ts +43 -0
- package/build/services/widgetPlacements.d.ts.map +1 -0
- package/build/services/widgetPlacements.js +83 -0
- package/build/services/widgetPlacements.js.map +1 -0
- package/build/stores/apps.d.ts +3 -1
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +37 -11
- package/build/stores/apps.js.map +1 -1
- package/build/types/applet.d.ts +16 -0
- package/build/types/applet.d.ts.map +1 -1
- package/build/types/applet.js.map +1 -1
- package/package.json +7 -7
- package/src/generated/releaseMetadata.ts +4 -4
- package/src/index.ts +1 -0
- package/src/services/AppRegistry.ts +8 -2
- package/src/services/LocalMiniappRuntime.ts +362 -2
- package/src/services/MiniappLauncher.ts +3 -0
- package/src/services/MiniappWidgetMode.ts +62 -0
- package/src/services/manifestActions.ts +29 -1
- package/src/services/widgetPlacements.ts +121 -0
- package/src/stores/apps.ts +45 -11
- package/src/types/applet.ts +18 -0
|
@@ -51,6 +51,9 @@ import { MiniappIcons } from "./MiniappIcons";
|
|
|
51
51
|
import { pairedControllersFrom, readyControllersKey } from "./controllers";
|
|
52
52
|
import { DERIVED_SETTINGS_KEYS, SETTINGS_BRIDGE_KEYS, controllersSettingFrom, isDerivedSettingsKey, selectSettingsBridgeKeys, validateSettingsSet, } from "./settingsBridge";
|
|
53
53
|
import { mayUsePermission } from "./miniappPermissions";
|
|
54
|
+
import { miniappRunningRegistry } from "./MiniappRunningRegistry";
|
|
55
|
+
import { miniappWidgetMode } from "./MiniappWidgetMode";
|
|
56
|
+
import { dropHost as dropWidgetHost, dropProvider as dropWidgetProvider, emptyWidgetPlacements, frameOf as widgetFrameOf, hostsOf as widgetHostsOf, isProviderPlaced, placedProviders, placementOf, placeWidget, setFrame as setWidgetFrame, unplaceWidget, } from "./widgetPlacements";
|
|
54
57
|
import { activeDashboard, addDashboardApp, displayViewsFor, isDashboardApp, isOverlayApp, onOverlayAppsChange, removeDashboardApp, setOverlayViewer, } from "./pages/overlayApps";
|
|
55
58
|
import { pagesEligible } from "./pages/pageEligibility";
|
|
56
59
|
import nativeNavigationService from "./NativeNavigationService";
|
|
@@ -227,6 +230,7 @@ const PERMISSION_TYPE_TO_CANONICAL = {
|
|
|
227
230
|
SPEAKERS: "speakers",
|
|
228
231
|
MANAGE_MINIAPPS: "manageMiniapps",
|
|
229
232
|
PLATFORM_SETTINGS: "platformSettings",
|
|
233
|
+
HOST_WIDGETS: "hostWidgets",
|
|
230
234
|
};
|
|
231
235
|
/**
|
|
232
236
|
* User API keys (Settings > API Keys) a miniapp may read via
|
|
@@ -329,6 +333,7 @@ const ALL_CANONICAL_PERMISSIONS = [
|
|
|
329
333
|
"displayMirror",
|
|
330
334
|
"manageMiniapps",
|
|
331
335
|
"platformSettings",
|
|
336
|
+
"hostWidgets",
|
|
332
337
|
];
|
|
333
338
|
/**
|
|
334
339
|
* Build the {location, microphone, camera, notifications, calendar, apiKeys}
|
|
@@ -1075,6 +1080,8 @@ class LocalMiniappRuntime {
|
|
|
1075
1080
|
// current on-glasses frame, this clears the glasses (or restores the
|
|
1076
1081
|
// core app's saved frame).
|
|
1077
1082
|
localDisplayManager.onUnmount(packageName);
|
|
1083
|
+
// As a widget host its placements go; as a provider its boxes are forgotten.
|
|
1084
|
+
this.onWidgetPartyGone(packageName);
|
|
1078
1085
|
this.connectedApps.delete(packageName);
|
|
1079
1086
|
this.recomputeMicRequirements();
|
|
1080
1087
|
if (releasedMicGateOverride) {
|
|
@@ -1466,6 +1473,24 @@ class LocalMiniappRuntime {
|
|
|
1466
1473
|
case MiniappRequestType.ACTION_RESULT:
|
|
1467
1474
|
this.handleActionResult(packageName, payload);
|
|
1468
1475
|
break;
|
|
1476
|
+
case MiniappRequestType.WIDGETS_LIST:
|
|
1477
|
+
this.handleWidgetsList(packageName, requestId);
|
|
1478
|
+
break;
|
|
1479
|
+
case MiniappRequestType.WIDGET_PLACE:
|
|
1480
|
+
void this.handleWidgetPlace(packageName, payload, requestId);
|
|
1481
|
+
break;
|
|
1482
|
+
case MiniappRequestType.WIDGET_UNPLACE:
|
|
1483
|
+
void this.handleWidgetUnplace(packageName, payload, requestId);
|
|
1484
|
+
break;
|
|
1485
|
+
case MiniappRequestType.WIDGET_SEND:
|
|
1486
|
+
void this.handleWidgetSend(packageName, payload, requestId);
|
|
1487
|
+
break;
|
|
1488
|
+
case MiniappRequestType.WIDGET_UPDATE:
|
|
1489
|
+
this.handleWidgetUpdate(packageName, payload);
|
|
1490
|
+
break;
|
|
1491
|
+
case MiniappRequestType.WIDGET_RESULT:
|
|
1492
|
+
this.handleWidgetResult(packageName, payload);
|
|
1493
|
+
break;
|
|
1469
1494
|
case MiniappRequestType.SYSTEM_EXIT:
|
|
1470
1495
|
void this.handleSystemExit(packageName, requestId);
|
|
1471
1496
|
break;
|
|
@@ -1791,7 +1816,9 @@ class LocalMiniappRuntime {
|
|
|
1791
1816
|
}
|
|
1792
1817
|
/** The app holding `gesture` (the most recent claimer), or null when it is unclaimed. */
|
|
1793
1818
|
gestureClaimant(gesture) {
|
|
1794
|
-
|
|
1819
|
+
const claimant = gestureClaimant(this.gestureClaims, gesture);
|
|
1820
|
+
// A widget-only app hears no touch input; its claim waits until the wearer opens it.
|
|
1821
|
+
return claimant !== null && miniappWidgetMode.isWidgetOnly(claimant) ? null : claimant;
|
|
1795
1822
|
}
|
|
1796
1823
|
/** Has this app registered the gesture, even if a later claimant currently receives it? */
|
|
1797
1824
|
hasGestureClaim(packageName, gesture) {
|
|
@@ -2055,6 +2082,10 @@ class LocalMiniappRuntime {
|
|
|
2055
2082
|
console.log(`${LOG_TAG}: dropping display from unmounted app ${packageName}`);
|
|
2056
2083
|
return;
|
|
2057
2084
|
}
|
|
2085
|
+
if (miniappWidgetMode.isWidgetOnly(packageName)) {
|
|
2086
|
+
this.sendResult(packageName, requestId, true);
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2058
2089
|
if (!payload.layout || typeof payload.layout !== "object") {
|
|
2059
2090
|
this.sendResult(packageName, requestId, false, undefined, {
|
|
2060
2091
|
code: MiniappErrorCode.INTERNAL,
|
|
@@ -2093,6 +2124,11 @@ class LocalMiniappRuntime {
|
|
|
2093
2124
|
this.sendResult(packageName, requestId, true, { status: "blocked", reason: "app stopped" });
|
|
2094
2125
|
return;
|
|
2095
2126
|
}
|
|
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
|
+
}
|
|
2096
2132
|
if (!Array.isArray(payload.elements)) {
|
|
2097
2133
|
this.sendResult(packageName, requestId, false, undefined, {
|
|
2098
2134
|
code: MiniappErrorCode.INTERNAL,
|
|
@@ -4971,6 +5007,9 @@ class LocalMiniappRuntime {
|
|
|
4971
5007
|
for (const packageName of matchedSubs) {
|
|
4972
5008
|
if (only !== undefined && packageName !== only)
|
|
4973
5009
|
continue;
|
|
5010
|
+
// The wearer's touches reach a widget-only app through its host's widget events alone.
|
|
5011
|
+
if (normalizedStream === MiniappStreamType.TOUCH_EVENT && miniappWidgetMode.isWidgetOnly(packageName))
|
|
5012
|
+
continue;
|
|
4974
5013
|
let transcriptionRoute;
|
|
4975
5014
|
if (normalizedStream.startsWith("transcription:") && transcriptionSource) {
|
|
4976
5015
|
const routes = this.appTranscriptionRoutesForEvent(packageName, normalizedStream);
|
|
@@ -5413,6 +5452,298 @@ class LocalMiniappRuntime {
|
|
|
5413
5452
|
// Best-effort: a failed stop leaves the app running, which is safe.
|
|
5414
5453
|
}
|
|
5415
5454
|
}
|
|
5455
|
+
// ---------------------------------------------------------------------------
|
|
5456
|
+
// Dashboard widgets (session.widgets): a provider serves widgets it declares,
|
|
5457
|
+
// a host with HOST_WIDGETS places them. The provider runs in widget mode
|
|
5458
|
+
// while anything of it is placed and stops once nothing is.
|
|
5459
|
+
// ---------------------------------------------------------------------------
|
|
5460
|
+
widgetPlacements = emptyWidgetPlacements();
|
|
5461
|
+
/** Outstanding widget event calls, keyed by host-generated callId. */
|
|
5462
|
+
widgetCalls = new Map();
|
|
5463
|
+
widgetCallSeq = 0;
|
|
5464
|
+
/** Elements per widget frame; a card is small, and the link is slow. */
|
|
5465
|
+
static WIDGET_FRAME_ELEMENT_CAP = 64;
|
|
5466
|
+
requireWidgetHost(packageName, requestId, op) {
|
|
5467
|
+
return this.requirePermission(packageName, requestId, "HOST_WIDGETS", `to ${op} widgets`);
|
|
5468
|
+
}
|
|
5469
|
+
declaredWidget(app, widgetId) {
|
|
5470
|
+
return (app.widgets ?? []).find((widget) => widget.id === widgetId);
|
|
5471
|
+
}
|
|
5472
|
+
/** The target app and its declared widget, or a reply to the host saying why not. */
|
|
5473
|
+
widgetTarget(hostPackageName, payload, requestId) {
|
|
5474
|
+
const target = payload.packageName;
|
|
5475
|
+
const widgetId = payload.widgetId;
|
|
5476
|
+
const app = target ? this.interopApps().find((a) => a.packageName === target) : undefined;
|
|
5477
|
+
if (!target || !app) {
|
|
5478
|
+
this.sendResult(hostPackageName, requestId, false, undefined, {
|
|
5479
|
+
code: MiniappErrorCode.APP_NOT_FOUND,
|
|
5480
|
+
message: `Miniapp not found: ${target ?? "(missing packageName)"}`,
|
|
5481
|
+
});
|
|
5482
|
+
return null;
|
|
5483
|
+
}
|
|
5484
|
+
if (!widgetId || !this.declaredWidget(app, widgetId)) {
|
|
5485
|
+
this.sendResult(hostPackageName, requestId, false, undefined, {
|
|
5486
|
+
code: MiniappErrorCode.WIDGET_NOT_FOUND,
|
|
5487
|
+
message: `${target} does not declare widget "${widgetId ?? "(missing widgetId)"}"`,
|
|
5488
|
+
});
|
|
5489
|
+
return null;
|
|
5490
|
+
}
|
|
5491
|
+
return { app, widgetId };
|
|
5492
|
+
}
|
|
5493
|
+
handleWidgetsList(hostPackageName, requestId) {
|
|
5494
|
+
if (!this.requireWidgetHost(hostPackageName, requestId, "list"))
|
|
5495
|
+
return;
|
|
5496
|
+
const rows = [];
|
|
5497
|
+
for (const app of this.interopApps()) {
|
|
5498
|
+
if (!app.packageName || app.packageName === hostPackageName || app.packageName.includes("@empty"))
|
|
5499
|
+
continue;
|
|
5500
|
+
for (const widget of app.widgets ?? []) {
|
|
5501
|
+
rows.push({
|
|
5502
|
+
packageName: app.packageName,
|
|
5503
|
+
appName: app.name,
|
|
5504
|
+
id: widget.id,
|
|
5505
|
+
name: widget.name,
|
|
5506
|
+
sizes: widget.sizes,
|
|
5507
|
+
defaultSize: widget.defaultSize,
|
|
5508
|
+
running: app.running,
|
|
5509
|
+
compatible: app.compatibility?.isCompatible !== false,
|
|
5510
|
+
});
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5513
|
+
this.sendResult(hostPackageName, requestId, true, rows);
|
|
5514
|
+
}
|
|
5515
|
+
parseWidgetBox(raw) {
|
|
5516
|
+
const box = raw;
|
|
5517
|
+
if (!box || typeof box !== "object")
|
|
5518
|
+
return null;
|
|
5519
|
+
if (box.size !== "XS" && box.size !== "SM" && box.size !== "MD" && box.size !== "LG")
|
|
5520
|
+
return null;
|
|
5521
|
+
const width = Number(box.width);
|
|
5522
|
+
const height = Number(box.height);
|
|
5523
|
+
if (!(width > 0) || !(height > 0))
|
|
5524
|
+
return null;
|
|
5525
|
+
return { size: box.size, width: Math.round(width), height: Math.round(height) };
|
|
5526
|
+
}
|
|
5527
|
+
sendWidgetCall(providerPackageName, widgetId, call) {
|
|
5528
|
+
const callId = `wdg-${Date.now().toString(36)}-${this.widgetCallSeq++}`;
|
|
5529
|
+
this.sendToMiniapp(providerPackageName, {
|
|
5530
|
+
type: MiniappResponseType.WIDGET_CALL,
|
|
5531
|
+
callId,
|
|
5532
|
+
widgetId,
|
|
5533
|
+
kind: call.kind,
|
|
5534
|
+
...(call.kind === "box" ? { box: call.box } : { event: call.event }),
|
|
5535
|
+
});
|
|
5536
|
+
return callId;
|
|
5537
|
+
}
|
|
5538
|
+
/**
|
|
5539
|
+
* session.widgets.place: record the host's box, wake the provider in widget
|
|
5540
|
+
* mode when it is not running, hand it the box, and answer with the last
|
|
5541
|
+
* frame the provider pushed for the widget.
|
|
5542
|
+
*/
|
|
5543
|
+
async handleWidgetPlace(hostPackageName, payload, requestId) {
|
|
5544
|
+
if (!this.requireWidgetHost(hostPackageName, requestId, "place"))
|
|
5545
|
+
return;
|
|
5546
|
+
const target = this.widgetTarget(hostPackageName, payload, requestId);
|
|
5547
|
+
if (!target)
|
|
5548
|
+
return;
|
|
5549
|
+
const { app, widgetId } = target;
|
|
5550
|
+
const box = this.parseWidgetBox(payload.box);
|
|
5551
|
+
if (!box) {
|
|
5552
|
+
this.sendResult(hostPackageName, requestId, false, undefined, {
|
|
5553
|
+
code: MiniappErrorCode.INVALID_ARGUMENT,
|
|
5554
|
+
message: "place needs a box {size, width, height}",
|
|
5555
|
+
});
|
|
5556
|
+
return;
|
|
5557
|
+
}
|
|
5558
|
+
if (app.compatibility && app.compatibility.isCompatible === false) {
|
|
5559
|
+
this.sendResult(hostPackageName, requestId, false, undefined, {
|
|
5560
|
+
code: MiniappErrorCode.APP_NOT_COMPATIBLE,
|
|
5561
|
+
message: `${app.packageName} is not compatible with the connected glasses`,
|
|
5562
|
+
});
|
|
5563
|
+
return;
|
|
5564
|
+
}
|
|
5565
|
+
const provider = app.packageName;
|
|
5566
|
+
// Record the placement before the wake so the spawn lands in widget mode
|
|
5567
|
+
// and an unplace racing it cannot stop the provider early.
|
|
5568
|
+
this.widgetPlacements = placeWidget(this.widgetPlacements, { hostPackageName, providerPackageName: provider, widgetId, box });
|
|
5569
|
+
miniappWidgetMode.setPlaced(provider, true);
|
|
5570
|
+
if (!miniappRunningRegistry.has(provider))
|
|
5571
|
+
miniappWidgetMode.setWidgetOnly(provider, true);
|
|
5572
|
+
try {
|
|
5573
|
+
await this.wakeInteropApp(provider);
|
|
5574
|
+
}
|
|
5575
|
+
catch (e) {
|
|
5576
|
+
this.widgetPlacements = unplaceWidget(this.widgetPlacements, hostPackageName, provider, widgetId);
|
|
5577
|
+
void this.settleWidgetProvider(provider);
|
|
5578
|
+
this.sendResult(hostPackageName, requestId, false, undefined, {
|
|
5579
|
+
code: MiniappErrorCode.WAKE_FAILED,
|
|
5580
|
+
message: e?.message ?? `failed to wake ${provider}`,
|
|
5581
|
+
});
|
|
5582
|
+
return;
|
|
5583
|
+
}
|
|
5584
|
+
// The host may have unplaced it during the wake.
|
|
5585
|
+
if (placementOf(this.widgetPlacements, hostPackageName, provider, widgetId) && this.connectedApps.has(provider)) {
|
|
5586
|
+
this.sendWidgetCall(provider, widgetId, { kind: "box", box });
|
|
5587
|
+
}
|
|
5588
|
+
this.sendResult(hostPackageName, requestId, true, { frame: widgetFrameOf(this.widgetPlacements, provider, widgetId) });
|
|
5589
|
+
}
|
|
5590
|
+
async handleWidgetUnplace(hostPackageName, payload, requestId) {
|
|
5591
|
+
if (!this.requireWidgetHost(hostPackageName, requestId, "unplace"))
|
|
5592
|
+
return;
|
|
5593
|
+
const target = this.widgetTarget(hostPackageName, payload, requestId);
|
|
5594
|
+
if (!target)
|
|
5595
|
+
return;
|
|
5596
|
+
const provider = target.app.packageName;
|
|
5597
|
+
const had = placementOf(this.widgetPlacements, hostPackageName, provider, target.widgetId);
|
|
5598
|
+
this.widgetPlacements = unplaceWidget(this.widgetPlacements, hostPackageName, provider, target.widgetId);
|
|
5599
|
+
this.sendResult(hostPackageName, requestId, true);
|
|
5600
|
+
if (!had)
|
|
5601
|
+
return;
|
|
5602
|
+
if (this.connectedApps.has(provider) && widgetHostsOf(this.widgetPlacements, provider, target.widgetId).length === 0) {
|
|
5603
|
+
this.sendWidgetCall(provider, target.widgetId, { kind: "box", box: null });
|
|
5604
|
+
}
|
|
5605
|
+
await this.settleWidgetProvider(provider);
|
|
5606
|
+
}
|
|
5607
|
+
/** A provider with nothing placed and no wearer-opened run of its own stops. */
|
|
5608
|
+
async settleWidgetProvider(providerPackageName) {
|
|
5609
|
+
if (isProviderPlaced(this.widgetPlacements, providerPackageName))
|
|
5610
|
+
return;
|
|
5611
|
+
miniappWidgetMode.setPlaced(providerPackageName, false);
|
|
5612
|
+
if (!miniappWidgetMode.isWidgetOnly(providerPackageName))
|
|
5613
|
+
return;
|
|
5614
|
+
// Widget mode stays set until the context is gone, so the tray never
|
|
5615
|
+
// sees the stopping app as a normal one.
|
|
5616
|
+
try {
|
|
5617
|
+
await this.stopInteropApp(providerPackageName);
|
|
5618
|
+
}
|
|
5619
|
+
catch (err) {
|
|
5620
|
+
console.warn(`${LOG_TAG}: stopping widget provider ${providerPackageName} failed`, err);
|
|
5621
|
+
}
|
|
5622
|
+
}
|
|
5623
|
+
/** session.widgets.send: forward input to a placed widget and relay the provider's answer. */
|
|
5624
|
+
async handleWidgetSend(hostPackageName, payload, requestId) {
|
|
5625
|
+
if (!this.requireWidgetHost(hostPackageName, requestId, "send"))
|
|
5626
|
+
return;
|
|
5627
|
+
const target = this.widgetTarget(hostPackageName, payload, requestId);
|
|
5628
|
+
if (!target)
|
|
5629
|
+
return;
|
|
5630
|
+
const provider = target.app.packageName;
|
|
5631
|
+
const event = payload.event;
|
|
5632
|
+
if (!event || typeof event !== "object" || typeof event.type !== "string") {
|
|
5633
|
+
this.sendResult(hostPackageName, requestId, false, undefined, {
|
|
5634
|
+
code: MiniappErrorCode.INVALID_ARGUMENT,
|
|
5635
|
+
message: "send needs an event with a string type",
|
|
5636
|
+
});
|
|
5637
|
+
return;
|
|
5638
|
+
}
|
|
5639
|
+
if (!placementOf(this.widgetPlacements, hostPackageName, provider, target.widgetId)) {
|
|
5640
|
+
this.sendResult(hostPackageName, requestId, false, undefined, {
|
|
5641
|
+
code: MiniappErrorCode.WIDGET_NOT_PLACED,
|
|
5642
|
+
message: `${hostPackageName} has not placed ${provider}/${target.widgetId}`,
|
|
5643
|
+
});
|
|
5644
|
+
return;
|
|
5645
|
+
}
|
|
5646
|
+
// Floored at 6 s for the same reason as actions: a provider just woken
|
|
5647
|
+
// buffers the call for 5 s while it registers.
|
|
5648
|
+
const timeoutMs = Math.min(Math.max(Number(payload.timeoutMs) || 5_000, 6_000), 30_000);
|
|
5649
|
+
try {
|
|
5650
|
+
await this.wakeInteropApp(provider);
|
|
5651
|
+
}
|
|
5652
|
+
catch (e) {
|
|
5653
|
+
this.sendResult(hostPackageName, requestId, false, undefined, {
|
|
5654
|
+
code: MiniappErrorCode.WAKE_FAILED,
|
|
5655
|
+
message: e?.message ?? `failed to wake ${provider}`,
|
|
5656
|
+
});
|
|
5657
|
+
return;
|
|
5658
|
+
}
|
|
5659
|
+
if (!this.connectedApps.has(provider)) {
|
|
5660
|
+
this.sendResult(hostPackageName, requestId, false, undefined, {
|
|
5661
|
+
code: MiniappErrorCode.WAKE_FAILED,
|
|
5662
|
+
message: `${provider} disconnected before the event could be delivered`,
|
|
5663
|
+
});
|
|
5664
|
+
return;
|
|
5665
|
+
}
|
|
5666
|
+
const callId = this.sendWidgetCall(provider, target.widgetId, { kind: "event", event });
|
|
5667
|
+
const timer = BgTimer.setTimeout(() => {
|
|
5668
|
+
this.widgetCalls.delete(callId);
|
|
5669
|
+
this.sendResult(hostPackageName, requestId, false, undefined, {
|
|
5670
|
+
code: MiniappErrorCode.ACTION_TIMEOUT,
|
|
5671
|
+
message: `widget event "${event.type}" timed out after ${timeoutMs}ms`,
|
|
5672
|
+
});
|
|
5673
|
+
}, timeoutMs);
|
|
5674
|
+
this.widgetCalls.set(callId, { hostPackageName, providerPackageName: provider, hostRequestId: requestId, timer });
|
|
5675
|
+
}
|
|
5676
|
+
/** Provider → host: a WIDGET_RESULT for a pending send; box acknowledgements have no pending call and are dropped. */
|
|
5677
|
+
handleWidgetResult(providerPackageName, payload) {
|
|
5678
|
+
const callId = payload.callId;
|
|
5679
|
+
if (!callId)
|
|
5680
|
+
return;
|
|
5681
|
+
const pending = this.widgetCalls.get(callId);
|
|
5682
|
+
if (!pending || pending.providerPackageName !== providerPackageName)
|
|
5683
|
+
return;
|
|
5684
|
+
this.widgetCalls.delete(callId);
|
|
5685
|
+
BgTimer.clearTimeout(pending.timer);
|
|
5686
|
+
if (payload.ok === true) {
|
|
5687
|
+
const result = payload.result;
|
|
5688
|
+
if (this.actionPayloadTooLarge(result)) {
|
|
5689
|
+
this.sendResult(pending.hostPackageName, pending.hostRequestId, false, undefined, {
|
|
5690
|
+
code: MiniappErrorCode.PAYLOAD_TOO_LARGE,
|
|
5691
|
+
message: "widget event result exceeded the 256 KB cap",
|
|
5692
|
+
});
|
|
5693
|
+
return;
|
|
5694
|
+
}
|
|
5695
|
+
this.sendResult(pending.hostPackageName, pending.hostRequestId, true, result ?? null);
|
|
5696
|
+
}
|
|
5697
|
+
else {
|
|
5698
|
+
const error = payload.error ?? {
|
|
5699
|
+
code: MiniappErrorCode.INTERNAL,
|
|
5700
|
+
message: "widget event handler error",
|
|
5701
|
+
};
|
|
5702
|
+
this.sendResult(pending.hostPackageName, pending.hostRequestId, false, undefined, error);
|
|
5703
|
+
}
|
|
5704
|
+
}
|
|
5705
|
+
/** Provider → host, one-shot: cache the frame and push it to every host holding the widget. */
|
|
5706
|
+
handleWidgetUpdate(providerPackageName, payload) {
|
|
5707
|
+
const widgetId = payload.widgetId;
|
|
5708
|
+
const elements = payload.elements;
|
|
5709
|
+
if (!widgetId || !Array.isArray(elements))
|
|
5710
|
+
return;
|
|
5711
|
+
const app = this.interopApps().find((a) => a.packageName === providerPackageName);
|
|
5712
|
+
if (!app || !this.declaredWidget(app, widgetId))
|
|
5713
|
+
return;
|
|
5714
|
+
if (elements.length > LocalMiniappRuntime.WIDGET_FRAME_ELEMENT_CAP || this.actionPayloadTooLarge(elements)) {
|
|
5715
|
+
console.warn(`${LOG_TAG}: dropping oversized widget frame from ${providerPackageName}/${widgetId}`);
|
|
5716
|
+
return;
|
|
5717
|
+
}
|
|
5718
|
+
this.widgetPlacements = setWidgetFrame(this.widgetPlacements, providerPackageName, widgetId, elements);
|
|
5719
|
+
for (const host of widgetHostsOf(this.widgetPlacements, providerPackageName, widgetId)) {
|
|
5720
|
+
this.sendToMiniapp(host, { type: MiniappResponseType.WIDGET_FRAME, packageName: providerPackageName, widgetId, elements });
|
|
5721
|
+
}
|
|
5722
|
+
}
|
|
5723
|
+
/** A miniapp disconnected: as a host its placements go, as a provider its boxes are forgotten and its frames kept. */
|
|
5724
|
+
onWidgetPartyGone(packageName) {
|
|
5725
|
+
const providersBefore = placedProviders(this.widgetPlacements);
|
|
5726
|
+
this.widgetPlacements = dropWidgetHost(this.widgetPlacements, packageName);
|
|
5727
|
+
this.widgetPlacements = dropWidgetProvider(this.widgetPlacements, packageName);
|
|
5728
|
+
miniappWidgetMode.setPlaced(packageName, false);
|
|
5729
|
+
miniappWidgetMode.setWidgetOnly(packageName, false);
|
|
5730
|
+
for (const [callId, pending] of Array.from(this.widgetCalls)) {
|
|
5731
|
+
if (pending.hostPackageName !== packageName && pending.providerPackageName !== packageName)
|
|
5732
|
+
continue;
|
|
5733
|
+
this.widgetCalls.delete(callId);
|
|
5734
|
+
BgTimer.clearTimeout(pending.timer);
|
|
5735
|
+
if (pending.providerPackageName === packageName) {
|
|
5736
|
+
this.sendResult(pending.hostPackageName, pending.hostRequestId, false, undefined, {
|
|
5737
|
+
code: MiniappErrorCode.WAKE_FAILED,
|
|
5738
|
+
message: `${packageName} stopped before answering`,
|
|
5739
|
+
});
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5742
|
+
for (const provider of providersBefore) {
|
|
5743
|
+
if (provider !== packageName)
|
|
5744
|
+
void this.settleWidgetProvider(provider);
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5416
5747
|
/** True iff `value` serializes larger than the action payload cap. */
|
|
5417
5748
|
actionPayloadTooLarge(value) {
|
|
5418
5749
|
try {
|