@kehto/runtime 0.24.0 → 0.25.0
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 +33 -18
- package/dist/index.d.ts +43 -19
- package/dist/index.js +84 -14
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -84,21 +84,17 @@ phase records remain history rather than active compatibility guidance.
|
|
|
84
84
|
|
|
85
85
|
## NAP-INTENT runtime boundary
|
|
86
86
|
|
|
87
|
-
Kehto
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
readied the verified target and dispatched its convention. The final result
|
|
99
|
-
includes `handled`, `handler`, `windowId`, and `convention`. The target receives
|
|
100
|
-
one runtime-attested `inc.event` carrying that queryless convention and opaque
|
|
101
|
-
payload; `intent.deliver` is not part of the merged contract.
|
|
87
|
+
Kehto follows [NAP-INTENT PR #106](https://github.com/napplet/naps/blob/fc121fc264615482143eda86125863d2e1f741a2/naps/NAP-INTENT.md) at `fc121fc264615482143eda86125863d2e1f741a2`. The runtime admits source-side `intent.invoke`,
|
|
88
|
+
`intent.available`, and `intent.handlers` through the authenticated session and
|
|
89
|
+
ACL/firewall boundary. Services validate the normalized queryless convention and
|
|
90
|
+
resolve the sender's opaque catalog ID from the trusted source window.
|
|
91
|
+
|
|
92
|
+
Registered services receive a frozen `ServiceRuntimeContext` with live-window
|
|
93
|
+
facts and recipient-policy-aware sends. Success acknowledges retained delivery
|
|
94
|
+
responsibility with `ok`, `archetype`, `action`, `convention`, and `handler`.
|
|
95
|
+
After current target readiness, the controller sends one `intent.deliver` with
|
|
96
|
+
parent-attested sender and opaque payload. Completion belongs to host observation,
|
|
97
|
+
not another source result. No INC carrier is required.
|
|
102
98
|
|
|
103
99
|
## NAP-OUTBOX denial boundary
|
|
104
100
|
|
|
@@ -109,8 +105,9 @@ the protocol-defined correlated result wire: `outbox.query.result` with
|
|
|
109
105
|
`events: []` and `error`. The runtime does not synthesize an
|
|
110
106
|
`outbox.query.error` message.
|
|
111
107
|
|
|
112
|
-
The
|
|
113
|
-
`@
|
|
108
|
+
The installed `@napplet/nap@0.32.0` contract trails PR #106. The bounded local
|
|
109
|
+
`@kehto/services` intent types and their removal condition are documented in
|
|
110
|
+
[conformance policy](https://github.com/kehto/web/blob/main/docs/policies/NIP-5D-CONFORMANCE.md).
|
|
114
111
|
|
|
115
112
|
Phase 105 completed released package adoption and persistent installed-manifest
|
|
116
113
|
controllers for the live Paja and playground hosts.
|
|
@@ -226,10 +223,24 @@ implementations of `FirewallStateContainer` must provide this method.
|
|
|
226
223
|
- `cleanupNappState` — remove persisted state when a napplet window closes
|
|
227
224
|
|
|
228
225
|
### Service dispatch
|
|
229
|
-
- `routeServiceMessage` —
|
|
226
|
+
- `routeServiceMessage` — policy-free domain selection primitive; never a fallback for untrusted ingress
|
|
230
227
|
- `notifyServiceWindowDestroyed` — lifecycle fan-out to every service handler
|
|
231
228
|
- Service-only NAP domains, including `dm` and `fs`, route through handlers registered with `runtime.registerService('<domain>', handler)`.
|
|
232
229
|
|
|
230
|
+
For domains outside the built-in dispatcher, handlers opt in with an exact
|
|
231
|
+
`messageCapabilities` map (existing ACL capability or explicit `null` per inbound
|
|
232
|
+
type). `Runtime.handleMessage` requires a live unblocked source, an explicit
|
|
233
|
+
`isDomainAllowed` grant, the declared ACL grant, and firewall admission. Unknown
|
|
234
|
+
types and extension policy denials stay silent on the wire. Registration alone
|
|
235
|
+
attaches a service; it does not expose or grant a domain. Fixed NAP routes retain
|
|
236
|
+
their own authorization. Pending replies are invalidated by service/source
|
|
237
|
+
replacement or teardown and recheck current domain/ACL policy.
|
|
238
|
+
|
|
239
|
+
Browser hosts must provide the owning extension contract and inject its granted
|
|
240
|
+
namespace binding before scripts run. Stock Paja/playground bindings do not inject
|
|
241
|
+
arbitrary extensions. See [Register a service](../../docs/how-tos/register-service.md#domains-outside-the-built-in-dispatcher)
|
|
242
|
+
for the complete host contract and spec bounds.
|
|
243
|
+
|
|
233
244
|
### Types
|
|
234
245
|
40+ interfaces — including `Runtime`, `RuntimeAdapter`, `SendToNapplet`, `RelayPoolAdapter`, `ServiceHandler`, `ServiceRegistry`, `NappletMessage`, `SessionEntry`, `AclEntryExternal`, `AclCheckEvent`, and the per-adapter hook types — are exported from `./types.js` for host-app integration.
|
|
235
246
|
|
|
@@ -247,3 +258,7 @@ Generated API module: `docs/api/modules/_kehto_runtime.html` (run `pnpm docs:api
|
|
|
247
258
|
## License
|
|
248
259
|
|
|
249
260
|
MIT
|
|
261
|
+
|
|
262
|
+
## GAMEPAD integration
|
|
263
|
+
|
|
264
|
+
This non-normative guidance defers to the living [NAP-GAMEPAD](https://github.com/napplet/naps/pull/108). Services export `createGamepadService` and `browserGamepadOptions`; shell exports `denyNappletControllerAccess`. Paja and the playground register one host reader and deny native gamepad/HID/USB paths before frame execution. Source-bound subscriptions receive immediate full snapshots; only the visible host's input owner gets live controls. Focus loss supplies neutral controls and timestamp zero; unsubscribe, window destruction, and service removal release polling. Non-browser or denied native readers report the proposal's unavailable reasons. See [Controller input](https://github.com/kehto/web/blob/main/docs/gamepad.md).
|
package/dist/index.d.ts
CHANGED
|
@@ -566,9 +566,9 @@ interface ServiceRuntimeContext {
|
|
|
566
566
|
/**
|
|
567
567
|
* Send a runtime-originated message after current recipient policy checks.
|
|
568
568
|
*
|
|
569
|
-
* The
|
|
570
|
-
*
|
|
571
|
-
*
|
|
569
|
+
* The target must be a live source-bound session whose immutable environment
|
|
570
|
+
* exposes the domain. Mapped recipient capabilities also require their current
|
|
571
|
+
* ACL grant. GAMEPAD state has no invented recipient capability mapping.
|
|
572
572
|
*
|
|
573
573
|
* @param windowId - Intended live recipient window.
|
|
574
574
|
* @param message - Runtime-originated NIP-5D envelope.
|
|
@@ -583,12 +583,12 @@ interface ServiceRuntimeContext {
|
|
|
583
583
|
*
|
|
584
584
|
* @example
|
|
585
585
|
* ```ts
|
|
586
|
-
* const
|
|
587
|
-
* descriptor: { name: '
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
* send({ type: '
|
|
586
|
+
* const exampleHandler: ServiceHandler = {
|
|
587
|
+
* descriptor: { name: 'example', version: '1.0.0' },
|
|
588
|
+
* messageCapabilities: { 'example.ping': null },
|
|
589
|
+
* handleMessage(_windowId, message, send) {
|
|
590
|
+
* if (message.type === 'example.ping') {
|
|
591
|
+
* send({ type: 'example.pong' });
|
|
592
592
|
* }
|
|
593
593
|
* },
|
|
594
594
|
* };
|
|
@@ -597,6 +597,19 @@ interface ServiceRuntimeContext {
|
|
|
597
597
|
interface ServiceHandler {
|
|
598
598
|
/** Metadata describing this service. */
|
|
599
599
|
descriptor: ServiceDescriptor;
|
|
600
|
+
/**
|
|
601
|
+
* Explicit inbound types for a domain outside the built-in dispatcher.
|
|
602
|
+
* Each own key is an exact `domain.action` type; its value is an existing
|
|
603
|
+
* ACL capability or explicit `null` for no additional capability requirement.
|
|
604
|
+
* Omission keeps non-built-in services attachment-only. Built-in NAP routes
|
|
605
|
+
* ignore this map and retain their canonical capability mapping.
|
|
606
|
+
*
|
|
607
|
+
* Extension dispatch also requires a live, unblocked source, an explicit
|
|
608
|
+
* `RuntimeAdapter.isDomainAllowed` grant, and firewall admission. This map
|
|
609
|
+
* neither injects a namespace binding nor grants any capability. The handler
|
|
610
|
+
* owns payload validation and its extension's response contract.
|
|
611
|
+
*/
|
|
612
|
+
messageCapabilities?: Readonly<Record<string, Capability | null>>;
|
|
600
613
|
/**
|
|
601
614
|
* Attach this handler to one runtime instance.
|
|
602
615
|
*
|
|
@@ -623,14 +636,14 @@ interface ServiceHandler {
|
|
|
623
636
|
}
|
|
624
637
|
/**
|
|
625
638
|
* Registry of services available to napplets.
|
|
626
|
-
*
|
|
627
|
-
*
|
|
639
|
+
* Keys select exact wire domains for direct routing (e.g., 'notify').
|
|
640
|
+
* Metadata/attachment alone does not expose a domain. Custom domains require
|
|
641
|
+
* ServiceHandler.messageCapabilities and explicit host domain authorization.
|
|
628
642
|
*
|
|
629
643
|
* @example
|
|
630
644
|
* ```ts
|
|
631
645
|
* const services: ServiceRegistry = {
|
|
632
|
-
*
|
|
633
|
-
* notifications: notificationHandler,
|
|
646
|
+
* notify: notificationHandler,
|
|
634
647
|
* };
|
|
635
648
|
* ```
|
|
636
649
|
*/
|
|
@@ -692,8 +705,9 @@ interface RuntimeAdapter {
|
|
|
692
705
|
sendToNapplet: SendToNapplet;
|
|
693
706
|
/**
|
|
694
707
|
* Determines whether a session-established napplet may invoke a NAP domain.
|
|
695
|
-
*
|
|
696
|
-
*
|
|
708
|
+
* Built-in routes remain eligible when omitted; extension routes require
|
|
709
|
+
* an explicit true result. Browser shells must derive this decision from
|
|
710
|
+
* the source-bound namespace exposed before napplet scripts execute.
|
|
697
711
|
*/
|
|
698
712
|
isDomainAllowed?: (windowId: string, domain: string) => boolean;
|
|
699
713
|
/**
|
|
@@ -792,14 +806,17 @@ interface RuntimeAdapter {
|
|
|
792
806
|
/**
|
|
793
807
|
* Optional service extensions. Shell/host registers service handlers here
|
|
794
808
|
* for static initialization. Services can also be added dynamically via
|
|
795
|
-
* runtime.registerService().
|
|
809
|
+
* runtime.registerService(). Keys select exact domains (e.g., 'notify').
|
|
810
|
+
* Outside built-in domains, handlers also need messageCapabilities and an
|
|
811
|
+
* explicit isDomainAllowed grant. Hosts own each extension's NAP contract,
|
|
812
|
+
* payload validation, and pre-execution browser namespace binding.
|
|
796
813
|
*
|
|
797
814
|
* @example
|
|
798
815
|
* ```ts
|
|
799
816
|
* const hooks: RuntimeAdapter = {
|
|
800
817
|
* // ... required adapters ...
|
|
801
818
|
* services: {
|
|
802
|
-
*
|
|
819
|
+
* notify: myNotificationServiceHandler,
|
|
803
820
|
* },
|
|
804
821
|
* };
|
|
805
822
|
* ```
|
|
@@ -1456,7 +1473,11 @@ interface Runtime {
|
|
|
1456
1473
|
* Register a service handler dynamically after runtime creation.
|
|
1457
1474
|
* If a handler is already registered for this name, it is replaced.
|
|
1458
1475
|
*
|
|
1459
|
-
*
|
|
1476
|
+
* Built-in domains keep their NAP routing. Other domains require the
|
|
1477
|
+
* handler's exact messageCapabilities map and an explicit host domain grant.
|
|
1478
|
+
* Registration never injects namespace bindings or grants capabilities.
|
|
1479
|
+
*
|
|
1480
|
+
* @param name - Exact domain name (e.g., 'notify' or a host extension)
|
|
1460
1481
|
* @param handler - The service handler implementation
|
|
1461
1482
|
*/
|
|
1462
1483
|
registerService(name: string, handler: ServiceHandler): void;
|
|
@@ -1580,9 +1601,12 @@ declare function cleanupNappState(pubkey: string, dTag: string, aggregateHash: s
|
|
|
1580
1601
|
* Route a NappletMessage envelope to the matching service handler.
|
|
1581
1602
|
*
|
|
1582
1603
|
* Direct NAP-domain services are routed by the domain in their wire
|
|
1583
|
-
* message.type (e.g., '
|
|
1604
|
+
* message.type (e.g., 'notify.send' -> 'notify' service). INC messages
|
|
1584
1605
|
* are owned by the INC runtime; their topic values are opaque here.
|
|
1585
1606
|
*
|
|
1607
|
+
* This low-level selection primitive performs no source, domain, ACL,
|
|
1608
|
+
* messageCapabilities, or firewall checks. Never call it as a fallback for
|
|
1609
|
+
* untrusted input; host ingress must use Runtime.handleMessage.
|
|
1586
1610
|
* Returns true if a service handled the message, false otherwise.
|
|
1587
1611
|
*
|
|
1588
1612
|
* @param windowId - The napplet window that sent the message
|
package/dist/index.js
CHANGED
|
@@ -565,11 +565,52 @@ function relayEventResultFromCarrier(carrier) {
|
|
|
565
565
|
// src/runtime.ts
|
|
566
566
|
import { createDispatch } from "@napplet/core";
|
|
567
567
|
|
|
568
|
+
// src/service-extensions.ts
|
|
569
|
+
import { ALL_CAPABILITIES } from "@kehto/acl/capabilities";
|
|
570
|
+
function createServiceExtensionRouter(context) {
|
|
571
|
+
const { hooks, services, registrations, sessions, acl, enforce, firewall } = context;
|
|
572
|
+
const windowLifetimes = /* @__PURE__ */ new Map();
|
|
573
|
+
function handleMessage(windowId, message) {
|
|
574
|
+
const dot = message.type.indexOf(".");
|
|
575
|
+
const domain = message.type.slice(0, dot);
|
|
576
|
+
if (dot <= 0 || dot === message.type.length - 1 || !/^[a-z][a-z0-9-]*$/.test(domain) || !Object.hasOwn(services, domain)) return;
|
|
577
|
+
const handler = services[domain];
|
|
578
|
+
const policies = handler.messageCapabilities;
|
|
579
|
+
if (!policies || !Object.hasOwn(policies, message.type)) return;
|
|
580
|
+
const capability = policies[message.type];
|
|
581
|
+
if (capability !== null && !ALL_CAPABILITIES.includes(capability)) return;
|
|
582
|
+
const session = sessions.getEntryByWindowId(windowId);
|
|
583
|
+
const registration = registrations.get(domain);
|
|
584
|
+
if (!session || !registration) return;
|
|
585
|
+
const allowed = () => hooks.isDomainAllowed?.(windowId, domain) === true && !acl.isBlocked("", session.dTag, session.aggregateHash) && (capability === null || enforce(windowId, capability, message).allowed);
|
|
586
|
+
if (!allowed() || firewall(windowId, message, capability, false) === "drop") return;
|
|
587
|
+
let lifetime = windowLifetimes.get(windowId);
|
|
588
|
+
if (!lifetime) {
|
|
589
|
+
lifetime = {};
|
|
590
|
+
windowLifetimes.set(windowId, lifetime);
|
|
591
|
+
}
|
|
592
|
+
handler.handleMessage(windowId, message, (response) => {
|
|
593
|
+
if (registrations.get(domain) !== registration || services[domain] !== handler || windowLifetimes.get(windowId) !== lifetime || sessions.getEntryByWindowId(windowId) !== session) return;
|
|
594
|
+
if (!response || typeof response.type !== "string" || !response.type.startsWith(`${domain}.`) || response.type.length <= dot + 1) return;
|
|
595
|
+
if (allowed()) hooks.sendToNapplet(windowId, response);
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
return {
|
|
599
|
+
handleMessage,
|
|
600
|
+
destroyWindow(windowId) {
|
|
601
|
+
windowLifetimes.delete(windowId);
|
|
602
|
+
},
|
|
603
|
+
destroy() {
|
|
604
|
+
windowLifetimes.clear();
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
568
609
|
// src/service-dispatch.ts
|
|
569
610
|
function routeServiceMessage(windowId, message, services, sendToNapplet) {
|
|
570
611
|
if (message.type === "inc.emit") return false;
|
|
571
612
|
const domain = message.type.split(".")[0];
|
|
572
|
-
const handler = services[domain];
|
|
613
|
+
const handler = Object.hasOwn(services, domain) ? services[domain] : void 0;
|
|
573
614
|
if (handler) {
|
|
574
615
|
handler.handleMessage(windowId, message, (msg) => sendToNapplet(windowId, msg));
|
|
575
616
|
return true;
|
|
@@ -1616,6 +1657,7 @@ function cleanupNappState(pubkey, dTag, aggregateHash, statePersistence) {
|
|
|
1616
1657
|
// src/domain-handlers.ts
|
|
1617
1658
|
function createRuntimeDomainHandlers(context) {
|
|
1618
1659
|
return {
|
|
1660
|
+
gamepad: (windowId, msg) => handleServiceOnlyMessage(context, "gamepad", windowId, msg),
|
|
1619
1661
|
storage: (windowId, msg) => handleStorageMessage(context, windowId, msg),
|
|
1620
1662
|
media: (windowId, msg) => handleMediaMessage(context, windowId, msg),
|
|
1621
1663
|
keys: (windowId, msg) => handleKeysMessage(context, windowId, msg),
|
|
@@ -1766,6 +1808,7 @@ function createNapEnvelopeDispatcher(handlers) {
|
|
|
1766
1808
|
const adapt = (handler) => (msg) => {
|
|
1767
1809
|
if (currentWindowId !== null) handler(currentWindowId, msg);
|
|
1768
1810
|
};
|
|
1811
|
+
napDispatch.registerNap("gamepad", adapt(handlers.gamepad));
|
|
1769
1812
|
napDispatch.registerNap("relay", adapt(handlers.relay));
|
|
1770
1813
|
napDispatch.registerNap("identity", adapt(handlers.identity));
|
|
1771
1814
|
napDispatch.registerNap("count", adapt(handlers.count));
|
|
@@ -1848,13 +1891,18 @@ function buildObservation(envelope, windowId, senderCap, sessionRegistry, getFoc
|
|
|
1848
1891
|
}
|
|
1849
1892
|
function createFirewallGate(config) {
|
|
1850
1893
|
const { firewallState, sessionRegistry, hooks, fireConsent } = config;
|
|
1851
|
-
return function firewallGate(windowId, envelope, senderCap) {
|
|
1894
|
+
return function firewallGate(windowId, envelope, senderCap, respond = true) {
|
|
1852
1895
|
const obs = buildObservation(envelope, windowId, senderCap, sessionRegistry, hooks.getFocusContext);
|
|
1853
1896
|
const result = firewallState.evaluate(obs);
|
|
1854
1897
|
const { decision, action, ruleId, reason } = result;
|
|
1855
1898
|
const napplet = obs.napplet;
|
|
1856
1899
|
const opClass = obs.opClass;
|
|
1857
1900
|
if (decision === "reject" || decision === "prompt") {
|
|
1901
|
+
if (!respond) {
|
|
1902
|
+
hooks.onFirewallEvent?.({ windowId, napplet, opClass, decision, action, ruleId, reason, message: envelope });
|
|
1903
|
+
if (decision === "prompt") fireConsent(windowId, napplet);
|
|
1904
|
+
return "drop";
|
|
1905
|
+
}
|
|
1858
1906
|
const intentDenial = createIntentPolicyDenial(envelope);
|
|
1859
1907
|
if (intentDenial) {
|
|
1860
1908
|
hooks.sendToNapplet(windowId, intentDenial);
|
|
@@ -1905,14 +1953,18 @@ function createDenialResponse(envelope, error) {
|
|
|
1905
1953
|
const type = denialResponseType(envelope);
|
|
1906
1954
|
return type ? { type, id, error } : null;
|
|
1907
1955
|
}
|
|
1908
|
-
function createMessageHandler(hooks, sessionRegistry, enforceNap, dispatchNapEnvelope, firewallGate) {
|
|
1956
|
+
function createMessageHandler(hooks, sessionRegistry, enforceNap, dispatchNapEnvelope, serviceRouting, firewallGate) {
|
|
1909
1957
|
return (windowId, msg) => {
|
|
1910
|
-
if (typeof msg !== "object" || msg === null || !("type" in msg)) return;
|
|
1958
|
+
if (typeof msg !== "object" || msg === null || Array.isArray(msg) || !("type" in msg) || typeof msg.type !== "string") return;
|
|
1911
1959
|
const envelope = msg;
|
|
1912
1960
|
const dotIdx = envelope.type.indexOf(".");
|
|
1913
1961
|
if (dotIdx === -1) return;
|
|
1914
1962
|
if (!sessionRegistry.getEntryByWindowId(windowId)) return;
|
|
1915
1963
|
const domain = envelope.type.slice(0, dotIdx);
|
|
1964
|
+
if (!serviceRouting.builtInDomains.has(domain)) {
|
|
1965
|
+
serviceRouting.extensions.handleMessage(windowId, envelope);
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1916
1968
|
if (hooks.isDomainAllowed && !hooks.isDomainAllowed(windowId, domain)) return;
|
|
1917
1969
|
if (isIdentityOrThemeMessage(envelope) && !createCanonicalDomainResult(envelope)) return;
|
|
1918
1970
|
const caps = resolveCapabilitiesNap(envelope);
|
|
@@ -1965,7 +2017,8 @@ function createRuntimeInstance(context) {
|
|
|
1965
2017
|
serviceRegistry,
|
|
1966
2018
|
sessionRegistry,
|
|
1967
2019
|
subscriptions,
|
|
1968
|
-
consentHandlerRef
|
|
2020
|
+
consentHandlerRef,
|
|
2021
|
+
extensions
|
|
1969
2022
|
} = context;
|
|
1970
2023
|
const undeclaredServiceConsents = /* @__PURE__ */ new Set();
|
|
1971
2024
|
const attachedServices = /* @__PURE__ */ new Map();
|
|
@@ -1993,8 +2046,8 @@ function createRuntimeInstance(context) {
|
|
|
1993
2046
|
try {
|
|
1994
2047
|
if (isDomainAllowed && !isDomainAllowed(windowId, domain)) return false;
|
|
1995
2048
|
const { recipientCap } = resolveCapabilitiesNap(message);
|
|
1996
|
-
if (!recipientCap) return false;
|
|
1997
|
-
if (!aclState.check("", entry.dTag, entry.aggregateHash, recipientCap)) {
|
|
2049
|
+
if (!recipientCap && message.type !== "gamepad.state") return false;
|
|
2050
|
+
if (recipientCap && !aclState.check("", entry.dTag, entry.aggregateHash, recipientCap)) {
|
|
1998
2051
|
return false;
|
|
1999
2052
|
}
|
|
2000
2053
|
sendToNapplet(windowId, message);
|
|
@@ -2029,6 +2082,8 @@ function createRuntimeInstance(context) {
|
|
|
2029
2082
|
eventBuffer.bufferAndDeliver(createInjectedEvent(hooks, topic, payload), null);
|
|
2030
2083
|
},
|
|
2031
2084
|
destroy() {
|
|
2085
|
+
extensions.destroy();
|
|
2086
|
+
registeredServices.clear();
|
|
2032
2087
|
for (const name of attachedServices.keys()) detachService(name);
|
|
2033
2088
|
manifestCache.persist();
|
|
2034
2089
|
aclState.persist();
|
|
@@ -2037,13 +2092,13 @@ function createRuntimeInstance(context) {
|
|
|
2037
2092
|
subscriptions.clear();
|
|
2038
2093
|
incRuntime.clear();
|
|
2039
2094
|
eventBuffer.clear();
|
|
2040
|
-
registeredServices.clear();
|
|
2041
2095
|
undeclaredServiceConsents.clear();
|
|
2042
2096
|
},
|
|
2043
2097
|
registerConsentHandler(handler) {
|
|
2044
2098
|
consentHandlerRef.current = handler;
|
|
2045
2099
|
},
|
|
2046
2100
|
registerService(name, handler) {
|
|
2101
|
+
registeredServices.delete(name);
|
|
2047
2102
|
detachService(name);
|
|
2048
2103
|
serviceRegistry[name] = handler;
|
|
2049
2104
|
registeredServices.set(name, {
|
|
@@ -2054,11 +2109,12 @@ function createRuntimeInstance(context) {
|
|
|
2054
2109
|
attachService(name, handler);
|
|
2055
2110
|
},
|
|
2056
2111
|
unregisterService(name) {
|
|
2112
|
+
registeredServices.delete(name);
|
|
2057
2113
|
detachService(name);
|
|
2058
2114
|
delete serviceRegistry[name];
|
|
2059
|
-
registeredServices.delete(name);
|
|
2060
2115
|
},
|
|
2061
2116
|
destroyWindow(windowId) {
|
|
2117
|
+
extensions.destroyWindow(windowId);
|
|
2062
2118
|
for (const [key] of subscriptions) {
|
|
2063
2119
|
if (key.startsWith(`${windowId}:`)) {
|
|
2064
2120
|
subscriptions.delete(key);
|
|
@@ -2087,8 +2143,10 @@ function createRuntime(hooks) {
|
|
|
2087
2143
|
const serviceRegistry = { ...hooks.services };
|
|
2088
2144
|
const registeredServices = createRegisteredServices(serviceRegistry);
|
|
2089
2145
|
const firewallState = createFirewallState(hooks.firewallPersistence);
|
|
2146
|
+
let extensions;
|
|
2090
2147
|
const sessionRegistry = createSessionRegistry(hooks.onPendingUpdate, (windowId) => {
|
|
2091
2148
|
firewallState.resetInitBudget(windowId);
|
|
2149
|
+
extensions?.destroyWindow(windowId);
|
|
2092
2150
|
});
|
|
2093
2151
|
let incRuntime = null;
|
|
2094
2152
|
const aclState = createAclState(hooks.aclPersistence, "permissive", (mutation) => {
|
|
@@ -2151,14 +2209,25 @@ function createRuntime(hooks) {
|
|
|
2151
2209
|
firewallState.load();
|
|
2152
2210
|
manifestCache.load();
|
|
2153
2211
|
const domainHandlers = createRuntimeDomainHandlers({ hooks, serviceRegistry, sessionRegistry, aclState });
|
|
2154
|
-
const
|
|
2212
|
+
const napHandlers = {
|
|
2155
2213
|
relay: createRelayHandler({ hooks, serviceRegistry, subscriptions, eventBuffer, replayDetector }),
|
|
2156
2214
|
identity: createIdentityHandler({ hooks, serviceRegistry }),
|
|
2157
2215
|
count: createCountHandler({ hooks, serviceRegistry }),
|
|
2158
2216
|
inc: incRuntime.handleMessage,
|
|
2159
2217
|
...domainHandlers
|
|
2218
|
+
};
|
|
2219
|
+
const dispatchNapEnvelope = createNapEnvelopeDispatcher(napHandlers);
|
|
2220
|
+
const builtInDomains = /* @__PURE__ */ new Set([...Object.keys(napHandlers), "shell", "signer"]);
|
|
2221
|
+
extensions = createServiceExtensionRouter({
|
|
2222
|
+
hooks,
|
|
2223
|
+
services: serviceRegistry,
|
|
2224
|
+
registrations: registeredServices,
|
|
2225
|
+
sessions: sessionRegistry,
|
|
2226
|
+
acl: aclState,
|
|
2227
|
+
enforce: enforceNap,
|
|
2228
|
+
firewall: firewallGate
|
|
2160
2229
|
});
|
|
2161
|
-
const handleMessage = createMessageHandler(hooks, sessionRegistry, enforceNap, dispatchNapEnvelope, firewallGate);
|
|
2230
|
+
const handleMessage = createMessageHandler(hooks, sessionRegistry, enforceNap, dispatchNapEnvelope, { builtInDomains, extensions }, firewallGate);
|
|
2162
2231
|
return createRuntimeInstance({
|
|
2163
2232
|
hooks,
|
|
2164
2233
|
serviceRegistry,
|
|
@@ -2172,14 +2241,15 @@ function createRuntime(hooks) {
|
|
|
2172
2241
|
firewallState,
|
|
2173
2242
|
manifestCache,
|
|
2174
2243
|
consentHandlerRef,
|
|
2175
|
-
handleMessage
|
|
2244
|
+
handleMessage,
|
|
2245
|
+
extensions
|
|
2176
2246
|
});
|
|
2177
2247
|
}
|
|
2178
2248
|
|
|
2179
2249
|
// src/index.ts
|
|
2180
|
-
import { ALL_CAPABILITIES } from "@kehto/acl/capabilities";
|
|
2250
|
+
import { ALL_CAPABILITIES as ALL_CAPABILITIES2 } from "@kehto/acl/capabilities";
|
|
2181
2251
|
export {
|
|
2182
|
-
ALL_CAPABILITIES,
|
|
2252
|
+
ALL_CAPABILITIES2 as ALL_CAPABILITIES,
|
|
2183
2253
|
RING_BUFFER_SIZE,
|
|
2184
2254
|
cleanupNappState,
|
|
2185
2255
|
createAclState,
|