@kehto/services 0.8.0 → 0.9.1
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 +2 -2
- package/dist/index.d.ts +297 -1
- package/dist/index.js +227 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -154,7 +154,7 @@ runtime.registerService('keys', createKeysService({ hostBridge: electronBridge }
|
|
|
154
154
|
|
|
155
155
|
Plug a `HostKeysBridge` when the default document listener is insufficient: Electron or Tauri apps that need to register OS-level global hotkeys (chords delivered even when the host window is not focused), native shells that route chords through a platform-specific hotkey manager (macOS Carbon, Linux X11 grab, Windows RegisterHotKey), or test harnesses that inject synthetic events through a controlled `EventTarget`. The bridge owns subscription lifecycle; the service retains per-window bookkeeping (so `onWindowDestroyed` cleanup stays identical across paths).
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
A napplet drives this end to end via `@napplet/sdk` — `keys.registerAction` to claim a chord and `keys.onAction` to receive dispatches against the real backend.
|
|
158
158
|
|
|
159
159
|
### Reserved Chords
|
|
160
160
|
|
|
@@ -344,7 +344,7 @@ runtime.registerService('media', createMediaService({ hostBridge: electronBridge
|
|
|
344
344
|
|
|
345
345
|
Plug a `HostMediaBridge` when `navigator.mediaSession` is insufficient: Electron apps that need to route transport events through the main process (lock-screen integration on Windows, Now Playing integration on macOS), Linux shells that speak MPRIS over D-Bus, native mobile wrappers that forward to AVPlayer / ExoPlayer, or test harnesses that record action events without touching the DOM. The bridge owns metadata/state mirroring and OS action routing; the service retains per-session bookkeeping (sessionRegistry + per-window send handles) so `media.command` dispatch semantics stay identical across paths.
|
|
346
346
|
|
|
347
|
-
|
|
347
|
+
A napplet drives this end to end via `@napplet/nub/media` — `mediaCreateSession({ owner: 'napplet', ... })`, `mediaReportState`, and `mediaOnCommand` against the real backend.
|
|
348
348
|
|
|
349
349
|
## Public API
|
|
350
350
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1981,4 +1981,300 @@ interface HttpUploaderOptions {
|
|
|
1981
1981
|
*/
|
|
1982
1982
|
declare function createHttpUploader(options: HttpUploaderOptions): Uploader;
|
|
1983
1983
|
|
|
1984
|
-
|
|
1984
|
+
/**
|
|
1985
|
+
* intent-types.ts — NAP-INTENT (archetype intent dispatch) value types.
|
|
1986
|
+
*
|
|
1987
|
+
* Kehto-internal model for the NAP-INTENT wire contract (upstream draft:
|
|
1988
|
+
* napplet/nubs NAP-INTENT, namespace `window.napplet.intent`). The installed
|
|
1989
|
+
* `@napplet/core` peer predates these types, so — as with {@link ./cvm-types} —
|
|
1990
|
+
* kehto defines them here, wire-compatible with the upstream shapes, rather
|
|
1991
|
+
* than redefining the generic NIP-5D envelope.
|
|
1992
|
+
*
|
|
1993
|
+
* NAP-INTENT standardizes the *envelope*, not the payload. `archetype` is the
|
|
1994
|
+
* routing axis (which role, whose default), `protocol` is the parsing axis
|
|
1995
|
+
* (which NAP-N wire format shapes `payload`); the two are orthogonal.
|
|
1996
|
+
*
|
|
1997
|
+
* @packageDocumentation
|
|
1998
|
+
*/
|
|
1999
|
+
/**
|
|
2000
|
+
* How the shell should pick the handling napplet for an intent.
|
|
2001
|
+
*
|
|
2002
|
+
* - `"default"` — route to the user's default handler for the archetype.
|
|
2003
|
+
* - `"choose"` — prompt the user with an "open with…" chooser.
|
|
2004
|
+
* - any other string — a specific napplet dTag (cross-napplet targeting; the
|
|
2005
|
+
* shell SHOULD require the user to have authorized this for the caller).
|
|
2006
|
+
*/
|
|
2007
|
+
type IntentHandlerPreference = 'default' | 'choose' | (string & {});
|
|
2008
|
+
/** Window-behavior hints carried with an intent invoke. */
|
|
2009
|
+
interface IntentBehavior {
|
|
2010
|
+
/** Bring the handler window to the foreground. */
|
|
2011
|
+
focus?: boolean;
|
|
2012
|
+
/** Force a new window rather than reusing an existing handler instance. */
|
|
2013
|
+
newWindow?: boolean;
|
|
2014
|
+
/** Prefer reusing an already-open handler instance when one exists. */
|
|
2015
|
+
reuse?: boolean;
|
|
2016
|
+
}
|
|
2017
|
+
/** A request to dispatch an action to a napplet of a given archetype. */
|
|
2018
|
+
interface IntentRequest {
|
|
2019
|
+
/** Role slug, e.g. `"note"` (see the archetype registry). */
|
|
2020
|
+
archetype: string;
|
|
2021
|
+
/** Verb, default `"open"` (e.g. `"open"` | `"edit"` | `"pick"` | `"share"`). */
|
|
2022
|
+
action?: string;
|
|
2023
|
+
/** NAP-N id shaping `payload`; omit to use the archetype's recommended default. */
|
|
2024
|
+
protocol?: string;
|
|
2025
|
+
/** Opaque payload, typed by `protocol`. */
|
|
2026
|
+
payload?: unknown;
|
|
2027
|
+
/** Handler-selection preference. */
|
|
2028
|
+
handler?: IntentHandlerPreference;
|
|
2029
|
+
/** Window-behavior hints. */
|
|
2030
|
+
behavior?: IntentBehavior;
|
|
2031
|
+
}
|
|
2032
|
+
/** A napplet that can fulfill an archetype, sourced from the manifest catalog. */
|
|
2033
|
+
interface IntentCandidate {
|
|
2034
|
+
/** dTag of the napplet that can fulfill the archetype. */
|
|
2035
|
+
dTag: string;
|
|
2036
|
+
/** Human-readable title from the manifest. */
|
|
2037
|
+
title?: string;
|
|
2038
|
+
/** Verbs this candidate supports for the archetype. */
|
|
2039
|
+
actions: string[];
|
|
2040
|
+
/** NAP-N ids this candidate accepts for the archetype. */
|
|
2041
|
+
protocols: string[];
|
|
2042
|
+
/** Whether this candidate is the user/runtime default for the archetype. */
|
|
2043
|
+
isDefault?: boolean;
|
|
2044
|
+
}
|
|
2045
|
+
/** Availability of an archetype, sourced from the installed-napplet catalog. */
|
|
2046
|
+
interface IntentAvailability {
|
|
2047
|
+
/** The archetype this availability describes. */
|
|
2048
|
+
archetype: string;
|
|
2049
|
+
/** True when at least one installed napplet fulfills the archetype. */
|
|
2050
|
+
available: boolean;
|
|
2051
|
+
/** Candidate napplets that fulfill the archetype (from manifests, not instances). */
|
|
2052
|
+
candidates: IntentCandidate[];
|
|
2053
|
+
/** True when a user/runtime default handler is set for the archetype. */
|
|
2054
|
+
hasDefault: boolean;
|
|
2055
|
+
}
|
|
2056
|
+
/** The result of an intent invocation. */
|
|
2057
|
+
interface IntentResult {
|
|
2058
|
+
/** Whether the intent was dispatched successfully. */
|
|
2059
|
+
ok: boolean;
|
|
2060
|
+
/** The requested archetype, echoed back. */
|
|
2061
|
+
archetype: string;
|
|
2062
|
+
/** The resolved action (defaults to `"open"` when the request omits it). */
|
|
2063
|
+
action: string;
|
|
2064
|
+
/** True when a handler accepted the dispatch. */
|
|
2065
|
+
handled: boolean;
|
|
2066
|
+
/** dTag of the napplet that handled the intent, when one did. */
|
|
2067
|
+
handler?: string;
|
|
2068
|
+
/** Window id the handler was created or focused in. */
|
|
2069
|
+
windowId?: string;
|
|
2070
|
+
/** The wire format actually used to deliver `payload`. */
|
|
2071
|
+
protocol?: string;
|
|
2072
|
+
/** Error reason when the intent could not be fulfilled. */
|
|
2073
|
+
error?: string;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
/**
|
|
2077
|
+
* intent-service.ts — NAP-INTENT (archetype intent dispatch) reference service.
|
|
2078
|
+
*
|
|
2079
|
+
* Shell-side handler for the NAP-INTENT wire protocol. It is a pure envelope
|
|
2080
|
+
* router: it validates `intent.*` envelopes, delegates archetype resolution,
|
|
2081
|
+
* default handling, window lifecycle, and payload delivery to an injected
|
|
2082
|
+
* {@link IntentResolver}, and posts the correlated result / push messages
|
|
2083
|
+
* (echoing the request `id`) back to the napplet.
|
|
2084
|
+
*
|
|
2085
|
+
* The resolver is injected (options-as-bridge) so this service has no shell or
|
|
2086
|
+
* window-manager dependency and is fully unit-testable. A concrete
|
|
2087
|
+
* catalog-backed resolver ships alongside as {@link createCatalogIntentResolver}.
|
|
2088
|
+
*
|
|
2089
|
+
* ──────────────────────────── Responsibilities ────────────────────────────
|
|
2090
|
+
* Inbound: intent.invoke, intent.available, intent.handlers
|
|
2091
|
+
* Outbound: intent.invoke.result, intent.available.result,
|
|
2092
|
+
* intent.handlers.result, intent.changed
|
|
2093
|
+
*
|
|
2094
|
+
* The shell owns archetype→handler resolution, the user's default-handler
|
|
2095
|
+
* preference, the "open with…" chooser, and window creation/focus — all behind
|
|
2096
|
+
* the {@link IntentResolver}. This service only marshals the wire protocol and
|
|
2097
|
+
* fans `intent.changed` pushes out to the napplets it has served.
|
|
2098
|
+
*
|
|
2099
|
+
* @example
|
|
2100
|
+
* ```ts
|
|
2101
|
+
* import { createIntentService, createCatalogIntentResolver } from '@kehto/services';
|
|
2102
|
+
*
|
|
2103
|
+
* const resolver = createCatalogIntentResolver({ loadCatalog, windows });
|
|
2104
|
+
* runtime.registerService('intent', createIntentService({ resolver }));
|
|
2105
|
+
* ```
|
|
2106
|
+
*
|
|
2107
|
+
* @packageDocumentation
|
|
2108
|
+
*/
|
|
2109
|
+
|
|
2110
|
+
/** Context passed to {@link IntentResolver.invoke} for trust/attribution. */
|
|
2111
|
+
interface IntentResolverContext {
|
|
2112
|
+
/** Window id of the napplet that issued the request. */
|
|
2113
|
+
windowId: string;
|
|
2114
|
+
}
|
|
2115
|
+
/**
|
|
2116
|
+
* Abstract intent resolver. Implementors own the installed-napplet catalog,
|
|
2117
|
+
* archetype→handler resolution, the user's default-handler preference, the
|
|
2118
|
+
* "open with…" chooser, window creation/focus, and payload delivery. The
|
|
2119
|
+
* service translates wire envelopes into these calls and back.
|
|
2120
|
+
*/
|
|
2121
|
+
interface IntentResolver {
|
|
2122
|
+
/**
|
|
2123
|
+
* Resolve `request.archetype` to a handler, create or focus its window, and
|
|
2124
|
+
* deliver `payload`. Returns once the handler is resolved and the window
|
|
2125
|
+
* created; delivery MAY complete asynchronously.
|
|
2126
|
+
*/
|
|
2127
|
+
invoke(request: IntentRequest, context: IntentResolverContext): IntentResult | Promise<IntentResult>;
|
|
2128
|
+
/** Report whether the runtime can currently satisfy `archetype`, and how. */
|
|
2129
|
+
available(archetype: string): IntentAvailability | Promise<IntentAvailability>;
|
|
2130
|
+
/** Report availability for every archetype the runtime can currently satisfy. */
|
|
2131
|
+
handlers(): IntentAvailability[] | Promise<IntentAvailability[]>;
|
|
2132
|
+
/**
|
|
2133
|
+
* Register for availability changes (a napplet installed/removed, or a default
|
|
2134
|
+
* handler changed). The service forwards each change to served napplets as an
|
|
2135
|
+
* `intent.changed` push. Returns an unsubscribe handle. Resolvers whose
|
|
2136
|
+
* catalog never changes at runtime MAY omit this.
|
|
2137
|
+
*/
|
|
2138
|
+
onChanged?(listener: (availability: IntentAvailability) => void): () => void;
|
|
2139
|
+
}
|
|
2140
|
+
/** Options for {@link createIntentService}. */
|
|
2141
|
+
interface IntentServiceOptions {
|
|
2142
|
+
/** The intent resolver the shell uses to route archetypes. Required. */
|
|
2143
|
+
resolver: IntentResolver;
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Create the NAP-INTENT service handler.
|
|
2147
|
+
*
|
|
2148
|
+
* @param options - Must provide an {@link IntentResolver}.
|
|
2149
|
+
* @returns A `ServiceHandler` ready for `runtime.registerService('intent', handler)`.
|
|
2150
|
+
* @throws If `options.resolver` is missing.
|
|
2151
|
+
*/
|
|
2152
|
+
declare function createIntentService(options: IntentServiceOptions): ServiceHandler;
|
|
2153
|
+
|
|
2154
|
+
/**
|
|
2155
|
+
* catalog-intent-resolver.ts — NAP-INTENT concrete {@link IntentResolver}.
|
|
2156
|
+
*
|
|
2157
|
+
* A reference resolver that satisfies {@link IntentResolver} from an
|
|
2158
|
+
* installed-napplet catalog plus host-supplied policy: the user's
|
|
2159
|
+
* default-handler preference, an optional "open with…" chooser, the archetype's
|
|
2160
|
+
* recommended default protocol, and a window controller that creates or focuses
|
|
2161
|
+
* the handler window and delivers the payload.
|
|
2162
|
+
*
|
|
2163
|
+
* Resolution policy (mirrors an OS implicit-intent + default-app model):
|
|
2164
|
+
* 1. Gather candidates for the archetype from the catalog.
|
|
2165
|
+
* 2. Pick the handler — an explicit `handler` dTag, a user choice
|
|
2166
|
+
* (`handler: "choose"`), the user's default, the sole candidate, or a
|
|
2167
|
+
* deterministic first-candidate fallback.
|
|
2168
|
+
* 3. Validate the resolved handler supports the requested `action` and
|
|
2169
|
+
* `protocol` (or fall back to the archetype's recommended default protocol,
|
|
2170
|
+
* then the candidate's first accepted protocol).
|
|
2171
|
+
* 4. Open or focus the handler window and return its id.
|
|
2172
|
+
*
|
|
2173
|
+
* The catalog, defaults, chooser, and window controller are all injected, so
|
|
2174
|
+
* this resolver carries no shell, manifest, or DOM dependency and is fully
|
|
2175
|
+
* unit-testable.
|
|
2176
|
+
*
|
|
2177
|
+
* @packageDocumentation
|
|
2178
|
+
*/
|
|
2179
|
+
|
|
2180
|
+
/** The actions/protocols a napplet fulfills for a single archetype. */
|
|
2181
|
+
interface IntentArchetypeSupport {
|
|
2182
|
+
/** Verbs this napplet supports for the archetype (e.g. `["open", "edit"]`). */
|
|
2183
|
+
actions: string[];
|
|
2184
|
+
/** NAP-N protocol ids this napplet accepts for the archetype. */
|
|
2185
|
+
protocols: string[];
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* One installed napplet's intent surface, derived from its signed NIP-5A
|
|
2189
|
+
* manifest. Keyed by archetype slug so a single napplet can fulfill several
|
|
2190
|
+
* roles.
|
|
2191
|
+
*/
|
|
2192
|
+
interface IntentCatalogEntry {
|
|
2193
|
+
/** The napplet's dTag. */
|
|
2194
|
+
dTag: string;
|
|
2195
|
+
/** Human-readable title from the manifest. */
|
|
2196
|
+
title?: string;
|
|
2197
|
+
/** Archetype slug → the actions/protocols this napplet fulfills for it. */
|
|
2198
|
+
archetypes: Record<string, IntentArchetypeSupport>;
|
|
2199
|
+
}
|
|
2200
|
+
/** Parameters handed to {@link IntentWindowController.open}. */
|
|
2201
|
+
interface IntentOpenParams {
|
|
2202
|
+
/** dTag of the resolved handler napplet. */
|
|
2203
|
+
dTag: string;
|
|
2204
|
+
/** The archetype being dispatched. */
|
|
2205
|
+
archetype: string;
|
|
2206
|
+
/** The resolved action (e.g. `"open"`). */
|
|
2207
|
+
action: string;
|
|
2208
|
+
/** The wire format the payload is delivered with, when one was resolved. */
|
|
2209
|
+
protocol?: string;
|
|
2210
|
+
/** The opaque payload to deliver. */
|
|
2211
|
+
payload?: unknown;
|
|
2212
|
+
/** Window-behavior hints from the request. */
|
|
2213
|
+
behavior?: IntentBehavior;
|
|
2214
|
+
/** Window id of the napplet that issued the intent. */
|
|
2215
|
+
callerWindowId: string;
|
|
2216
|
+
}
|
|
2217
|
+
/** Creates or focuses the handler window and delivers the intent payload. */
|
|
2218
|
+
interface IntentWindowController {
|
|
2219
|
+
/**
|
|
2220
|
+
* Create or focus the handler window and deliver `payload` via `protocol`.
|
|
2221
|
+
* Returns the window id the handler was created or focused in.
|
|
2222
|
+
*/
|
|
2223
|
+
open(params: IntentOpenParams): {
|
|
2224
|
+
windowId: string;
|
|
2225
|
+
} | Promise<{
|
|
2226
|
+
windowId: string;
|
|
2227
|
+
}>;
|
|
2228
|
+
}
|
|
2229
|
+
/** Options for {@link createCatalogIntentResolver}. */
|
|
2230
|
+
interface CatalogIntentResolverOptions {
|
|
2231
|
+
/** Return the installed-napplet catalog (signed NIP-5A manifests). Required. */
|
|
2232
|
+
loadCatalog(): IntentCatalogEntry[] | Promise<IntentCatalogEntry[]>;
|
|
2233
|
+
/** Window controller used to create/focus the resolved handler's window. Required. */
|
|
2234
|
+
windows: IntentWindowController;
|
|
2235
|
+
/**
|
|
2236
|
+
* The user's default handler dTag for an archetype, or `undefined` when none
|
|
2237
|
+
* is set. Default-handler settings are user state — never set by napplets.
|
|
2238
|
+
*/
|
|
2239
|
+
getDefaultHandler?(archetype: string): string | undefined;
|
|
2240
|
+
/**
|
|
2241
|
+
* Resolve a `handler: "choose"` prompt (or a no-default ambiguity) to a dTag.
|
|
2242
|
+
* Returning `undefined` means the user cancelled. When omitted, ambiguous
|
|
2243
|
+
* resolution falls back to the first candidate.
|
|
2244
|
+
*/
|
|
2245
|
+
chooseHandler?(archetype: string, candidates: IntentCandidate[], callerWindowId: string): string | undefined | Promise<string | undefined>;
|
|
2246
|
+
/** The archetype's recommended default protocol when the caller omits one. */
|
|
2247
|
+
defaultProtocol?(archetype: string): string | undefined;
|
|
2248
|
+
}
|
|
2249
|
+
/**
|
|
2250
|
+
* A {@link IntentResolver} backed by a catalog, with a host hook to announce
|
|
2251
|
+
* catalog/default changes.
|
|
2252
|
+
*/
|
|
2253
|
+
interface CatalogIntentResolver extends IntentResolver {
|
|
2254
|
+
/**
|
|
2255
|
+
* Announce that the catalog or default handler for `archetype` changed. The
|
|
2256
|
+
* resolver recomputes availability and notifies `onChanged` listeners, which
|
|
2257
|
+
* the intent service forwards to napplets as `intent.changed` pushes.
|
|
2258
|
+
*/
|
|
2259
|
+
notifyChanged(archetype: string): void;
|
|
2260
|
+
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Create a catalog-backed NAP-INTENT resolver.
|
|
2263
|
+
*
|
|
2264
|
+
* @param options - Catalog loader and window controller (required) plus
|
|
2265
|
+
* optional default-handler, chooser, and default-protocol policy hooks.
|
|
2266
|
+
* @returns A {@link CatalogIntentResolver} for `createIntentService({ resolver })`.
|
|
2267
|
+
* @throws If `options.loadCatalog` or `options.windows` is missing.
|
|
2268
|
+
*
|
|
2269
|
+
* @example
|
|
2270
|
+
* ```ts
|
|
2271
|
+
* const resolver = createCatalogIntentResolver({
|
|
2272
|
+
* loadCatalog: () => installedNapplets,
|
|
2273
|
+
* windows: { open: ({ dTag }) => ({ windowId: openWindow(dTag) }) },
|
|
2274
|
+
* getDefaultHandler: (a) => userDefaults[a],
|
|
2275
|
+
* });
|
|
2276
|
+
* ```
|
|
2277
|
+
*/
|
|
2278
|
+
declare function createCatalogIntentResolver(options: CatalogIntentResolverOptions): CatalogIntentResolver;
|
|
2279
|
+
|
|
2280
|
+
export { type AudioServiceOptions, type AudioSource, type CacheServiceOptions, type CatalogIntentResolver, type CatalogIntentResolverOptions, type ConfigSchemaValidation, type ConfigService, type ConfigServiceOptions, type CoordinatedRelayOptions, type HostCacheBridge, type HostKeyEvent, type HostKeysBridge, type HostMediaBridge, type HttpUploaderOptions, type HttpUploaderRails, type IdentityServiceOptions, type IntentArchetypeSupport, type IntentAvailability, type IntentBehavior, type IntentCandidate, type IntentCatalogEntry, type IntentHandlerPreference, type IntentOpenParams, type IntentRequest, type IntentResolver, type IntentResolverContext, type IntentResult, type IntentServiceOptions, type IntentWindowController, type KeysServiceOptions, type MediaMetadataLike, type MediaPlaybackOwner, type MediaServiceOptions, type MediaSessionCreateOptions, type MediaSessionTarget, type MediaSourceRef, type NostrTag, type Notification, type NotificationServiceOptions, type NotifyServiceOptions, type OutboxPublishOptions, type OutboxPublishResult, type OutboxQueryOptions, type OutboxRelayPlan, type OutboxRelayPool, type OutboxResult, type OutboxRouter, type OutboxRouterSubscription, type OutboxServiceOptions, type OutboxStrategy, type OutboxSubscribeOptions, type OutboxSubscriptionSink, type OutboxTarget, type RailServerConfig, type RelayListEntry, type RelayPoolOutboxRouterOptions, type RelayPoolServiceOptions, type ResourceService, type ResourceServiceOptions, type SignEvent, type ThemeService, type ThemeServiceOptions, type UploadDimensions, type UploadRail, type UploadRequest, type UploadResult, type UploadServiceOptions, type UploadState, type UploadStatus, type Uploader, type UploaderContext, createAudioService, createBrowserMediaBridge, createCacheService, createCatalogIntentResolver, createConfigService, createCoordinatedRelay, createHttpUploader, createIdentityService, createIntentService, createKeysService, createMediaService, createNotificationService, createNotifyService, createOutboxService, createRelayPoolOutboxRouter, createRelayPoolService, createResourceService, createThemeService, createUploadService };
|
package/dist/index.js
CHANGED
|
@@ -2396,6 +2396,227 @@ function toErrorMessage3(err) {
|
|
|
2396
2396
|
return "upload failed";
|
|
2397
2397
|
}
|
|
2398
2398
|
|
|
2399
|
+
// src/intent-service.ts
|
|
2400
|
+
var INTENT_SERVICE_VERSION = "1.0.0";
|
|
2401
|
+
var INTENT_DESCRIPTOR = {
|
|
2402
|
+
name: "intent",
|
|
2403
|
+
version: INTENT_SERVICE_VERSION,
|
|
2404
|
+
description: "NAP-INTENT archetype intent dispatch \u2014 invoke/available/handlers"
|
|
2405
|
+
};
|
|
2406
|
+
function toErrorMessage4(err) {
|
|
2407
|
+
if (err instanceof Error) return err.message;
|
|
2408
|
+
if (typeof err === "string") return err;
|
|
2409
|
+
return "intent request failed";
|
|
2410
|
+
}
|
|
2411
|
+
function createIntentService(options) {
|
|
2412
|
+
if (!options || typeof options.resolver !== "object" || options.resolver === null) {
|
|
2413
|
+
throw new Error("createIntentService: options.resolver is required");
|
|
2414
|
+
}
|
|
2415
|
+
const { resolver } = options;
|
|
2416
|
+
const windows = /* @__PURE__ */ new Map();
|
|
2417
|
+
resolver.onChanged?.((availability) => {
|
|
2418
|
+
for (const send of windows.values()) {
|
|
2419
|
+
send({ type: "intent.changed", availability });
|
|
2420
|
+
}
|
|
2421
|
+
});
|
|
2422
|
+
function settle(call, send, resultType, id, onValue) {
|
|
2423
|
+
let pending;
|
|
2424
|
+
try {
|
|
2425
|
+
pending = Promise.resolve(call());
|
|
2426
|
+
} catch (err) {
|
|
2427
|
+
send({ type: resultType, id, error: toErrorMessage4(err) });
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
pending.then((value) => send(onValue(value))).catch((err) => send({ type: resultType, id, error: toErrorMessage4(err) }));
|
|
2431
|
+
}
|
|
2432
|
+
function handleInvoke(windowId, msg, send) {
|
|
2433
|
+
const m = msg;
|
|
2434
|
+
const id = m.id ?? "";
|
|
2435
|
+
const request = m.request;
|
|
2436
|
+
if (!request || typeof request !== "object" || typeof request.archetype !== "string" || request.archetype.length === 0) {
|
|
2437
|
+
send({ type: "intent.invoke.result", id, error: "invalid request" });
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
settle(
|
|
2441
|
+
() => resolver.invoke(request, { windowId }),
|
|
2442
|
+
send,
|
|
2443
|
+
"intent.invoke.result",
|
|
2444
|
+
id,
|
|
2445
|
+
(result) => ({ type: "intent.invoke.result", id, result })
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
function handleAvailable(msg, send) {
|
|
2449
|
+
const m = msg;
|
|
2450
|
+
const id = m.id ?? "";
|
|
2451
|
+
if (typeof m.archetype !== "string" || m.archetype.length === 0) {
|
|
2452
|
+
send({ type: "intent.available.result", id, error: "invalid archetype" });
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
const archetype = m.archetype;
|
|
2456
|
+
settle(
|
|
2457
|
+
() => resolver.available(archetype),
|
|
2458
|
+
send,
|
|
2459
|
+
"intent.available.result",
|
|
2460
|
+
id,
|
|
2461
|
+
(availability) => ({ type: "intent.available.result", id, availability })
|
|
2462
|
+
);
|
|
2463
|
+
}
|
|
2464
|
+
function handleHandlers(msg, send) {
|
|
2465
|
+
const m = msg;
|
|
2466
|
+
const id = m.id ?? "";
|
|
2467
|
+
settle(
|
|
2468
|
+
() => resolver.handlers(),
|
|
2469
|
+
send,
|
|
2470
|
+
"intent.handlers.result",
|
|
2471
|
+
id,
|
|
2472
|
+
(handlers) => ({ type: "intent.handlers.result", id, handlers })
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2475
|
+
return {
|
|
2476
|
+
descriptor: INTENT_DESCRIPTOR,
|
|
2477
|
+
handleMessage(windowId, message, send) {
|
|
2478
|
+
windows.set(windowId, send);
|
|
2479
|
+
switch (message.type) {
|
|
2480
|
+
case "intent.invoke":
|
|
2481
|
+
handleInvoke(windowId, message, send);
|
|
2482
|
+
return;
|
|
2483
|
+
case "intent.available":
|
|
2484
|
+
handleAvailable(message, send);
|
|
2485
|
+
return;
|
|
2486
|
+
case "intent.handlers":
|
|
2487
|
+
handleHandlers(message, send);
|
|
2488
|
+
return;
|
|
2489
|
+
default:
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
},
|
|
2493
|
+
onWindowDestroyed(windowId) {
|
|
2494
|
+
windows.delete(windowId);
|
|
2495
|
+
}
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
// src/catalog-intent-resolver.ts
|
|
2500
|
+
function candidatesFor(catalog, archetype, defaultHandler) {
|
|
2501
|
+
const candidates = [];
|
|
2502
|
+
for (const entry of catalog) {
|
|
2503
|
+
const support = entry.archetypes[archetype];
|
|
2504
|
+
if (!support) continue;
|
|
2505
|
+
candidates.push({
|
|
2506
|
+
dTag: entry.dTag,
|
|
2507
|
+
...entry.title === void 0 ? {} : { title: entry.title },
|
|
2508
|
+
actions: support.actions,
|
|
2509
|
+
protocols: support.protocols,
|
|
2510
|
+
...entry.dTag === defaultHandler ? { isDefault: true } : {}
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
return candidates;
|
|
2514
|
+
}
|
|
2515
|
+
function fail(archetype, action, error) {
|
|
2516
|
+
return { ok: false, archetype, action, handled: false, error };
|
|
2517
|
+
}
|
|
2518
|
+
function createCatalogIntentResolver(options) {
|
|
2519
|
+
if (!options || typeof options.loadCatalog !== "function") {
|
|
2520
|
+
throw new Error("createCatalogIntentResolver: options.loadCatalog is required");
|
|
2521
|
+
}
|
|
2522
|
+
if (!options.windows || typeof options.windows.open !== "function") {
|
|
2523
|
+
throw new Error("createCatalogIntentResolver: options.windows is required");
|
|
2524
|
+
}
|
|
2525
|
+
const { loadCatalog, windows, getDefaultHandler, chooseHandler, defaultProtocol } = options;
|
|
2526
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2527
|
+
async function availabilityFor(archetype) {
|
|
2528
|
+
const catalog = await loadCatalog();
|
|
2529
|
+
const def = getDefaultHandler?.(archetype);
|
|
2530
|
+
const candidates = candidatesFor(catalog, archetype, def);
|
|
2531
|
+
return {
|
|
2532
|
+
archetype,
|
|
2533
|
+
available: candidates.length > 0,
|
|
2534
|
+
candidates,
|
|
2535
|
+
hasDefault: def !== void 0 && candidates.some((c) => c.dTag === def)
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
async function pickHandler(archetype, candidates, preference, callerWindowId) {
|
|
2539
|
+
if (typeof preference === "string" && preference !== "default" && preference !== "choose") {
|
|
2540
|
+
return preference;
|
|
2541
|
+
}
|
|
2542
|
+
if (preference === "choose") {
|
|
2543
|
+
const picked = await chooseHandler?.(archetype, candidates, callerWindowId);
|
|
2544
|
+
return picked ?? null;
|
|
2545
|
+
}
|
|
2546
|
+
const def = getDefaultHandler?.(archetype);
|
|
2547
|
+
if (def !== void 0 && candidates.some((c) => c.dTag === def)) return def;
|
|
2548
|
+
if (candidates.length === 1) return candidates[0].dTag;
|
|
2549
|
+
if (chooseHandler) {
|
|
2550
|
+
const picked = await chooseHandler(archetype, candidates, callerWindowId);
|
|
2551
|
+
return picked ?? null;
|
|
2552
|
+
}
|
|
2553
|
+
return candidates[0].dTag;
|
|
2554
|
+
}
|
|
2555
|
+
async function invoke(request, context) {
|
|
2556
|
+
const archetype = request.archetype;
|
|
2557
|
+
const action = request.action ?? "open";
|
|
2558
|
+
const catalog = await loadCatalog();
|
|
2559
|
+
const candidates = candidatesFor(catalog, archetype, getDefaultHandler?.(archetype));
|
|
2560
|
+
if (candidates.length === 0) return fail(archetype, action, "no handler");
|
|
2561
|
+
const pickedDTag = await pickHandler(archetype, candidates, request.handler, context.windowId);
|
|
2562
|
+
if (pickedDTag === null) return fail(archetype, action, "user cancelled");
|
|
2563
|
+
const handler = candidates.find((c) => c.dTag === pickedDTag);
|
|
2564
|
+
if (!handler) return fail(archetype, action, "no handler");
|
|
2565
|
+
if (!handler.actions.includes(action)) return fail(archetype, action, "unsupported action");
|
|
2566
|
+
const protocol = request.protocol ?? defaultProtocol?.(archetype) ?? handler.protocols[0];
|
|
2567
|
+
if (protocol !== void 0 && handler.protocols.length > 0 && !handler.protocols.includes(protocol)) {
|
|
2568
|
+
return fail(archetype, action, "unsupported protocol");
|
|
2569
|
+
}
|
|
2570
|
+
let windowId;
|
|
2571
|
+
try {
|
|
2572
|
+
const opened = await windows.open({
|
|
2573
|
+
dTag: handler.dTag,
|
|
2574
|
+
archetype,
|
|
2575
|
+
action,
|
|
2576
|
+
...protocol === void 0 ? {} : { protocol },
|
|
2577
|
+
...request.payload === void 0 ? {} : { payload: request.payload },
|
|
2578
|
+
...request.behavior === void 0 ? {} : { behavior: request.behavior },
|
|
2579
|
+
callerWindowId: context.windowId
|
|
2580
|
+
});
|
|
2581
|
+
windowId = opened.windowId;
|
|
2582
|
+
} catch {
|
|
2583
|
+
return fail(archetype, action, "invoke failed");
|
|
2584
|
+
}
|
|
2585
|
+
return {
|
|
2586
|
+
ok: true,
|
|
2587
|
+
archetype,
|
|
2588
|
+
action,
|
|
2589
|
+
handled: true,
|
|
2590
|
+
handler: handler.dTag,
|
|
2591
|
+
windowId,
|
|
2592
|
+
...protocol === void 0 ? {} : { protocol }
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
async function handlers() {
|
|
2596
|
+
const catalog = await loadCatalog();
|
|
2597
|
+
const archetypes = /* @__PURE__ */ new Set();
|
|
2598
|
+
for (const entry of catalog) {
|
|
2599
|
+
for (const slug of Object.keys(entry.archetypes)) archetypes.add(slug);
|
|
2600
|
+
}
|
|
2601
|
+
return Promise.all([...archetypes].map((a) => availabilityFor(a)));
|
|
2602
|
+
}
|
|
2603
|
+
return {
|
|
2604
|
+
invoke,
|
|
2605
|
+
available: availabilityFor,
|
|
2606
|
+
handlers,
|
|
2607
|
+
onChanged(listener) {
|
|
2608
|
+
listeners.add(listener);
|
|
2609
|
+
return () => listeners.delete(listener);
|
|
2610
|
+
},
|
|
2611
|
+
notifyChanged(archetype) {
|
|
2612
|
+
if (listeners.size === 0) return;
|
|
2613
|
+
void availabilityFor(archetype).then((availability) => {
|
|
2614
|
+
for (const listener of listeners) listener(availability);
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2399
2620
|
// src/cvm-service.ts
|
|
2400
2621
|
var CVM_SERVICE_VERSION = "1.0.0";
|
|
2401
2622
|
var CVM_DESCRIPTOR = {
|
|
@@ -2438,7 +2659,7 @@ function createCvmService(options) {
|
|
|
2438
2659
|
const m = msg;
|
|
2439
2660
|
const id = m.id ?? "";
|
|
2440
2661
|
void transport.discover(m.query).then((servers) => send({ type: "cvm.discover.result", id, servers })).catch(
|
|
2441
|
-
(err) => send({ type: "cvm.discover.result", id, servers: [], error:
|
|
2662
|
+
(err) => send({ type: "cvm.discover.result", id, servers: [], error: toErrorMessage5(err) })
|
|
2442
2663
|
);
|
|
2443
2664
|
}
|
|
2444
2665
|
function handleRequest(windowId, msg, send) {
|
|
@@ -2454,7 +2675,7 @@ function createCvmService(options) {
|
|
|
2454
2675
|
}
|
|
2455
2676
|
openSession(windowId, m.server, send);
|
|
2456
2677
|
void transport.request(m.server, m.message, m.options).then((message) => send({ type: "cvm.request.result", id, message })).catch(
|
|
2457
|
-
(err) => send({ type: "cvm.request.result", id, error:
|
|
2678
|
+
(err) => send({ type: "cvm.request.result", id, error: toErrorMessage5(err) })
|
|
2458
2679
|
);
|
|
2459
2680
|
}
|
|
2460
2681
|
function handleClose(windowId, msg, send) {
|
|
@@ -2465,7 +2686,7 @@ function createCvmService(options) {
|
|
|
2465
2686
|
return;
|
|
2466
2687
|
}
|
|
2467
2688
|
closeSession(windowId, m.server.pubkey);
|
|
2468
|
-
void transport.close(m.server).then(() => send({ type: "cvm.close.result", id })).catch((err) => send({ type: "cvm.close.result", id, error:
|
|
2689
|
+
void transport.close(m.server).then(() => send({ type: "cvm.close.result", id })).catch((err) => send({ type: "cvm.close.result", id, error: toErrorMessage5(err) }));
|
|
2469
2690
|
}
|
|
2470
2691
|
return {
|
|
2471
2692
|
descriptor: CVM_DESCRIPTOR,
|
|
@@ -2496,7 +2717,7 @@ function createCvmService(options) {
|
|
|
2496
2717
|
}
|
|
2497
2718
|
};
|
|
2498
2719
|
}
|
|
2499
|
-
function
|
|
2720
|
+
function toErrorMessage5(err) {
|
|
2500
2721
|
if (err instanceof Error) return err.message;
|
|
2501
2722
|
if (typeof err === "string") return err;
|
|
2502
2723
|
return "cvm request failed";
|
|
@@ -2505,11 +2726,13 @@ export {
|
|
|
2505
2726
|
createAudioService,
|
|
2506
2727
|
createBrowserMediaBridge,
|
|
2507
2728
|
createCacheService,
|
|
2729
|
+
createCatalogIntentResolver,
|
|
2508
2730
|
createConfigService,
|
|
2509
2731
|
createCoordinatedRelay,
|
|
2510
2732
|
createCvmService,
|
|
2511
2733
|
createHttpUploader,
|
|
2512
2734
|
createIdentityService,
|
|
2735
|
+
createIntentService,
|
|
2513
2736
|
createKeysService,
|
|
2514
2737
|
createMediaService,
|
|
2515
2738
|
createNotificationService,
|