@kehto/services 0.20.1 → 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
@@ -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 without choosing a runtime's network policy. By default it delegates every syntactically valid URL to the injected `fetch` resolver; `resource.info.schemes` is advisory and never authorizes or blocks a request. Runtimes that want origin grants can supply `isOriginGranted`, `getConnectGrants`, and `resolveIdentity` together, while permissive runtimes can omit all three. 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 from the resolver. Browser fetch failures, including responses hidden by CORS, use the canonical `network-error`; a resolver that can read the bytes returns them normally.
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
  /**
@@ -1406,16 +1408,16 @@ declare function createConfigService(options: ConfigServiceOptions): ConfigServi
1406
1408
  * ```
1407
1409
  */
1408
1410
 
1409
- /** Runtime-disclosed support for one resource URL scheme. */
1410
- interface ResourceSchemeInfo {
1411
- scheme: string;
1412
- enabled: boolean;
1413
- }
1414
- /** Advisory resource capability and policy limits disclosed by the runtime. */
1415
- interface ResourceInfo {
1416
- schemes: ResourceSchemeInfo[];
1417
- maxBytes?: number;
1418
- 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[];
1419
1421
  }
1420
1422
  /** Context passed to a host-provided resource info resolver. */
1421
1423
  interface ResourceInfoContext {
@@ -1445,13 +1447,9 @@ interface ResourceServiceOptions {
1445
1447
  * canonical protocol error classification.
1446
1448
  *
1447
1449
  * @param url - The URL from the resource.bytes request
1448
- * @param init - Read-only method and request-scoped AbortSignal
1450
+ * @param init - Read-only method, request-scoped AbortSignal, and optional advisory Blossom servers
1449
1451
  */
1450
- fetch(url: string, init: {
1451
- method?: string;
1452
- headers?: Record<string, string>;
1453
- signal: AbortSignal;
1454
- }): Promise<Response>;
1452
+ fetch(url: string, init: ResourceFetchInit): Promise<Response>;
1455
1453
  /**
1456
1454
  * Returns true if `origin` is present in `grants` (the list returned by
1457
1455
  * `getConnectGrants` for the napplet's dTag + aggregateHash).
@@ -2849,4 +2847,4 @@ interface FsService extends ServiceHandler {
2849
2847
  */
2850
2848
  declare function createFsService(options: FsServiceOptions): FsService;
2851
2849
 
2852
- 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
@@ -2336,7 +2336,8 @@ function normalizeResourceInfo(info) {
2336
2336
  return {
2337
2337
  schemes: Array.isArray(info.schemes) ? info.schemes.filter((scheme) => typeof scheme?.scheme === "string").map((scheme) => ({ scheme: scheme.scheme, enabled: scheme.enabled !== false })) : [],
2338
2338
  ...typeof info.maxBytes === "number" ? { maxBytes: info.maxBytes } : {},
2339
- ...typeof info.maxUrls === "number" ? { maxUrls: info.maxUrls } : {}
2339
+ ...typeof info.maxUrls === "number" ? { maxUrls: info.maxUrls } : {},
2340
+ ...typeof info.maxServers === "number" ? { maxServers: info.maxServers } : {}
2340
2341
  };
2341
2342
  }
2342
2343
  async function resolveResourceInfo(options, windowId) {
@@ -2375,10 +2376,11 @@ function classifyResourceFailure(error) {
2375
2376
  message: error instanceof Error ? error.message : String(error)
2376
2377
  };
2377
2378
  }
2378
- async function fetchResourceItem(options, windowId, info, url, signal) {
2379
+ async function fetchResourceItem(options, windowId, info, request, signal) {
2380
+ const { url } = request;
2379
2381
  const parsedUrl = parseResourceUrl(url);
2380
2382
  if (!parsedUrl) return resourceInvalidRequest(url, `invalid URL: ${url}`);
2381
- if (options.isOriginGranted && options.getConnectGrants) {
2383
+ if (parsedUrl.origin !== "null" && options.isOriginGranted && options.getConnectGrants) {
2382
2384
  const identity = options.resolveIdentity?.(windowId) ?? null;
2383
2385
  if (!identity) {
2384
2386
  return {
@@ -2402,7 +2404,8 @@ async function fetchResourceItem(options, windowId, info, url, signal) {
2402
2404
  try {
2403
2405
  const response = await options.fetch(url, {
2404
2406
  method: "GET",
2405
- signal
2407
+ signal,
2408
+ ...parsedUrl.protocol === "blossom:" && request.servers !== void 0 ? { servers: [...request.servers] } : {}
2406
2409
  });
2407
2410
  const buffer = await response.arrayBuffer();
2408
2411
  if (info.maxBytes !== void 0 && buffer.byteLength > info.maxBytes) {
@@ -2426,12 +2429,12 @@ async function fetchResourceItem(options, windowId, info, url, signal) {
2426
2429
  }
2427
2430
  }
2428
2431
  async function handleBytes(options, state, windowId, msg, send) {
2429
- const { requestId, url } = msg;
2432
+ const { requestId, ...request } = msg;
2430
2433
  const controller = new AbortController();
2431
2434
  trackRequest(state, requestId, windowId, controller);
2432
2435
  try {
2433
2436
  const info = await resolveResourceInfo(options, windowId);
2434
- const item = await fetchResourceItem(options, windowId, info, url, controller.signal);
2437
+ const item = await fetchResourceItem(options, windowId, info, request, controller.signal);
2435
2438
  if (controller.signal.aborted) return;
2436
2439
  if (!item.ok) {
2437
2440
  sendResourceError(send, requestId, item.message, item.error);
@@ -2453,9 +2456,9 @@ async function handleBytes(options, state, windowId, msg, send) {
2453
2456
  }
2454
2457
  }
2455
2458
  async function handleBytesMany(options, state, windowId, msg, send) {
2456
- const { requestId, urls } = msg;
2457
- if (!Array.isArray(urls) || urls.length === 0 || urls.some((url) => typeof url !== "string")) {
2458
- sendBytesManyError(send, requestId, "resource.bytesMany requires a non-empty urls array", "invalid-request");
2459
+ const { requestId, requests } = msg;
2460
+ if (!isResourceRequestList(requests)) {
2461
+ sendBytesManyError(send, requestId, "resource.bytesMany requires a non-empty requests array", "invalid-request");
2459
2462
  return;
2460
2463
  }
2461
2464
  let info;
@@ -2466,7 +2469,7 @@ async function handleBytesMany(options, state, windowId, msg, send) {
2466
2469
  sendBytesManyError(send, requestId, failure.message, failure.error);
2467
2470
  return;
2468
2471
  }
2469
- if (info.maxUrls !== void 0 && urls.length > info.maxUrls) {
2472
+ if (info.maxUrls !== void 0 && requests.length > info.maxUrls) {
2470
2473
  sendBytesManyError(send, requestId, `resource.bytesMany exceeds ${info.maxUrls} URLs`, "too-large");
2471
2474
  return;
2472
2475
  }
@@ -2474,8 +2477,8 @@ async function handleBytesMany(options, state, windowId, msg, send) {
2474
2477
  trackRequest(state, requestId, windowId, controller);
2475
2478
  try {
2476
2479
  const items = [];
2477
- for (const url of urls) {
2478
- const item = await fetchResourceItem(options, windowId, info, url, controller.signal);
2480
+ for (const request of requests) {
2481
+ const item = await fetchResourceItem(options, windowId, info, request, controller.signal);
2479
2482
  if (controller.signal.aborted) return;
2480
2483
  if (item.ok) {
2481
2484
  items.push({
@@ -2507,6 +2510,12 @@ async function handleBytesMany(options, state, windowId, msg, send) {
2507
2510
  untrackRequest(state, windowId, requestId);
2508
2511
  }
2509
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
+ }
2510
2519
  function handleCancel(state, windowId, requestId) {
2511
2520
  const entry = state.inFlight.get(requestKey(windowId, requestId));
2512
2521
  if (entry) {
@@ -2544,11 +2553,15 @@ function createResourceService(options) {
2544
2553
  const m = message;
2545
2554
  const requestId = requestIdFromMessage(m);
2546
2555
  if (!requestId) return;
2547
- if (typeof m.url !== "string") {
2548
- 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");
2549
2558
  return;
2550
2559
  }
2551
- 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(() => {
2552
2565
  });
2553
2566
  return;
2554
2567
  }
@@ -2564,7 +2577,7 @@ function createResourceService(options) {
2564
2577
  const m = message;
2565
2578
  const requestId = requestIdFromMessage(m);
2566
2579
  if (!requestId) return;
2567
- handleBytesMany(options, state, windowId, { requestId, urls: m.urls ?? [] }, send).catch(() => {
2580
+ handleBytesMany(options, state, windowId, { requestId, requests: m.requests ?? [] }, send).catch(() => {
2568
2581
  });
2569
2582
  return;
2570
2583
  }