@branch-fiction/extension-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,69 @@
1
+ //#region src/sdk-source.d.ts
2
+ declare function extensionSdkSource(): string;
3
+ type ExtensionProviderBinding = {
4
+ baseURL: string;
5
+ proxyBaseURL: string;
6
+ modelKey?: string;
7
+ providerType?: string;
8
+ reasoning?: import('@earendil-works/pi-ai').ThinkingLevel;
9
+ };
10
+ type ExtensionCtx = {
11
+ extensionId: string;
12
+ bookId: string | null;
13
+ providers: Record<string, ExtensionProviderBinding>;
14
+ config: Record<string, unknown>;
15
+ dark: boolean;
16
+ };
17
+ type WorkerSpawnHandle<T> = Promise<T> & {
18
+ onLog(handler: (args: unknown[]) => void): WorkerSpawnHandle<T>;
19
+ cancel(): void;
20
+ };
21
+ type WorkerSpawnOptions = {
22
+ singletonKey?: string;
23
+ };
24
+ declare function isTaskAlreadyRunningError(err: unknown): boolean;
25
+ interface ExtensionSDK {
26
+ extensionId: string;
27
+ hostOrigin: string;
28
+ dark: boolean;
29
+ providers: Record<string, ExtensionProviderBinding>;
30
+ config: Record<string, unknown>;
31
+ onReady(fn: (ctx: ExtensionCtx) => void): void;
32
+ db: {
33
+ query(sql: string, params?: unknown[]): Promise<{
34
+ rows: unknown[];
35
+ changes: number;
36
+ }>;
37
+ };
38
+ fs: {
39
+ read(relPath: string): Promise<Uint8Array>;
40
+ write(relPath: string, bytes: Uint8Array): Promise<void>;
41
+ list(relPath?: string): Promise<{
42
+ name: string;
43
+ isDirectory: boolean;
44
+ }[]>;
45
+ };
46
+ worker: {
47
+ spawn<T = unknown>(task: string, payload?: unknown, opts?: WorkerSpawnOptions): WorkerSpawnHandle<T>;
48
+ };
49
+ log(...args: unknown[]): void;
50
+ }
51
+ interface ExtensionHost {
52
+ extensionId: string;
53
+ bookId: string | null;
54
+ providers: Record<string, ExtensionProviderBinding>;
55
+ config: Record<string, unknown>;
56
+ dbPath: string;
57
+ fs: {
58
+ read(relPath: string): Promise<Uint8Array>;
59
+ write(relPath: string, bytes: Uint8Array): Promise<void>;
60
+ list(relPath?: string): Promise<{
61
+ name: string;
62
+ isDirectory: boolean;
63
+ }[]>;
64
+ };
65
+ log(...args: unknown[]): void;
66
+ }
67
+ //#endregion
68
+ export { ExtensionCtx, ExtensionHost, ExtensionProviderBinding, ExtensionSDK, WorkerSpawnHandle, WorkerSpawnOptions, extensionSdkSource, isTaskAlreadyRunningError };
69
+ //# sourceMappingURL=sdk-source.d.ts.map
@@ -0,0 +1,205 @@
1
+ //#region src/sdk-source.ts
2
+ function extensionSdkSource() {
3
+ return `(${extensionSdkClient.toString()})();`;
4
+ }
5
+ function extensionSdkClient() {
6
+ const params = new URLSearchParams(window.location.search);
7
+ const token = params.get("token") ?? "";
8
+ const scriptSrc = document.currentScript instanceof HTMLScriptElement ? document.currentScript.src : "";
9
+ const hostOrigin = params.get("host") ?? (scriptSrc ? new URL(scriptSrc).origin : window.location.origin);
10
+ if (!token) console.error("[extension-sdk] missing token query param");
11
+ const darkParam = params.get("dark");
12
+ const dark = darkParam === "1" || darkParam === "true" ? true : darkParam === "0" || darkParam === "false" ? false : window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
13
+ try {
14
+ const root = document.documentElement;
15
+ root.classList.remove("light", "dark");
16
+ root.classList.add(dark ? "dark" : "light");
17
+ } catch {}
18
+ const dataBase = `${hostOrigin.replace(/\/+$/, "")}/extension-data/${token}`;
19
+ const logPrefix = "[extension]";
20
+ const readyListeners = [];
21
+ let context = null;
22
+ function bytesToBase64(bytes) {
23
+ let bin = "";
24
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
25
+ return btoa(bin);
26
+ }
27
+ function base64ToBytes(b64) {
28
+ const bin = atob(b64);
29
+ const out = new Uint8Array(bin.length);
30
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
31
+ return out;
32
+ }
33
+ async function postJson(path, body) {
34
+ const r = await fetch(`${dataBase}${path}`, {
35
+ method: "POST",
36
+ headers: { "Content-Type": "application/json" },
37
+ body: JSON.stringify(body)
38
+ });
39
+ if (!r.ok) throw new Error(`${path} failed: ${r.status} ${await r.text()}`);
40
+ return r.json();
41
+ }
42
+ async function* parseSse(stream) {
43
+ const reader = stream.getReader();
44
+ const decoder = new TextDecoder();
45
+ let buf = "";
46
+ let event = "message";
47
+ let dataLines = [];
48
+ function flush() {
49
+ if (dataLines.length === 0 && event === "message") return null;
50
+ const out = {
51
+ event,
52
+ data: dataLines.join("\n")
53
+ };
54
+ event = "message";
55
+ dataLines = [];
56
+ return out;
57
+ }
58
+ for (;;) {
59
+ const { value, done } = await reader.read();
60
+ if (done) break;
61
+ buf += decoder.decode(value, { stream: true });
62
+ let nl;
63
+ while ((nl = buf.indexOf("\n")) !== -1) {
64
+ const raw = buf.slice(0, nl);
65
+ buf = buf.slice(nl + 1);
66
+ const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
67
+ if (line === "") {
68
+ const ev = flush();
69
+ if (ev) yield ev;
70
+ continue;
71
+ }
72
+ if (line.startsWith(":")) continue;
73
+ const colon = line.indexOf(":");
74
+ const field = colon === -1 ? line : line.slice(0, colon);
75
+ const value = colon === -1 ? "" : line.slice(colon + 1).startsWith(" ") ? line.slice(colon + 2) : line.slice(colon + 1);
76
+ if (field === "event") event = value;
77
+ else if (field === "data") dataLines.push(value);
78
+ }
79
+ }
80
+ const tail = flush();
81
+ if (tail) yield tail;
82
+ }
83
+ function spawnWorker(task, payload, opts) {
84
+ const ac = new AbortController();
85
+ const logHandlers = [];
86
+ let taskId = null;
87
+ let cancelled = false;
88
+ const handle = (async () => {
89
+ const r = await fetch(`${dataBase}/task/start`, {
90
+ method: "POST",
91
+ headers: {
92
+ "Content-Type": "application/json",
93
+ Accept: "text/event-stream"
94
+ },
95
+ body: JSON.stringify({
96
+ task,
97
+ payload: payload ?? null,
98
+ singletonKey: opts?.singletonKey ?? null
99
+ }),
100
+ signal: ac.signal
101
+ });
102
+ if (!r.ok || !r.body) {
103
+ const err = /* @__PURE__ */ new Error(`worker.spawn failed: ${r.status} ${await r.text().catch(() => "")}`);
104
+ if (r.status === 409) err.name = "TaskAlreadyRunningError";
105
+ throw err;
106
+ }
107
+ for await (const ev of parseSse(r.body)) if (ev.event === "started") try {
108
+ taskId = JSON.parse(ev.data).taskId;
109
+ } catch {}
110
+ else if (ev.event === "log") {
111
+ let args = [];
112
+ try {
113
+ args = JSON.parse(ev.data).args ?? [];
114
+ } catch {}
115
+ for (const h of logHandlers) try {
116
+ h(args);
117
+ } catch (e) {
118
+ console.error("[extension-sdk] log handler threw", e);
119
+ }
120
+ } else if (ev.event === "result") return JSON.parse(ev.data).value;
121
+ else if (ev.event === "error") {
122
+ const { message } = JSON.parse(ev.data);
123
+ throw new Error(message);
124
+ }
125
+ throw new Error("worker.spawn stream ended without result");
126
+ })();
127
+ handle.onLog = (handler) => {
128
+ logHandlers.push(handler);
129
+ return handle;
130
+ };
131
+ handle.cancel = () => {
132
+ if (cancelled) return;
133
+ cancelled = true;
134
+ ac.abort();
135
+ if (taskId) fetch(`${dataBase}/task/${taskId}/cancel`, { method: "POST" }).catch(() => {});
136
+ };
137
+ return handle;
138
+ }
139
+ const sdk = {
140
+ extensionId: "",
141
+ hostOrigin,
142
+ dark,
143
+ providers: {},
144
+ config: {},
145
+ onReady(fn) {
146
+ if (context) fn(context);
147
+ else readyListeners.push(fn);
148
+ },
149
+ db: { async query(sql, params) {
150
+ return postJson("/db/query", {
151
+ sql,
152
+ params: params ?? []
153
+ });
154
+ } },
155
+ fs: {
156
+ async read(relPath) {
157
+ return base64ToBytes((await postJson("/fs/read", { relPath })).bytesBase64);
158
+ },
159
+ async write(relPath, bytes) {
160
+ await postJson("/fs/write", {
161
+ relPath,
162
+ bytesBase64: bytesToBase64(bytes)
163
+ });
164
+ },
165
+ async list(relPath) {
166
+ return postJson("/fs/list", { relPath: relPath ?? null });
167
+ }
168
+ },
169
+ worker: { spawn(task, payload, opts) {
170
+ return spawnWorker(task, payload, opts);
171
+ } },
172
+ log(...args) {
173
+ console.log(logPrefix, ...args);
174
+ }
175
+ };
176
+ window.extensionSDK = sdk;
177
+ (async () => {
178
+ try {
179
+ const r = await fetch(`${dataBase}/context`);
180
+ if (!r.ok) throw new Error(`context ${r.status}`);
181
+ const ctx = {
182
+ ...await r.json(),
183
+ dark
184
+ };
185
+ context = ctx;
186
+ sdk.extensionId = ctx.extensionId;
187
+ sdk.providers = ctx.providers ?? {};
188
+ sdk.config = ctx.config ?? {};
189
+ for (const fn of readyListeners) try {
190
+ fn(ctx);
191
+ } catch (e) {
192
+ console.error("[extension-sdk] onReady handler threw", e);
193
+ }
194
+ } catch (e) {
195
+ console.error("[extension-sdk] failed to fetch context", e);
196
+ }
197
+ })();
198
+ }
199
+ function isTaskAlreadyRunningError(err) {
200
+ return err instanceof Error && err.name === "TaskAlreadyRunningError";
201
+ }
202
+ //#endregion
203
+ export { extensionSdkSource, isTaskAlreadyRunningError };
204
+
205
+ //# sourceMappingURL=sdk-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk-source.js","names":[],"sources":["../src/sdk-source.ts"],"sourcesContent":["// Returns the iframe-side SDK as an IIFE string, served at /extension-sdk.js.\nexport function extensionSdkSource(): string {\n return `(${extensionSdkClient.toString()})();`;\n}\n\nfunction extensionSdkClient() {\n type ProviderBinding = {\n baseURL: string;\n proxyBaseURL: string;\n modelKey?: string;\n providerType?: string;\n reasoning?: string;\n };\n type Ctx = {\n extensionId: string;\n bookId: string | null;\n providers: Record<string, ProviderBinding>;\n config: Record<string, unknown>;\n dark: boolean;\n };\n\n const params = new URLSearchParams(window.location.search);\n const token = params.get('token') ?? '';\n // Sandboxed iframe origin is opaque; prefer `host` query param, fall back to SDK script origin.\n const scriptSrc =\n document.currentScript instanceof HTMLScriptElement ? document.currentScript.src : '';\n const hostOrigin =\n params.get('host') ??\n (scriptSrc ? new URL(scriptSrc).origin : window.location.origin);\n if (!token) console.error('[extension-sdk] missing token query param');\n\n const darkParam = params.get('dark');\n const dark =\n darkParam === '1' || darkParam === 'true'\n ? true\n : darkParam === '0' || darkParam === 'false'\n ? false\n : (window.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false);\n try {\n const root = document.documentElement;\n root.classList.remove('light', 'dark');\n root.classList.add(dark ? 'dark' : 'light');\n } catch {\n /* ignore */\n }\n\n const dataBase = `${hostOrigin.replace(/\\/+$/, '')}/extension-data/${token}`;\n const logPrefix = '[extension]';\n\n const readyListeners: Array<(ctx: Ctx) => void> = [];\n let context: Ctx | null = null;\n\n function bytesToBase64(bytes: Uint8Array): string {\n let bin = '';\n for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);\n return btoa(bin);\n }\n function base64ToBytes(b64: string): Uint8Array {\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n }\n\n async function postJson<T>(path: string, body: unknown): Promise<T> {\n const r = await fetch(`${dataBase}${path}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body)\n });\n if (!r.ok) throw new Error(`${path} failed: ${r.status} ${await r.text()}`);\n return r.json() as Promise<T>;\n }\n\n type SseEvent = { event: string; data: string };\n async function* parseSse(stream: ReadableStream<Uint8Array>): AsyncGenerator<SseEvent> {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buf = '';\n let event = 'message';\n let dataLines: string[] = [];\n\n function flush(): SseEvent | null {\n if (dataLines.length === 0 && event === 'message') return null;\n const out = { event, data: dataLines.join('\\n') };\n event = 'message';\n dataLines = [];\n return out;\n }\n\n for (;;) {\n const { value, done } = await reader.read();\n if (done) break;\n buf += decoder.decode(value, { stream: true });\n // Split on LF; SSE lines can use CRLF too — strip trailing \\r.\n let nl: number;\n while ((nl = buf.indexOf('\\n')) !== -1) {\n const raw = buf.slice(0, nl);\n buf = buf.slice(nl + 1);\n const line = raw.endsWith('\\r') ? raw.slice(0, -1) : raw;\n if (line === '') {\n const ev = flush();\n if (ev) yield ev;\n continue;\n }\n if (line.startsWith(':')) continue; // comment / keep-alive\n const colon = line.indexOf(':');\n const field = colon === -1 ? line : line.slice(0, colon);\n const value =\n colon === -1\n ? ''\n : line.slice(colon + 1).startsWith(' ')\n ? line.slice(colon + 2)\n : line.slice(colon + 1);\n if (field === 'event') event = value;\n else if (field === 'data') dataLines.push(value);\n }\n }\n const tail = flush();\n if (tail) yield tail;\n }\n\n type WorkerSpawnHandle<T> = Promise<T> & {\n onLog(handler: (args: unknown[]) => void): WorkerSpawnHandle<T>;\n cancel(): void;\n };\n\n type WorkerSpawnOptions = { singletonKey?: string };\n\n function spawnWorker<T>(\n task: string,\n payload?: unknown,\n opts?: WorkerSpawnOptions\n ): WorkerSpawnHandle<T> {\n const ac = new AbortController();\n const logHandlers: Array<(args: unknown[]) => void> = [];\n let taskId: string | null = null;\n let cancelled = false;\n\n const promise: Promise<T> = (async () => {\n const r = await fetch(`${dataBase}/task/start`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' },\n body: JSON.stringify({\n task,\n payload: payload ?? null,\n singletonKey: opts?.singletonKey ?? null\n }),\n signal: ac.signal\n });\n if (!r.ok || !r.body) {\n const err = new Error(\n `worker.spawn failed: ${r.status} ${await r.text().catch(() => '')}`\n );\n if (r.status === 409) err.name = 'TaskAlreadyRunningError';\n throw err;\n }\n for await (const ev of parseSse(r.body)) {\n if (ev.event === 'started') {\n try {\n taskId = (JSON.parse(ev.data) as { taskId: string }).taskId;\n } catch {\n /* ignore */\n }\n } else if (ev.event === 'log') {\n let args: unknown[] = [];\n try {\n args = (JSON.parse(ev.data) as { args: unknown[] }).args ?? [];\n } catch {\n /* ignore */\n }\n for (const h of logHandlers) {\n try {\n h(args);\n } catch (e) {\n console.error('[extension-sdk] log handler threw', e);\n }\n }\n } else if (ev.event === 'result') {\n const parsed = JSON.parse(ev.data) as { value: T };\n return parsed.value;\n } else if (ev.event === 'error') {\n const { message } = JSON.parse(ev.data) as { message: string };\n throw new Error(message);\n }\n }\n throw new Error('worker.spawn stream ended without result');\n })();\n\n const handle = promise as WorkerSpawnHandle<T>;\n handle.onLog = (handler) => {\n logHandlers.push(handler);\n return handle;\n };\n handle.cancel = () => {\n if (cancelled) return;\n cancelled = true;\n ac.abort();\n if (taskId) {\n void fetch(`${dataBase}/task/${taskId}/cancel`, { method: 'POST' }).catch(\n () => {}\n );\n }\n };\n return handle;\n }\n\n const sdk = {\n extensionId: '' as string,\n hostOrigin,\n dark,\n providers: {} as Record<string, ProviderBinding>,\n config: {} as Record<string, unknown>,\n onReady(fn: (ctx: Ctx) => void) {\n if (context) fn(context);\n else readyListeners.push(fn);\n },\n db: {\n async query(sql: string, params?: unknown[]) {\n return postJson<{ rows: unknown[]; changes: number }>('/db/query', {\n sql,\n params: params ?? []\n });\n }\n },\n fs: {\n async read(relPath: string): Promise<Uint8Array> {\n const r = await postJson<{ bytesBase64: string }>('/fs/read', { relPath });\n return base64ToBytes(r.bytesBase64);\n },\n async write(relPath: string, bytes: Uint8Array): Promise<void> {\n await postJson<unknown>('/fs/write', {\n relPath,\n bytesBase64: bytesToBase64(bytes)\n });\n },\n async list(relPath?: string) {\n return postJson<{ name: string; isDirectory: boolean }[]>('/fs/list', {\n relPath: relPath ?? null\n });\n }\n },\n worker: {\n spawn<T = unknown>(\n task: string,\n payload?: unknown,\n opts?: WorkerSpawnOptions\n ): WorkerSpawnHandle<T> {\n return spawnWorker<T>(task, payload, opts);\n }\n },\n log(...args: unknown[]) {\n console.log(logPrefix, ...args);\n }\n };\n\n (window as unknown as { extensionSDK: typeof sdk }).extensionSDK = sdk;\n\n void (async () => {\n try {\n const r = await fetch(`${dataBase}/context`);\n if (!r.ok) throw new Error(`context ${r.status}`);\n const ctx = { ...((await r.json()) as Ctx), dark };\n context = ctx;\n sdk.extensionId = ctx.extensionId;\n sdk.providers = ctx.providers ?? {};\n sdk.config = ctx.config ?? {};\n for (const fn of readyListeners) {\n try {\n fn(ctx);\n } catch (e) {\n console.error('[extension-sdk] onReady handler threw', e);\n }\n }\n } catch (e) {\n console.error('[extension-sdk] failed to fetch context', e);\n }\n })();\n}\n\n// Type surface extension authors get when importing this package.\n\nexport type ExtensionProviderBinding = {\n baseURL: string;\n proxyBaseURL: string;\n modelKey?: string;\n providerType?: string;\n reasoning?: import('@earendil-works/pi-ai').ThinkingLevel;\n};\n\nexport type ExtensionCtx = {\n extensionId: string;\n bookId: string | null;\n providers: Record<string, ExtensionProviderBinding>;\n config: Record<string, unknown>;\n dark: boolean;\n};\n\nexport type WorkerSpawnHandle<T> = Promise<T> & {\n onLog(handler: (args: unknown[]) => void): WorkerSpawnHandle<T>;\n cancel(): void;\n};\n\nexport type WorkerSpawnOptions = {\n // single-flight per extension + book; duplicates reject with TaskAlreadyRunningError\n singletonKey?: string;\n};\n\nexport function isTaskAlreadyRunningError(err: unknown): boolean {\n return err instanceof Error && err.name === 'TaskAlreadyRunningError';\n}\n\nexport interface ExtensionSDK {\n // Populated after `onReady`. Empty string before then.\n extensionId: string;\n // Origin of the host's HTTP server (the one serving /extension-data/*).\n hostOrigin: string;\n dark: boolean;\n providers: Record<string, ExtensionProviderBinding>;\n config: Record<string, unknown>;\n onReady(fn: (ctx: ExtensionCtx) => void): void;\n db: {\n query(sql: string, params?: unknown[]): Promise<{ rows: unknown[]; changes: number }>;\n };\n fs: {\n read(relPath: string): Promise<Uint8Array>;\n write(relPath: string, bytes: Uint8Array): Promise<void>;\n list(relPath?: string): Promise<{ name: string; isDirectory: boolean }[]>;\n };\n worker: {\n spawn<T = unknown>(\n task: string,\n payload?: unknown,\n opts?: WorkerSpawnOptions\n ): WorkerSpawnHandle<T>;\n };\n log(...args: unknown[]): void;\n}\n\nexport interface ExtensionHost {\n extensionId: string;\n bookId: string | null;\n providers: Record<string, ExtensionProviderBinding>;\n config: Record<string, unknown>;\n // Absolute path to this extension's private SQLite file. Open it with whichever\n // client you prefer (`node:sqlite`, better-sqlite3, drizzle, kysely, …).\n dbPath: string;\n fs: {\n read(relPath: string): Promise<Uint8Array>;\n write(relPath: string, bytes: Uint8Array): Promise<void>;\n list(relPath?: string): Promise<{ name: string; isDirectory: boolean }[]>;\n };\n log(...args: unknown[]): void;\n}\n"],"mappings":"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"}