@immediately-run/sdk 0.7.0 → 0.8.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/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mounts.cjs.map +1 -1
- package/dist/mounts.d.cts +24 -3
- package/dist/mounts.d.ts +24 -3
- package/dist/mounts.js.map +1 -1
- package/dist/netFetch.cjs +12 -2
- package/dist/netFetch.cjs.map +1 -1
- package/dist/netFetch.d.cts +48 -1
- package/dist/netFetch.d.ts +48 -1
- package/dist/netFetch.js +10 -1
- package/dist/netFetch.js.map +1 -1
- package/dist/runtime.cjs +3 -3
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.cts +3 -4
- package/dist/runtime.d.ts +3 -4
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/secrets.cjs +63 -0
- package/dist/secrets.cjs.map +1 -0
- package/dist/secrets.d.cts +83 -0
- package/dist/secrets.d.ts +83 -0
- package/dist/secrets.js +34 -0
- package/dist/secrets.js.map +1 -0
- package/dist/version.cjs +29 -0
- package/dist/version.cjs.map +1 -0
- package/dist/version.d.cts +4 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +3 -1
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ __reExport(index_exports, require("./contribute"), module.exports);
|
|
|
31
31
|
__reExport(index_exports, require("./catalog"), module.exports);
|
|
32
32
|
__reExport(index_exports, require("./ipc"), module.exports);
|
|
33
33
|
__reExport(index_exports, require("./netFetch"), module.exports);
|
|
34
|
+
__reExport(index_exports, require("./secrets"), module.exports);
|
|
34
35
|
__reExport(index_exports, require("./tasks"), module.exports);
|
|
35
36
|
__reExport(index_exports, require("./runtime"), module.exports);
|
|
36
37
|
__reExport(index_exports, require("./protocolStream"), module.exports);
|
|
@@ -53,6 +54,7 @@ __reExport(index_exports, require("./sandboxTypes"), module.exports);
|
|
|
53
54
|
...require("./catalog"),
|
|
54
55
|
...require("./ipc"),
|
|
55
56
|
...require("./netFetch"),
|
|
57
|
+
...require("./secrets"),
|
|
56
58
|
...require("./tasks"),
|
|
57
59
|
...require("./runtime"),
|
|
58
60
|
...require("./protocolStream"),
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\nexport * from './components/MDXComponents';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './netFetch';\nexport * from './tasks';\nexport * from './runtime';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\n"],"mappings":";;;;;;;;;;;;;;;AAAA;AAAA;AAAA,0BAAc,0BAAd;AACA,0BAAc,sBADd;AAEA,0BAAc,mBAFd;AAGA,0BAAc,iCAHd;AAIA,0BAAc,uCAJd;AAKA,0BAAc,oBALd;AAMA,0BAAc,mBANd;AAOA,0BAAc,oBAPd;AAQA,0BAAc,4BARd;AASA,0BAAc,qBATd;AAUA,0BAAc,yBAVd;AAWA,0BAAc,qBAXd;AAYA,0BAAc,yBAZd;AAaA,0BAAc,sBAbd;AAcA,0BAAc,kBAdd;AAeA,0BAAc,uBAfd;AAgBA,0BAAc,
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\nexport * from './components/MDXComponents';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './tasks';\nexport * from './runtime';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\n"],"mappings":";;;;;;;;;;;;;;;AAAA;AAAA;AAAA,0BAAc,0BAAd;AACA,0BAAc,sBADd;AAEA,0BAAc,mBAFd;AAGA,0BAAc,iCAHd;AAIA,0BAAc,uCAJd;AAKA,0BAAc,oBALd;AAMA,0BAAc,mBANd;AAOA,0BAAc,oBAPd;AAQA,0BAAc,4BARd;AASA,0BAAc,qBATd;AAUA,0BAAc,yBAVd;AAWA,0BAAc,qBAXd;AAYA,0BAAc,yBAZd;AAaA,0BAAc,sBAbd;AAcA,0BAAc,kBAdd;AAeA,0BAAc,uBAfd;AAgBA,0BAAc,sBAhBd;AAiBA,0BAAc,oBAjBd;AAkBA,0BAAc,sBAlBd;AAmBA,0BAAc,6BAnBd;AAoBA,0BAAc,2BApBd;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -9,16 +9,18 @@ export { HostTheme, getHostTheme, onHostThemeChange, setHostTheme, useHostTheme
|
|
|
9
9
|
export { EditorContext, getEditorContext, onEditorContextChange, useEditorContext } from './editorContext.cjs';
|
|
10
10
|
export { EditorOpenError, EditorWriteError, createFile, createFolder, deleteEntry, openInEditor, renameEntry, uploadFile } from './editor.cjs';
|
|
11
11
|
export { FormFactor, FormFactorClass, Orientation, getFormFactor, onFormFactorChange, useFormFactor } from './formFactor.cjs';
|
|
12
|
-
export { GrantRecord, Member, MountQuery, ResolvedUser, Role, SandboxMount, SpaceError, SpaceInfo, createSpace, findMount, getAppMountPath, getMounts, getSpaceMembers, listAllSpaces, listGrants, listSpaces, lookupUser, mount, mountSpace, onMountsChange, openAppSpace, requestMount, requestSpace, revokeGrant, setSpaceRole, shareSpace, unmountSpace, unshareSpace, useMounts, waitForMount } from './mounts.cjs';
|
|
12
|
+
export { GrantRecord, Member, MountQuery, MountRemoveReason, RemovedMount, ResolvedUser, Role, SandboxMount, SpaceError, SpaceInfo, createSpace, findMount, getAppMountPath, getMounts, getSpaceMembers, listAllSpaces, listGrants, listSpaces, lookupUser, mount, mountSpace, onMountsChange, openAppSpace, requestMount, requestSpace, revokeGrant, setSpaceRole, shareSpace, unmountSpace, unshareSpace, useMounts, waitForMount } from './mounts.cjs';
|
|
13
13
|
export { ContributeMode, ContributeOptions, ContributionEvent, ContributionResult, contribute } from './contribute.cjs';
|
|
14
14
|
export { ApiMethod, getCatalog, invoke, invokeStream, onCatalogChange, useCatalog } from './catalog.cjs';
|
|
15
15
|
export { RegionMessage, onRegionMessage, postToRegion, useRegionMessage } from './ipc.cjs';
|
|
16
|
-
export { HostFetchInit, HostFetchResponse, hostFetch } from './netFetch.cjs';
|
|
16
|
+
export { HostFetchInit, HostFetchResponse, HostFetchStreamEvent, HostFetchStreamResult, hostFetch, hostFetchStream } from './netFetch.cjs';
|
|
17
|
+
export { SecretError, SecretGrant, SecretHints, SecretQuery, SecretType, SecretView, getSecrets, onSecretsChange, requestAddSecret, requestSecret, revokeSecret, useSecrets } from './secrets.cjs';
|
|
17
18
|
export { FileCap, TaskInput, cancelTask, capFile, completeTask, getTaskInput, invokeTask, useTaskInput } from './tasks.cjs';
|
|
18
|
-
export { SDK_PROTOCOL_VERSION,
|
|
19
|
+
export { SDK_PROTOCOL_VERSION, SdkHandshake, announceHandshake, sdkHandshake } from './runtime.cjs';
|
|
19
20
|
export { StreamError, StreamFrame, StreamTransport, consumeStream, protocolStream } from './protocolStream.cjs';
|
|
20
21
|
export { EvaluationContext, FileQueryResult, FilesMetadata, Metadata, MetadataQueryFunction, MetadataQueryResult, ModuleExports } from './sandboxTypes.cjs';
|
|
21
22
|
export { ImmediatelyRunGlobal, getHostRuntime } from './hostRuntime.cjs';
|
|
23
|
+
export { SDK_VERSION } from './version.cjs';
|
|
22
24
|
import 'react';
|
|
23
25
|
import './TinkerableContext.cjs';
|
|
24
26
|
import './RoutingSpec.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -9,16 +9,18 @@ export { HostTheme, getHostTheme, onHostThemeChange, setHostTheme, useHostTheme
|
|
|
9
9
|
export { EditorContext, getEditorContext, onEditorContextChange, useEditorContext } from './editorContext.js';
|
|
10
10
|
export { EditorOpenError, EditorWriteError, createFile, createFolder, deleteEntry, openInEditor, renameEntry, uploadFile } from './editor.js';
|
|
11
11
|
export { FormFactor, FormFactorClass, Orientation, getFormFactor, onFormFactorChange, useFormFactor } from './formFactor.js';
|
|
12
|
-
export { GrantRecord, Member, MountQuery, ResolvedUser, Role, SandboxMount, SpaceError, SpaceInfo, createSpace, findMount, getAppMountPath, getMounts, getSpaceMembers, listAllSpaces, listGrants, listSpaces, lookupUser, mount, mountSpace, onMountsChange, openAppSpace, requestMount, requestSpace, revokeGrant, setSpaceRole, shareSpace, unmountSpace, unshareSpace, useMounts, waitForMount } from './mounts.js';
|
|
12
|
+
export { GrantRecord, Member, MountQuery, MountRemoveReason, RemovedMount, ResolvedUser, Role, SandboxMount, SpaceError, SpaceInfo, createSpace, findMount, getAppMountPath, getMounts, getSpaceMembers, listAllSpaces, listGrants, listSpaces, lookupUser, mount, mountSpace, onMountsChange, openAppSpace, requestMount, requestSpace, revokeGrant, setSpaceRole, shareSpace, unmountSpace, unshareSpace, useMounts, waitForMount } from './mounts.js';
|
|
13
13
|
export { ContributeMode, ContributeOptions, ContributionEvent, ContributionResult, contribute } from './contribute.js';
|
|
14
14
|
export { ApiMethod, getCatalog, invoke, invokeStream, onCatalogChange, useCatalog } from './catalog.js';
|
|
15
15
|
export { RegionMessage, onRegionMessage, postToRegion, useRegionMessage } from './ipc.js';
|
|
16
|
-
export { HostFetchInit, HostFetchResponse, hostFetch } from './netFetch.js';
|
|
16
|
+
export { HostFetchInit, HostFetchResponse, HostFetchStreamEvent, HostFetchStreamResult, hostFetch, hostFetchStream } from './netFetch.js';
|
|
17
|
+
export { SecretError, SecretGrant, SecretHints, SecretQuery, SecretType, SecretView, getSecrets, onSecretsChange, requestAddSecret, requestSecret, revokeSecret, useSecrets } from './secrets.js';
|
|
17
18
|
export { FileCap, TaskInput, cancelTask, capFile, completeTask, getTaskInput, invokeTask, useTaskInput } from './tasks.js';
|
|
18
|
-
export { SDK_PROTOCOL_VERSION,
|
|
19
|
+
export { SDK_PROTOCOL_VERSION, SdkHandshake, announceHandshake, sdkHandshake } from './runtime.js';
|
|
19
20
|
export { StreamError, StreamFrame, StreamTransport, consumeStream, protocolStream } from './protocolStream.js';
|
|
20
21
|
export { EvaluationContext, FileQueryResult, FilesMetadata, Metadata, MetadataQueryFunction, MetadataQueryResult, ModuleExports } from './sandboxTypes.js';
|
|
21
22
|
export { ImmediatelyRunGlobal, getHostRuntime } from './hostRuntime.js';
|
|
23
|
+
export { SDK_VERSION } from './version.js';
|
|
22
24
|
import 'react';
|
|
23
25
|
import './TinkerableContext.js';
|
|
24
26
|
import './RoutingSpec.js';
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\nexport * from './components/MDXComponents';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './netFetch';\nexport * from './tasks';\nexport * from './runtime';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\n"],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./MDXProvider\";\nexport * from \"./routing\";\nexport * from \"./boot\";\nexport * from './components/Include';\nexport * from './components/MDXComponents';\nexport * from './hooks'\nexport * from './auth';\nexport * from './theme';\nexport * from './editorContext';\nexport * from './editor';\nexport * from './formFactor';\nexport * from './mounts';\nexport * from './contribute';\nexport * from './catalog';\nexport * from './ipc';\nexport * from './netFetch';\nexport * from './secrets';\nexport * from './tasks';\nexport * from './runtime';\nexport * from './protocolStream';\nexport * from './sandboxTypes';\n"],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
|
package/dist/mounts.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mounts.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { protocolRequest } from './sandboxUtils';\nimport { getHostRuntime } from './hostRuntime';\n\n/**\n * The absolute path where this app's own repository filesystem is mounted\n * (FILE_SHARING_SPEC §11.2). Prefer this over hardcoding `/app`: the repo is\n * dual-mounted at both `/app` (back-compat) and its canonical `/mnt/{hash}`\n * address, and this returns the canonical one the host reports. Falls back to\n * `/app` when the host hasn't reported a canonical path (older host / before the\n * report arrives) — both paths are live, so either resolves the same files.\n */\nexport const getAppMountPath = (): string => getHostRuntime()?.appMountPath ?? '/app';\n\n/**\n * A filesystem mount available to the sandbox, mirrored from the host window.\n *\n * Mounts appear on demand — call {@link openAppSpace} / {@link mountSpace} to ask\n * the host to mount a Firestore-backed \"space\"; it appears at `/spaces/{id}`.\n * Read or subscribe to the set, then access the files through the `fs` module at\n * the mount's `path`.\n */\nexport interface SandboxMount {\n /** Absolute path where the mount is reachable (e.g. `/spaces/{id}`). */\n path: string;\n /** Backend kind, e.g. `'firestore'`. */\n type: string;\n /** Optional stable identifier (the spaceId, for spaces). */\n id?: string;\n /**\n * Access mode of the granted view: `'rw'` (read-write) or `'ro'` (read-only).\n * A live role downgrade re-announces the same mount with `mode: 'ro'`; apps\n * observing `onMountsChange` see the change and writes start failing `EROFS`.\n * Absent on the primary repo mount (treated as read-write).\n */\n mode?: \"ro\" | \"rw\";\n}\n\ninterface MountService {\n getMounts(): SandboxMount[];\n onChange(listener: (mounts: SandboxMount[]) => void): { dispose(): void };\n}\n\n// `module.evaluation.module.bundler` is the sandbox bundler injected into the\n// evaluation context (same path the other SDK helpers reach for `messageBus`).\nconst mountService = (): MountService => {\n // @ts-ignore - injected by the sandbox runtime\n return module.evaluation.module.bundler.mounts;\n};\n\n/** A predicate-style matcher for {@link findMount} / {@link waitForMount}. */\nexport type MountQuery = { type?: string; id?: string; path?: string };\n\nconst matches = (mount: SandboxMount, query: MountQuery): boolean =>\n (query.type === undefined || mount.type === query.type) &&\n (query.id === undefined || mount.id === query.id) &&\n (query.path === undefined || mount.path === query.path);\n\n/**\n * Returns the mounts currently available. Poll this whenever you need a one-off\n * read; use {@link onMountsChange} or {@link useMounts} to react to changes.\n */\nexport const getMounts = (): SandboxMount[] => mountService().getMounts();\n\n/** Returns the first mount matching `query`, or `undefined`. */\nexport const findMount = (query: MountQuery): SandboxMount | undefined =>\n getMounts().find((m) => matches(m, query));\n\n/**\n * Subscribe to mount changes. The listener is invoked immediately with the\n * current mounts, then again on every change. Returns an unsubscribe fn.\n */\nexport const onMountsChange = (listener: (mounts: SandboxMount[]) => void): (() => void) => {\n const disposable = mountService().onChange(listener);\n return () => disposable.dispose();\n};\n\n/**\n * Resolves once a mount matching `query` is present (immediately if it already\n * is). Handy for \"use it when it appears\" — e.g.\n * `await waitForMount({ type: 'firestore' })` before reading `/firestore`.\n */\nexport const waitForMount = (query: MountQuery): Promise<SandboxMount> =>\n new Promise((resolve) => {\n const unsubscribe = onMountsChange((mounts) => {\n const found = mounts.find((m) => matches(m, query));\n if (found) {\n // Defer unsubscribe so we don't dispose during the initial replay call.\n Promise.resolve().then(unsubscribe);\n resolve(found);\n }\n });\n });\n\n/** React hook returning the mounts currently available, re-rendering on change. */\nexport const useMounts = (): SandboxMount[] => {\n const [mounts, setMounts] = useState<SandboxMount[]>(getMounts);\n useEffect(() => onMountsChange(setMounts), []);\n return mounts;\n};\n\n// ---------------------------------------------------------------------------\n// Spaces — on-demand, shareable Firestore-backed filesystems.\n// The host owns all UX: if you aren't signed in, or the space doesn't exist or\n// isn't accessible, the parent window presents sign-in / create / request-access\n// and only then resolves these calls. See docs/specs/FILE_SHARING_SPEC.md.\n// ---------------------------------------------------------------------------\n\n/** Summary of a space, as returned by {@link listSpaces}. */\nexport interface SpaceInfo {\n spaceId: string;\n role?: 'owner' | 'writer' | 'reader';\n owner?: string;\n name?: string;\n}\n\n/** An error from a space operation, carrying a machine-readable `code`. */\nexport interface SpaceError extends Error {\n code:\n | 'auth-required'\n | 'cancelled'\n | 'forbidden'\n | 'not-found'\n | 'unsupported-scheme'\n | 'unknown';\n}\n\ntype SpaceResult =\n | { ok: true; data: unknown }\n | { ok: false; code: string; message: string };\n\n// Issue a spaces protocol request, unwrapping the host's {ok,data} envelope and\n// throwing a typed SpaceError on failure.\nconst request = async <T = unknown>(\n method: string,\n query: Record<string, unknown> = {},\n): Promise<T> => {\n const res = (await protocolRequest('spaces', method, [query])) as SpaceResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'space request failed') as SpaceError;\n err.code = (res?.code as SpaceError['code']) ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n// Request a space mount, then wait until the host actually registers it. The\n// host announces the mount (`mount-add`) separately from the protocol reply, so\n// an immediate read could otherwise race the mount.\nconst requestMountInternal = async (\n method: string,\n query: Record<string, unknown>,\n): Promise<SandboxMount> => {\n const mount = await request<SandboxMount>(method, query);\n return waitForMount({ id: mount.id ?? mount.path });\n};\n\n/**\n * Open this app's workspace for the signed-in user (the zero-config path). The\n * `slot` names which workspace (default `'default'`); pass distinct slots for\n * multiple filesystems in one app. On a missing slot the host shows a\n * create-or-pick dialog. Rejects with a {@link SpaceError} (`.code`) on cancel.\n */\nexport const openAppSpace = (slot = 'default'): Promise<SandboxMount> =>\n requestMountInternal('open', { slot });\n\n/**\n * Mount a filesystem by its **universal mount id** (UI_AS_APPS_SPEC §3.5) —\n * `scheme:locator`, e.g. `space:{spaceId}` or `github:owner/repo@ref`. Backend-blind:\n * the host resolves the scheme. A scheme with no resolver rejects with\n * {@link SpaceError} `unsupported-scheme`.\n */\nexport const mount = (mountId: string): Promise<SandboxMount> =>\n requestMountInternal('mount', { mount: mountId });\n\n/** Mount a specific space by id (e.g. one shared with you, or from a link). A thin\n * shim over {@link mount} with the `space:` scheme. */\nexport const mountSpace = (query: { spaceId: string }): Promise<SandboxMount> =>\n mount(`space:${query.spaceId}`);\n\n/**\n * Ask the user to grant a filesystem to this app — the §8.6 powerbox. The app\n * asks; the HOST shows the user their mounts and the access choice (which mount,\n * an optional subtree, read-only vs read-write); the USER picks or declines. The\n * app never sees the list — it resolves with the single granted mount, or rejects\n * with a {@link SpaceError} (`cancelled`) if declined. The granted scope is\n * enforced host-side: the mount is chroot'd / `ro`-limited accordingly.\n *\n * Backend-general (§3.5): the picker offers whatever mounts the user has (today,\n * their spaces). Returns the granted mount by its universal id.\n */\nexport const requestMount = (): Promise<SandboxMount> =>\n requestMountInternal('request', {});\n\n/** @deprecated renamed to {@link requestMount} (backend-general, §3.5). */\nexport const requestSpace = requestMount;\n\n/** Create a brand-new space, optionally binding it to this app (a slot). */\nexport const createSpace = (\n opts: { name?: string; slot?: string; bindToApp?: boolean } = {}\n): Promise<SandboxMount> => requestMountInternal('create', opts);\n\n/** List spaces you can access — all of them, or just those bound to this app. */\nexport const listSpaces = (opts: { app?: boolean } = {}): Promise<SpaceInfo[]> =>\n request<SpaceInfo[]>('list', opts);\n\n/** Release a mounted space (stops its listener on the host). */\nexport const unmountSpace = async (query: { spaceId: string }): Promise<void> => {\n await request('unmount', query);\n};\n\n// ---------------------------------------------------------------------------\n// Space management (the space-manager app) — UI_AS_APPS_SPEC §5.2. These are\n// ELEVATED: enumerating all the user's spaces is `spaces:user`; mutating\n// membership (share/unshare/setRole) and resolving handles is `spaces:admin`.\n// The host enforces the owner-lockout invariant (a space always keeps an owner,\n// T41) and rate-limits handle lookups (L1); the OAuth/identity token never\n// crosses to the app.\n// ---------------------------------------------------------------------------\n\nexport type Role = 'owner' | 'writer' | 'reader';\n\n/** A member of a space (for the share/manage UI). */\nexport interface Member {\n /** `user:{uid}` | `group:{gid}`. */\n principal: string;\n role: Role;\n login?: string;\n avatarUrl?: string;\n}\n\n/** A handle resolved to a principal (handle → who). */\nexport interface ResolvedUser {\n uid: string;\n login: string;\n avatarUrl?: string;\n}\n\n/** Enumerate ALL the user's spaces (not just this app's) — `spaces:user`. */\nexport const listAllSpaces = (): Promise<SpaceInfo[]> => request<SpaceInfo[]>('listAll', {});\n\n/** Read a space's members one-shot — `spaces:admin`. */\nexport const getSpaceMembers = (spaceId: string): Promise<Member[]> =>\n request<Member[]>('members', { spaceId });\n\n/** Invite a user (by provider handle) to a space at a role — `spaces:admin`. The\n * host resolves the handle, so the app never sees other users' uids except the\n * one it invited. */\nexport const shareSpace = async (spaceId: string, login: string, role: Role): Promise<void> => {\n await request('share', { spaceId, login, role });\n};\n\n/** Remove a member from a space — `spaces:admin`. Refused if it would orphan the\n * space (owner-lockout, T41). */\nexport const unshareSpace = async (spaceId: string, uid: string): Promise<void> => {\n await request('unshare', { spaceId, uid });\n};\n\n/** Change a member's role — `spaces:admin`. Refused if it would drop the sole\n * owner (owner-lockout, T41). */\nexport const setSpaceRole = async (spaceId: string, uid: string, role: Role): Promise<void> => {\n await request('setRole', { spaceId, uid, role });\n};\n\n/** Resolve a provider handle to a principal (for the invite flow) — `spaces:admin`,\n * rate-limited host-side. */\nexport const lookupUser = (login: string): Promise<ResolvedUser> =>\n request<ResolvedUser>('lookupUser', { login });\n\n/** One durable grant an app holds, for the §8.11 capability audit view. */\nexport interface GrantRecord {\n /** The app's provider-qualified identity (`provider__namespace__repository`). */\n appKey: string;\n spaceId: string;\n /** Universal mount id (§3.5). */\n mountId: string;\n subtree?: string;\n mode: 'ro' | 'rw';\n name?: string;\n}\n\n/** Enumerate every (app, mount) grant the user holds — the audit view\n * (§8.11). Elevated `spaces:admin`. */\nexport const listGrants = (): Promise<GrantRecord[]> => request<GrantRecord[]>('grants', {});\n\n/** Revoke one app's grant on a space — durable (the app can't re-mount) plus a\n * best-effort live teardown. Elevated `spaces:admin`. */\nexport const revokeGrant = async (appKey: string, spaceId: string): Promise<void> => {\n await request('revokeGrant', { appKey, spaceId });\n};\n"],"mappings":"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","names":["mount"]}
|
|
1
|
+
{"version":3,"sources":["../src/mounts.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { protocolRequest } from './sandboxUtils';\nimport { getHostRuntime } from './hostRuntime';\n\n/**\n * The absolute path where this app's own repository filesystem is mounted\n * (FILE_SHARING_SPEC §11.2). Prefer this over hardcoding `/app`: the repo is\n * dual-mounted at both `/app` (back-compat) and its canonical `/mnt/{hash}`\n * address, and this returns the canonical one the host reports. Falls back to\n * `/app` when the host hasn't reported a canonical path (older host / before the\n * report arrives) — both paths are live, so either resolves the same files.\n */\nexport const getAppMountPath = (): string => getHostRuntime()?.appMountPath ?? '/app';\n\n/**\n * A filesystem mount available to the sandbox, mirrored from the host window.\n *\n * Mounts appear on demand — call {@link openAppSpace} / {@link mountSpace} to ask\n * the host to mount a Firestore-backed \"space\"; it appears at `/spaces/{id}`.\n * Read or subscribe to the set, then access the files through the `fs` module at\n * the mount's `path`.\n */\nexport interface SandboxMount {\n /** Absolute path where the mount is reachable (e.g. `/spaces/{id}`). */\n path: string;\n /** Backend kind, e.g. `'firestore'`. */\n type: string;\n /** Optional stable identifier (the spaceId, for spaces). */\n id?: string;\n /**\n * Access mode of the granted view: `'rw'` (read-write) or `'ro'` (read-only).\n * A live role downgrade re-announces the same mount with `mode: 'ro'`; apps\n * observing `onMountsChange` see the change and writes start failing `EROFS`.\n * Absent on the primary repo mount (treated as read-write).\n */\n mode?: \"ro\" | \"rw\";\n}\n\n/**\n * Why a mounted filesystem was removed, surfaced on the removed descriptor so an\n * app can say *why* it vanished instead of failing mutely (auth-mount §\"mount-remove\"\n * / AM2-4):\n * - `revoked` — a durable grant was revoked (revokeGrant / consent withdrawal);\n * - `unshared` — the granting user's membership was removed (or downgraded out);\n * - `signed-out` — sign-out tore down every mount;\n * - `unmounted` — the app's own `unmountSpace` (or region teardown);\n * - `deleted` — the space was soft-deleted.\n * An older host that sends no reason is read as `'revoked'` (most conservative).\n */\nexport type MountRemoveReason =\n | \"revoked\"\n | \"unshared\"\n | \"signed-out\"\n | \"unmounted\"\n | \"deleted\";\n\n/** A descriptor delivered as REMOVED to a mounts-change listener: the mount that\n * went away, plus the `reason` it did. */\nexport interface RemovedMount extends SandboxMount {\n reason: MountRemoveReason;\n}\n\ninterface MountService {\n getMounts(): SandboxMount[];\n onChange(\n listener: (mounts: SandboxMount[], removed: RemovedMount[]) => void,\n ): { dispose(): void };\n}\n\n// `module.evaluation.module.bundler` is the sandbox bundler injected into the\n// evaluation context (same path the other SDK helpers reach for `messageBus`).\nconst mountService = (): MountService => {\n // @ts-ignore - injected by the sandbox runtime\n return module.evaluation.module.bundler.mounts;\n};\n\n/** A predicate-style matcher for {@link findMount} / {@link waitForMount}. */\nexport type MountQuery = { type?: string; id?: string; path?: string };\n\nconst matches = (mount: SandboxMount, query: MountQuery): boolean =>\n (query.type === undefined || mount.type === query.type) &&\n (query.id === undefined || mount.id === query.id) &&\n (query.path === undefined || mount.path === query.path);\n\n/**\n * Returns the mounts currently available. Poll this whenever you need a one-off\n * read; use {@link onMountsChange} or {@link useMounts} to react to changes.\n */\nexport const getMounts = (): SandboxMount[] => mountService().getMounts();\n\n/** Returns the first mount matching `query`, or `undefined`. */\nexport const findMount = (query: MountQuery): SandboxMount | undefined =>\n getMounts().find((m) => matches(m, query));\n\n/**\n * Subscribe to mount changes. The listener is invoked immediately with the\n * current mounts (and an empty `removed`), then again on every change. The second\n * argument carries the descriptors REMOVED by that change, each with its `reason`\n * (AM2-4) — so an app can react to *why* a mount vanished (e.g. tell the user a\n * shared space was `unshared` vs `deleted`). It is empty on adds and on the\n * initial replay. Returns an unsubscribe fn.\n */\nexport const onMountsChange = (\n listener: (mounts: SandboxMount[], removed: RemovedMount[]) => void,\n): (() => void) => {\n const disposable = mountService().onChange(listener);\n return () => disposable.dispose();\n};\n\n/**\n * Resolves once a mount matching `query` is present (immediately if it already\n * is). Handy for \"use it when it appears\" — e.g.\n * `await waitForMount({ type: 'firestore' })` before reading `/firestore`.\n */\nexport const waitForMount = (query: MountQuery): Promise<SandboxMount> =>\n new Promise((resolve) => {\n const unsubscribe = onMountsChange((mounts) => {\n const found = mounts.find((m) => matches(m, query));\n if (found) {\n // Defer unsubscribe so we don't dispose during the initial replay call.\n Promise.resolve().then(unsubscribe);\n resolve(found);\n }\n });\n });\n\n/** React hook returning the mounts currently available, re-rendering on change. */\nexport const useMounts = (): SandboxMount[] => {\n const [mounts, setMounts] = useState<SandboxMount[]>(getMounts);\n useEffect(() => onMountsChange(setMounts), []);\n return mounts;\n};\n\n// ---------------------------------------------------------------------------\n// Spaces — on-demand, shareable Firestore-backed filesystems.\n// The host owns all UX: if you aren't signed in, or the space doesn't exist or\n// isn't accessible, the parent window presents sign-in / create / request-access\n// and only then resolves these calls. See docs/specs/FILE_SHARING_SPEC.md.\n// ---------------------------------------------------------------------------\n\n/** Summary of a space, as returned by {@link listSpaces}. */\nexport interface SpaceInfo {\n spaceId: string;\n role?: 'owner' | 'writer' | 'reader';\n owner?: string;\n name?: string;\n}\n\n/** An error from a space operation, carrying a machine-readable `code`. */\nexport interface SpaceError extends Error {\n code:\n | 'auth-required'\n | 'cancelled'\n | 'forbidden'\n | 'not-found'\n | 'unsupported-scheme'\n | 'unknown';\n}\n\ntype SpaceResult =\n | { ok: true; data: unknown }\n | { ok: false; code: string; message: string };\n\n// Issue a spaces protocol request, unwrapping the host's {ok,data} envelope and\n// throwing a typed SpaceError on failure.\nconst request = async <T = unknown>(\n method: string,\n query: Record<string, unknown> = {},\n): Promise<T> => {\n const res = (await protocolRequest('spaces', method, [query])) as SpaceResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'space request failed') as SpaceError;\n err.code = (res?.code as SpaceError['code']) ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n// Request a space mount, then wait until the host actually registers it. The\n// host announces the mount (`mount-add`) separately from the protocol reply, so\n// an immediate read could otherwise race the mount.\nconst requestMountInternal = async (\n method: string,\n query: Record<string, unknown>,\n): Promise<SandboxMount> => {\n const mount = await request<SandboxMount>(method, query);\n return waitForMount({ id: mount.id ?? mount.path });\n};\n\n/**\n * Open this app's workspace for the signed-in user (the zero-config path). The\n * `slot` names which workspace (default `'default'`); pass distinct slots for\n * multiple filesystems in one app. On a missing slot the host shows a\n * create-or-pick dialog. Rejects with a {@link SpaceError} (`.code`) on cancel.\n */\nexport const openAppSpace = (slot = 'default'): Promise<SandboxMount> =>\n requestMountInternal('open', { slot });\n\n/**\n * Mount a filesystem by its **universal mount id** (UI_AS_APPS_SPEC §3.5) —\n * `scheme:locator`, e.g. `space:{spaceId}` or `github:owner/repo@ref`. Backend-blind:\n * the host resolves the scheme. A scheme with no resolver rejects with\n * {@link SpaceError} `unsupported-scheme`.\n */\nexport const mount = (mountId: string): Promise<SandboxMount> =>\n requestMountInternal('mount', { mount: mountId });\n\n/** Mount a specific space by id (e.g. one shared with you, or from a link). A thin\n * shim over {@link mount} with the `space:` scheme. */\nexport const mountSpace = (query: { spaceId: string }): Promise<SandboxMount> =>\n mount(`space:${query.spaceId}`);\n\n/**\n * Ask the user to grant a filesystem to this app — the §8.6 powerbox. The app\n * asks; the HOST shows the user their mounts and the access choice (which mount,\n * an optional subtree, read-only vs read-write); the USER picks or declines. The\n * app never sees the list — it resolves with the single granted mount, or rejects\n * with a {@link SpaceError} (`cancelled`) if declined. The granted scope is\n * enforced host-side: the mount is chroot'd / `ro`-limited accordingly.\n *\n * Backend-general (§3.5): the picker offers whatever mounts the user has (today,\n * their spaces). Returns the granted mount by its universal id.\n */\nexport const requestMount = (): Promise<SandboxMount> =>\n requestMountInternal('request', {});\n\n/** @deprecated renamed to {@link requestMount} (backend-general, §3.5). */\nexport const requestSpace = requestMount;\n\n/** Create a brand-new space, optionally binding it to this app (a slot). */\nexport const createSpace = (\n opts: { name?: string; slot?: string; bindToApp?: boolean } = {}\n): Promise<SandboxMount> => requestMountInternal('create', opts);\n\n/** List spaces you can access — all of them, or just those bound to this app. */\nexport const listSpaces = (opts: { app?: boolean } = {}): Promise<SpaceInfo[]> =>\n request<SpaceInfo[]>('list', opts);\n\n/** Release a mounted space (stops its listener on the host). */\nexport const unmountSpace = async (query: { spaceId: string }): Promise<void> => {\n await request('unmount', query);\n};\n\n// ---------------------------------------------------------------------------\n// Space management (the space-manager app) — UI_AS_APPS_SPEC §5.2. These are\n// ELEVATED: enumerating all the user's spaces is `spaces:user`; mutating\n// membership (share/unshare/setRole) and resolving handles is `spaces:admin`.\n// The host enforces the owner-lockout invariant (a space always keeps an owner,\n// T41) and rate-limits handle lookups (L1); the OAuth/identity token never\n// crosses to the app.\n// ---------------------------------------------------------------------------\n\nexport type Role = 'owner' | 'writer' | 'reader';\n\n/** A member of a space (for the share/manage UI). */\nexport interface Member {\n /** `user:{uid}` | `group:{gid}`. */\n principal: string;\n role: Role;\n login?: string;\n avatarUrl?: string;\n}\n\n/** A handle resolved to a principal (handle → who). */\nexport interface ResolvedUser {\n uid: string;\n login: string;\n avatarUrl?: string;\n}\n\n/** Enumerate ALL the user's spaces (not just this app's) — `spaces:user`. */\nexport const listAllSpaces = (): Promise<SpaceInfo[]> => request<SpaceInfo[]>('listAll', {});\n\n/** Read a space's members one-shot — `spaces:admin`. */\nexport const getSpaceMembers = (spaceId: string): Promise<Member[]> =>\n request<Member[]>('members', { spaceId });\n\n/** Invite a user (by provider handle) to a space at a role — `spaces:admin`. The\n * host resolves the handle, so the app never sees other users' uids except the\n * one it invited. */\nexport const shareSpace = async (spaceId: string, login: string, role: Role): Promise<void> => {\n await request('share', { spaceId, login, role });\n};\n\n/** Remove a member from a space — `spaces:admin`. Refused if it would orphan the\n * space (owner-lockout, T41). */\nexport const unshareSpace = async (spaceId: string, uid: string): Promise<void> => {\n await request('unshare', { spaceId, uid });\n};\n\n/** Change a member's role — `spaces:admin`. Refused if it would drop the sole\n * owner (owner-lockout, T41). */\nexport const setSpaceRole = async (spaceId: string, uid: string, role: Role): Promise<void> => {\n await request('setRole', { spaceId, uid, role });\n};\n\n/** Resolve a provider handle to a principal (for the invite flow) — `spaces:admin`,\n * rate-limited host-side. */\nexport const lookupUser = (login: string): Promise<ResolvedUser> =>\n request<ResolvedUser>('lookupUser', { login });\n\n/** One durable grant an app holds, for the §8.11 capability audit view. */\nexport interface GrantRecord {\n /** The app's provider-qualified identity (`provider__namespace__repository`). */\n appKey: string;\n spaceId: string;\n /** Universal mount id (§3.5). */\n mountId: string;\n subtree?: string;\n mode: 'ro' | 'rw';\n name?: string;\n}\n\n/** Enumerate every (app, mount) grant the user holds — the audit view\n * (§8.11). Elevated `spaces:admin`. */\nexport const listGrants = (): Promise<GrantRecord[]> => request<GrantRecord[]>('grants', {});\n\n/** Revoke one app's grant on a space — durable (the app can't re-mount) plus a\n * best-effort live teardown. Elevated `spaces:admin`. */\nexport const revokeGrant = async (appKey: string, spaceId: string): Promise<void> => {\n await request('revokeGrant', { appKey, spaceId });\n};\n"],"mappings":"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","names":["mount"]}
|
package/dist/mounts.d.cts
CHANGED
|
@@ -30,6 +30,23 @@ interface SandboxMount {
|
|
|
30
30
|
*/
|
|
31
31
|
mode?: "ro" | "rw";
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Why a mounted filesystem was removed, surfaced on the removed descriptor so an
|
|
35
|
+
* app can say *why* it vanished instead of failing mutely (auth-mount §"mount-remove"
|
|
36
|
+
* / AM2-4):
|
|
37
|
+
* - `revoked` — a durable grant was revoked (revokeGrant / consent withdrawal);
|
|
38
|
+
* - `unshared` — the granting user's membership was removed (or downgraded out);
|
|
39
|
+
* - `signed-out` — sign-out tore down every mount;
|
|
40
|
+
* - `unmounted` — the app's own `unmountSpace` (or region teardown);
|
|
41
|
+
* - `deleted` — the space was soft-deleted.
|
|
42
|
+
* An older host that sends no reason is read as `'revoked'` (most conservative).
|
|
43
|
+
*/
|
|
44
|
+
type MountRemoveReason = "revoked" | "unshared" | "signed-out" | "unmounted" | "deleted";
|
|
45
|
+
/** A descriptor delivered as REMOVED to a mounts-change listener: the mount that
|
|
46
|
+
* went away, plus the `reason` it did. */
|
|
47
|
+
interface RemovedMount extends SandboxMount {
|
|
48
|
+
reason: MountRemoveReason;
|
|
49
|
+
}
|
|
33
50
|
/** A predicate-style matcher for {@link findMount} / {@link waitForMount}. */
|
|
34
51
|
type MountQuery = {
|
|
35
52
|
type?: string;
|
|
@@ -45,9 +62,13 @@ declare const getMounts: () => SandboxMount[];
|
|
|
45
62
|
declare const findMount: (query: MountQuery) => SandboxMount | undefined;
|
|
46
63
|
/**
|
|
47
64
|
* Subscribe to mount changes. The listener is invoked immediately with the
|
|
48
|
-
* current mounts, then again on every change.
|
|
65
|
+
* current mounts (and an empty `removed`), then again on every change. The second
|
|
66
|
+
* argument carries the descriptors REMOVED by that change, each with its `reason`
|
|
67
|
+
* (AM2-4) — so an app can react to *why* a mount vanished (e.g. tell the user a
|
|
68
|
+
* shared space was `unshared` vs `deleted`). It is empty on adds and on the
|
|
69
|
+
* initial replay. Returns an unsubscribe fn.
|
|
49
70
|
*/
|
|
50
|
-
declare const onMountsChange: (listener: (mounts: SandboxMount[]) => void) => (() => void);
|
|
71
|
+
declare const onMountsChange: (listener: (mounts: SandboxMount[], removed: RemovedMount[]) => void) => (() => void);
|
|
51
72
|
/**
|
|
52
73
|
* Resolves once a mount matching `query` is present (immediately if it already
|
|
53
74
|
* is). Handy for "use it when it appears" — e.g.
|
|
@@ -164,4 +185,4 @@ declare const listGrants: () => Promise<GrantRecord[]>;
|
|
|
164
185
|
* best-effort live teardown. Elevated `spaces:admin`. */
|
|
165
186
|
declare const revokeGrant: (appKey: string, spaceId: string) => Promise<void>;
|
|
166
187
|
|
|
167
|
-
export { type GrantRecord, type Member, type MountQuery, type ResolvedUser, type Role, type SandboxMount, type SpaceError, type SpaceInfo, createSpace, findMount, getAppMountPath, getMounts, getSpaceMembers, listAllSpaces, listGrants, listSpaces, lookupUser, mount, mountSpace, onMountsChange, openAppSpace, requestMount, requestSpace, revokeGrant, setSpaceRole, shareSpace, unmountSpace, unshareSpace, useMounts, waitForMount };
|
|
188
|
+
export { type GrantRecord, type Member, type MountQuery, type MountRemoveReason, type RemovedMount, type ResolvedUser, type Role, type SandboxMount, type SpaceError, type SpaceInfo, createSpace, findMount, getAppMountPath, getMounts, getSpaceMembers, listAllSpaces, listGrants, listSpaces, lookupUser, mount, mountSpace, onMountsChange, openAppSpace, requestMount, requestSpace, revokeGrant, setSpaceRole, shareSpace, unmountSpace, unshareSpace, useMounts, waitForMount };
|
package/dist/mounts.d.ts
CHANGED
|
@@ -30,6 +30,23 @@ interface SandboxMount {
|
|
|
30
30
|
*/
|
|
31
31
|
mode?: "ro" | "rw";
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Why a mounted filesystem was removed, surfaced on the removed descriptor so an
|
|
35
|
+
* app can say *why* it vanished instead of failing mutely (auth-mount §"mount-remove"
|
|
36
|
+
* / AM2-4):
|
|
37
|
+
* - `revoked` — a durable grant was revoked (revokeGrant / consent withdrawal);
|
|
38
|
+
* - `unshared` — the granting user's membership was removed (or downgraded out);
|
|
39
|
+
* - `signed-out` — sign-out tore down every mount;
|
|
40
|
+
* - `unmounted` — the app's own `unmountSpace` (or region teardown);
|
|
41
|
+
* - `deleted` — the space was soft-deleted.
|
|
42
|
+
* An older host that sends no reason is read as `'revoked'` (most conservative).
|
|
43
|
+
*/
|
|
44
|
+
type MountRemoveReason = "revoked" | "unshared" | "signed-out" | "unmounted" | "deleted";
|
|
45
|
+
/** A descriptor delivered as REMOVED to a mounts-change listener: the mount that
|
|
46
|
+
* went away, plus the `reason` it did. */
|
|
47
|
+
interface RemovedMount extends SandboxMount {
|
|
48
|
+
reason: MountRemoveReason;
|
|
49
|
+
}
|
|
33
50
|
/** A predicate-style matcher for {@link findMount} / {@link waitForMount}. */
|
|
34
51
|
type MountQuery = {
|
|
35
52
|
type?: string;
|
|
@@ -45,9 +62,13 @@ declare const getMounts: () => SandboxMount[];
|
|
|
45
62
|
declare const findMount: (query: MountQuery) => SandboxMount | undefined;
|
|
46
63
|
/**
|
|
47
64
|
* Subscribe to mount changes. The listener is invoked immediately with the
|
|
48
|
-
* current mounts, then again on every change.
|
|
65
|
+
* current mounts (and an empty `removed`), then again on every change. The second
|
|
66
|
+
* argument carries the descriptors REMOVED by that change, each with its `reason`
|
|
67
|
+
* (AM2-4) — so an app can react to *why* a mount vanished (e.g. tell the user a
|
|
68
|
+
* shared space was `unshared` vs `deleted`). It is empty on adds and on the
|
|
69
|
+
* initial replay. Returns an unsubscribe fn.
|
|
49
70
|
*/
|
|
50
|
-
declare const onMountsChange: (listener: (mounts: SandboxMount[]) => void) => (() => void);
|
|
71
|
+
declare const onMountsChange: (listener: (mounts: SandboxMount[], removed: RemovedMount[]) => void) => (() => void);
|
|
51
72
|
/**
|
|
52
73
|
* Resolves once a mount matching `query` is present (immediately if it already
|
|
53
74
|
* is). Handy for "use it when it appears" — e.g.
|
|
@@ -164,4 +185,4 @@ declare const listGrants: () => Promise<GrantRecord[]>;
|
|
|
164
185
|
* best-effort live teardown. Elevated `spaces:admin`. */
|
|
165
186
|
declare const revokeGrant: (appKey: string, spaceId: string) => Promise<void>;
|
|
166
187
|
|
|
167
|
-
export { type GrantRecord, type Member, type MountQuery, type ResolvedUser, type Role, type SandboxMount, type SpaceError, type SpaceInfo, createSpace, findMount, getAppMountPath, getMounts, getSpaceMembers, listAllSpaces, listGrants, listSpaces, lookupUser, mount, mountSpace, onMountsChange, openAppSpace, requestMount, requestSpace, revokeGrant, setSpaceRole, shareSpace, unmountSpace, unshareSpace, useMounts, waitForMount };
|
|
188
|
+
export { type GrantRecord, type Member, type MountQuery, type MountRemoveReason, type RemovedMount, type ResolvedUser, type Role, type SandboxMount, type SpaceError, type SpaceInfo, createSpace, findMount, getAppMountPath, getMounts, getSpaceMembers, listAllSpaces, listGrants, listSpaces, lookupUser, mount, mountSpace, onMountsChange, openAppSpace, requestMount, requestSpace, revokeGrant, setSpaceRole, shareSpace, unmountSpace, unshareSpace, useMounts, waitForMount };
|
package/dist/mounts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mounts.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { protocolRequest } from './sandboxUtils';\nimport { getHostRuntime } from './hostRuntime';\n\n/**\n * The absolute path where this app's own repository filesystem is mounted\n * (FILE_SHARING_SPEC §11.2). Prefer this over hardcoding `/app`: the repo is\n * dual-mounted at both `/app` (back-compat) and its canonical `/mnt/{hash}`\n * address, and this returns the canonical one the host reports. Falls back to\n * `/app` when the host hasn't reported a canonical path (older host / before the\n * report arrives) — both paths are live, so either resolves the same files.\n */\nexport const getAppMountPath = (): string => getHostRuntime()?.appMountPath ?? '/app';\n\n/**\n * A filesystem mount available to the sandbox, mirrored from the host window.\n *\n * Mounts appear on demand — call {@link openAppSpace} / {@link mountSpace} to ask\n * the host to mount a Firestore-backed \"space\"; it appears at `/spaces/{id}`.\n * Read or subscribe to the set, then access the files through the `fs` module at\n * the mount's `path`.\n */\nexport interface SandboxMount {\n /** Absolute path where the mount is reachable (e.g. `/spaces/{id}`). */\n path: string;\n /** Backend kind, e.g. `'firestore'`. */\n type: string;\n /** Optional stable identifier (the spaceId, for spaces). */\n id?: string;\n /**\n * Access mode of the granted view: `'rw'` (read-write) or `'ro'` (read-only).\n * A live role downgrade re-announces the same mount with `mode: 'ro'`; apps\n * observing `onMountsChange` see the change and writes start failing `EROFS`.\n * Absent on the primary repo mount (treated as read-write).\n */\n mode?: \"ro\" | \"rw\";\n}\n\ninterface MountService {\n getMounts(): SandboxMount[];\n onChange(listener: (mounts: SandboxMount[]) => void): { dispose(): void };\n}\n\n// `module.evaluation.module.bundler` is the sandbox bundler injected into the\n// evaluation context (same path the other SDK helpers reach for `messageBus`).\nconst mountService = (): MountService => {\n // @ts-ignore - injected by the sandbox runtime\n return module.evaluation.module.bundler.mounts;\n};\n\n/** A predicate-style matcher for {@link findMount} / {@link waitForMount}. */\nexport type MountQuery = { type?: string; id?: string; path?: string };\n\nconst matches = (mount: SandboxMount, query: MountQuery): boolean =>\n (query.type === undefined || mount.type === query.type) &&\n (query.id === undefined || mount.id === query.id) &&\n (query.path === undefined || mount.path === query.path);\n\n/**\n * Returns the mounts currently available. Poll this whenever you need a one-off\n * read; use {@link onMountsChange} or {@link useMounts} to react to changes.\n */\nexport const getMounts = (): SandboxMount[] => mountService().getMounts();\n\n/** Returns the first mount matching `query`, or `undefined`. */\nexport const findMount = (query: MountQuery): SandboxMount | undefined =>\n getMounts().find((m) => matches(m, query));\n\n/**\n * Subscribe to mount changes. The listener is invoked immediately with the\n * current mounts, then again on every change. Returns an unsubscribe fn.\n */\nexport const onMountsChange = (listener: (mounts: SandboxMount[]) => void): (() => void) => {\n const disposable = mountService().onChange(listener);\n return () => disposable.dispose();\n};\n\n/**\n * Resolves once a mount matching `query` is present (immediately if it already\n * is). Handy for \"use it when it appears\" — e.g.\n * `await waitForMount({ type: 'firestore' })` before reading `/firestore`.\n */\nexport const waitForMount = (query: MountQuery): Promise<SandboxMount> =>\n new Promise((resolve) => {\n const unsubscribe = onMountsChange((mounts) => {\n const found = mounts.find((m) => matches(m, query));\n if (found) {\n // Defer unsubscribe so we don't dispose during the initial replay call.\n Promise.resolve().then(unsubscribe);\n resolve(found);\n }\n });\n });\n\n/** React hook returning the mounts currently available, re-rendering on change. */\nexport const useMounts = (): SandboxMount[] => {\n const [mounts, setMounts] = useState<SandboxMount[]>(getMounts);\n useEffect(() => onMountsChange(setMounts), []);\n return mounts;\n};\n\n// ---------------------------------------------------------------------------\n// Spaces — on-demand, shareable Firestore-backed filesystems.\n// The host owns all UX: if you aren't signed in, or the space doesn't exist or\n// isn't accessible, the parent window presents sign-in / create / request-access\n// and only then resolves these calls. See docs/specs/FILE_SHARING_SPEC.md.\n// ---------------------------------------------------------------------------\n\n/** Summary of a space, as returned by {@link listSpaces}. */\nexport interface SpaceInfo {\n spaceId: string;\n role?: 'owner' | 'writer' | 'reader';\n owner?: string;\n name?: string;\n}\n\n/** An error from a space operation, carrying a machine-readable `code`. */\nexport interface SpaceError extends Error {\n code:\n | 'auth-required'\n | 'cancelled'\n | 'forbidden'\n | 'not-found'\n | 'unsupported-scheme'\n | 'unknown';\n}\n\ntype SpaceResult =\n | { ok: true; data: unknown }\n | { ok: false; code: string; message: string };\n\n// Issue a spaces protocol request, unwrapping the host's {ok,data} envelope and\n// throwing a typed SpaceError on failure.\nconst request = async <T = unknown>(\n method: string,\n query: Record<string, unknown> = {},\n): Promise<T> => {\n const res = (await protocolRequest('spaces', method, [query])) as SpaceResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'space request failed') as SpaceError;\n err.code = (res?.code as SpaceError['code']) ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n// Request a space mount, then wait until the host actually registers it. The\n// host announces the mount (`mount-add`) separately from the protocol reply, so\n// an immediate read could otherwise race the mount.\nconst requestMountInternal = async (\n method: string,\n query: Record<string, unknown>,\n): Promise<SandboxMount> => {\n const mount = await request<SandboxMount>(method, query);\n return waitForMount({ id: mount.id ?? mount.path });\n};\n\n/**\n * Open this app's workspace for the signed-in user (the zero-config path). The\n * `slot` names which workspace (default `'default'`); pass distinct slots for\n * multiple filesystems in one app. On a missing slot the host shows a\n * create-or-pick dialog. Rejects with a {@link SpaceError} (`.code`) on cancel.\n */\nexport const openAppSpace = (slot = 'default'): Promise<SandboxMount> =>\n requestMountInternal('open', { slot });\n\n/**\n * Mount a filesystem by its **universal mount id** (UI_AS_APPS_SPEC §3.5) —\n * `scheme:locator`, e.g. `space:{spaceId}` or `github:owner/repo@ref`. Backend-blind:\n * the host resolves the scheme. A scheme with no resolver rejects with\n * {@link SpaceError} `unsupported-scheme`.\n */\nexport const mount = (mountId: string): Promise<SandboxMount> =>\n requestMountInternal('mount', { mount: mountId });\n\n/** Mount a specific space by id (e.g. one shared with you, or from a link). A thin\n * shim over {@link mount} with the `space:` scheme. */\nexport const mountSpace = (query: { spaceId: string }): Promise<SandboxMount> =>\n mount(`space:${query.spaceId}`);\n\n/**\n * Ask the user to grant a filesystem to this app — the §8.6 powerbox. The app\n * asks; the HOST shows the user their mounts and the access choice (which mount,\n * an optional subtree, read-only vs read-write); the USER picks or declines. The\n * app never sees the list — it resolves with the single granted mount, or rejects\n * with a {@link SpaceError} (`cancelled`) if declined. The granted scope is\n * enforced host-side: the mount is chroot'd / `ro`-limited accordingly.\n *\n * Backend-general (§3.5): the picker offers whatever mounts the user has (today,\n * their spaces). Returns the granted mount by its universal id.\n */\nexport const requestMount = (): Promise<SandboxMount> =>\n requestMountInternal('request', {});\n\n/** @deprecated renamed to {@link requestMount} (backend-general, §3.5). */\nexport const requestSpace = requestMount;\n\n/** Create a brand-new space, optionally binding it to this app (a slot). */\nexport const createSpace = (\n opts: { name?: string; slot?: string; bindToApp?: boolean } = {}\n): Promise<SandboxMount> => requestMountInternal('create', opts);\n\n/** List spaces you can access — all of them, or just those bound to this app. */\nexport const listSpaces = (opts: { app?: boolean } = {}): Promise<SpaceInfo[]> =>\n request<SpaceInfo[]>('list', opts);\n\n/** Release a mounted space (stops its listener on the host). */\nexport const unmountSpace = async (query: { spaceId: string }): Promise<void> => {\n await request('unmount', query);\n};\n\n// ---------------------------------------------------------------------------\n// Space management (the space-manager app) — UI_AS_APPS_SPEC §5.2. These are\n// ELEVATED: enumerating all the user's spaces is `spaces:user`; mutating\n// membership (share/unshare/setRole) and resolving handles is `spaces:admin`.\n// The host enforces the owner-lockout invariant (a space always keeps an owner,\n// T41) and rate-limits handle lookups (L1); the OAuth/identity token never\n// crosses to the app.\n// ---------------------------------------------------------------------------\n\nexport type Role = 'owner' | 'writer' | 'reader';\n\n/** A member of a space (for the share/manage UI). */\nexport interface Member {\n /** `user:{uid}` | `group:{gid}`. */\n principal: string;\n role: Role;\n login?: string;\n avatarUrl?: string;\n}\n\n/** A handle resolved to a principal (handle → who). */\nexport interface ResolvedUser {\n uid: string;\n login: string;\n avatarUrl?: string;\n}\n\n/** Enumerate ALL the user's spaces (not just this app's) — `spaces:user`. */\nexport const listAllSpaces = (): Promise<SpaceInfo[]> => request<SpaceInfo[]>('listAll', {});\n\n/** Read a space's members one-shot — `spaces:admin`. */\nexport const getSpaceMembers = (spaceId: string): Promise<Member[]> =>\n request<Member[]>('members', { spaceId });\n\n/** Invite a user (by provider handle) to a space at a role — `spaces:admin`. The\n * host resolves the handle, so the app never sees other users' uids except the\n * one it invited. */\nexport const shareSpace = async (spaceId: string, login: string, role: Role): Promise<void> => {\n await request('share', { spaceId, login, role });\n};\n\n/** Remove a member from a space — `spaces:admin`. Refused if it would orphan the\n * space (owner-lockout, T41). */\nexport const unshareSpace = async (spaceId: string, uid: string): Promise<void> => {\n await request('unshare', { spaceId, uid });\n};\n\n/** Change a member's role — `spaces:admin`. Refused if it would drop the sole\n * owner (owner-lockout, T41). */\nexport const setSpaceRole = async (spaceId: string, uid: string, role: Role): Promise<void> => {\n await request('setRole', { spaceId, uid, role });\n};\n\n/** Resolve a provider handle to a principal (for the invite flow) — `spaces:admin`,\n * rate-limited host-side. */\nexport const lookupUser = (login: string): Promise<ResolvedUser> =>\n request<ResolvedUser>('lookupUser', { login });\n\n/** One durable grant an app holds, for the §8.11 capability audit view. */\nexport interface GrantRecord {\n /** The app's provider-qualified identity (`provider__namespace__repository`). */\n appKey: string;\n spaceId: string;\n /** Universal mount id (§3.5). */\n mountId: string;\n subtree?: string;\n mode: 'ro' | 'rw';\n name?: string;\n}\n\n/** Enumerate every (app, mount) grant the user holds — the audit view\n * (§8.11). Elevated `spaces:admin`. */\nexport const listGrants = (): Promise<GrantRecord[]> => request<GrantRecord[]>('grants', {});\n\n/** Revoke one app's grant on a space — durable (the app can't re-mount) plus a\n * best-effort live teardown. Elevated `spaces:admin`. */\nexport const revokeGrant = async (appKey: string, spaceId: string): Promise<void> => {\n await request('revokeGrant', { appKey, spaceId });\n};\n"],"mappings":"AAAA,SAAS,WAAW,gBAAgB;AACpC,SAAS,uBAAuB;AAChC,SAAS,sBAAsB;AAUxB,MAAM,kBAAkB,MAAc,eAAe,GAAG,gBAAgB;AAiC/E,MAAM,eAAe,MAAoB;AAEvC,SAAO,OAAO,WAAW,OAAO,QAAQ;AAC1C;AAKA,MAAM,UAAU,CAACA,QAAqB,WACnC,MAAM,SAAS,UAAaA,OAAM,SAAS,MAAM,UACjD,MAAM,OAAO,UAAaA,OAAM,OAAO,MAAM,QAC7C,MAAM,SAAS,UAAaA,OAAM,SAAS,MAAM;AAM7C,MAAM,YAAY,MAAsB,aAAa,EAAE,UAAU;AAGjE,MAAM,YAAY,CAAC,UACxB,UAAU,EAAE,KAAK,CAAC,MAAM,QAAQ,GAAG,KAAK,CAAC;AAMpC,MAAM,iBAAiB,CAAC,aAA6D;AAC1F,QAAM,aAAa,aAAa,EAAE,SAAS,QAAQ;AACnD,SAAO,MAAM,WAAW,QAAQ;AAClC;AAOO,MAAM,eAAe,CAAC,UAC3B,IAAI,QAAQ,CAAC,YAAY;AACvB,QAAM,cAAc,eAAe,CAAC,WAAW;AAC7C,UAAM,QAAQ,OAAO,KAAK,CAAC,MAAM,QAAQ,GAAG,KAAK,CAAC;AAClD,QAAI,OAAO;AAET,cAAQ,QAAQ,EAAE,KAAK,WAAW;AAClC,cAAQ,KAAK;AAAA,IACf;AAAA,EACF,CAAC;AACH,CAAC;AAGI,MAAM,YAAY,MAAsB;AAC7C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAyB,SAAS;AAC9D,YAAU,MAAM,eAAe,SAAS,GAAG,CAAC,CAAC;AAC7C,SAAO;AACT;AAkCA,MAAM,UAAU,OACd,QACA,QAAiC,CAAC,MACnB;AACf,QAAM,MAAO,MAAM,gBAAgB,UAAU,QAAQ,CAAC,KAAK,CAAC;AAC5D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,sBAAsB;AAC5D,QAAI,OAAQ,KAAK,QAA+B;AAChD,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAKA,MAAM,uBAAuB,OAC3B,QACA,UAC0B;AAC1B,QAAMA,SAAQ,MAAM,QAAsB,QAAQ,KAAK;AACvD,SAAO,aAAa,EAAE,IAAIA,OAAM,MAAMA,OAAM,KAAK,CAAC;AACpD;AAQO,MAAM,eAAe,CAAC,OAAO,cAClC,qBAAqB,QAAQ,EAAE,KAAK,CAAC;AAQhC,MAAM,QAAQ,CAAC,YACpB,qBAAqB,SAAS,EAAE,OAAO,QAAQ,CAAC;AAI3C,MAAM,aAAa,CAAC,UACzB,MAAM,SAAS,MAAM,OAAO,EAAE;AAazB,MAAM,eAAe,MAC1B,qBAAqB,WAAW,CAAC,CAAC;AAG7B,MAAM,eAAe;AAGrB,MAAM,cAAc,CACzB,OAA8D,CAAC,MACrC,qBAAqB,UAAU,IAAI;AAGxD,MAAM,aAAa,CAAC,OAA0B,CAAC,MACpD,QAAqB,QAAQ,IAAI;AAG5B,MAAM,eAAe,OAAO,UAA8C;AAC/E,QAAM,QAAQ,WAAW,KAAK;AAChC;AA8BO,MAAM,gBAAgB,MAA4B,QAAqB,WAAW,CAAC,CAAC;AAGpF,MAAM,kBAAkB,CAAC,YAC9B,QAAkB,WAAW,EAAE,QAAQ,CAAC;AAKnC,MAAM,aAAa,OAAO,SAAiB,OAAe,SAA8B;AAC7F,QAAM,QAAQ,SAAS,EAAE,SAAS,OAAO,KAAK,CAAC;AACjD;AAIO,MAAM,eAAe,OAAO,SAAiB,QAA+B;AACjF,QAAM,QAAQ,WAAW,EAAE,SAAS,IAAI,CAAC;AAC3C;AAIO,MAAM,eAAe,OAAO,SAAiB,KAAa,SAA8B;AAC7F,QAAM,QAAQ,WAAW,EAAE,SAAS,KAAK,KAAK,CAAC;AACjD;AAIO,MAAM,aAAa,CAAC,UACzB,QAAsB,cAAc,EAAE,MAAM,CAAC;AAgBxC,MAAM,aAAa,MAA8B,QAAuB,UAAU,CAAC,CAAC;AAIpF,MAAM,cAAc,OAAO,QAAgB,YAAmC;AACnF,QAAM,QAAQ,eAAe,EAAE,QAAQ,QAAQ,CAAC;AAClD;","names":["mount"]}
|
|
1
|
+
{"version":3,"sources":["../src/mounts.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { protocolRequest } from './sandboxUtils';\nimport { getHostRuntime } from './hostRuntime';\n\n/**\n * The absolute path where this app's own repository filesystem is mounted\n * (FILE_SHARING_SPEC §11.2). Prefer this over hardcoding `/app`: the repo is\n * dual-mounted at both `/app` (back-compat) and its canonical `/mnt/{hash}`\n * address, and this returns the canonical one the host reports. Falls back to\n * `/app` when the host hasn't reported a canonical path (older host / before the\n * report arrives) — both paths are live, so either resolves the same files.\n */\nexport const getAppMountPath = (): string => getHostRuntime()?.appMountPath ?? '/app';\n\n/**\n * A filesystem mount available to the sandbox, mirrored from the host window.\n *\n * Mounts appear on demand — call {@link openAppSpace} / {@link mountSpace} to ask\n * the host to mount a Firestore-backed \"space\"; it appears at `/spaces/{id}`.\n * Read or subscribe to the set, then access the files through the `fs` module at\n * the mount's `path`.\n */\nexport interface SandboxMount {\n /** Absolute path where the mount is reachable (e.g. `/spaces/{id}`). */\n path: string;\n /** Backend kind, e.g. `'firestore'`. */\n type: string;\n /** Optional stable identifier (the spaceId, for spaces). */\n id?: string;\n /**\n * Access mode of the granted view: `'rw'` (read-write) or `'ro'` (read-only).\n * A live role downgrade re-announces the same mount with `mode: 'ro'`; apps\n * observing `onMountsChange` see the change and writes start failing `EROFS`.\n * Absent on the primary repo mount (treated as read-write).\n */\n mode?: \"ro\" | \"rw\";\n}\n\n/**\n * Why a mounted filesystem was removed, surfaced on the removed descriptor so an\n * app can say *why* it vanished instead of failing mutely (auth-mount §\"mount-remove\"\n * / AM2-4):\n * - `revoked` — a durable grant was revoked (revokeGrant / consent withdrawal);\n * - `unshared` — the granting user's membership was removed (or downgraded out);\n * - `signed-out` — sign-out tore down every mount;\n * - `unmounted` — the app's own `unmountSpace` (or region teardown);\n * - `deleted` — the space was soft-deleted.\n * An older host that sends no reason is read as `'revoked'` (most conservative).\n */\nexport type MountRemoveReason =\n | \"revoked\"\n | \"unshared\"\n | \"signed-out\"\n | \"unmounted\"\n | \"deleted\";\n\n/** A descriptor delivered as REMOVED to a mounts-change listener: the mount that\n * went away, plus the `reason` it did. */\nexport interface RemovedMount extends SandboxMount {\n reason: MountRemoveReason;\n}\n\ninterface MountService {\n getMounts(): SandboxMount[];\n onChange(\n listener: (mounts: SandboxMount[], removed: RemovedMount[]) => void,\n ): { dispose(): void };\n}\n\n// `module.evaluation.module.bundler` is the sandbox bundler injected into the\n// evaluation context (same path the other SDK helpers reach for `messageBus`).\nconst mountService = (): MountService => {\n // @ts-ignore - injected by the sandbox runtime\n return module.evaluation.module.bundler.mounts;\n};\n\n/** A predicate-style matcher for {@link findMount} / {@link waitForMount}. */\nexport type MountQuery = { type?: string; id?: string; path?: string };\n\nconst matches = (mount: SandboxMount, query: MountQuery): boolean =>\n (query.type === undefined || mount.type === query.type) &&\n (query.id === undefined || mount.id === query.id) &&\n (query.path === undefined || mount.path === query.path);\n\n/**\n * Returns the mounts currently available. Poll this whenever you need a one-off\n * read; use {@link onMountsChange} or {@link useMounts} to react to changes.\n */\nexport const getMounts = (): SandboxMount[] => mountService().getMounts();\n\n/** Returns the first mount matching `query`, or `undefined`. */\nexport const findMount = (query: MountQuery): SandboxMount | undefined =>\n getMounts().find((m) => matches(m, query));\n\n/**\n * Subscribe to mount changes. The listener is invoked immediately with the\n * current mounts (and an empty `removed`), then again on every change. The second\n * argument carries the descriptors REMOVED by that change, each with its `reason`\n * (AM2-4) — so an app can react to *why* a mount vanished (e.g. tell the user a\n * shared space was `unshared` vs `deleted`). It is empty on adds and on the\n * initial replay. Returns an unsubscribe fn.\n */\nexport const onMountsChange = (\n listener: (mounts: SandboxMount[], removed: RemovedMount[]) => void,\n): (() => void) => {\n const disposable = mountService().onChange(listener);\n return () => disposable.dispose();\n};\n\n/**\n * Resolves once a mount matching `query` is present (immediately if it already\n * is). Handy for \"use it when it appears\" — e.g.\n * `await waitForMount({ type: 'firestore' })` before reading `/firestore`.\n */\nexport const waitForMount = (query: MountQuery): Promise<SandboxMount> =>\n new Promise((resolve) => {\n const unsubscribe = onMountsChange((mounts) => {\n const found = mounts.find((m) => matches(m, query));\n if (found) {\n // Defer unsubscribe so we don't dispose during the initial replay call.\n Promise.resolve().then(unsubscribe);\n resolve(found);\n }\n });\n });\n\n/** React hook returning the mounts currently available, re-rendering on change. */\nexport const useMounts = (): SandboxMount[] => {\n const [mounts, setMounts] = useState<SandboxMount[]>(getMounts);\n useEffect(() => onMountsChange(setMounts), []);\n return mounts;\n};\n\n// ---------------------------------------------------------------------------\n// Spaces — on-demand, shareable Firestore-backed filesystems.\n// The host owns all UX: if you aren't signed in, or the space doesn't exist or\n// isn't accessible, the parent window presents sign-in / create / request-access\n// and only then resolves these calls. See docs/specs/FILE_SHARING_SPEC.md.\n// ---------------------------------------------------------------------------\n\n/** Summary of a space, as returned by {@link listSpaces}. */\nexport interface SpaceInfo {\n spaceId: string;\n role?: 'owner' | 'writer' | 'reader';\n owner?: string;\n name?: string;\n}\n\n/** An error from a space operation, carrying a machine-readable `code`. */\nexport interface SpaceError extends Error {\n code:\n | 'auth-required'\n | 'cancelled'\n | 'forbidden'\n | 'not-found'\n | 'unsupported-scheme'\n | 'unknown';\n}\n\ntype SpaceResult =\n | { ok: true; data: unknown }\n | { ok: false; code: string; message: string };\n\n// Issue a spaces protocol request, unwrapping the host's {ok,data} envelope and\n// throwing a typed SpaceError on failure.\nconst request = async <T = unknown>(\n method: string,\n query: Record<string, unknown> = {},\n): Promise<T> => {\n const res = (await protocolRequest('spaces', method, [query])) as SpaceResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'space request failed') as SpaceError;\n err.code = (res?.code as SpaceError['code']) ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n// Request a space mount, then wait until the host actually registers it. The\n// host announces the mount (`mount-add`) separately from the protocol reply, so\n// an immediate read could otherwise race the mount.\nconst requestMountInternal = async (\n method: string,\n query: Record<string, unknown>,\n): Promise<SandboxMount> => {\n const mount = await request<SandboxMount>(method, query);\n return waitForMount({ id: mount.id ?? mount.path });\n};\n\n/**\n * Open this app's workspace for the signed-in user (the zero-config path). The\n * `slot` names which workspace (default `'default'`); pass distinct slots for\n * multiple filesystems in one app. On a missing slot the host shows a\n * create-or-pick dialog. Rejects with a {@link SpaceError} (`.code`) on cancel.\n */\nexport const openAppSpace = (slot = 'default'): Promise<SandboxMount> =>\n requestMountInternal('open', { slot });\n\n/**\n * Mount a filesystem by its **universal mount id** (UI_AS_APPS_SPEC §3.5) —\n * `scheme:locator`, e.g. `space:{spaceId}` or `github:owner/repo@ref`. Backend-blind:\n * the host resolves the scheme. A scheme with no resolver rejects with\n * {@link SpaceError} `unsupported-scheme`.\n */\nexport const mount = (mountId: string): Promise<SandboxMount> =>\n requestMountInternal('mount', { mount: mountId });\n\n/** Mount a specific space by id (e.g. one shared with you, or from a link). A thin\n * shim over {@link mount} with the `space:` scheme. */\nexport const mountSpace = (query: { spaceId: string }): Promise<SandboxMount> =>\n mount(`space:${query.spaceId}`);\n\n/**\n * Ask the user to grant a filesystem to this app — the §8.6 powerbox. The app\n * asks; the HOST shows the user their mounts and the access choice (which mount,\n * an optional subtree, read-only vs read-write); the USER picks or declines. The\n * app never sees the list — it resolves with the single granted mount, or rejects\n * with a {@link SpaceError} (`cancelled`) if declined. The granted scope is\n * enforced host-side: the mount is chroot'd / `ro`-limited accordingly.\n *\n * Backend-general (§3.5): the picker offers whatever mounts the user has (today,\n * their spaces). Returns the granted mount by its universal id.\n */\nexport const requestMount = (): Promise<SandboxMount> =>\n requestMountInternal('request', {});\n\n/** @deprecated renamed to {@link requestMount} (backend-general, §3.5). */\nexport const requestSpace = requestMount;\n\n/** Create a brand-new space, optionally binding it to this app (a slot). */\nexport const createSpace = (\n opts: { name?: string; slot?: string; bindToApp?: boolean } = {}\n): Promise<SandboxMount> => requestMountInternal('create', opts);\n\n/** List spaces you can access — all of them, or just those bound to this app. */\nexport const listSpaces = (opts: { app?: boolean } = {}): Promise<SpaceInfo[]> =>\n request<SpaceInfo[]>('list', opts);\n\n/** Release a mounted space (stops its listener on the host). */\nexport const unmountSpace = async (query: { spaceId: string }): Promise<void> => {\n await request('unmount', query);\n};\n\n// ---------------------------------------------------------------------------\n// Space management (the space-manager app) — UI_AS_APPS_SPEC §5.2. These are\n// ELEVATED: enumerating all the user's spaces is `spaces:user`; mutating\n// membership (share/unshare/setRole) and resolving handles is `spaces:admin`.\n// The host enforces the owner-lockout invariant (a space always keeps an owner,\n// T41) and rate-limits handle lookups (L1); the OAuth/identity token never\n// crosses to the app.\n// ---------------------------------------------------------------------------\n\nexport type Role = 'owner' | 'writer' | 'reader';\n\n/** A member of a space (for the share/manage UI). */\nexport interface Member {\n /** `user:{uid}` | `group:{gid}`. */\n principal: string;\n role: Role;\n login?: string;\n avatarUrl?: string;\n}\n\n/** A handle resolved to a principal (handle → who). */\nexport interface ResolvedUser {\n uid: string;\n login: string;\n avatarUrl?: string;\n}\n\n/** Enumerate ALL the user's spaces (not just this app's) — `spaces:user`. */\nexport const listAllSpaces = (): Promise<SpaceInfo[]> => request<SpaceInfo[]>('listAll', {});\n\n/** Read a space's members one-shot — `spaces:admin`. */\nexport const getSpaceMembers = (spaceId: string): Promise<Member[]> =>\n request<Member[]>('members', { spaceId });\n\n/** Invite a user (by provider handle) to a space at a role — `spaces:admin`. The\n * host resolves the handle, so the app never sees other users' uids except the\n * one it invited. */\nexport const shareSpace = async (spaceId: string, login: string, role: Role): Promise<void> => {\n await request('share', { spaceId, login, role });\n};\n\n/** Remove a member from a space — `spaces:admin`. Refused if it would orphan the\n * space (owner-lockout, T41). */\nexport const unshareSpace = async (spaceId: string, uid: string): Promise<void> => {\n await request('unshare', { spaceId, uid });\n};\n\n/** Change a member's role — `spaces:admin`. Refused if it would drop the sole\n * owner (owner-lockout, T41). */\nexport const setSpaceRole = async (spaceId: string, uid: string, role: Role): Promise<void> => {\n await request('setRole', { spaceId, uid, role });\n};\n\n/** Resolve a provider handle to a principal (for the invite flow) — `spaces:admin`,\n * rate-limited host-side. */\nexport const lookupUser = (login: string): Promise<ResolvedUser> =>\n request<ResolvedUser>('lookupUser', { login });\n\n/** One durable grant an app holds, for the §8.11 capability audit view. */\nexport interface GrantRecord {\n /** The app's provider-qualified identity (`provider__namespace__repository`). */\n appKey: string;\n spaceId: string;\n /** Universal mount id (§3.5). */\n mountId: string;\n subtree?: string;\n mode: 'ro' | 'rw';\n name?: string;\n}\n\n/** Enumerate every (app, mount) grant the user holds — the audit view\n * (§8.11). Elevated `spaces:admin`. */\nexport const listGrants = (): Promise<GrantRecord[]> => request<GrantRecord[]>('grants', {});\n\n/** Revoke one app's grant on a space — durable (the app can't re-mount) plus a\n * best-effort live teardown. Elevated `spaces:admin`. */\nexport const revokeGrant = async (appKey: string, spaceId: string): Promise<void> => {\n await request('revokeGrant', { appKey, spaceId });\n};\n"],"mappings":"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","names":["mount"]}
|
package/dist/netFetch.cjs
CHANGED
|
@@ -18,10 +18,12 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var netFetch_exports = {};
|
|
20
20
|
__export(netFetch_exports, {
|
|
21
|
-
hostFetch: () => hostFetch
|
|
21
|
+
hostFetch: () => hostFetch,
|
|
22
|
+
hostFetchStream: () => hostFetchStream
|
|
22
23
|
});
|
|
23
24
|
module.exports = __toCommonJS(netFetch_exports);
|
|
24
25
|
var import_sandboxUtils = require("./sandboxUtils");
|
|
26
|
+
var import_protocolStream = require("./protocolStream");
|
|
25
27
|
const hostFetch = async (url, init = {}) => {
|
|
26
28
|
const res = await (0, import_sandboxUtils.protocolRequest)("fetch", "fetch", [
|
|
27
29
|
{ url, method: init.method, headers: init.headers, body: init.body }
|
|
@@ -33,8 +35,16 @@ const hostFetch = async (url, init = {}) => {
|
|
|
33
35
|
}
|
|
34
36
|
return res.data;
|
|
35
37
|
};
|
|
38
|
+
function hostFetchStream(url, init = {}) {
|
|
39
|
+
return (0, import_protocolStream.protocolStream)(
|
|
40
|
+
"protocol-fetch",
|
|
41
|
+
"fetchStream",
|
|
42
|
+
[{ url, method: init.method, headers: init.headers, body: init.body }]
|
|
43
|
+
);
|
|
44
|
+
}
|
|
36
45
|
// Annotate the CommonJS export names for ESM import in node:
|
|
37
46
|
0 && (module.exports = {
|
|
38
|
-
hostFetch
|
|
47
|
+
hostFetch,
|
|
48
|
+
hostFetchStream
|
|
39
49
|
});
|
|
40
50
|
//# sourceMappingURL=netFetch.cjs.map
|
package/dist/netFetch.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/netFetch.ts"],"sourcesContent":["// hostFetch — the app-facing side of the §5.11 parent-fetch proxy. The app calls\n// `hostFetch(url, init)`; the HOST performs the fetch with its real origin, but\n// only after validating `url` against your manifest's\n// `requests.\"net:fetch\".hosts` ∩ the user's consented hosts, blocking SSRF\n// targets, omitting immediately.run credentials, refusing redirects, and bounding\n// the response size. No raw network handle ever crosses the boundary (§8.10) —\n// only the serialized response.\n\nimport { protocolRequest } from './sandboxUtils';\n\nexport interface HostFetchInit {\n method?: string;\n headers?: Record<string, string>;\n /** Request body for non-GET/HEAD methods (string). */\n body?: string;\n}\n\nexport interface HostFetchResponse {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: string;\n /** True if the body hit the host's size cap and was truncated. */\n truncated: boolean;\n}\n\n/**\n * Fetch through the host's parent-fetch proxy (§5.11). Requires the `net:fetch`\n * capability with `url`'s origin in your effective allowlist (manifest ∩ the\n * user's consent) — both are arranged at load via the consent screen.\n *\n * A reachable server's reply (including a non-2xx status) RESOLVES — inspect\n * `.status`. A gate/SSRF/transport failure REJECTS with an {@link Error} carrying\n * a machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `too-large`, or `network`.\n */\nexport const hostFetch = async (\n url: string,\n init: HostFetchInit = {},\n): Promise<HostFetchResponse> => {\n const res = (await protocolRequest('fetch', 'fetch', [\n { url, method: init.method, headers: init.headers, body: init.body },\n ])) as\n | { ok: true; data: HostFetchResponse }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'hostFetch failed') as Error & { code?: string };\n err.code = (res && 'code' in res ? res.code : undefined) ?? 'unknown';\n throw err;\n }\n return res.data;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,0BAAgC;
|
|
1
|
+
{"version":3,"sources":["../src/netFetch.ts"],"sourcesContent":["// hostFetch — the app-facing side of the §5.11 parent-fetch proxy. The app calls\n// `hostFetch(url, init)`; the HOST performs the fetch with its real origin, but\n// only after validating `url` against your manifest's\n// `requests.\"net:fetch\".hosts` ∩ the user's consented hosts, blocking SSRF\n// targets, omitting immediately.run credentials, refusing redirects, and bounding\n// the response size. No raw network handle ever crosses the boundary (§8.10) —\n// only the serialized response.\n\nimport { protocolRequest } from './sandboxUtils';\nimport { protocolStream } from './protocolStream';\n\nexport interface HostFetchInit {\n method?: string;\n headers?: Record<string, string>;\n /** Request body for non-GET/HEAD methods (string). */\n body?: string;\n}\n\nexport interface HostFetchResponse {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: string;\n /** True if the body hit the host's size cap and was truncated. */\n truncated: boolean;\n}\n\n/**\n * Fetch through the host's parent-fetch proxy (§5.11). Requires the `net:fetch`\n * capability with `url`'s origin in your effective allowlist (manifest ∩ the\n * user's consent) — both are arranged at load via the consent screen.\n *\n * A reachable server's reply (including a non-2xx status) RESOLVES — inspect\n * `.status`. A gate/SSRF/transport failure REJECTS with an {@link Error} carrying\n * a machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `too-large`, or `network`.\n */\nexport const hostFetch = async (\n url: string,\n init: HostFetchInit = {},\n): Promise<HostFetchResponse> => {\n const res = (await protocolRequest('fetch', 'fetch', [\n { url, method: init.method, headers: init.headers, body: init.body },\n ])) as\n | { ok: true; data: HostFetchResponse }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'hostFetch failed') as Error & { code?: string };\n err.code = (res && 'code' in res ? res.code : undefined) ?? 'unknown';\n throw err;\n }\n return res.data;\n};\n\n/** One streamed slice of the response body (a chunk as it arrives from the\n * host). Concatenate `.chunk` across the stream to rebuild the body. */\nexport interface HostFetchStreamEvent {\n chunk: string;\n}\n\n/** The terminal value of a {@link hostFetchStream} run — the response metadata,\n * delivered once after the last body chunk. There is no `body` field: the body\n * arrived as the stream of {@link HostFetchStreamEvent}s. */\nexport interface HostFetchStreamResult {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n /** True if the stream hit the host's cumulative byte cap and was truncated. */\n truncated: boolean;\n /** Total decoded body bytes delivered. */\n bytes: number;\n}\n\n/**\n * Stream a response through the host's parent-fetch proxy (§5.11 streaming;\n * `LLM_AND_AGENTS_SPEC §2.2`) — the streaming counterpart of {@link hostFetch},\n * for SSE / LLM token streaming. Same `net:fetch` gate (manifest ∩ consent),\n * same credential-less rule and per-hop SSRF re-check; the response body is\n * pumped as a sequence of {@link HostFetchStreamEvent} chunks instead of a single\n * buffered reply.\n *\n * ```ts\n * let body = '';\n * for await (const { chunk } of hostFetchStream(url, { method: 'POST', body })) {\n * body += chunk; // e.g. parse SSE / token deltas as they arrive\n * }\n * ```\n *\n * The generator **returns** a {@link HostFetchStreamResult} (status + headers +\n * `truncated`/`bytes`) when the body completes. A gate/SSRF/transport failure —\n * or one of the host's stream bounds — **throws** a `StreamError` (from\n * `./protocolStream`) carrying a\n * machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `idle-timeout` / `total-timeout` (the host's stream bounds), `byte-cap`, or\n * `network`. The stream is bounded by the host's idle/total timeouts and a\n * cumulative byte cap, so it cannot be used as an unbounded transfer channel.\n *\n * Requires the host to implement the `protocol-fetch` streaming emitter; until\n * that lands a streaming request fails with `not-streamable` and callers should\n * fall back to {@link hostFetch} (`LLM_AND_AGENTS_SPEC §2.2`, roadmap P3-71).\n */\nexport function hostFetchStream(\n url: string,\n init: HostFetchInit = {},\n): AsyncGenerator<HostFetchStreamEvent, HostFetchStreamResult, void> {\n return protocolStream<HostFetchStreamEvent, HostFetchStreamResult>(\n 'protocol-fetch',\n 'fetchStream',\n [{ url, method: init.method, headers: init.headers, body: init.body }],\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,0BAAgC;AAChC,4BAA+B;AA6BxB,MAAM,YAAY,OACvB,KACA,OAAsB,CAAC,MACQ;AAC/B,QAAM,MAAO,UAAM,qCAAgB,SAAS,SAAS;AAAA,IACnD,EAAE,KAAK,QAAQ,KAAK,QAAQ,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK;AAAA,EACrE,CAAC;AAID,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,kBAAkB;AACxD,QAAI,QAAQ,OAAO,UAAU,MAAM,IAAI,OAAO,WAAc;AAC5D,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAkDO,SAAS,gBACd,KACA,OAAsB,CAAC,GAC4C;AACnE,aAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,CAAC,EAAE,KAAK,QAAQ,KAAK,QAAQ,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK,CAAC;AAAA,EACvE;AACF;","names":[]}
|
package/dist/netFetch.d.cts
CHANGED
|
@@ -24,5 +24,52 @@ interface HostFetchResponse {
|
|
|
24
24
|
* `too-large`, or `network`.
|
|
25
25
|
*/
|
|
26
26
|
declare const hostFetch: (url: string, init?: HostFetchInit) => Promise<HostFetchResponse>;
|
|
27
|
+
/** One streamed slice of the response body (a chunk as it arrives from the
|
|
28
|
+
* host). Concatenate `.chunk` across the stream to rebuild the body. */
|
|
29
|
+
interface HostFetchStreamEvent {
|
|
30
|
+
chunk: string;
|
|
31
|
+
}
|
|
32
|
+
/** The terminal value of a {@link hostFetchStream} run — the response metadata,
|
|
33
|
+
* delivered once after the last body chunk. There is no `body` field: the body
|
|
34
|
+
* arrived as the stream of {@link HostFetchStreamEvent}s. */
|
|
35
|
+
interface HostFetchStreamResult {
|
|
36
|
+
status: number;
|
|
37
|
+
statusText: string;
|
|
38
|
+
headers: Record<string, string>;
|
|
39
|
+
/** True if the stream hit the host's cumulative byte cap and was truncated. */
|
|
40
|
+
truncated: boolean;
|
|
41
|
+
/** Total decoded body bytes delivered. */
|
|
42
|
+
bytes: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Stream a response through the host's parent-fetch proxy (§5.11 streaming;
|
|
46
|
+
* `LLM_AND_AGENTS_SPEC §2.2`) — the streaming counterpart of {@link hostFetch},
|
|
47
|
+
* for SSE / LLM token streaming. Same `net:fetch` gate (manifest ∩ consent),
|
|
48
|
+
* same credential-less rule and per-hop SSRF re-check; the response body is
|
|
49
|
+
* pumped as a sequence of {@link HostFetchStreamEvent} chunks instead of a single
|
|
50
|
+
* buffered reply.
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* let body = '';
|
|
54
|
+
* for await (const { chunk } of hostFetchStream(url, { method: 'POST', body })) {
|
|
55
|
+
* body += chunk; // e.g. parse SSE / token deltas as they arrive
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* The generator **returns** a {@link HostFetchStreamResult} (status + headers +
|
|
60
|
+
* `truncated`/`bytes`) when the body completes. A gate/SSRF/transport failure —
|
|
61
|
+
* or one of the host's stream bounds — **throws** a `StreamError` (from
|
|
62
|
+
* `./protocolStream`) carrying a
|
|
63
|
+
* machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),
|
|
64
|
+
* `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),
|
|
65
|
+
* `idle-timeout` / `total-timeout` (the host's stream bounds), `byte-cap`, or
|
|
66
|
+
* `network`. The stream is bounded by the host's idle/total timeouts and a
|
|
67
|
+
* cumulative byte cap, so it cannot be used as an unbounded transfer channel.
|
|
68
|
+
*
|
|
69
|
+
* Requires the host to implement the `protocol-fetch` streaming emitter; until
|
|
70
|
+
* that lands a streaming request fails with `not-streamable` and callers should
|
|
71
|
+
* fall back to {@link hostFetch} (`LLM_AND_AGENTS_SPEC §2.2`, roadmap P3-71).
|
|
72
|
+
*/
|
|
73
|
+
declare function hostFetchStream(url: string, init?: HostFetchInit): AsyncGenerator<HostFetchStreamEvent, HostFetchStreamResult, void>;
|
|
27
74
|
|
|
28
|
-
export { type HostFetchInit, type HostFetchResponse, hostFetch };
|
|
75
|
+
export { type HostFetchInit, type HostFetchResponse, type HostFetchStreamEvent, type HostFetchStreamResult, hostFetch, hostFetchStream };
|
package/dist/netFetch.d.ts
CHANGED
|
@@ -24,5 +24,52 @@ interface HostFetchResponse {
|
|
|
24
24
|
* `too-large`, or `network`.
|
|
25
25
|
*/
|
|
26
26
|
declare const hostFetch: (url: string, init?: HostFetchInit) => Promise<HostFetchResponse>;
|
|
27
|
+
/** One streamed slice of the response body (a chunk as it arrives from the
|
|
28
|
+
* host). Concatenate `.chunk` across the stream to rebuild the body. */
|
|
29
|
+
interface HostFetchStreamEvent {
|
|
30
|
+
chunk: string;
|
|
31
|
+
}
|
|
32
|
+
/** The terminal value of a {@link hostFetchStream} run — the response metadata,
|
|
33
|
+
* delivered once after the last body chunk. There is no `body` field: the body
|
|
34
|
+
* arrived as the stream of {@link HostFetchStreamEvent}s. */
|
|
35
|
+
interface HostFetchStreamResult {
|
|
36
|
+
status: number;
|
|
37
|
+
statusText: string;
|
|
38
|
+
headers: Record<string, string>;
|
|
39
|
+
/** True if the stream hit the host's cumulative byte cap and was truncated. */
|
|
40
|
+
truncated: boolean;
|
|
41
|
+
/** Total decoded body bytes delivered. */
|
|
42
|
+
bytes: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Stream a response through the host's parent-fetch proxy (§5.11 streaming;
|
|
46
|
+
* `LLM_AND_AGENTS_SPEC §2.2`) — the streaming counterpart of {@link hostFetch},
|
|
47
|
+
* for SSE / LLM token streaming. Same `net:fetch` gate (manifest ∩ consent),
|
|
48
|
+
* same credential-less rule and per-hop SSRF re-check; the response body is
|
|
49
|
+
* pumped as a sequence of {@link HostFetchStreamEvent} chunks instead of a single
|
|
50
|
+
* buffered reply.
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* let body = '';
|
|
54
|
+
* for await (const { chunk } of hostFetchStream(url, { method: 'POST', body })) {
|
|
55
|
+
* body += chunk; // e.g. parse SSE / token deltas as they arrive
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* The generator **returns** a {@link HostFetchStreamResult} (status + headers +
|
|
60
|
+
* `truncated`/`bytes`) when the body completes. A gate/SSRF/transport failure —
|
|
61
|
+
* or one of the host's stream bounds — **throws** a `StreamError` (from
|
|
62
|
+
* `./protocolStream`) carrying a
|
|
63
|
+
* machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),
|
|
64
|
+
* `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),
|
|
65
|
+
* `idle-timeout` / `total-timeout` (the host's stream bounds), `byte-cap`, or
|
|
66
|
+
* `network`. The stream is bounded by the host's idle/total timeouts and a
|
|
67
|
+
* cumulative byte cap, so it cannot be used as an unbounded transfer channel.
|
|
68
|
+
*
|
|
69
|
+
* Requires the host to implement the `protocol-fetch` streaming emitter; until
|
|
70
|
+
* that lands a streaming request fails with `not-streamable` and callers should
|
|
71
|
+
* fall back to {@link hostFetch} (`LLM_AND_AGENTS_SPEC §2.2`, roadmap P3-71).
|
|
72
|
+
*/
|
|
73
|
+
declare function hostFetchStream(url: string, init?: HostFetchInit): AsyncGenerator<HostFetchStreamEvent, HostFetchStreamResult, void>;
|
|
27
74
|
|
|
28
|
-
export { type HostFetchInit, type HostFetchResponse, hostFetch };
|
|
75
|
+
export { type HostFetchInit, type HostFetchResponse, type HostFetchStreamEvent, type HostFetchStreamResult, hostFetch, hostFetchStream };
|
package/dist/netFetch.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { protocolRequest } from "./sandboxUtils";
|
|
2
|
+
import { protocolStream } from "./protocolStream";
|
|
2
3
|
const hostFetch = async (url, init = {}) => {
|
|
3
4
|
const res = await protocolRequest("fetch", "fetch", [
|
|
4
5
|
{ url, method: init.method, headers: init.headers, body: init.body }
|
|
@@ -10,7 +11,15 @@ const hostFetch = async (url, init = {}) => {
|
|
|
10
11
|
}
|
|
11
12
|
return res.data;
|
|
12
13
|
};
|
|
14
|
+
function hostFetchStream(url, init = {}) {
|
|
15
|
+
return protocolStream(
|
|
16
|
+
"protocol-fetch",
|
|
17
|
+
"fetchStream",
|
|
18
|
+
[{ url, method: init.method, headers: init.headers, body: init.body }]
|
|
19
|
+
);
|
|
20
|
+
}
|
|
13
21
|
export {
|
|
14
|
-
hostFetch
|
|
22
|
+
hostFetch,
|
|
23
|
+
hostFetchStream
|
|
15
24
|
};
|
|
16
25
|
//# sourceMappingURL=netFetch.js.map
|
package/dist/netFetch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/netFetch.ts"],"sourcesContent":["// hostFetch — the app-facing side of the §5.11 parent-fetch proxy. The app calls\n// `hostFetch(url, init)`; the HOST performs the fetch with its real origin, but\n// only after validating `url` against your manifest's\n// `requests.\"net:fetch\".hosts` ∩ the user's consented hosts, blocking SSRF\n// targets, omitting immediately.run credentials, refusing redirects, and bounding\n// the response size. No raw network handle ever crosses the boundary (§8.10) —\n// only the serialized response.\n\nimport { protocolRequest } from './sandboxUtils';\n\nexport interface HostFetchInit {\n method?: string;\n headers?: Record<string, string>;\n /** Request body for non-GET/HEAD methods (string). */\n body?: string;\n}\n\nexport interface HostFetchResponse {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: string;\n /** True if the body hit the host's size cap and was truncated. */\n truncated: boolean;\n}\n\n/**\n * Fetch through the host's parent-fetch proxy (§5.11). Requires the `net:fetch`\n * capability with `url`'s origin in your effective allowlist (manifest ∩ the\n * user's consent) — both are arranged at load via the consent screen.\n *\n * A reachable server's reply (including a non-2xx status) RESOLVES — inspect\n * `.status`. A gate/SSRF/transport failure REJECTS with an {@link Error} carrying\n * a machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `too-large`, or `network`.\n */\nexport const hostFetch = async (\n url: string,\n init: HostFetchInit = {},\n): Promise<HostFetchResponse> => {\n const res = (await protocolRequest('fetch', 'fetch', [\n { url, method: init.method, headers: init.headers, body: init.body },\n ])) as\n | { ok: true; data: HostFetchResponse }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'hostFetch failed') as Error & { code?: string };\n err.code = (res && 'code' in res ? res.code : undefined) ?? 'unknown';\n throw err;\n }\n return res.data;\n};\n"],"mappings":"AAQA,SAAS,uBAAuB;
|
|
1
|
+
{"version":3,"sources":["../src/netFetch.ts"],"sourcesContent":["// hostFetch — the app-facing side of the §5.11 parent-fetch proxy. The app calls\n// `hostFetch(url, init)`; the HOST performs the fetch with its real origin, but\n// only after validating `url` against your manifest's\n// `requests.\"net:fetch\".hosts` ∩ the user's consented hosts, blocking SSRF\n// targets, omitting immediately.run credentials, refusing redirects, and bounding\n// the response size. No raw network handle ever crosses the boundary (§8.10) —\n// only the serialized response.\n\nimport { protocolRequest } from './sandboxUtils';\nimport { protocolStream } from './protocolStream';\n\nexport interface HostFetchInit {\n method?: string;\n headers?: Record<string, string>;\n /** Request body for non-GET/HEAD methods (string). */\n body?: string;\n}\n\nexport interface HostFetchResponse {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: string;\n /** True if the body hit the host's size cap and was truncated. */\n truncated: boolean;\n}\n\n/**\n * Fetch through the host's parent-fetch proxy (§5.11). Requires the `net:fetch`\n * capability with `url`'s origin in your effective allowlist (manifest ∩ the\n * user's consent) — both are arranged at load via the consent screen.\n *\n * A reachable server's reply (including a non-2xx status) RESOLVES — inspect\n * `.status`. A gate/SSRF/transport failure REJECTS with an {@link Error} carrying\n * a machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `too-large`, or `network`.\n */\nexport const hostFetch = async (\n url: string,\n init: HostFetchInit = {},\n): Promise<HostFetchResponse> => {\n const res = (await protocolRequest('fetch', 'fetch', [\n { url, method: init.method, headers: init.headers, body: init.body },\n ])) as\n | { ok: true; data: HostFetchResponse }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'hostFetch failed') as Error & { code?: string };\n err.code = (res && 'code' in res ? res.code : undefined) ?? 'unknown';\n throw err;\n }\n return res.data;\n};\n\n/** One streamed slice of the response body (a chunk as it arrives from the\n * host). Concatenate `.chunk` across the stream to rebuild the body. */\nexport interface HostFetchStreamEvent {\n chunk: string;\n}\n\n/** The terminal value of a {@link hostFetchStream} run — the response metadata,\n * delivered once after the last body chunk. There is no `body` field: the body\n * arrived as the stream of {@link HostFetchStreamEvent}s. */\nexport interface HostFetchStreamResult {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n /** True if the stream hit the host's cumulative byte cap and was truncated. */\n truncated: boolean;\n /** Total decoded body bytes delivered. */\n bytes: number;\n}\n\n/**\n * Stream a response through the host's parent-fetch proxy (§5.11 streaming;\n * `LLM_AND_AGENTS_SPEC §2.2`) — the streaming counterpart of {@link hostFetch},\n * for SSE / LLM token streaming. Same `net:fetch` gate (manifest ∩ consent),\n * same credential-less rule and per-hop SSRF re-check; the response body is\n * pumped as a sequence of {@link HostFetchStreamEvent} chunks instead of a single\n * buffered reply.\n *\n * ```ts\n * let body = '';\n * for await (const { chunk } of hostFetchStream(url, { method: 'POST', body })) {\n * body += chunk; // e.g. parse SSE / token deltas as they arrive\n * }\n * ```\n *\n * The generator **returns** a {@link HostFetchStreamResult} (status + headers +\n * `truncated`/`bytes`) when the body completes. A gate/SSRF/transport failure —\n * or one of the host's stream bounds — **throws** a `StreamError` (from\n * `./protocolStream`) carrying a\n * machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `idle-timeout` / `total-timeout` (the host's stream bounds), `byte-cap`, or\n * `network`. The stream is bounded by the host's idle/total timeouts and a\n * cumulative byte cap, so it cannot be used as an unbounded transfer channel.\n *\n * Requires the host to implement the `protocol-fetch` streaming emitter; until\n * that lands a streaming request fails with `not-streamable` and callers should\n * fall back to {@link hostFetch} (`LLM_AND_AGENTS_SPEC §2.2`, roadmap P3-71).\n */\nexport function hostFetchStream(\n url: string,\n init: HostFetchInit = {},\n): AsyncGenerator<HostFetchStreamEvent, HostFetchStreamResult, void> {\n return protocolStream<HostFetchStreamEvent, HostFetchStreamResult>(\n 'protocol-fetch',\n 'fetchStream',\n [{ url, method: init.method, headers: init.headers, body: init.body }],\n );\n}\n"],"mappings":"AAQA,SAAS,uBAAuB;AAChC,SAAS,sBAAsB;AA6BxB,MAAM,YAAY,OACvB,KACA,OAAsB,CAAC,MACQ;AAC/B,QAAM,MAAO,MAAM,gBAAgB,SAAS,SAAS;AAAA,IACnD,EAAE,KAAK,QAAQ,KAAK,QAAQ,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK;AAAA,EACrE,CAAC;AAID,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,kBAAkB;AACxD,QAAI,QAAQ,OAAO,UAAU,MAAM,IAAI,OAAO,WAAc;AAC5D,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAkDO,SAAS,gBACd,KACA,OAAsB,CAAC,GAC4C;AACnE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,CAAC,EAAE,KAAK,QAAQ,KAAK,QAAQ,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK,CAAC;AAAA,EACvE;AACF;","names":[]}
|
package/dist/runtime.cjs
CHANGED
|
@@ -19,18 +19,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var runtime_exports = {};
|
|
20
20
|
__export(runtime_exports, {
|
|
21
21
|
SDK_PROTOCOL_VERSION: () => SDK_PROTOCOL_VERSION,
|
|
22
|
-
SDK_VERSION: () => SDK_VERSION,
|
|
22
|
+
SDK_VERSION: () => import_version.SDK_VERSION,
|
|
23
23
|
announceHandshake: () => announceHandshake,
|
|
24
24
|
getHostRuntime: () => import_hostRuntime.getHostRuntime,
|
|
25
25
|
sdkHandshake: () => sdkHandshake
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(runtime_exports);
|
|
28
28
|
var import_sandboxUtils = require("./sandboxUtils");
|
|
29
|
+
var import_version = require("./version");
|
|
29
30
|
var import_hostRuntime = require("./hostRuntime");
|
|
30
31
|
const SDK_PROTOCOL_VERSION = "1.0.0";
|
|
31
|
-
const SDK_VERSION = "0.4.0";
|
|
32
32
|
const sdkHandshake = () => ({
|
|
33
|
-
sdkVersion: SDK_VERSION,
|
|
33
|
+
sdkVersion: import_version.SDK_VERSION,
|
|
34
34
|
protocolVersion: SDK_PROTOCOL_VERSION
|
|
35
35
|
});
|
|
36
36
|
function announceHandshake() {
|
package/dist/runtime.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["// Runtime discovery + version handshake (SDK_PACKAGING_SPEC §4/§6).\n//\n// Today the SDK reaches the host through the INJECTED sandbox services\n// (`module.evaluation.module.bundler.*`, see sandboxUtils). The packaging migration\n// makes the SDK an app-pinnable npm dependency that finds the runtime through a\n// stable, versioned global the sandbox publishes BEFORE evaluating app code:\n//\n// globalThis.__immediatelyRun__ = { runtimeVersion, protocolVersion, transport }\n//\n// Phase 1 (behind a flag, injection still active): the SDK can READ that global\n// when present (else fall back to injection), and ANNOUNCE its own version +\n// protocol so the host can record + version-check it (§6/T45). The transport itself\n// is unchanged here — this only wires the discovery + handshake fields so the check\n// exists when app-pinned versions become real.\nimport { sendMessage, addListener } from './sandboxUtils';\n\n// `getHostRuntime` + `ImmediatelyRunGlobal` live in the leaf `hostRuntime` module\n// (imports nothing) and are re-exported here for a stable public API. This breaks\n// the sandboxUtils↔runtime import cycle: sandboxUtils reads `getHostRuntime` from\n// the leaf, while runtime still imports sandboxUtils for the handshake — one\n// direction only, no cycle.\nexport { getHostRuntime } from './hostRuntime';\nexport type { ImmediatelyRunGlobal } from './hostRuntime';\n\n/** The wire protocol (postMessage envelope / channels / methods) THIS SDK speaks.\n * Additive-only (§9); bump only for a backwards-compatible extension. */\nexport const SDK_PROTOCOL_VERSION = '1.0.0';\n\n/** This SDK's package version
|
|
1
|
+
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["// Runtime discovery + version handshake (SDK_PACKAGING_SPEC §4/§6).\n//\n// Today the SDK reaches the host through the INJECTED sandbox services\n// (`module.evaluation.module.bundler.*`, see sandboxUtils). The packaging migration\n// makes the SDK an app-pinnable npm dependency that finds the runtime through a\n// stable, versioned global the sandbox publishes BEFORE evaluating app code:\n//\n// globalThis.__immediatelyRun__ = { runtimeVersion, protocolVersion, transport }\n//\n// Phase 1 (behind a flag, injection still active): the SDK can READ that global\n// when present (else fall back to injection), and ANNOUNCE its own version +\n// protocol so the host can record + version-check it (§6/T45). The transport itself\n// is unchanged here — this only wires the discovery + handshake fields so the check\n// exists when app-pinned versions become real.\nimport { sendMessage, addListener } from './sandboxUtils';\nimport { SDK_VERSION } from './version';\n\n// `getHostRuntime` + `ImmediatelyRunGlobal` live in the leaf `hostRuntime` module\n// (imports nothing) and are re-exported here for a stable public API. This breaks\n// the sandboxUtils↔runtime import cycle: sandboxUtils reads `getHostRuntime` from\n// the leaf, while runtime still imports sandboxUtils for the handshake — one\n// direction only, no cycle.\nexport { getHostRuntime } from './hostRuntime';\nexport type { ImmediatelyRunGlobal } from './hostRuntime';\n\n/** The wire protocol (postMessage envelope / channels / methods) THIS SDK speaks.\n * Additive-only (§9); bump only for a backwards-compatible extension. */\nexport const SDK_PROTOCOL_VERSION = '1.0.0';\n\n/** This SDK's package version, baked from package.json at build (SP2-6,\n * `scripts/gen-version.mjs`). Re-exported so the public surface is unchanged\n * (`@immediately-run/sdk` → `SDK_VERSION`); imported above for the handshake. */\nexport { SDK_VERSION };\n\n/** This SDK's handshake payload — the version + protocol the host records + checks\n * against `HOST_PROTOCOL_VERSION` (§6/T45). */\nexport interface SdkHandshake {\n sdkVersion: string;\n protocolVersion: string;\n}\nexport const sdkHandshake = (): SdkHandshake => ({\n sdkVersion: SDK_VERSION,\n protocolVersion: SDK_PROTOCOL_VERSION,\n});\n\n/**\n * Announce this SDK's version to the host (§6). Sends `sdk-handshake` eagerly\n * (best-effort — the host may already be listening) AND replies to a host\n * `request-handshake` (the robust path, mirroring the other `request-*` pulls).\n * Idempotent; safe to call more than once. Returns an unsubscribe fn.\n */\nexport function announceHandshake(): () => void {\n const send = () => {\n try {\n sendMessage('sdk-handshake', sdkHandshake() as unknown as Record<string, unknown>);\n } catch {\n /* transport not ready yet — the request-handshake reply covers it */\n }\n };\n send();\n return addListener('request-handshake', send);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,0BAAyC;AACzC,qBAA4B;AAO5B,yBAA+B;AAKxB,MAAM,uBAAuB;AAa7B,MAAM,eAAe,OAAqB;AAAA,EAC/C,YAAY;AAAA,EACZ,iBAAiB;AACnB;AAQO,SAAS,oBAAgC;AAC9C,QAAM,OAAO,MAAM;AACjB,QAAI;AACF,2CAAY,iBAAiB,aAAa,CAAuC;AAAA,IACnF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,OAAK;AACL,aAAO,iCAAY,qBAAqB,IAAI;AAC9C;","names":[]}
|
package/dist/runtime.d.cts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
+
export { SDK_VERSION } from './version.cjs';
|
|
1
2
|
export { ImmediatelyRunGlobal, getHostRuntime } from './hostRuntime.cjs';
|
|
2
3
|
|
|
3
4
|
/** The wire protocol (postMessage envelope / channels / methods) THIS SDK speaks.
|
|
4
5
|
* Additive-only (§9); bump only for a backwards-compatible extension. */
|
|
5
6
|
declare const SDK_PROTOCOL_VERSION = "1.0.0";
|
|
6
|
-
|
|
7
|
-
* inject it later; a constant is fine while versions are still effectively fixed). */
|
|
8
|
-
declare const SDK_VERSION = "0.4.0";
|
|
7
|
+
|
|
9
8
|
/** This SDK's handshake payload — the version + protocol the host records + checks
|
|
10
9
|
* against `HOST_PROTOCOL_VERSION` (§6/T45). */
|
|
11
10
|
interface SdkHandshake {
|
|
@@ -21,4 +20,4 @@ declare const sdkHandshake: () => SdkHandshake;
|
|
|
21
20
|
*/
|
|
22
21
|
declare function announceHandshake(): () => void;
|
|
23
22
|
|
|
24
|
-
export { SDK_PROTOCOL_VERSION,
|
|
23
|
+
export { SDK_PROTOCOL_VERSION, type SdkHandshake, announceHandshake, sdkHandshake };
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
+
export { SDK_VERSION } from './version.js';
|
|
1
2
|
export { ImmediatelyRunGlobal, getHostRuntime } from './hostRuntime.js';
|
|
2
3
|
|
|
3
4
|
/** The wire protocol (postMessage envelope / channels / methods) THIS SDK speaks.
|
|
4
5
|
* Additive-only (§9); bump only for a backwards-compatible extension. */
|
|
5
6
|
declare const SDK_PROTOCOL_VERSION = "1.0.0";
|
|
6
|
-
|
|
7
|
-
* inject it later; a constant is fine while versions are still effectively fixed). */
|
|
8
|
-
declare const SDK_VERSION = "0.4.0";
|
|
7
|
+
|
|
9
8
|
/** This SDK's handshake payload — the version + protocol the host records + checks
|
|
10
9
|
* against `HOST_PROTOCOL_VERSION` (§6/T45). */
|
|
11
10
|
interface SdkHandshake {
|
|
@@ -21,4 +20,4 @@ declare const sdkHandshake: () => SdkHandshake;
|
|
|
21
20
|
*/
|
|
22
21
|
declare function announceHandshake(): () => void;
|
|
23
22
|
|
|
24
|
-
export { SDK_PROTOCOL_VERSION,
|
|
23
|
+
export { SDK_PROTOCOL_VERSION, type SdkHandshake, announceHandshake, sdkHandshake };
|
package/dist/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { sendMessage, addListener } from "./sandboxUtils";
|
|
2
|
+
import { SDK_VERSION } from "./version";
|
|
2
3
|
import { getHostRuntime } from "./hostRuntime";
|
|
3
4
|
const SDK_PROTOCOL_VERSION = "1.0.0";
|
|
4
|
-
const SDK_VERSION = "0.4.0";
|
|
5
5
|
const sdkHandshake = () => ({
|
|
6
6
|
sdkVersion: SDK_VERSION,
|
|
7
7
|
protocolVersion: SDK_PROTOCOL_VERSION
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["// Runtime discovery + version handshake (SDK_PACKAGING_SPEC §4/§6).\n//\n// Today the SDK reaches the host through the INJECTED sandbox services\n// (`module.evaluation.module.bundler.*`, see sandboxUtils). The packaging migration\n// makes the SDK an app-pinnable npm dependency that finds the runtime through a\n// stable, versioned global the sandbox publishes BEFORE evaluating app code:\n//\n// globalThis.__immediatelyRun__ = { runtimeVersion, protocolVersion, transport }\n//\n// Phase 1 (behind a flag, injection still active): the SDK can READ that global\n// when present (else fall back to injection), and ANNOUNCE its own version +\n// protocol so the host can record + version-check it (§6/T45). The transport itself\n// is unchanged here — this only wires the discovery + handshake fields so the check\n// exists when app-pinned versions become real.\nimport { sendMessage, addListener } from './sandboxUtils';\n\n// `getHostRuntime` + `ImmediatelyRunGlobal` live in the leaf `hostRuntime` module\n// (imports nothing) and are re-exported here for a stable public API. This breaks\n// the sandboxUtils↔runtime import cycle: sandboxUtils reads `getHostRuntime` from\n// the leaf, while runtime still imports sandboxUtils for the handshake — one\n// direction only, no cycle.\nexport { getHostRuntime } from './hostRuntime';\nexport type { ImmediatelyRunGlobal } from './hostRuntime';\n\n/** The wire protocol (postMessage envelope / channels / methods) THIS SDK speaks.\n * Additive-only (§9); bump only for a backwards-compatible extension. */\nexport const SDK_PROTOCOL_VERSION = '1.0.0';\n\n/** This SDK's package version
|
|
1
|
+
{"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["// Runtime discovery + version handshake (SDK_PACKAGING_SPEC §4/§6).\n//\n// Today the SDK reaches the host through the INJECTED sandbox services\n// (`module.evaluation.module.bundler.*`, see sandboxUtils). The packaging migration\n// makes the SDK an app-pinnable npm dependency that finds the runtime through a\n// stable, versioned global the sandbox publishes BEFORE evaluating app code:\n//\n// globalThis.__immediatelyRun__ = { runtimeVersion, protocolVersion, transport }\n//\n// Phase 1 (behind a flag, injection still active): the SDK can READ that global\n// when present (else fall back to injection), and ANNOUNCE its own version +\n// protocol so the host can record + version-check it (§6/T45). The transport itself\n// is unchanged here — this only wires the discovery + handshake fields so the check\n// exists when app-pinned versions become real.\nimport { sendMessage, addListener } from './sandboxUtils';\nimport { SDK_VERSION } from './version';\n\n// `getHostRuntime` + `ImmediatelyRunGlobal` live in the leaf `hostRuntime` module\n// (imports nothing) and are re-exported here for a stable public API. This breaks\n// the sandboxUtils↔runtime import cycle: sandboxUtils reads `getHostRuntime` from\n// the leaf, while runtime still imports sandboxUtils for the handshake — one\n// direction only, no cycle.\nexport { getHostRuntime } from './hostRuntime';\nexport type { ImmediatelyRunGlobal } from './hostRuntime';\n\n/** The wire protocol (postMessage envelope / channels / methods) THIS SDK speaks.\n * Additive-only (§9); bump only for a backwards-compatible extension. */\nexport const SDK_PROTOCOL_VERSION = '1.0.0';\n\n/** This SDK's package version, baked from package.json at build (SP2-6,\n * `scripts/gen-version.mjs`). Re-exported so the public surface is unchanged\n * (`@immediately-run/sdk` → `SDK_VERSION`); imported above for the handshake. */\nexport { SDK_VERSION };\n\n/** This SDK's handshake payload — the version + protocol the host records + checks\n * against `HOST_PROTOCOL_VERSION` (§6/T45). */\nexport interface SdkHandshake {\n sdkVersion: string;\n protocolVersion: string;\n}\nexport const sdkHandshake = (): SdkHandshake => ({\n sdkVersion: SDK_VERSION,\n protocolVersion: SDK_PROTOCOL_VERSION,\n});\n\n/**\n * Announce this SDK's version to the host (§6). Sends `sdk-handshake` eagerly\n * (best-effort — the host may already be listening) AND replies to a host\n * `request-handshake` (the robust path, mirroring the other `request-*` pulls).\n * Idempotent; safe to call more than once. Returns an unsubscribe fn.\n */\nexport function announceHandshake(): () => void {\n const send = () => {\n try {\n sendMessage('sdk-handshake', sdkHandshake() as unknown as Record<string, unknown>);\n } catch {\n /* transport not ready yet — the request-handshake reply covers it */\n }\n };\n send();\n return addListener('request-handshake', send);\n}\n"],"mappings":"AAcA,SAAS,aAAa,mBAAmB;AACzC,SAAS,mBAAmB;AAO5B,SAAS,sBAAsB;AAKxB,MAAM,uBAAuB;AAa7B,MAAM,eAAe,OAAqB;AAAA,EAC/C,YAAY;AAAA,EACZ,iBAAiB;AACnB;AAQO,SAAS,oBAAgC;AAC9C,QAAM,OAAO,MAAM;AACjB,QAAI;AACF,kBAAY,iBAAiB,aAAa,CAAuC;AAAA,IACnF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,OAAK;AACL,SAAO,YAAY,qBAAqB,IAAI;AAC9C;","names":[]}
|
package/dist/secrets.cjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var secrets_exports = {};
|
|
20
|
+
__export(secrets_exports, {
|
|
21
|
+
getSecrets: () => getSecrets,
|
|
22
|
+
onSecretsChange: () => onSecretsChange,
|
|
23
|
+
requestAddSecret: () => requestAddSecret,
|
|
24
|
+
requestSecret: () => requestSecret,
|
|
25
|
+
revokeSecret: () => revokeSecret,
|
|
26
|
+
useSecrets: () => useSecrets
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(secrets_exports);
|
|
29
|
+
var import_sandboxUtils = require("./sandboxUtils");
|
|
30
|
+
var import_pushChannel = require("./pushChannel");
|
|
31
|
+
const request = async (method, query = {}) => {
|
|
32
|
+
const res = await (0, import_sandboxUtils.protocolRequest)("secrets", method, [query]);
|
|
33
|
+
if (!res || res.ok !== true) {
|
|
34
|
+
const err = new Error(res?.message ?? "secret request failed");
|
|
35
|
+
err.code = res?.code ?? "unknown";
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
return res.data;
|
|
39
|
+
};
|
|
40
|
+
const requestAddSecret = (hints = {}) => request("add", hints);
|
|
41
|
+
const requestSecret = (query = {}) => request("request", query);
|
|
42
|
+
const revokeSecret = async (id) => {
|
|
43
|
+
await request("revoke", { id });
|
|
44
|
+
};
|
|
45
|
+
const channel = (0, import_pushChannel.createPushChannel)({
|
|
46
|
+
pushType: "secrets-metadata",
|
|
47
|
+
requestType: "request-secrets-metadata",
|
|
48
|
+
initial: [],
|
|
49
|
+
parse: (msg) => Array.isArray(msg.secrets) ? msg.secrets : void 0
|
|
50
|
+
});
|
|
51
|
+
const getSecrets = () => channel.get();
|
|
52
|
+
const onSecretsChange = (listener) => channel.onChange(listener);
|
|
53
|
+
const useSecrets = () => channel.use();
|
|
54
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
55
|
+
0 && (module.exports = {
|
|
56
|
+
getSecrets,
|
|
57
|
+
onSecretsChange,
|
|
58
|
+
requestAddSecret,
|
|
59
|
+
requestSecret,
|
|
60
|
+
revokeSecret,
|
|
61
|
+
useSecrets
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=secrets.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/secrets.ts"],"sourcesContent":["// The host-owned secret store — app-facing surface (SECRETS_SPEC §4/§5).\n//\n// An app can ASK the user to store a secret (`requestAddSecret`), be GRANTED the\n// right to USE a specific secret (`requestSecret`, the powerbox flow), LIST\n// secret METADATA (`getSecrets`/`useSecrets`, never values), and REVOKE one\n// (`revokeSecret`). The secret VALUE never crosses this boundary: it is read\n// host-side, once, at the `net:fetch` injection point (SECRETS_SPEC §6). These\n// functions move only hints, metadata, and grant handles.\n//\n// Resolves LLM_AND_AGENTS_SPEC D2 — the host-mediated BYOK key store. Inert until\n// the host implements `protocol-secrets` + the `secrets-metadata` channel\n// (SECRETS_SPEC §3/§6 host work, roadmap P1.E); the contract is shipped here so\n// apps (e.g. the in-browser coding agent, P3-73) can be written against it.\nimport { protocolRequest } from './sandboxUtils';\nimport { createPushChannel } from './pushChannel';\n\n/** The closed secret-type vocabulary (SECRETS_SPEC §2). `api-key` is always\n * origin-bound; `oauth-refresh` is reserved (no substitution in v1). */\nexport type SecretType = 'api-key' | 'bearer-token' | 'oauth-refresh';\n\n/**\n * The metadata-only projection of a stored secret (SECRETS_SPEC §2/§4) — exactly\n * what `secrets:list` and the powerbox return. **There is no `value` field by\n * design**: the plaintext is never part of any record an app receives.\n */\nexport interface SecretView {\n id: string;\n type: SecretType;\n family?: string;\n description: string;\n /** Required for `type:'api-key'` — the one https origin it may be sent to. */\n boundOrigin?: string;\n /** ISO-8601, or null if never used (drives the §8.15 90-day expiry). */\n lastUsedAt: string | null;\n}\n\n/** Hints for the host's \"add secret\" modal (SECRETS_SPEC §4 `secrets:add`). The\n * app supplies only hints; the user types the value into host chrome. */\nexport interface SecretHints {\n type?: SecretType;\n family?: string;\n /** Pre-fill the bound origin (e.g. `https://api.anthropic.com`). */\n suggestedOrigin?: string;\n description?: string;\n}\n\n/** What `requestSecret()` matches against in the powerbox picker (SECRETS_SPEC §5). */\nexport interface SecretQuery {\n type?: SecretType;\n family?: string;\n}\n\n/**\n * The result of a granted `requestSecret()` — a durable `(appKey, secretId)` use\n * grant plus the secret's metadata. **Never the value.** Hold onto nothing but\n * this; the host substitutes the value into matching `net:fetch` requests.\n */\nexport interface SecretGrant {\n /** Opaque handle for the minted `(appKey, secretId)` grant. */\n grantId: string;\n /** Metadata of the bound secret (no value). */\n secret: SecretView;\n}\n\n/** An error from a secret operation, carrying a machine-readable `code`. */\nexport interface SecretError extends Error {\n code: 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'invalid-params' | 'unknown';\n}\n\ntype SecretResult =\n | { ok: true; data: unknown }\n | { ok: false; code: string; message: string };\n\n// Issue a `protocol-secrets` request, unwrapping the host's {ok,data} envelope\n// and throwing a typed SecretError on failure (mirrors mounts.ts `request`).\nconst request = async <T = unknown>(\n method: string,\n query: object = {},\n): Promise<T> => {\n const res = (await protocolRequest('secrets', method, [query])) as SecretResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'secret request failed') as SecretError;\n err.code = (res?.code as SecretError['code']) ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n/**\n * Ask the user to store a new secret (SECRETS_SPEC §4 `secrets:add`). Opens a\n * **host-drawn** modal (the value is typed into host chrome, never via the app);\n * resolves with the new secret's {@link SecretView} metadata, or rejects with a\n * {@link SecretError} (`cancelled` if the user dismisses the modal). Requires the\n * `secrets:add` capability.\n */\nexport const requestAddSecret = (hints: SecretHints = {}): Promise<SecretView> =>\n request<SecretView>('add', hints);\n\n/**\n * Ask the user to bind one of their stored secrets to this app (SECRETS_SPEC §5,\n * the powerbox flow — modeled on `requestSpace()`). The host draws a picker of\n * **only the user's matching secrets**; the user picks, declines, or creates one.\n * On success the host records a durable `(appKey, secretId)` grant and resolves\n * with a {@link SecretGrant} (handle + metadata, **never the value**).\n *\n * **No existence oracle (T20/T27):** a decline, an ungranted secret, and a\n * nonexistent secret are indistinguishable — all reject with a {@link SecretError}\n * `cancelled`; the app never sees the list it chose from.\n */\nexport const requestSecret = (query: SecretQuery = {}): Promise<SecretGrant> =>\n request<SecretGrant>('request', query);\n\n/**\n * Delete a stored secret and tombstone every dependent per-app use grant\n * (SECRETS_SPEC §4 `secrets:revoke`, §8.15 cascade). Requires `secrets:revoke`.\n */\nexport const revokeSecret = async (id: string): Promise<void> => {\n await request('revoke', { id });\n};\n\n// The metadata-only `secrets-metadata` channel (Recipe A): the host pushes the\n// current secret metadata on change and replays it on register-frame; gated by\n// `secrets:list`. NEVER carries a value (SECRETS_SPEC §4).\nconst channel = createPushChannel<SecretView[]>({\n pushType: 'secrets-metadata',\n requestType: 'request-secrets-metadata',\n initial: [],\n parse: (msg) => (Array.isArray(msg.secrets) ? (msg.secrets as SecretView[]) : undefined),\n});\n\n/** The metadata of the user's stored secrets (never values), `secrets:list`. Poll\n * for a one-off read; use {@link onSecretsChange}/{@link useSecrets} to react. */\nexport const getSecrets = (): SecretView[] => channel.get();\n\n/** Subscribe to secret-metadata changes (added/revoked). Invoked immediately with\n * the current list, then on every change. Returns an unsubscribe fn. */\nexport const onSecretsChange = (listener: (secrets: SecretView[]) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning the user's secret metadata (never values), re-rendering\n * on change. For the Settings app (SECRETS_SPEC §7). */\nexport const useSecrets = (): SecretView[] => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,0BAAgC;AAChC,yBAAkC;AA6DlC,MAAM,UAAU,OACd,QACA,QAAgB,CAAC,MACF;AACf,QAAM,MAAO,UAAM,qCAAgB,WAAW,QAAQ,CAAC,KAAK,CAAC;AAC7D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,uBAAuB;AAC7D,QAAI,OAAQ,KAAK,QAAgC;AACjD,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AASO,MAAM,mBAAmB,CAAC,QAAqB,CAAC,MACrD,QAAoB,OAAO,KAAK;AAa3B,MAAM,gBAAgB,CAAC,QAAqB,CAAC,MAClD,QAAqB,WAAW,KAAK;AAMhC,MAAM,eAAe,OAAO,OAA8B;AAC/D,QAAM,QAAQ,UAAU,EAAE,GAAG,CAAC;AAChC;AAKA,MAAM,cAAU,sCAAgC;AAAA,EAC9C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,CAAC;AAAA,EACV,OAAO,CAAC,QAAS,MAAM,QAAQ,IAAI,OAAO,IAAK,IAAI,UAA2B;AAChF,CAAC;AAIM,MAAM,aAAa,MAAoB,QAAQ,IAAI;AAInD,MAAM,kBAAkB,CAAC,aAC9B,QAAQ,SAAS,QAAQ;AAIpB,MAAM,aAAa,MAAoB,QAAQ,IAAI;","names":[]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/** The closed secret-type vocabulary (SECRETS_SPEC §2). `api-key` is always
|
|
2
|
+
* origin-bound; `oauth-refresh` is reserved (no substitution in v1). */
|
|
3
|
+
type SecretType = 'api-key' | 'bearer-token' | 'oauth-refresh';
|
|
4
|
+
/**
|
|
5
|
+
* The metadata-only projection of a stored secret (SECRETS_SPEC §2/§4) — exactly
|
|
6
|
+
* what `secrets:list` and the powerbox return. **There is no `value` field by
|
|
7
|
+
* design**: the plaintext is never part of any record an app receives.
|
|
8
|
+
*/
|
|
9
|
+
interface SecretView {
|
|
10
|
+
id: string;
|
|
11
|
+
type: SecretType;
|
|
12
|
+
family?: string;
|
|
13
|
+
description: string;
|
|
14
|
+
/** Required for `type:'api-key'` — the one https origin it may be sent to. */
|
|
15
|
+
boundOrigin?: string;
|
|
16
|
+
/** ISO-8601, or null if never used (drives the §8.15 90-day expiry). */
|
|
17
|
+
lastUsedAt: string | null;
|
|
18
|
+
}
|
|
19
|
+
/** Hints for the host's "add secret" modal (SECRETS_SPEC §4 `secrets:add`). The
|
|
20
|
+
* app supplies only hints; the user types the value into host chrome. */
|
|
21
|
+
interface SecretHints {
|
|
22
|
+
type?: SecretType;
|
|
23
|
+
family?: string;
|
|
24
|
+
/** Pre-fill the bound origin (e.g. `https://api.anthropic.com`). */
|
|
25
|
+
suggestedOrigin?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
}
|
|
28
|
+
/** What `requestSecret()` matches against in the powerbox picker (SECRETS_SPEC §5). */
|
|
29
|
+
interface SecretQuery {
|
|
30
|
+
type?: SecretType;
|
|
31
|
+
family?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The result of a granted `requestSecret()` — a durable `(appKey, secretId)` use
|
|
35
|
+
* grant plus the secret's metadata. **Never the value.** Hold onto nothing but
|
|
36
|
+
* this; the host substitutes the value into matching `net:fetch` requests.
|
|
37
|
+
*/
|
|
38
|
+
interface SecretGrant {
|
|
39
|
+
/** Opaque handle for the minted `(appKey, secretId)` grant. */
|
|
40
|
+
grantId: string;
|
|
41
|
+
/** Metadata of the bound secret (no value). */
|
|
42
|
+
secret: SecretView;
|
|
43
|
+
}
|
|
44
|
+
/** An error from a secret operation, carrying a machine-readable `code`. */
|
|
45
|
+
interface SecretError extends Error {
|
|
46
|
+
code: 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'invalid-params' | 'unknown';
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Ask the user to store a new secret (SECRETS_SPEC §4 `secrets:add`). Opens a
|
|
50
|
+
* **host-drawn** modal (the value is typed into host chrome, never via the app);
|
|
51
|
+
* resolves with the new secret's {@link SecretView} metadata, or rejects with a
|
|
52
|
+
* {@link SecretError} (`cancelled` if the user dismisses the modal). Requires the
|
|
53
|
+
* `secrets:add` capability.
|
|
54
|
+
*/
|
|
55
|
+
declare const requestAddSecret: (hints?: SecretHints) => Promise<SecretView>;
|
|
56
|
+
/**
|
|
57
|
+
* Ask the user to bind one of their stored secrets to this app (SECRETS_SPEC §5,
|
|
58
|
+
* the powerbox flow — modeled on `requestSpace()`). The host draws a picker of
|
|
59
|
+
* **only the user's matching secrets**; the user picks, declines, or creates one.
|
|
60
|
+
* On success the host records a durable `(appKey, secretId)` grant and resolves
|
|
61
|
+
* with a {@link SecretGrant} (handle + metadata, **never the value**).
|
|
62
|
+
*
|
|
63
|
+
* **No existence oracle (T20/T27):** a decline, an ungranted secret, and a
|
|
64
|
+
* nonexistent secret are indistinguishable — all reject with a {@link SecretError}
|
|
65
|
+
* `cancelled`; the app never sees the list it chose from.
|
|
66
|
+
*/
|
|
67
|
+
declare const requestSecret: (query?: SecretQuery) => Promise<SecretGrant>;
|
|
68
|
+
/**
|
|
69
|
+
* Delete a stored secret and tombstone every dependent per-app use grant
|
|
70
|
+
* (SECRETS_SPEC §4 `secrets:revoke`, §8.15 cascade). Requires `secrets:revoke`.
|
|
71
|
+
*/
|
|
72
|
+
declare const revokeSecret: (id: string) => Promise<void>;
|
|
73
|
+
/** The metadata of the user's stored secrets (never values), `secrets:list`. Poll
|
|
74
|
+
* for a one-off read; use {@link onSecretsChange}/{@link useSecrets} to react. */
|
|
75
|
+
declare const getSecrets: () => SecretView[];
|
|
76
|
+
/** Subscribe to secret-metadata changes (added/revoked). Invoked immediately with
|
|
77
|
+
* the current list, then on every change. Returns an unsubscribe fn. */
|
|
78
|
+
declare const onSecretsChange: (listener: (secrets: SecretView[]) => void) => (() => void);
|
|
79
|
+
/** React hook returning the user's secret metadata (never values), re-rendering
|
|
80
|
+
* on change. For the Settings app (SECRETS_SPEC §7). */
|
|
81
|
+
declare const useSecrets: () => SecretView[];
|
|
82
|
+
|
|
83
|
+
export { type SecretError, type SecretGrant, type SecretHints, type SecretQuery, type SecretType, type SecretView, getSecrets, onSecretsChange, requestAddSecret, requestSecret, revokeSecret, useSecrets };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/** The closed secret-type vocabulary (SECRETS_SPEC §2). `api-key` is always
|
|
2
|
+
* origin-bound; `oauth-refresh` is reserved (no substitution in v1). */
|
|
3
|
+
type SecretType = 'api-key' | 'bearer-token' | 'oauth-refresh';
|
|
4
|
+
/**
|
|
5
|
+
* The metadata-only projection of a stored secret (SECRETS_SPEC §2/§4) — exactly
|
|
6
|
+
* what `secrets:list` and the powerbox return. **There is no `value` field by
|
|
7
|
+
* design**: the plaintext is never part of any record an app receives.
|
|
8
|
+
*/
|
|
9
|
+
interface SecretView {
|
|
10
|
+
id: string;
|
|
11
|
+
type: SecretType;
|
|
12
|
+
family?: string;
|
|
13
|
+
description: string;
|
|
14
|
+
/** Required for `type:'api-key'` — the one https origin it may be sent to. */
|
|
15
|
+
boundOrigin?: string;
|
|
16
|
+
/** ISO-8601, or null if never used (drives the §8.15 90-day expiry). */
|
|
17
|
+
lastUsedAt: string | null;
|
|
18
|
+
}
|
|
19
|
+
/** Hints for the host's "add secret" modal (SECRETS_SPEC §4 `secrets:add`). The
|
|
20
|
+
* app supplies only hints; the user types the value into host chrome. */
|
|
21
|
+
interface SecretHints {
|
|
22
|
+
type?: SecretType;
|
|
23
|
+
family?: string;
|
|
24
|
+
/** Pre-fill the bound origin (e.g. `https://api.anthropic.com`). */
|
|
25
|
+
suggestedOrigin?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
}
|
|
28
|
+
/** What `requestSecret()` matches against in the powerbox picker (SECRETS_SPEC §5). */
|
|
29
|
+
interface SecretQuery {
|
|
30
|
+
type?: SecretType;
|
|
31
|
+
family?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The result of a granted `requestSecret()` — a durable `(appKey, secretId)` use
|
|
35
|
+
* grant plus the secret's metadata. **Never the value.** Hold onto nothing but
|
|
36
|
+
* this; the host substitutes the value into matching `net:fetch` requests.
|
|
37
|
+
*/
|
|
38
|
+
interface SecretGrant {
|
|
39
|
+
/** Opaque handle for the minted `(appKey, secretId)` grant. */
|
|
40
|
+
grantId: string;
|
|
41
|
+
/** Metadata of the bound secret (no value). */
|
|
42
|
+
secret: SecretView;
|
|
43
|
+
}
|
|
44
|
+
/** An error from a secret operation, carrying a machine-readable `code`. */
|
|
45
|
+
interface SecretError extends Error {
|
|
46
|
+
code: 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'invalid-params' | 'unknown';
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Ask the user to store a new secret (SECRETS_SPEC §4 `secrets:add`). Opens a
|
|
50
|
+
* **host-drawn** modal (the value is typed into host chrome, never via the app);
|
|
51
|
+
* resolves with the new secret's {@link SecretView} metadata, or rejects with a
|
|
52
|
+
* {@link SecretError} (`cancelled` if the user dismisses the modal). Requires the
|
|
53
|
+
* `secrets:add` capability.
|
|
54
|
+
*/
|
|
55
|
+
declare const requestAddSecret: (hints?: SecretHints) => Promise<SecretView>;
|
|
56
|
+
/**
|
|
57
|
+
* Ask the user to bind one of their stored secrets to this app (SECRETS_SPEC §5,
|
|
58
|
+
* the powerbox flow — modeled on `requestSpace()`). The host draws a picker of
|
|
59
|
+
* **only the user's matching secrets**; the user picks, declines, or creates one.
|
|
60
|
+
* On success the host records a durable `(appKey, secretId)` grant and resolves
|
|
61
|
+
* with a {@link SecretGrant} (handle + metadata, **never the value**).
|
|
62
|
+
*
|
|
63
|
+
* **No existence oracle (T20/T27):** a decline, an ungranted secret, and a
|
|
64
|
+
* nonexistent secret are indistinguishable — all reject with a {@link SecretError}
|
|
65
|
+
* `cancelled`; the app never sees the list it chose from.
|
|
66
|
+
*/
|
|
67
|
+
declare const requestSecret: (query?: SecretQuery) => Promise<SecretGrant>;
|
|
68
|
+
/**
|
|
69
|
+
* Delete a stored secret and tombstone every dependent per-app use grant
|
|
70
|
+
* (SECRETS_SPEC §4 `secrets:revoke`, §8.15 cascade). Requires `secrets:revoke`.
|
|
71
|
+
*/
|
|
72
|
+
declare const revokeSecret: (id: string) => Promise<void>;
|
|
73
|
+
/** The metadata of the user's stored secrets (never values), `secrets:list`. Poll
|
|
74
|
+
* for a one-off read; use {@link onSecretsChange}/{@link useSecrets} to react. */
|
|
75
|
+
declare const getSecrets: () => SecretView[];
|
|
76
|
+
/** Subscribe to secret-metadata changes (added/revoked). Invoked immediately with
|
|
77
|
+
* the current list, then on every change. Returns an unsubscribe fn. */
|
|
78
|
+
declare const onSecretsChange: (listener: (secrets: SecretView[]) => void) => (() => void);
|
|
79
|
+
/** React hook returning the user's secret metadata (never values), re-rendering
|
|
80
|
+
* on change. For the Settings app (SECRETS_SPEC §7). */
|
|
81
|
+
declare const useSecrets: () => SecretView[];
|
|
82
|
+
|
|
83
|
+
export { type SecretError, type SecretGrant, type SecretHints, type SecretQuery, type SecretType, type SecretView, getSecrets, onSecretsChange, requestAddSecret, requestSecret, revokeSecret, useSecrets };
|
package/dist/secrets.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { protocolRequest } from "./sandboxUtils";
|
|
2
|
+
import { createPushChannel } from "./pushChannel";
|
|
3
|
+
const request = async (method, query = {}) => {
|
|
4
|
+
const res = await protocolRequest("secrets", method, [query]);
|
|
5
|
+
if (!res || res.ok !== true) {
|
|
6
|
+
const err = new Error(res?.message ?? "secret request failed");
|
|
7
|
+
err.code = res?.code ?? "unknown";
|
|
8
|
+
throw err;
|
|
9
|
+
}
|
|
10
|
+
return res.data;
|
|
11
|
+
};
|
|
12
|
+
const requestAddSecret = (hints = {}) => request("add", hints);
|
|
13
|
+
const requestSecret = (query = {}) => request("request", query);
|
|
14
|
+
const revokeSecret = async (id) => {
|
|
15
|
+
await request("revoke", { id });
|
|
16
|
+
};
|
|
17
|
+
const channel = createPushChannel({
|
|
18
|
+
pushType: "secrets-metadata",
|
|
19
|
+
requestType: "request-secrets-metadata",
|
|
20
|
+
initial: [],
|
|
21
|
+
parse: (msg) => Array.isArray(msg.secrets) ? msg.secrets : void 0
|
|
22
|
+
});
|
|
23
|
+
const getSecrets = () => channel.get();
|
|
24
|
+
const onSecretsChange = (listener) => channel.onChange(listener);
|
|
25
|
+
const useSecrets = () => channel.use();
|
|
26
|
+
export {
|
|
27
|
+
getSecrets,
|
|
28
|
+
onSecretsChange,
|
|
29
|
+
requestAddSecret,
|
|
30
|
+
requestSecret,
|
|
31
|
+
revokeSecret,
|
|
32
|
+
useSecrets
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/secrets.ts"],"sourcesContent":["// The host-owned secret store — app-facing surface (SECRETS_SPEC §4/§5).\n//\n// An app can ASK the user to store a secret (`requestAddSecret`), be GRANTED the\n// right to USE a specific secret (`requestSecret`, the powerbox flow), LIST\n// secret METADATA (`getSecrets`/`useSecrets`, never values), and REVOKE one\n// (`revokeSecret`). The secret VALUE never crosses this boundary: it is read\n// host-side, once, at the `net:fetch` injection point (SECRETS_SPEC §6). These\n// functions move only hints, metadata, and grant handles.\n//\n// Resolves LLM_AND_AGENTS_SPEC D2 — the host-mediated BYOK key store. Inert until\n// the host implements `protocol-secrets` + the `secrets-metadata` channel\n// (SECRETS_SPEC §3/§6 host work, roadmap P1.E); the contract is shipped here so\n// apps (e.g. the in-browser coding agent, P3-73) can be written against it.\nimport { protocolRequest } from './sandboxUtils';\nimport { createPushChannel } from './pushChannel';\n\n/** The closed secret-type vocabulary (SECRETS_SPEC §2). `api-key` is always\n * origin-bound; `oauth-refresh` is reserved (no substitution in v1). */\nexport type SecretType = 'api-key' | 'bearer-token' | 'oauth-refresh';\n\n/**\n * The metadata-only projection of a stored secret (SECRETS_SPEC §2/§4) — exactly\n * what `secrets:list` and the powerbox return. **There is no `value` field by\n * design**: the plaintext is never part of any record an app receives.\n */\nexport interface SecretView {\n id: string;\n type: SecretType;\n family?: string;\n description: string;\n /** Required for `type:'api-key'` — the one https origin it may be sent to. */\n boundOrigin?: string;\n /** ISO-8601, or null if never used (drives the §8.15 90-day expiry). */\n lastUsedAt: string | null;\n}\n\n/** Hints for the host's \"add secret\" modal (SECRETS_SPEC §4 `secrets:add`). The\n * app supplies only hints; the user types the value into host chrome. */\nexport interface SecretHints {\n type?: SecretType;\n family?: string;\n /** Pre-fill the bound origin (e.g. `https://api.anthropic.com`). */\n suggestedOrigin?: string;\n description?: string;\n}\n\n/** What `requestSecret()` matches against in the powerbox picker (SECRETS_SPEC §5). */\nexport interface SecretQuery {\n type?: SecretType;\n family?: string;\n}\n\n/**\n * The result of a granted `requestSecret()` — a durable `(appKey, secretId)` use\n * grant plus the secret's metadata. **Never the value.** Hold onto nothing but\n * this; the host substitutes the value into matching `net:fetch` requests.\n */\nexport interface SecretGrant {\n /** Opaque handle for the minted `(appKey, secretId)` grant. */\n grantId: string;\n /** Metadata of the bound secret (no value). */\n secret: SecretView;\n}\n\n/** An error from a secret operation, carrying a machine-readable `code`. */\nexport interface SecretError extends Error {\n code: 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'invalid-params' | 'unknown';\n}\n\ntype SecretResult =\n | { ok: true; data: unknown }\n | { ok: false; code: string; message: string };\n\n// Issue a `protocol-secrets` request, unwrapping the host's {ok,data} envelope\n// and throwing a typed SecretError on failure (mirrors mounts.ts `request`).\nconst request = async <T = unknown>(\n method: string,\n query: object = {},\n): Promise<T> => {\n const res = (await protocolRequest('secrets', method, [query])) as SecretResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'secret request failed') as SecretError;\n err.code = (res?.code as SecretError['code']) ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n/**\n * Ask the user to store a new secret (SECRETS_SPEC §4 `secrets:add`). Opens a\n * **host-drawn** modal (the value is typed into host chrome, never via the app);\n * resolves with the new secret's {@link SecretView} metadata, or rejects with a\n * {@link SecretError} (`cancelled` if the user dismisses the modal). Requires the\n * `secrets:add` capability.\n */\nexport const requestAddSecret = (hints: SecretHints = {}): Promise<SecretView> =>\n request<SecretView>('add', hints);\n\n/**\n * Ask the user to bind one of their stored secrets to this app (SECRETS_SPEC §5,\n * the powerbox flow — modeled on `requestSpace()`). The host draws a picker of\n * **only the user's matching secrets**; the user picks, declines, or creates one.\n * On success the host records a durable `(appKey, secretId)` grant and resolves\n * with a {@link SecretGrant} (handle + metadata, **never the value**).\n *\n * **No existence oracle (T20/T27):** a decline, an ungranted secret, and a\n * nonexistent secret are indistinguishable — all reject with a {@link SecretError}\n * `cancelled`; the app never sees the list it chose from.\n */\nexport const requestSecret = (query: SecretQuery = {}): Promise<SecretGrant> =>\n request<SecretGrant>('request', query);\n\n/**\n * Delete a stored secret and tombstone every dependent per-app use grant\n * (SECRETS_SPEC §4 `secrets:revoke`, §8.15 cascade). Requires `secrets:revoke`.\n */\nexport const revokeSecret = async (id: string): Promise<void> => {\n await request('revoke', { id });\n};\n\n// The metadata-only `secrets-metadata` channel (Recipe A): the host pushes the\n// current secret metadata on change and replays it on register-frame; gated by\n// `secrets:list`. NEVER carries a value (SECRETS_SPEC §4).\nconst channel = createPushChannel<SecretView[]>({\n pushType: 'secrets-metadata',\n requestType: 'request-secrets-metadata',\n initial: [],\n parse: (msg) => (Array.isArray(msg.secrets) ? (msg.secrets as SecretView[]) : undefined),\n});\n\n/** The metadata of the user's stored secrets (never values), `secrets:list`. Poll\n * for a one-off read; use {@link onSecretsChange}/{@link useSecrets} to react. */\nexport const getSecrets = (): SecretView[] => channel.get();\n\n/** Subscribe to secret-metadata changes (added/revoked). Invoked immediately with\n * the current list, then on every change. Returns an unsubscribe fn. */\nexport const onSecretsChange = (listener: (secrets: SecretView[]) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning the user's secret metadata (never values), re-rendering\n * on change. For the Settings app (SECRETS_SPEC §7). */\nexport const useSecrets = (): SecretView[] => channel.use();\n"],"mappings":"AAaA,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AA6DlC,MAAM,UAAU,OACd,QACA,QAAgB,CAAC,MACF;AACf,QAAM,MAAO,MAAM,gBAAgB,WAAW,QAAQ,CAAC,KAAK,CAAC;AAC7D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,uBAAuB;AAC7D,QAAI,OAAQ,KAAK,QAAgC;AACjD,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AASO,MAAM,mBAAmB,CAAC,QAAqB,CAAC,MACrD,QAAoB,OAAO,KAAK;AAa3B,MAAM,gBAAgB,CAAC,QAAqB,CAAC,MAClD,QAAqB,WAAW,KAAK;AAMhC,MAAM,eAAe,OAAO,OAA8B;AAC/D,QAAM,QAAQ,UAAU,EAAE,GAAG,CAAC;AAChC;AAKA,MAAM,UAAU,kBAAgC;AAAA,EAC9C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,CAAC;AAAA,EACV,OAAO,CAAC,QAAS,MAAM,QAAQ,IAAI,OAAO,IAAK,IAAI,UAA2B;AAChF,CAAC;AAIM,MAAM,aAAa,MAAoB,QAAQ,IAAI;AAInD,MAAM,kBAAkB,CAAC,aAC9B,QAAQ,SAAS,QAAQ;AAIpB,MAAM,aAAa,MAAoB,QAAQ,IAAI;","names":[]}
|
package/dist/version.cjs
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var version_exports = {};
|
|
20
|
+
__export(version_exports, {
|
|
21
|
+
SDK_VERSION: () => SDK_VERSION
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(version_exports);
|
|
24
|
+
const SDK_VERSION = "0.8.1";
|
|
25
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
26
|
+
0 && (module.exports = {
|
|
27
|
+
SDK_VERSION
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=version.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.8.1';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,cAAc;","names":[]}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.8.1';\n"],"mappings":"AAIO,MAAM,cAAc;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@immediately-run/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Runtime SDK for code executing inside an immediately.run sandbox.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "github:immediately-run/immediately-run-sdk",
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
],
|
|
26
26
|
"sideEffects": false,
|
|
27
27
|
"scripts": {
|
|
28
|
+
"gen:version": "node scripts/gen-version.mjs",
|
|
29
|
+
"prebuild": "node scripts/gen-version.mjs",
|
|
28
30
|
"build": "tsup",
|
|
29
31
|
"test": "jest",
|
|
30
32
|
"check:circular": "node scripts/check-circular.mjs",
|