@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 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 implementation for a draft NIP-5D
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.31.0 <0.32.0`. The exact installed contracts are core 0.31.1 / nap 0.31.2
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 the draft NAP-RESOURCE request lifecycle and current result shapes. It fail-closes when no scheme is configured, checks per-identity origin grants, enforces disclosed size/bulk caps, scopes cancellation to the requesting window, drops cancelled terminal envelopes, and never forwards response headers. The host policy fetch must return byte-classified output and owns scheme-specific SSRF, redirect, integrity, and SVG handling.
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 an **authenticated fetch proxy** — read-only, atomic.
1377
+ * NAP-RESOURCE is a runtime-mediated byte resolver — read-only, atomic.
1376
1378
  *
1377
- * The host fetch boundary performs scheme-specific I/O and MUST return only a
1378
- * policy-checked, byte-classified response. This service independently enforces
1379
- * identity, scheme disclosure, origin grants, bulk limits, response-size caps,
1380
- * cancellation, and the current wire shape. It never forwards upstream headers.
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
- * Host integration: provide `fetch`, `isOriginGranted`, `getConnectGrants`,
1384
- * and `resolveIdentity`. ALL FOUR are required from day one (H-03 prevention).
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
- /** Runtime-disclosed support for one resource URL scheme. */
1401
- interface ResourceSchemeInfo {
1402
- scheme: string;
1403
- enabled: boolean;
1404
- }
1405
- /** Advisory resource capability and policy limits disclosed by the runtime. */
1406
- interface ResourceInfo {
1407
- schemes: ResourceSchemeInfo[];
1408
- maxBytes?: number;
1409
- maxUrls?: number;
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
- * ALL FOUR fields are required. The factory throws at construction if any is
1425
- * missing — H-03 prevention: the grants source (`getConnectGrants`) MUST be
1426
- * wired from day one so there is no window where resource requests bypass the
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
- * Host-supplied policy fetch implementation. Receives the URL, a partial init
1434
- * (method, headers, signal), and returns a sanitized `Response`.
1441
+ * Runtime-supplied resource resolver. Receives the requested URL and returns
1442
+ * a byte-classified `Response`.
1435
1443
  *
1436
- * The returned `content-type` MUST be derived from inspected output bytes,
1437
- * never an upstream header. The host boundary also owns redirect-by-redirect
1438
- * private-address checks, SVG rasterization, and scheme-specific integrity.
1439
- * Throw `ResourceServiceError` to preserve a protocol error classification.
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 - Method, headers (from napplet), and an AbortSignal
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. Omit to expose the reference
1491
- * service's fail-closed default (no enabled schemes or numeric limits).
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 host policy failure with a stable NAP-RESOURCE error classification. */
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
- * On-construction guard (H-03 prevention): all four options are validated at
1518
- * factory call time. If any is missing, the factory throws immediately with a
1519
- * message containing `[RESOURCE-01 / H-03]` so misconfigured shell apps fail
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 - REQUIRED: fetch, isOriginGranted, getConnectGrants, resolveIdentity
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) => globalThis.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 ResourceInfo, type ResourceInfoContext, type ResourceInfoProvider, type ResourceSchemeInfo, 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 };
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" || typeof options?.isOriginGranted !== "function" || typeof options?.getConnectGrants !== "function" || typeof options?.resolveIdentity !== "function") {
2275
+ if (typeof options?.fetch !== "function") {
2276
2276
  throw new Error(
2277
- "[RESOURCE-01 / H-03] createResourceService requires {fetch, isOriginGranted, getConnectGrants, resolveIdentity} \u2014 all four options are required from day one. The grants source (getConnectGrants) MUST be wired at construction time to prevent unguarded fetch proxying."
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, identity, info, url, signal) {
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
- const scheme = parsedUrl.protocol.slice(0, -1).toLowerCase();
2373
- if (!info.schemes.some((item) => item.enabled && item.scheme.toLowerCase() === scheme)) {
2374
- return { ok: false, url, error: "unsupported-scheme", message: `scheme ${scheme} is not enabled` };
2375
- }
2376
- const origin = parsedUrl.origin;
2377
- const grants = options.getConnectGrants(identity.dTag, identity.aggregateHash);
2378
- if (!options.isOriginGranted(origin, grants)) {
2379
- return {
2380
- ok: false,
2381
- url,
2382
- error: "blocked-by-policy",
2383
- message: `origin ${origin} not granted`
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, url } = msg;
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, identity, info, url, controller.signal);
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, urls } = msg;
2446
- if (!Array.isArray(urls) || urls.length === 0 || urls.some((url) => typeof url !== "string")) {
2447
- sendBytesManyError(send, requestId, "resource.bytesMany requires a non-empty urls array", "invalid-request");
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 && urls.length > info.maxUrls) {
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 url of urls) {
2472
- const item = await fetchResourceItem(options, identity, info, url, controller.signal);
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 reference service \u2014 shell-proxied authenticated fetch (RESOURCE-01..06)"
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, { requestId, url: m.url }, send).catch(() => {
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, urls: m.urls ?? [] }, send).catch(() => {
2580
+ handleBytesMany(options, state, windowId, { requestId, requests: m.requests ?? [] }, send).catch(() => {
2562
2581
  });
2563
2582
  return;
2564
2583
  }