@kehto/services 0.20.0 → 0.21.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 +3 -3
- package/dist/index.d.ts +55 -52
- package/dist/index.js +61 -42
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Reference service handlers for the napplet protocol — identity, relay pool,
|
|
|
4
4
|
cache, keys, media, notify, theme, link, common, lists, serial, BLE, WebRTC,
|
|
5
5
|
and DM.
|
|
6
6
|
|
|
7
|
-
> **Alpha status:** Kehto is an early runtime
|
|
7
|
+
> **Alpha status:** Kehto is an early runtime toolkit for a draft NIP-5D
|
|
8
8
|
> protocol. NAP contracts and service envelopes are not final; treat these
|
|
9
9
|
> handlers as reference implementations for the current draft.
|
|
10
10
|
|
|
@@ -17,7 +17,7 @@ pnpm add @kehto/services
|
|
|
17
17
|
## Published Napplet Compatibility
|
|
18
18
|
|
|
19
19
|
`@kehto/services` publishes against `@napplet/core` and `@napplet/nap`
|
|
20
|
-
`>=0.
|
|
20
|
+
`>=0.32.0 <0.33.0`. The exact installed contracts are core 0.32.0 / nap 0.32.0
|
|
21
21
|
from NAP-INTENT authority `5ac0490461ca6fec2f0d2e45b4835cf9bc08de24`,
|
|
22
22
|
napplet/web#199 source `3037200c932488f14f7f369b8583c39c9c16510a` / merge
|
|
23
23
|
`b3f0007867eac109fa4917fac9c285d3b7cc6155`, and Version Packages #198 release
|
|
@@ -44,7 +44,7 @@ Current draft posture:
|
|
|
44
44
|
- `createNotifyService` handles direct `notify.*` envelopes. It is not an INC
|
|
45
45
|
topic handler.
|
|
46
46
|
- `createOutboxService` supports `outbox.getEvent` from draft NAP-OUTBOX. Single-event lookups run through shell-owned relay routing and only return events whose ID matches the request. The draft wire contract keeps `outbox.query` one-shot and `outbox.subscribe` streaming; Kehto's concrete `createRelayPoolOutboxRouter` additionally exposes host-side `queryStream()` so verified query events can arrive before asynchronous NIP-65 discovery completes.
|
|
47
|
-
- `createResourceService` implements
|
|
47
|
+
- `createResourceService` implements NAP-RESOURCE at exact draft head `9511232f69313aa7953d110e35d32cc28d506f66` without choosing a runtime's network policy. Single requests carry optional advisory Blossom `servers`; bulk requests use canonical per-resource `requests: [{ url, servers? }]` entries. The service forwards hints to the injected resolver only for `blossom:`, preserves order and per-entry failures, and exposes optional `resource.info.maxServers`. By default it delegates every syntactically valid URL to the resolver; `resource.info.schemes` remains advisory. Runtimes that want hierarchical-origin grants can supply `isOriginGranted`, `getConnectGrants`, and `resolveIdentity` together, while scheme resolvers retain responsibility for hint validation, SSRF protection, redirects, and fallback policy. The service enforces supplied size/bulk caps, scopes cancellation to the requesting window, drops cancelled terminal envelopes, never forwards response headers, and preserves explicit `ResourceServiceError` codes.
|
|
48
48
|
- `createUploadService` supports `upload.info` from draft NAP-UPLOAD. Hosts may expose configured rails, return URL forms, maximum bytes, and MIME type policy without requiring napplets to start an upload.
|
|
49
49
|
- `createConfigService` implements draft NAP-CONFIG's shell-writer boundary: recursive bounded-subset schema validation, per-window schema/subscription state, deterministic defaults, invalid/orphan removal, version rollback rejection, scoped host persistence hooks, and settings-UI commits. Reads before schema registration fail closed with `no-schema`.
|
|
50
50
|
- `createDmService` keeps NAP-DM request correlation, per-window subscriptions, and packaged message shapes in runtime-owned code. Adapters cover concrete transports: verified NIP-17 gift wraps and relay history via `nostr-tools`, structural NDR runtimes with relay hooks, and Cordn/ContextVM coordinator clients.
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { MediaAction } from '@napplet/nap/media/types';
|
|
|
7
7
|
import { NotifyActionMessage, NotifyClickedMessage, NotifyDismissedMessage, NotifySendMessage, NotifyChannelRegisterMessage, NotifyControl } from '@napplet/nap/notify/types';
|
|
8
8
|
import { Theme, ThemeChangedMessage } from '@napplet/nap/theme/types';
|
|
9
9
|
import { ConfigSchemaErrorCode, NappletConfigSchema, ConfigValues } from '@napplet/nap/config/types';
|
|
10
|
+
import { ResourceInfo } from '@napplet/nap/resource/types';
|
|
11
|
+
export { ResourceBytesRequest, ResourceInfo, ResourceSchemeInfo } from '@napplet/nap/resource/types';
|
|
10
12
|
export { a as CvmCloseMessage, b as CvmCloseResultMessage, c as CvmDiscoverMessage, d as CvmDiscoverQuery, e as CvmDiscoverResultMessage, f as CvmEventMessage, g as CvmInboundMessage, h as CvmOutboundMessage, i as CvmRequestMessage, j as CvmRequestOptions, k as CvmRequestResultMessage, l as CvmServer, m as CvmServerRef, n as CvmService, o as CvmServiceOptions, C as CvmTransport, p as CvmTransportError, M as McpContentBlock, q as McpMessage, r as McpTool, s as McpToolResult, t as createCvmService } from './cvm-service-t5syG4tU.js';
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -1372,16 +1374,18 @@ declare function createConfigService(options: ConfigServiceOptions): ConfigServi
|
|
|
1372
1374
|
* resource.bytesMany.result, resource.bytesMany.error
|
|
1373
1375
|
*
|
|
1374
1376
|
* ──────────────────────── SCOPE BOUNDARY (RESOURCE-01) ────────────────────────
|
|
1375
|
-
* NAP-RESOURCE is
|
|
1377
|
+
* NAP-RESOURCE is a runtime-mediated byte resolver — read-only, atomic.
|
|
1376
1378
|
*
|
|
1377
|
-
*
|
|
1378
|
-
* policy
|
|
1379
|
-
*
|
|
1380
|
-
*
|
|
1379
|
+
* Kehto owns neutral wire lifecycle, cancellation, result projection, and
|
|
1380
|
+
* policy tooling. The runtime supplies the resolver and decides whether to use
|
|
1381
|
+
* the origin-grant hooks, which schemes to resolve, and which limits to expose.
|
|
1382
|
+
* Advisory `resource.info` scheme disclosure never authorizes or blocks a byte
|
|
1383
|
+
* request.
|
|
1381
1384
|
* ──────────────────────────────────────────────────────────────────────────────
|
|
1382
1385
|
*
|
|
1383
|
-
*
|
|
1384
|
-
*
|
|
1386
|
+
* Runtime integration requires only `fetch`. A runtime MAY also install the
|
|
1387
|
+
* optional origin-policy adapter (`isOriginGranted`, `getConnectGrants`, and
|
|
1388
|
+
* `resolveIdentity` together).
|
|
1385
1389
|
*
|
|
1386
1390
|
* @example
|
|
1387
1391
|
* ```ts
|
|
@@ -1389,24 +1393,31 @@ declare function createConfigService(options: ConfigServiceOptions): ConfigServi
|
|
|
1389
1393
|
*
|
|
1390
1394
|
* const resourceSvc = createResourceService({
|
|
1391
1395
|
* fetch: (url, init) => globalThis.fetch(url, init),
|
|
1396
|
+
* });
|
|
1397
|
+
* runtime.registerService('resource', resourceSvc);
|
|
1398
|
+
* ```
|
|
1399
|
+
*
|
|
1400
|
+
* @example
|
|
1401
|
+
* ```ts
|
|
1402
|
+
* const resourceSvc = createResourceService({
|
|
1403
|
+
* fetch: (url, init) => runtimeResources.resolve(url, init),
|
|
1392
1404
|
* isOriginGranted: (origin, grants) => grants.includes(origin),
|
|
1393
1405
|
* getConnectGrants: (dTag, hash) => myOriginGrantStore.getOrigins(dTag, hash),
|
|
1394
1406
|
* resolveIdentity: (windowId) => sessionRegistry.getEntryByWindowId(windowId) ?? null,
|
|
1395
1407
|
* });
|
|
1396
|
-
* runtime.registerService('resource', resourceSvc);
|
|
1397
1408
|
* ```
|
|
1398
1409
|
*/
|
|
1399
1410
|
|
|
1400
|
-
/**
|
|
1401
|
-
interface
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1411
|
+
/** Read-only metadata passed from the resource service to a runtime resolver. */
|
|
1412
|
+
interface ResourceFetchInit {
|
|
1413
|
+
/** Read-only HTTP method selected by the service. */
|
|
1414
|
+
method?: string;
|
|
1415
|
+
/** Optional runtime-owned request headers. */
|
|
1416
|
+
headers?: Record<string, string>;
|
|
1417
|
+
/** Request-scoped cancellation signal. */
|
|
1418
|
+
signal: AbortSignal;
|
|
1419
|
+
/** Advisory Blossom server origins for this resource only. */
|
|
1420
|
+
servers?: readonly string[];
|
|
1410
1421
|
}
|
|
1411
1422
|
/** Context passed to a host-provided resource info resolver. */
|
|
1412
1423
|
interface ResourceInfoContext {
|
|
@@ -1421,34 +1432,29 @@ type ResourceInfoProvider = ResourceInfo | ((context: ResourceInfoContext) => Re
|
|
|
1421
1432
|
/**
|
|
1422
1433
|
* Options for `createResourceService` (options-as-bridge per v1.6 Decision 18).
|
|
1423
1434
|
*
|
|
1424
|
-
*
|
|
1425
|
-
*
|
|
1426
|
-
*
|
|
1427
|
-
* grant check.
|
|
1428
|
-
*
|
|
1429
|
-
* @see PITFALLS.md:228 (H-03) — grants-source coupling must be present at construction
|
|
1435
|
+
* `fetch` is the only required field. Kehto does not select a runtime's scheme
|
|
1436
|
+
* or origin policy. The optional origin-policy fields form one adapter and must
|
|
1437
|
+
* therefore be supplied together when a runtime chooses to use it.
|
|
1430
1438
|
*/
|
|
1431
1439
|
interface ResourceServiceOptions {
|
|
1432
1440
|
/**
|
|
1433
|
-
*
|
|
1434
|
-
*
|
|
1441
|
+
* Runtime-supplied resource resolver. Receives the requested URL and returns
|
|
1442
|
+
* a byte-classified `Response`.
|
|
1435
1443
|
*
|
|
1436
|
-
* The
|
|
1437
|
-
*
|
|
1438
|
-
*
|
|
1439
|
-
*
|
|
1444
|
+
* The runtime owns scheme dispatch and every NAP-RESOURCE policy decision.
|
|
1445
|
+
* The returned `content-type` must describe the runtime-classified bytes, not
|
|
1446
|
+
* an untrusted upstream header. Throw `ResourceServiceError` to preserve a
|
|
1447
|
+
* canonical protocol error classification.
|
|
1440
1448
|
*
|
|
1441
1449
|
* @param url - The URL from the resource.bytes request
|
|
1442
|
-
* @param init -
|
|
1450
|
+
* @param init - Read-only method, request-scoped AbortSignal, and optional advisory Blossom servers
|
|
1443
1451
|
*/
|
|
1444
|
-
fetch(url: string, init:
|
|
1445
|
-
method?: string;
|
|
1446
|
-
headers?: Record<string, string>;
|
|
1447
|
-
signal: AbortSignal;
|
|
1448
|
-
}): Promise<Response>;
|
|
1452
|
+
fetch(url: string, init: ResourceFetchInit): Promise<Response>;
|
|
1449
1453
|
/**
|
|
1450
1454
|
* Returns true if `origin` is present in `grants` (the list returned by
|
|
1451
1455
|
* `getConnectGrants` for the napplet's dTag + aggregateHash).
|
|
1456
|
+
* Optional: when omitted with `getConnectGrants`, Kehto delegates directly
|
|
1457
|
+
* to the runtime resolver without an origin-grant decision.
|
|
1452
1458
|
*
|
|
1453
1459
|
* The reference implementation is simply `grants.includes(origin)`. Host apps
|
|
1454
1460
|
* may provide normalized-origin comparison if needed.
|
|
@@ -1456,21 +1462,19 @@ interface ResourceServiceOptions {
|
|
|
1456
1462
|
* @param origin - Parsed origin of the requested URL (scheme + host + port)
|
|
1457
1463
|
* @param grants - Readonly list from getConnectGrants for this napplet identity
|
|
1458
1464
|
*/
|
|
1459
|
-
isOriginGranted(origin: string, grants: readonly string[]): boolean;
|
|
1465
|
+
isOriginGranted?(origin: string, grants: readonly string[]): boolean;
|
|
1460
1466
|
/**
|
|
1461
1467
|
* Returns the list of allowed fetch origins for the given napplet identity.
|
|
1462
1468
|
* Called on every `resource.bytes` request — must be synchronous and fast.
|
|
1469
|
+
* Optional and paired with `isOriginGranted` plus `resolveIdentity`.
|
|
1463
1470
|
*
|
|
1464
1471
|
* Host-supplied grant source (e.g. a static per-dTag allowlist map, or any
|
|
1465
1472
|
* other host-controlled policy). Returns an empty array to deny all origins.
|
|
1466
1473
|
*
|
|
1467
|
-
* H-03 prevention: REQUIRED from day one — factory throws on construction
|
|
1468
|
-
* if omitted.
|
|
1469
|
-
*
|
|
1470
1474
|
* @param dTag - The napplet's d-tag (from session registry)
|
|
1471
1475
|
* @param aggregateHash - The napplet's aggregate hash (from session registry)
|
|
1472
1476
|
*/
|
|
1473
|
-
getConnectGrants(dTag: string, aggregateHash: string): readonly string[];
|
|
1477
|
+
getConnectGrants?(dTag: string, aggregateHash: string): readonly string[];
|
|
1474
1478
|
/**
|
|
1475
1479
|
* Resolve a windowId to the napplet's identity (dTag + aggregateHash).
|
|
1476
1480
|
* Returns null if the window is not in the session registry.
|
|
@@ -1479,7 +1483,7 @@ interface ResourceServiceOptions {
|
|
|
1479
1483
|
*
|
|
1480
1484
|
* @param windowId - The iframe window identifier
|
|
1481
1485
|
*/
|
|
1482
|
-
resolveIdentity(windowId: string): {
|
|
1486
|
+
resolveIdentity?(windowId: string): {
|
|
1483
1487
|
dTag: string;
|
|
1484
1488
|
aggregateHash: string;
|
|
1485
1489
|
} | null;
|
|
@@ -1487,8 +1491,8 @@ interface ResourceServiceOptions {
|
|
|
1487
1491
|
* Advisory NAP-RESOURCE introspection exposed through `resource.info`.
|
|
1488
1492
|
*
|
|
1489
1493
|
* Provide a static snapshot or a resolver when the shell wants to disclose
|
|
1490
|
-
* configured schemes and coarse limits.
|
|
1491
|
-
*
|
|
1494
|
+
* configured schemes and coarse limits. Scheme disclosure is advisory and is
|
|
1495
|
+
* never used as fetch authorization. Omit to disclose no schemes or limits.
|
|
1492
1496
|
*/
|
|
1493
1497
|
resourceInfo?: ResourceInfoProvider;
|
|
1494
1498
|
}
|
|
@@ -1498,7 +1502,7 @@ interface ResourceServiceOptions {
|
|
|
1498
1502
|
*/
|
|
1499
1503
|
type ResourceService = ServiceHandler;
|
|
1500
1504
|
type ResourceErrorCode = 'invalid-request' | 'not-found' | 'blocked-by-policy' | 'timeout' | 'too-large' | 'unsupported-scheme' | 'decode-failed' | 'network-error' | 'quota-exceeded';
|
|
1501
|
-
/** A
|
|
1505
|
+
/** A runtime resolver failure with a stable NAP-RESOURCE error classification. */
|
|
1502
1506
|
declare class ResourceServiceError extends Error {
|
|
1503
1507
|
readonly code: ResourceErrorCode;
|
|
1504
1508
|
/**
|
|
@@ -1514,15 +1518,14 @@ declare class ResourceServiceError extends Error {
|
|
|
1514
1518
|
* `resource.bytes`, `resource.bytesMany`, `resource.cancel`, and their
|
|
1515
1519
|
* result/error envelopes.
|
|
1516
1520
|
*
|
|
1517
|
-
*
|
|
1518
|
-
*
|
|
1519
|
-
*
|
|
1520
|
-
* loudly at startup rather than silently at first dispatch.
|
|
1521
|
+
* The runtime resolver is validated at construction. Runtime implementers may
|
|
1522
|
+
* additionally install the origin-grant adapter; Kehto provides and enforces
|
|
1523
|
+
* the hook contract without selecting the grants or requiring that policy.
|
|
1521
1524
|
*
|
|
1522
1525
|
* Returns a `ServiceHandler` (no `publishValues`-style surface — resource has
|
|
1523
1526
|
* no shell-initiated push beyond the response/error path).
|
|
1524
1527
|
*
|
|
1525
|
-
* @param options -
|
|
1528
|
+
* @param options - Runtime resolver plus optional runtime-owned policy helpers
|
|
1526
1529
|
* @returns ServiceHandler to register via `runtime.registerService('resource', handler)`
|
|
1527
1530
|
*
|
|
1528
1531
|
* @example
|
|
@@ -1530,7 +1533,7 @@ declare class ResourceServiceError extends Error {
|
|
|
1530
1533
|
* import { createResourceService } from '@kehto/services';
|
|
1531
1534
|
*
|
|
1532
1535
|
* const svc = createResourceService({
|
|
1533
|
-
* fetch: (url, init) =>
|
|
1536
|
+
* fetch: (url, init) => runtimeResources.resolve(url, init),
|
|
1534
1537
|
* isOriginGranted: (origin, grants) => grants.includes(origin),
|
|
1535
1538
|
* getConnectGrants: (dTag, hash) => myOriginGrantStore.getOrigins(dTag, hash),
|
|
1536
1539
|
* resolveIdentity: (windowId) => sessionRegistry.getEntryByWindowId(windowId) ?? null,
|
|
@@ -2844,4 +2847,4 @@ interface FsService extends ServiceHandler {
|
|
|
2844
2847
|
*/
|
|
2845
2848
|
declare function createFsService(options: FsServiceOptions): FsService;
|
|
2846
2849
|
|
|
2847
|
-
export { type BleServiceContext, type BleServiceOptions, type CacheServiceOptions, type CatalogIntentResolver, type CatalogIntentResolverOptions, type CommonServiceContext, type CommonServiceOptions, type ConfigSchemaValidation, type ConfigService, type ConfigServiceOptions, type ConfigSettingsContext, type CoordinatedRelayOptions, type CordnCodecResult, type CordnCoordinatorClient, type CordnCoordinatorSubscription, type CordnDmAdapterOptions, type CordnDmClient, type CordnGroupMessage, type CordnRelayCoordinatorOptions, type CountRequest, type CountResult, type CountServiceOptions, type DmAdapter, DmMemoryStore, type DmRelayPool, type DmService, type DmServiceOptions, type FsBackend, type FsBackendChange, type FsBackendWatch, type FsService, FsServiceError, type FsServiceOptions, type HostCacheBridge, type HostKeyEvent, type HostKeysBridge, type HostMediaBridge, type HttpUploaderOptions, type HttpUploaderRails, type IdentityServiceOptions, type IntentArchetypeSupport, type IntentCatalogEntry, type IntentDispatchParams, type IntentResolver, type IntentResolverContext, type IntentServiceOptions, type IntentTargetController, type IntentTargetDispatch, type KeysServiceOptions, type LinkOpenContext, type LinkServiceOptions, type ListsServiceContext, type ListsServiceOptions, type ManifestArchetypeInput, type MaybePromise, type MediaMetadataLike, type MediaPlaybackOwner, type MediaServiceOptions, type MediaSessionCreateOptions, type MediaSessionTarget, type MediaSourceRef, type NdrDmAdapterOptions, type NdrRelayTransport, type NdrRelayTransportOptions, type NdrRumorLike, type NdrRuntimeLike, type Nip17DmAdapterOptions, type NostrTag, type Notification, type NotificationServiceOptions, type NotifyInteractionMessage, type NotifyPresentation, type NotifyServiceOptions, type OutboxEventOptions, type OutboxEventResult, type OutboxPublishOptions, type OutboxPublishResult, type OutboxQueryOptions, type OutboxQueryStream, type OutboxQueryStreamSink, type OutboxRelayPlan, type OutboxRelayPool, type OutboxResult, type OutboxRouter, type OutboxRouterSubscription, type OutboxServiceOptions, type OutboxSubscribeOptions, type OutboxSubscriptionSink, type OutboxTarget, type RailServerConfig, type RelayListEntry, type RelayPoolOutboxRouterOptions, type RelayPoolServiceOptions, type ResourceErrorCode, type
|
|
2850
|
+
export { type BleServiceContext, type BleServiceOptions, type CacheServiceOptions, type CatalogIntentResolver, type CatalogIntentResolverOptions, type CommonServiceContext, type CommonServiceOptions, type ConfigSchemaValidation, type ConfigService, type ConfigServiceOptions, type ConfigSettingsContext, type CoordinatedRelayOptions, type CordnCodecResult, type CordnCoordinatorClient, type CordnCoordinatorSubscription, type CordnDmAdapterOptions, type CordnDmClient, type CordnGroupMessage, type CordnRelayCoordinatorOptions, type CountRequest, type CountResult, type CountServiceOptions, type DmAdapter, DmMemoryStore, type DmRelayPool, type DmService, type DmServiceOptions, type FsBackend, type FsBackendChange, type FsBackendWatch, type FsService, FsServiceError, type FsServiceOptions, type HostCacheBridge, type HostKeyEvent, type HostKeysBridge, type HostMediaBridge, type HttpUploaderOptions, type HttpUploaderRails, type IdentityServiceOptions, type IntentArchetypeSupport, type IntentCatalogEntry, type IntentDispatchParams, type IntentResolver, type IntentResolverContext, type IntentServiceOptions, type IntentTargetController, type IntentTargetDispatch, type KeysServiceOptions, type LinkOpenContext, type LinkServiceOptions, type ListsServiceContext, type ListsServiceOptions, type ManifestArchetypeInput, type MaybePromise, type MediaMetadataLike, type MediaPlaybackOwner, type MediaServiceOptions, type MediaSessionCreateOptions, type MediaSessionTarget, type MediaSourceRef, type NdrDmAdapterOptions, type NdrRelayTransport, type NdrRelayTransportOptions, type NdrRumorLike, type NdrRuntimeLike, type Nip17DmAdapterOptions, type NostrTag, type Notification, type NotificationServiceOptions, type NotifyInteractionMessage, type NotifyPresentation, type NotifyServiceOptions, type OutboxEventOptions, type OutboxEventResult, type OutboxPublishOptions, type OutboxPublishResult, type OutboxQueryOptions, type OutboxQueryStream, type OutboxQueryStreamSink, type OutboxRelayPlan, type OutboxRelayPool, type OutboxResult, type OutboxRouter, type OutboxRouterSubscription, type OutboxServiceOptions, type OutboxSubscribeOptions, type OutboxSubscriptionSink, type OutboxTarget, type RailServerConfig, type RelayListEntry, type RelayPoolOutboxRouterOptions, type RelayPoolServiceOptions, type ResourceErrorCode, type ResourceFetchInit, type ResourceInfoContext, type ResourceInfoProvider, type ResourceService, ResourceServiceError, type ResourceServiceOptions, type SerialServiceContext, type SerialServiceOptions, type SignEvent, type StreamingOutboxRouter, type ThemeService, type ThemeServiceOptions, type UploadDimensions, type UploadInfo, type UploadInfoContext, type UploadInfoProvider, type UploadRail, type UploadRailInfo, type UploadRequest, type UploadResult, type UploadServiceOptions, type UploadState, type UploadStatus, type Uploader, type UploaderContext, type WebrtcServiceContext, type WebrtcServiceOptions, createBleService, createBrowserMediaBridge, createCacheService, createCatalogIntentResolver, createCommonService, createConfigService, createCoordinatedRelay, createCordnDmAdapter, createCordnRelayCoordinatorClient, createCountService, createDmService, createFsService, createHttpUploader, createIdentityService, createIntentService, createKeysService, createLinkService, createListsService, createMediaService, createNdrDmAdapter, createNdrRelayTransport, createNip17DmAdapter, createNotificationService, createNotifyService, createOutboxService, createRelayPoolOutboxRouter, createRelayPoolService, createResourceService, createSerialService, createThemeService, createUploadService, createWebrtcService, manifestToIntentCatalogEntry, resolveConfigValues, validateConfigSchema };
|
package/dist/index.js
CHANGED
|
@@ -2272,11 +2272,20 @@ function requestKey(windowId, requestId) {
|
|
|
2272
2272
|
return `${windowId}\0${requestId}`;
|
|
2273
2273
|
}
|
|
2274
2274
|
function assertResourceOptions(options) {
|
|
2275
|
-
if (typeof options?.fetch !== "function"
|
|
2275
|
+
if (typeof options?.fetch !== "function") {
|
|
2276
2276
|
throw new Error(
|
|
2277
|
-
"[RESOURCE-01
|
|
2277
|
+
"[RESOURCE-01] createResourceService requires a runtime-provided fetch resolver."
|
|
2278
2278
|
);
|
|
2279
2279
|
}
|
|
2280
|
+
const hasOriginPolicy = options.isOriginGranted !== void 0 || options.getConnectGrants !== void 0;
|
|
2281
|
+
if (hasOriginPolicy && (typeof options.isOriginGranted !== "function" || typeof options.getConnectGrants !== "function" || typeof options.resolveIdentity !== "function")) {
|
|
2282
|
+
throw new Error(
|
|
2283
|
+
"[RESOURCE-01] optional origin policy requires {isOriginGranted, getConnectGrants, resolveIdentity} together."
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
if (options.resolveIdentity !== void 0 && typeof options.resolveIdentity !== "function") {
|
|
2287
|
+
throw new Error("[RESOURCE-01] resolveIdentity must be a function when provided.");
|
|
2288
|
+
}
|
|
2280
2289
|
}
|
|
2281
2290
|
function trackRequest(state, requestId, windowId, controller) {
|
|
2282
2291
|
const key = requestKey(windowId, requestId);
|
|
@@ -2327,12 +2336,13 @@ function normalizeResourceInfo(info) {
|
|
|
2327
2336
|
return {
|
|
2328
2337
|
schemes: Array.isArray(info.schemes) ? info.schemes.filter((scheme) => typeof scheme?.scheme === "string").map((scheme) => ({ scheme: scheme.scheme, enabled: scheme.enabled !== false })) : [],
|
|
2329
2338
|
...typeof info.maxBytes === "number" ? { maxBytes: info.maxBytes } : {},
|
|
2330
|
-
...typeof info.maxUrls === "number" ? { maxUrls: info.maxUrls } : {}
|
|
2339
|
+
...typeof info.maxUrls === "number" ? { maxUrls: info.maxUrls } : {},
|
|
2340
|
+
...typeof info.maxServers === "number" ? { maxServers: info.maxServers } : {}
|
|
2331
2341
|
};
|
|
2332
2342
|
}
|
|
2333
2343
|
async function resolveResourceInfo(options, windowId) {
|
|
2334
2344
|
const configured = options.resourceInfo ?? DEFAULT_RESOURCE_INFO;
|
|
2335
|
-
const info = typeof configured === "function" ? await configured({ windowId, identity: options.resolveIdentity(windowId) }) : configured;
|
|
2345
|
+
const info = typeof configured === "function" ? await configured({ windowId, identity: options.resolveIdentity?.(windowId) ?? null }) : configured;
|
|
2336
2346
|
return normalizeResourceInfo(info);
|
|
2337
2347
|
}
|
|
2338
2348
|
async function handleInfo(options, windowId, requestId, send) {
|
|
@@ -2366,27 +2376,36 @@ function classifyResourceFailure(error) {
|
|
|
2366
2376
|
message: error instanceof Error ? error.message : String(error)
|
|
2367
2377
|
};
|
|
2368
2378
|
}
|
|
2369
|
-
async function fetchResourceItem(options,
|
|
2379
|
+
async function fetchResourceItem(options, windowId, info, request, signal) {
|
|
2380
|
+
const { url } = request;
|
|
2370
2381
|
const parsedUrl = parseResourceUrl(url);
|
|
2371
2382
|
if (!parsedUrl) return resourceInvalidRequest(url, `invalid URL: ${url}`);
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2383
|
+
if (parsedUrl.origin !== "null" && options.isOriginGranted && options.getConnectGrants) {
|
|
2384
|
+
const identity = options.resolveIdentity?.(windowId) ?? null;
|
|
2385
|
+
if (!identity) {
|
|
2386
|
+
return {
|
|
2387
|
+
ok: false,
|
|
2388
|
+
url,
|
|
2389
|
+
error: "blocked-by-policy",
|
|
2390
|
+
message: "napplet identity not resolvable for runtime origin policy"
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
const origin = parsedUrl.origin;
|
|
2394
|
+
const grants = options.getConnectGrants(identity.dTag, identity.aggregateHash);
|
|
2395
|
+
if (!options.isOriginGranted(origin, grants)) {
|
|
2396
|
+
return {
|
|
2397
|
+
ok: false,
|
|
2398
|
+
url,
|
|
2399
|
+
error: "blocked-by-policy",
|
|
2400
|
+
message: `origin ${origin} not granted by runtime policy`
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2385
2403
|
}
|
|
2386
2404
|
try {
|
|
2387
2405
|
const response = await options.fetch(url, {
|
|
2388
2406
|
method: "GET",
|
|
2389
|
-
signal
|
|
2407
|
+
signal,
|
|
2408
|
+
...parsedUrl.protocol === "blossom:" && request.servers !== void 0 ? { servers: [...request.servers] } : {}
|
|
2390
2409
|
});
|
|
2391
2410
|
const buffer = await response.arrayBuffer();
|
|
2392
2411
|
if (info.maxBytes !== void 0 && buffer.byteLength > info.maxBytes) {
|
|
@@ -2410,17 +2429,12 @@ async function fetchResourceItem(options, identity, info, url, signal) {
|
|
|
2410
2429
|
}
|
|
2411
2430
|
}
|
|
2412
2431
|
async function handleBytes(options, state, windowId, msg, send) {
|
|
2413
|
-
const { requestId,
|
|
2414
|
-
const identity = options.resolveIdentity(windowId);
|
|
2415
|
-
if (!identity) {
|
|
2416
|
-
sendResourceError(send, requestId, "napplet identity not resolvable", "blocked-by-policy");
|
|
2417
|
-
return;
|
|
2418
|
-
}
|
|
2432
|
+
const { requestId, ...request } = msg;
|
|
2419
2433
|
const controller = new AbortController();
|
|
2420
2434
|
trackRequest(state, requestId, windowId, controller);
|
|
2421
2435
|
try {
|
|
2422
2436
|
const info = await resolveResourceInfo(options, windowId);
|
|
2423
|
-
const item = await fetchResourceItem(options,
|
|
2437
|
+
const item = await fetchResourceItem(options, windowId, info, request, controller.signal);
|
|
2424
2438
|
if (controller.signal.aborted) return;
|
|
2425
2439
|
if (!item.ok) {
|
|
2426
2440
|
sendResourceError(send, requestId, item.message, item.error);
|
|
@@ -2442,14 +2456,9 @@ async function handleBytes(options, state, windowId, msg, send) {
|
|
|
2442
2456
|
}
|
|
2443
2457
|
}
|
|
2444
2458
|
async function handleBytesMany(options, state, windowId, msg, send) {
|
|
2445
|
-
const { requestId,
|
|
2446
|
-
if (!
|
|
2447
|
-
sendBytesManyError(send, requestId, "resource.bytesMany requires a non-empty
|
|
2448
|
-
return;
|
|
2449
|
-
}
|
|
2450
|
-
const identity = options.resolveIdentity(windowId);
|
|
2451
|
-
if (!identity) {
|
|
2452
|
-
sendBytesManyError(send, requestId, "napplet identity not resolvable", "blocked-by-policy");
|
|
2459
|
+
const { requestId, requests } = msg;
|
|
2460
|
+
if (!isResourceRequestList(requests)) {
|
|
2461
|
+
sendBytesManyError(send, requestId, "resource.bytesMany requires a non-empty requests array", "invalid-request");
|
|
2453
2462
|
return;
|
|
2454
2463
|
}
|
|
2455
2464
|
let info;
|
|
@@ -2460,7 +2469,7 @@ async function handleBytesMany(options, state, windowId, msg, send) {
|
|
|
2460
2469
|
sendBytesManyError(send, requestId, failure.message, failure.error);
|
|
2461
2470
|
return;
|
|
2462
2471
|
}
|
|
2463
|
-
if (info.maxUrls !== void 0 &&
|
|
2472
|
+
if (info.maxUrls !== void 0 && requests.length > info.maxUrls) {
|
|
2464
2473
|
sendBytesManyError(send, requestId, `resource.bytesMany exceeds ${info.maxUrls} URLs`, "too-large");
|
|
2465
2474
|
return;
|
|
2466
2475
|
}
|
|
@@ -2468,8 +2477,8 @@ async function handleBytesMany(options, state, windowId, msg, send) {
|
|
|
2468
2477
|
trackRequest(state, requestId, windowId, controller);
|
|
2469
2478
|
try {
|
|
2470
2479
|
const items = [];
|
|
2471
|
-
for (const
|
|
2472
|
-
const item = await fetchResourceItem(options,
|
|
2480
|
+
for (const request of requests) {
|
|
2481
|
+
const item = await fetchResourceItem(options, windowId, info, request, controller.signal);
|
|
2473
2482
|
if (controller.signal.aborted) return;
|
|
2474
2483
|
if (item.ok) {
|
|
2475
2484
|
items.push({
|
|
@@ -2501,6 +2510,12 @@ async function handleBytesMany(options, state, windowId, msg, send) {
|
|
|
2501
2510
|
untrackRequest(state, windowId, requestId);
|
|
2502
2511
|
}
|
|
2503
2512
|
}
|
|
2513
|
+
function hasValidServerHints(request) {
|
|
2514
|
+
return request.servers === void 0 || Array.isArray(request.servers) && request.servers.every((server) => typeof server === "string");
|
|
2515
|
+
}
|
|
2516
|
+
function isResourceRequestList(value) {
|
|
2517
|
+
return Array.isArray(value) && value.length > 0 && value.every((request) => typeof request === "object" && request !== null && typeof request.url === "string" && hasValidServerHints(request));
|
|
2518
|
+
}
|
|
2504
2519
|
function handleCancel(state, windowId, requestId) {
|
|
2505
2520
|
const entry = state.inFlight.get(requestKey(windowId, requestId));
|
|
2506
2521
|
if (entry) {
|
|
@@ -2528,7 +2543,7 @@ function createResourceService(options) {
|
|
|
2528
2543
|
const descriptor = {
|
|
2529
2544
|
name: "resource",
|
|
2530
2545
|
version: RESOURCE_SERVICE_VERSION,
|
|
2531
|
-
description: "NAP-RESOURCE
|
|
2546
|
+
description: "NAP-RESOURCE service \u2014 runtime-resolved byte transport (RESOURCE-01..06)"
|
|
2532
2547
|
};
|
|
2533
2548
|
const handler = {
|
|
2534
2549
|
descriptor,
|
|
@@ -2538,11 +2553,15 @@ function createResourceService(options) {
|
|
|
2538
2553
|
const m = message;
|
|
2539
2554
|
const requestId = requestIdFromMessage(m);
|
|
2540
2555
|
if (!requestId) return;
|
|
2541
|
-
if (typeof m.url !== "string") {
|
|
2542
|
-
sendResourceError(send, requestId, "resource.bytes requires a URL", "invalid-request");
|
|
2556
|
+
if (typeof m.url !== "string" || !hasValidServerHints(m)) {
|
|
2557
|
+
sendResourceError(send, requestId, "resource.bytes requires a URL and optional string servers", "invalid-request");
|
|
2543
2558
|
return;
|
|
2544
2559
|
}
|
|
2545
|
-
handleBytes(options, state, windowId, {
|
|
2560
|
+
handleBytes(options, state, windowId, {
|
|
2561
|
+
requestId,
|
|
2562
|
+
url: m.url,
|
|
2563
|
+
...m.servers !== void 0 ? { servers: [...m.servers] } : {}
|
|
2564
|
+
}, send).catch(() => {
|
|
2546
2565
|
});
|
|
2547
2566
|
return;
|
|
2548
2567
|
}
|
|
@@ -2558,7 +2577,7 @@ function createResourceService(options) {
|
|
|
2558
2577
|
const m = message;
|
|
2559
2578
|
const requestId = requestIdFromMessage(m);
|
|
2560
2579
|
if (!requestId) return;
|
|
2561
|
-
handleBytesMany(options, state, windowId, { requestId,
|
|
2580
|
+
handleBytesMany(options, state, windowId, { requestId, requests: m.requests ?? [] }, send).catch(() => {
|
|
2562
2581
|
});
|
|
2563
2582
|
return;
|
|
2564
2583
|
}
|