@andesine/extensions 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/solid.js ADDED
@@ -0,0 +1,555 @@
1
+ import { createRenderer } from "solid-js/universal";
2
+ import { catchError, createContext, createSignal, useContext } from "solid-js";
3
+ import { createStore, reconcile } from "solid-js/store";
4
+ //#region src/solid/renderer.ts
5
+ const MAX_PATCHES_PER_MESSAGE = 1e3;
6
+ const MAX_JSON_DEPTH = 8;
7
+ const callbacks = /* @__PURE__ */ new Map();
8
+ const removedNodes = /* @__PURE__ */ new Set();
9
+ let nextID$1 = 1;
10
+ let queue = [];
11
+ let flushScheduled = false;
12
+ let sendPatches = () => {};
13
+ const releaseCallbacks = (node) => {
14
+ for (const callback of node.callbacks.values()) callbacks.delete(callback);
15
+ node.callbacks.clear();
16
+ node.children.forEach(releaseCallbacks);
17
+ };
18
+ const flush = () => {
19
+ for (const node of removedNodes) {
20
+ if (node.parent) continue;
21
+ releaseCallbacks(node);
22
+ queue.push({
23
+ op: "remove",
24
+ id: node.id
25
+ });
26
+ }
27
+ removedNodes.clear();
28
+ const patches = queue;
29
+ flushScheduled = false;
30
+ queue = [];
31
+ for (let index = 0; index < patches.length; index += MAX_PATCHES_PER_MESSAGE) sendPatches(patches.slice(index, index + MAX_PATCHES_PER_MESSAGE));
32
+ };
33
+ const scheduleFlush = () => {
34
+ if (flushScheduled) return;
35
+ flushScheduled = true;
36
+ queueMicrotask(flush);
37
+ };
38
+ const queuePatch = (patch) => {
39
+ queue.push(patch);
40
+ scheduleFlush();
41
+ };
42
+ const createNode = (type) => {
43
+ return {
44
+ id: nextID$1++,
45
+ type,
46
+ children: [],
47
+ callbacks: /* @__PURE__ */ new Map()
48
+ };
49
+ };
50
+ const toJSONValue = (value, depth = 0) => {
51
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return value;
52
+ if (depth >= MAX_JSON_DEPTH) throw new TypeError("Extension props are nested too deeply");
53
+ if (Array.isArray(value)) return value.map((item) => toJSONValue(item, depth + 1));
54
+ if (Object.getPrototypeOf(value) === Object.prototype) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, toJSONValue(item, depth + 1)]));
55
+ throw new TypeError("Extension props must be JSON values or functions");
56
+ };
57
+ const detach = (node) => {
58
+ const siblings = node.parent?.children;
59
+ if (siblings) siblings.splice(siblings.indexOf(node), 1);
60
+ node.parent = void 0;
61
+ };
62
+ const renderer = createRenderer({
63
+ createElement(type) {
64
+ const node = createNode(type);
65
+ queuePatch({
66
+ op: "create",
67
+ id: node.id,
68
+ component: type
69
+ });
70
+ return node;
71
+ },
72
+ createTextNode(value) {
73
+ const node = createNode("#text");
74
+ queuePatch({
75
+ op: "text",
76
+ id: node.id,
77
+ value: String(value)
78
+ });
79
+ return node;
80
+ },
81
+ replaceText(node, value) {
82
+ queuePatch({
83
+ op: "setText",
84
+ id: node.id,
85
+ value: String(value)
86
+ });
87
+ },
88
+ isTextNode(node) {
89
+ return node.type === "#text";
90
+ },
91
+ setProperty(node, name, value) {
92
+ const previous = node.callbacks.get(name);
93
+ if (previous) {
94
+ callbacks.delete(previous);
95
+ node.callbacks.delete(name);
96
+ }
97
+ if (typeof value === "function") {
98
+ const callback = nextID$1++;
99
+ callbacks.set(callback, value);
100
+ node.callbacks.set(name, callback);
101
+ queuePatch({
102
+ op: "callback",
103
+ id: node.id,
104
+ name,
105
+ callback
106
+ });
107
+ } else if (value === void 0) queuePatch({
108
+ op: "unset",
109
+ id: node.id,
110
+ name
111
+ });
112
+ else queuePatch({
113
+ op: "prop",
114
+ id: node.id,
115
+ name,
116
+ value: toJSONValue(value)
117
+ });
118
+ },
119
+ insertNode(parent, node, anchor) {
120
+ const index = anchor ? parent.children.indexOf(anchor) : -1;
121
+ detach(node);
122
+ node.parent = parent;
123
+ if (index >= 0) parent.children.splice(index, 0, node);
124
+ else parent.children.push(node);
125
+ queuePatch({
126
+ op: "insert",
127
+ parent: parent.id,
128
+ id: node.id,
129
+ before: anchor?.id ?? null
130
+ });
131
+ },
132
+ removeNode(_parent, node) {
133
+ detach(node);
134
+ removedNodes.add(node);
135
+ scheduleFlush();
136
+ },
137
+ getParentNode(node) {
138
+ return node.parent;
139
+ },
140
+ getFirstChild(node) {
141
+ return node.children[0];
142
+ },
143
+ getNextSibling(node) {
144
+ const siblings = node.parent?.children;
145
+ return siblings?.[siblings.indexOf(node) + 1];
146
+ }
147
+ });
148
+ const createViewRoot = (viewID) => {
149
+ const node = createNode("#root");
150
+ queuePatch({
151
+ op: "root",
152
+ id: node.id,
153
+ viewID
154
+ });
155
+ return node;
156
+ };
157
+ const removeViewRoot = (node) => {
158
+ releaseCallbacks(node);
159
+ queuePatch({
160
+ op: "remove",
161
+ id: node.id
162
+ });
163
+ };
164
+ const setPatchSender = (sender) => {
165
+ sendPatches = sender;
166
+ };
167
+ const getCallback = (callback) => {
168
+ return callbacks.get(callback);
169
+ };
170
+ const { render, effect, memo, createComponent, createElement, createTextNode, insertNode, insert, spread, setProp, mergeProps, use } = renderer;
171
+ //#endregion
172
+ //#region src/solid/components.ts
173
+ const hostComponent = (name) => {
174
+ return (props) => {
175
+ const node = createElement(name);
176
+ spread(node, props);
177
+ return node;
178
+ };
179
+ };
180
+ const Stack = hostComponent("Stack");
181
+ const Grid = hostComponent("Grid");
182
+ const Box = hostComponent("Box");
183
+ const Divider = hostComponent("Divider");
184
+ const ScrollArea = hostComponent("ScrollArea");
185
+ const Text = hostComponent("Text");
186
+ const Heading = hostComponent("Heading");
187
+ const Code = hostComponent("Code");
188
+ const Link = hostComponent("Link");
189
+ const Badge = hostComponent("Badge");
190
+ const Icon = hostComponent("Icon");
191
+ const Button = hostComponent("Button");
192
+ const IconButton = hostComponent("IconButton");
193
+ const Input = hostComponent("Input");
194
+ const Textarea = hostComponent("Textarea");
195
+ const Select = hostComponent("Select");
196
+ const Combobox = hostComponent("Combobox");
197
+ const Checkbox = hostComponent("Checkbox");
198
+ const Toggle = hostComponent("Toggle");
199
+ const ToggleGroup = hostComponent("ToggleGroup");
200
+ const ColorInput = hostComponent("ColorInput");
201
+ const Tooltip = hostComponent("Tooltip");
202
+ const Setting = hostComponent("Setting");
203
+ const List = hostComponent("List");
204
+ const Card = hostComponent("Card");
205
+ const Tree = hostComponent("Tree");
206
+ const Tabs = hostComponent("Tabs");
207
+ const Tab = hostComponent("Tab");
208
+ const Disclosure = hostComponent("Disclosure");
209
+ const Callout = hostComponent("Callout");
210
+ const Steps = hostComponent("Steps");
211
+ const Step = hostComponent("Step");
212
+ const Figure = hostComponent("Figure");
213
+ const CodeBlock = hostComponent("CodeBlock");
214
+ const TimeAgo = hostComponent("TimeAgo");
215
+ const Spinner = hostComponent("Spinner");
216
+ const Skeleton = hostComponent("Skeleton");
217
+ /** Where an element view shows the element's editable content (element views only). */
218
+ const ContentSlot = hostComponent("ContentSlot");
219
+ //#endregion
220
+ //#region src/solid/requests.ts
221
+ const pending = /* @__PURE__ */ new Map();
222
+ let nextID = 1;
223
+ let sendRequest = null;
224
+ /** A host request failed; `code` says why, for example `user_interaction_required`. */
225
+ var ExtensionRequestError = class extends Error {
226
+ code;
227
+ constructor(code, message) {
228
+ super(message);
229
+ this.code = code;
230
+ }
231
+ };
232
+ const setRequestSender = (sender) => {
233
+ sendRequest = sender;
234
+ };
235
+ const request = (method, params) => {
236
+ if (!sendRequest) return Promise.reject(new ExtensionRequestError("unavailable", "The extension is not running"));
237
+ const id = nextID++;
238
+ const send = sendRequest;
239
+ return new Promise((resolve, reject) => {
240
+ pending.set(id, {
241
+ resolve,
242
+ reject
243
+ });
244
+ send(id, method, params);
245
+ });
246
+ };
247
+ const settleRequest = (id, result, error) => {
248
+ const request = pending.get(id);
249
+ if (!request) return;
250
+ pending.delete(id);
251
+ if (error) request.reject(new ExtensionRequestError(error.code, error.message));
252
+ else request.resolve(result);
253
+ };
254
+ //#endregion
255
+ //#region src/solid/editor.ts
256
+ const ViewContext = createContext();
257
+ /**
258
+ * Edits the document from the current view. Andesine validates every edit and applies it as the
259
+ * member's own edit; edits reject with `ExtensionRequestError` when they do not fit.
260
+ */
261
+ const useEditor = () => {
262
+ const viewID = useContext(ViewContext) ?? "";
263
+ const asJSON = (content) => content;
264
+ return {
265
+ replace: async (content) => {
266
+ await request("editor.replace", {
267
+ viewID,
268
+ content: asJSON(content)
269
+ });
270
+ },
271
+ insertAfter: async (content) => {
272
+ await request("editor.insertAfter", {
273
+ viewID,
274
+ content: asJSON(content)
275
+ });
276
+ },
277
+ setElementProps: async (props) => {
278
+ await request("editor.setElementProps", {
279
+ viewID,
280
+ props
281
+ });
282
+ },
283
+ close: async () => {
284
+ await request("view.close", { viewID });
285
+ }
286
+ };
287
+ };
288
+ //#endregion
289
+ //#region src/solid/context.ts
290
+ /** The origin `apiFetch` accepts; use it as the SDK `baseURL`. */
291
+ const EXTENSION_API_URL = "https://api.andesine.invalid";
292
+ const SESSION_WAIT = 1e4;
293
+ const [permissions, setPermissions] = createSignal([]);
294
+ const [backendURL, setBackendURL] = createSignal(null);
295
+ const [session, setSession] = createSignal(null);
296
+ const [configuration, setConfiguration] = createSignal({});
297
+ let identity = {
298
+ extensionID: "",
299
+ instance: ""
300
+ };
301
+ const updateContext = (context) => {
302
+ setPermissions(context.permissions);
303
+ setBackendURL(context.backend);
304
+ setConfiguration(context.configuration);
305
+ };
306
+ const updateSession = (message) => {
307
+ identity = {
308
+ extensionID: message.extensionID,
309
+ instance: message.instance
310
+ };
311
+ setSession(message.token);
312
+ };
313
+ /**
314
+ * The configuration's non-secret values, with defaults for unset fields (reactive). Secret
315
+ * fields are readable only by the extension backend.
316
+ */
317
+ const useConfiguration = () => {
318
+ return configuration;
319
+ };
320
+ /** The member's effective permissions for this extension (reactive), e.g. `read:entries`. */
321
+ const usePermissions = () => permissions;
322
+ /** Whether the effective permissions include `permission`; a write permission includes its read. */
323
+ const hasPermission = (permission) => {
324
+ const write = permission.startsWith("read:") ? permission.slice(5) : null;
325
+ return permissions().some((granted) => granted === permission || granted === write);
326
+ };
327
+ /**
328
+ * A fetch implementation for the Andesine SDK: requests go through Andesine with the member's
329
+ * session, within the effective permissions. Forbidden operations answer with HTTP 403.
330
+ * @example createClient({ baseURL: EXTENSION_API_URL, fetch: apiFetch })
331
+ */
332
+ const apiFetch = async (input, init) => {
333
+ const outgoing = new Request(input, init);
334
+ const url = new URL(outgoing.url);
335
+ const hasBody = !["GET", "HEAD"].includes(outgoing.method);
336
+ if (url.origin !== "https://api.andesine.invalid") throw new TypeError(`apiFetch only accepts ${EXTENSION_API_URL} URLs`);
337
+ try {
338
+ const result = await request("api.fetch", {
339
+ method: outgoing.method,
340
+ path: `${url.pathname}${url.search}`,
341
+ ...hasBody && { body: await outgoing.text() }
342
+ });
343
+ return new Response(result.body, {
344
+ status: result.status,
345
+ headers: { "content-type": result.contentType }
346
+ });
347
+ } catch (error) {
348
+ if (!(error instanceof ExtensionRequestError) || error.code !== "forbidden") throw error;
349
+ return Response.json({
350
+ code: "FORBIDDEN",
351
+ message: "The extension cannot call this operation"
352
+ }, { status: 403 });
353
+ }
354
+ };
355
+ const waitForSession = () => {
356
+ return new Promise((resolve, reject) => {
357
+ const started = Date.now();
358
+ const check = () => {
359
+ const token = session();
360
+ if (token) resolve(token);
361
+ else if (Date.now() - started > SESSION_WAIT) reject(new ExtensionRequestError("unavailable", "No backend session is available"));
362
+ else setTimeout(check, 100);
363
+ };
364
+ check();
365
+ });
366
+ };
367
+ /** The extension's own backend, as declared in the manifest. */
368
+ const backend = {
369
+ /**
370
+ * Fetches a path of the backend URL with the member's session token in `Authorization`, and
371
+ * the extension ID and instance API URL in `Andesine-Extension` and `Andesine-Instance`; the
372
+ * backend verifies them with `verifySession` from `@andesine/sdk/extensions`. Its CORS
373
+ * configuration must allow these headers.
374
+ */
375
+ async fetch(path, init) {
376
+ const base = backendURL();
377
+ if (!base) throw new ExtensionRequestError("unavailable", "The extension has no backend");
378
+ const root = base.endsWith("/") ? base : `${base}/`;
379
+ const url = new URL(path.replace(/^\/+/, ""), root);
380
+ if (!url.href.startsWith(root)) throw new TypeError("The path is outside the backend URL");
381
+ const headers = new Headers(init?.headers);
382
+ headers.set("Authorization", `Bearer ${await waitForSession()}`);
383
+ headers.set("Andesine-Extension", identity.extensionID);
384
+ headers.set("Andesine-Instance", identity.instance);
385
+ return fetch(url, {
386
+ ...init,
387
+ headers,
388
+ credentials: "omit",
389
+ referrerPolicy: "no-referrer"
390
+ });
391
+ } };
392
+ //#endregion
393
+ //#region src/solid/runtime.ts
394
+ const PROTOCOL = 1;
395
+ const ViewProvider = ViewContext.Provider;
396
+ const views = /* @__PURE__ */ new Map();
397
+ let started = false;
398
+ const getErrorMessage = (error) => {
399
+ return String(error instanceof Error ? error.message : error).slice(0, 500);
400
+ };
401
+ /**
402
+ * Starts the extension frontend in its sandbox worker. `entries` maps the view entries that the
403
+ * manifest names to Solid components; each view receives its host props as reactive props.
404
+ */
405
+ const startExtension = (entries) => {
406
+ if (started) return;
407
+ started = true;
408
+ globalThis.addEventListener("message", function handleInit(event) {
409
+ if (!(event.data?.type === "andesine:init" && event.ports.length === 1)) return;
410
+ const port = event.ports[0];
411
+ const generation = event.data.generation;
412
+ let sequence = 0;
413
+ const send = (body) => {
414
+ port.postMessage({
415
+ ...body,
416
+ protocol: PROTOCOL,
417
+ generation,
418
+ sequence: sequence++
419
+ });
420
+ };
421
+ const sendError = (error, viewID) => {
422
+ send({
423
+ type: "error",
424
+ viewID,
425
+ message: getErrorMessage(error)
426
+ });
427
+ };
428
+ const createView = (viewID, entry, props) => {
429
+ const component = Object.hasOwn(entries, entry) ? entries[entry] : void 0;
430
+ if (!component || views.has(viewID)) {
431
+ sendError(`Unknown or duplicate view: ${entry}`, viewID);
432
+ return;
433
+ }
434
+ const root = createViewRoot(viewID);
435
+ const [viewProps, setViewProps] = createStore({ ...props });
436
+ const dispose = render(() => {
437
+ return catchError(() => {
438
+ return createComponent(ViewProvider, {
439
+ value: viewID,
440
+ get children() {
441
+ return createComponent(component, viewProps);
442
+ }
443
+ });
444
+ }, (error) => sendError(error, viewID));
445
+ }, root);
446
+ views.set(viewID, {
447
+ root,
448
+ setProps: (next) => setViewProps(reconcile(next)),
449
+ dispose
450
+ });
451
+ };
452
+ const disposeView = (viewID) => {
453
+ const view = views.get(viewID);
454
+ if (!view) return;
455
+ views.delete(viewID);
456
+ view.dispose();
457
+ removeViewRoot(view.root);
458
+ };
459
+ const handleMessage = (message) => {
460
+ if (message.type === "heartbeat") send({
461
+ type: "heartbeat",
462
+ id: message.id
463
+ });
464
+ else if (message.type === "view.create") createView(message.viewID, message.entry, message.props);
465
+ else if (message.type === "view.update") views.get(message.viewID)?.setProps(message.props);
466
+ else if (message.type === "view.dispose") disposeView(message.viewID);
467
+ else if (message.type === "context") updateContext(message);
468
+ else if (message.type === "session") updateSession(message);
469
+ else if (message.type === "response") settleRequest(message.id, message.result, message.error);
470
+ else if (message.type === "callback") getCallback(message.callback)?.(...message.args);
471
+ else if (message.type === "dispose") {
472
+ [...views.keys()].forEach(disposeView);
473
+ port.close();
474
+ globalThis.close();
475
+ }
476
+ };
477
+ globalThis.removeEventListener("message", handleInit);
478
+ globalThis.addEventListener("error", (error) => sendError(error.message));
479
+ globalThis.addEventListener("unhandledrejection", (event) => sendError(event.reason));
480
+ setPatchSender((patches) => send({
481
+ type: "patch",
482
+ patches
483
+ }));
484
+ setRequestSender((id, method, params) => send({
485
+ type: "request",
486
+ id,
487
+ method,
488
+ params
489
+ }));
490
+ port.addEventListener("message", (event) => {
491
+ try {
492
+ handleMessage(event.data);
493
+ } catch (error) {
494
+ sendError(error);
495
+ }
496
+ });
497
+ port.start();
498
+ send({ type: "ready" });
499
+ });
500
+ };
501
+ //#endregion
502
+ //#region src/solid/actions.ts
503
+ /** Copies text; the host shows a fallback dialog if the browser refuses. */
504
+ const copyText = async (text) => {
505
+ await request("host.copyText", { text });
506
+ };
507
+ /** Downloads a text file, for example `downloadFile("andesine.config.ts", source)`. */
508
+ const downloadFile = async (name, text, type) => {
509
+ await request("host.downloadFile", type ? {
510
+ name,
511
+ text,
512
+ type
513
+ } : {
514
+ name,
515
+ text
516
+ });
517
+ };
518
+ /** Opens an `https:` URL in a new tab; URLs outside the declared URLs need confirmation. */
519
+ const openURL = async (url) => {
520
+ await request("host.openURL", { url });
521
+ };
522
+ //#endregion
523
+ //#region src/solid/storage.ts
524
+ /**
525
+ * The extension's JSON key-value storage, shared with its backend. Keys have up to 256
526
+ * characters; values up to 128 KiB; the storage up to 10,000 entries and 10 MiB. The last write
527
+ * wins. Failed requests reject with `ExtensionRequestError`.
528
+ */
529
+ const storage = {
530
+ /** The entry, or null when the key is not set. */
531
+ async get(key) {
532
+ return await request("storage.get", { key }) ?? null;
533
+ },
534
+ /** Creates or replaces an entry; resolves with its update time. */
535
+ async set(key, value) {
536
+ const { updatedAt } = await request("storage.set", {
537
+ key,
538
+ value
539
+ });
540
+ return { updatedAt };
541
+ },
542
+ /** Resolves with whether the key was set. */
543
+ async delete(key) {
544
+ const { deleted } = await request("storage.delete", { key });
545
+ return deleted;
546
+ },
547
+ /** Entries in key order; a page can have fewer entries than `limit` when values are large. */
548
+ async list(options = {}) {
549
+ return await request("storage.list", { ...options });
550
+ }
551
+ };
552
+ //#endregion
553
+ export { Badge, Box, Button, Callout, Card, Checkbox, Code, CodeBlock, ColorInput, Combobox, ContentSlot, Disclosure, Divider, EXTENSION_API_URL, ExtensionRequestError, Figure, Grid, Heading, Icon, IconButton, Input, Link, List, ScrollArea, Select, Setting, Skeleton, Spinner, Stack, Step, Steps, Tab, Tabs, Text, Textarea, TimeAgo, Toggle, ToggleGroup, Tooltip, Tree, apiFetch, backend, copyText, createComponent, createElement, createTextNode, downloadFile, effect, hasPermission, insert, insertNode, memo, mergeProps, openURL, render, setProp, spread, startExtension, storage, use, useConfiguration, useEditor, usePermissions };
554
+
555
+ //# sourceMappingURL=solid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solid.js","names":["nextID"],"sources":["../src/solid/renderer.ts","../src/solid/components.ts","../src/solid/requests.ts","../src/solid/editor.ts","../src/solid/context.ts","../src/solid/runtime.ts","../src/solid/actions.ts","../src/solid/storage.ts"],"sourcesContent":["import { createRenderer } from \"solid-js/universal\";\nimport type { JSONValue, ViewPatch } from \"./protocol\";\n\n/** A node of the serialized view tree; it mirrors the host tree, which has the real DOM. */\ninterface ViewNode {\n id: number;\n type: string;\n parent?: ViewNode;\n children: ViewNode[];\n callbacks: Map<string, number>;\n}\n\ntype PatchSender = (patches: ViewPatch[]) => void;\n\nconst MAX_PATCHES_PER_MESSAGE = 1000;\nconst MAX_JSON_DEPTH = 8;\nconst callbacks = new Map<number, (...args: unknown[]) => unknown>();\n// Solid moves a node by removing and inserting it again, but the host deletes removed nodes,\n// so removals wait for the flush and apply only to nodes that are still detached.\nconst removedNodes = new Set<ViewNode>();\n\nlet nextID = 1;\nlet queue: ViewPatch[] = [];\nlet flushScheduled = false;\nlet sendPatches: PatchSender = () => {};\n\nconst releaseCallbacks = (node: ViewNode): void => {\n for (const callback of node.callbacks.values()) callbacks.delete(callback);\n\n node.callbacks.clear();\n node.children.forEach(releaseCallbacks);\n};\nconst flush = (): void => {\n for (const node of removedNodes) {\n if (node.parent) continue;\n\n releaseCallbacks(node);\n queue.push({ op: \"remove\", id: node.id });\n }\n\n removedNodes.clear();\n\n const patches = queue;\n\n flushScheduled = false;\n queue = [];\n\n for (let index = 0; index < patches.length; index += MAX_PATCHES_PER_MESSAGE) {\n sendPatches(patches.slice(index, index + MAX_PATCHES_PER_MESSAGE));\n }\n};\nconst scheduleFlush = (): void => {\n if (flushScheduled) return;\n\n flushScheduled = true;\n queueMicrotask(flush);\n};\nconst queuePatch = (patch: ViewPatch): void => {\n queue.push(patch);\n scheduleFlush();\n};\nconst createNode = (type: string): ViewNode => {\n return { id: nextID++, type, children: [], callbacks: new Map() };\n};\nconst toJSONValue = (value: unknown, depth = 0): JSONValue => {\n const isPrimitive =\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"boolean\" ||\n (typeof value === \"number\" && Number.isFinite(value));\n\n if (isPrimitive) return value as JSONValue;\n\n if (depth >= MAX_JSON_DEPTH) throw new TypeError(\"Extension props are nested too deeply\");\n\n if (Array.isArray(value)) return value.map((item) => toJSONValue(item, depth + 1));\n\n if (Object.getPrototypeOf(value) === Object.prototype) {\n return Object.fromEntries(\n Object.entries(value as object).map(([key, item]) => [key, toJSONValue(item, depth + 1)])\n );\n }\n\n throw new TypeError(\"Extension props must be JSON values or functions\");\n};\nconst detach = (node: ViewNode): void => {\n const siblings = node.parent?.children;\n\n if (siblings) siblings.splice(siblings.indexOf(node), 1);\n\n node.parent = undefined;\n};\nconst renderer = createRenderer<ViewNode>({\n createElement(type) {\n const node = createNode(type);\n\n queuePatch({ op: \"create\", id: node.id, component: type });\n\n return node;\n },\n // Solid passes numbers from JSX expressions as they are; the protocol carries strings.\n createTextNode(value) {\n const node = createNode(\"#text\");\n\n queuePatch({ op: \"text\", id: node.id, value: String(value) });\n\n return node;\n },\n replaceText(node, value) {\n queuePatch({ op: \"setText\", id: node.id, value: String(value) });\n },\n isTextNode(node) {\n return node.type === \"#text\";\n },\n setProperty(node, name, value) {\n const previous = node.callbacks.get(name);\n\n if (previous) {\n callbacks.delete(previous);\n node.callbacks.delete(name);\n }\n\n if (typeof value === \"function\") {\n const callback = nextID++;\n\n callbacks.set(callback, value as (...args: unknown[]) => unknown);\n node.callbacks.set(name, callback);\n queuePatch({ op: \"callback\", id: node.id, name, callback });\n } else if (value === undefined) {\n queuePatch({ op: \"unset\", id: node.id, name });\n } else {\n queuePatch({ op: \"prop\", id: node.id, name, value: toJSONValue(value) });\n }\n },\n insertNode(parent, node, anchor) {\n const index = anchor ? parent.children.indexOf(anchor) : -1;\n\n detach(node);\n node.parent = parent;\n\n if (index >= 0) {\n parent.children.splice(index, 0, node);\n } else {\n parent.children.push(node);\n }\n\n queuePatch({ op: \"insert\", parent: parent.id, id: node.id, before: anchor?.id ?? null });\n },\n removeNode(_parent, node) {\n detach(node);\n removedNodes.add(node);\n scheduleFlush();\n },\n getParentNode(node) {\n return node.parent;\n },\n getFirstChild(node) {\n return node.children[0];\n },\n getNextSibling(node) {\n const siblings = node.parent?.children;\n\n return siblings?.[siblings.indexOf(node) + 1];\n }\n});\nconst createViewRoot = (viewID: string): ViewNode => {\n const node = createNode(\"#root\");\n\n queuePatch({ op: \"root\", id: node.id, viewID });\n\n return node;\n};\nconst removeViewRoot = (node: ViewNode): void => {\n releaseCallbacks(node);\n queuePatch({ op: \"remove\", id: node.id });\n};\nconst setPatchSender = (sender: PatchSender): void => {\n sendPatches = sender;\n};\nconst getCallback = (callback: number): ((...args: unknown[]) => unknown) | undefined => {\n return callbacks.get(callback);\n};\n\nexport const {\n render,\n effect,\n memo,\n createComponent,\n createElement,\n createTextNode,\n insertNode,\n insert,\n spread,\n setProp,\n mergeProps,\n use\n} = renderer;\nexport { createViewRoot, removeViewRoot, setPatchSender, getCallback };\nexport type { ViewNode };\n","import type { JSX } from \"solid-js\";\nimport { createElement, spread } from \"./renderer\";\n\ninterface ParentProps {\n children?: JSX.Element;\n}\ninterface StyleProps {\n /** Utility classes of the Andesine UnoCSS preset; the extension build generates their CSS. */\n class?: string;\n}\ninterface ButtonBaseProps {\n variant?: \"text\" | \"solid\" | \"outlined\";\n color?: \"base\" | \"contrast\" | \"primary\" | \"danger\" | \"success\";\n size?: \"small\" | \"medium\";\n disabled?: boolean;\n onClick?(): void;\n}\ninterface FieldProps {\n value?: string;\n placeholder?: string;\n label?: string;\n disabled?: boolean;\n onInput?(value: string): void;\n}\ninterface Option {\n value: string;\n label: string;\n}\ninterface ChoiceProps {\n value?: string;\n options: Option[];\n placeholder?: string;\n disabled?: boolean;\n onChange?(value: string): void;\n}\ninterface ToggleProps {\n checked?: boolean;\n label?: string;\n disabled?: boolean;\n onChange?(checked: boolean): void;\n}\ninterface StackProps extends ParentProps, StyleProps {\n direction?: \"row\" | \"column\";\n gap?: Gap;\n align?: \"start\" | \"center\" | \"end\" | \"stretch\";\n justify?: \"start\" | \"center\" | \"end\" | \"between\";\n wrap?: boolean;\n}\ninterface GridProps extends ParentProps, StyleProps {\n columns?: number;\n gap?: Gap;\n}\ninterface BoxProps extends ParentProps, StyleProps {\n padding?: Gap;\n background?: \"none\" | \"soft\" | \"contrast\";\n border?: boolean;\n}\ninterface ScrollAreaProps extends ParentProps {\n maxHeight?: Size;\n}\ninterface TextProps extends ParentProps, StyleProps {\n size?: \"xs\" | \"sm\" | \"base\" | \"lg\";\n weight?: \"normal\" | \"medium\" | \"semibold\";\n tone?: Tone;\n}\ninterface HeadingProps extends ParentProps, StyleProps {\n level?: 1 | 2 | 3;\n}\ninterface LinkProps extends ParentProps {\n /** An `https:` URL within the extension's declared request URLs. */\n href: string;\n}\ninterface BadgeProps extends ParentProps {\n color?: \"base\" | \"primary\" | \"danger\" | \"success\";\n}\ninterface IconProps {\n /** An icon class, e.g. `i-lucide:rocket`, or `i-acme:logo?bg` for a colored icon. */\n name: string;\n size?: Size;\n tone?: Tone;\n}\ninterface ButtonProps extends ButtonBaseProps, ParentProps {\n loading?: boolean;\n}\ninterface IconButtonProps extends ButtonBaseProps {\n icon: string;\n label: string;\n}\ninterface InputProps extends FieldProps {\n type?: \"text\" | \"url\" | \"number\";\n onEnter?(): void;\n}\ninterface TextareaProps extends FieldProps {\n rows?: number;\n}\ninterface ToggleGroupProps {\n value?: string;\n options: Option[];\n disabled?: boolean;\n onChange?(value: string): void;\n}\ninterface ColorInputProps {\n /** A hex color, e.g. `#ff3617`. */\n value?: string;\n disabled?: boolean;\n onInput?(value: string): void;\n}\ninterface TooltipProps extends ParentProps {\n content: string;\n}\ninterface SettingProps extends ParentProps {\n label: string;\n description?: string;\n}\ninterface ListProps extends ParentProps {\n addLabel?: string;\n emptyLabel?: string;\n disabled?: boolean;\n onAdd?(): void;\n onRemove?(index: number): void;\n onMove?(from: number, to: number): void;\n}\ninterface CardProps extends ParentProps {\n color?: \"base\" | \"contrast\" | \"soft\";\n}\ninterface TreeItem {\n id: string;\n label: string;\n icon?: string;\n /** The parent item ID; top level when unset. */\n parent?: string;\n}\ninterface TreeProps {\n items: TreeItem[];\n selected?: string;\n emptyLabel?: string;\n onSelect?(id: string): void;\n}\ninterface TabsProps extends ParentProps {\n value?: string;\n onChange?(value: string): void;\n}\ninterface TabProps extends ParentProps {\n value: string;\n label: string;\n}\ninterface DisclosureProps extends ParentProps {\n label: string;\n open?: boolean;\n}\ninterface CalloutProps extends ParentProps {\n tone?: \"info\" | \"warning\" | \"danger\" | \"success\";\n title?: string;\n}\ninterface StepProps extends ParentProps {\n title: string;\n}\ninterface FigureProps {\n /** An `https:` image URL within the extension's declared request URLs. */\n src: string;\n alt: string;\n caption?: string;\n}\ninterface CodeBlockProps {\n code: string;\n language?: string;\n}\ninterface TimeAgoProps {\n /** An ISO date-time. */\n date: string;\n}\ninterface SpinnerProps {\n size?: Size;\n}\ninterface SkeletonProps {\n height?: Size;\n}\n\ntype Gap = \"none\" | \"small\" | \"medium\" | \"large\";\ntype Size = \"small\" | \"medium\" | \"large\";\ntype Tone = \"default\" | \"muted\" | \"danger\" | \"success\";\ntype EmptyProps = Record<string, never>;\n\n// Host components are serialized nodes; the host renders the matching Andesine component.\nconst hostComponent = <P extends object>(name: string) => {\n return (props: P): JSX.Element => {\n const node = createElement(name);\n\n spread(node, props);\n\n return node as unknown as JSX.Element;\n };\n};\nconst Stack = hostComponent<StackProps>(\"Stack\");\nconst Grid = hostComponent<GridProps>(\"Grid\");\nconst Box = hostComponent<BoxProps>(\"Box\");\nconst Divider = hostComponent<EmptyProps>(\"Divider\");\nconst ScrollArea = hostComponent<ScrollAreaProps>(\"ScrollArea\");\nconst Text = hostComponent<TextProps>(\"Text\");\nconst Heading = hostComponent<HeadingProps>(\"Heading\");\nconst Code = hostComponent<ParentProps>(\"Code\");\nconst Link = hostComponent<LinkProps>(\"Link\");\nconst Badge = hostComponent<BadgeProps>(\"Badge\");\nconst Icon = hostComponent<IconProps>(\"Icon\");\nconst Button = hostComponent<ButtonProps>(\"Button\");\nconst IconButton = hostComponent<IconButtonProps>(\"IconButton\");\nconst Input = hostComponent<InputProps>(\"Input\");\nconst Textarea = hostComponent<TextareaProps>(\"Textarea\");\nconst Select = hostComponent<ChoiceProps>(\"Select\");\nconst Combobox = hostComponent<ChoiceProps>(\"Combobox\");\nconst Checkbox = hostComponent<ToggleProps>(\"Checkbox\");\nconst Toggle = hostComponent<ToggleProps>(\"Toggle\");\nconst ToggleGroup = hostComponent<ToggleGroupProps>(\"ToggleGroup\");\nconst ColorInput = hostComponent<ColorInputProps>(\"ColorInput\");\nconst Tooltip = hostComponent<TooltipProps>(\"Tooltip\");\nconst Setting = hostComponent<SettingProps>(\"Setting\");\nconst List = hostComponent<ListProps>(\"List\");\nconst Card = hostComponent<CardProps>(\"Card\");\nconst Tree = hostComponent<TreeProps>(\"Tree\");\nconst Tabs = hostComponent<TabsProps>(\"Tabs\");\nconst Tab = hostComponent<TabProps>(\"Tab\");\nconst Disclosure = hostComponent<DisclosureProps>(\"Disclosure\");\nconst Callout = hostComponent<CalloutProps>(\"Callout\");\nconst Steps = hostComponent<ParentProps>(\"Steps\");\nconst Step = hostComponent<StepProps>(\"Step\");\nconst Figure = hostComponent<FigureProps>(\"Figure\");\nconst CodeBlock = hostComponent<CodeBlockProps>(\"CodeBlock\");\nconst TimeAgo = hostComponent<TimeAgoProps>(\"TimeAgo\");\nconst Spinner = hostComponent<SpinnerProps>(\"Spinner\");\nconst Skeleton = hostComponent<SkeletonProps>(\"Skeleton\");\n/** Where an element view shows the element's editable content (element views only). */\nconst ContentSlot = hostComponent<EmptyProps>(\"ContentSlot\");\n\nexport {\n Stack,\n Grid,\n Box,\n Divider,\n ScrollArea,\n Text,\n Heading,\n Code,\n Link,\n Badge,\n Icon,\n Button,\n IconButton,\n Input,\n Textarea,\n Select,\n Combobox,\n Checkbox,\n Toggle,\n ToggleGroup,\n ColorInput,\n Tooltip,\n Setting,\n List,\n Card,\n Tree,\n Tabs,\n Tab,\n Disclosure,\n Callout,\n Steps,\n Step,\n Figure,\n CodeBlock,\n TimeAgo,\n Spinner,\n Skeleton,\n ContentSlot\n};\nexport type {\n StackProps,\n GridProps,\n BoxProps,\n ScrollAreaProps,\n TextProps,\n HeadingProps,\n ParentProps,\n LinkProps,\n BadgeProps,\n IconProps,\n ButtonProps,\n IconButtonProps,\n InputProps,\n TextareaProps,\n ChoiceProps,\n ToggleProps,\n ToggleGroupProps,\n ColorInputProps,\n TooltipProps,\n SettingProps,\n ListProps,\n CardProps,\n TreeItem,\n TreeProps,\n TabsProps,\n TabProps,\n DisclosureProps,\n CalloutProps,\n StepProps,\n FigureProps,\n CodeBlockProps,\n TimeAgoProps,\n SpinnerProps,\n SkeletonProps,\n EmptyProps,\n StyleProps,\n Option\n};\n","import type { JSONValue, RequestErrorCode, RequestMethod } from \"./protocol\";\n\ninterface PendingRequest {\n resolve(result: JSONValue | undefined): void;\n reject(error: ExtensionRequestError): void;\n}\n\ntype RequestSender = (id: number, method: RequestMethod, params: JSONValue) => void;\n\nconst pending = new Map<number, PendingRequest>();\n\nlet nextID = 1;\nlet sendRequest: RequestSender | null = null;\n\n/** A host request failed; `code` says why, for example `user_interaction_required`. */\nclass ExtensionRequestError extends Error {\n constructor(\n readonly code: RequestErrorCode,\n message: string\n ) {\n super(message);\n }\n}\n\nconst setRequestSender = (sender: RequestSender): void => {\n sendRequest = sender;\n};\nconst request = (method: RequestMethod, params: JSONValue): Promise<JSONValue | undefined> => {\n if (!sendRequest) {\n return Promise.reject(new ExtensionRequestError(\"unavailable\", \"The extension is not running\"));\n }\n\n const id = nextID++;\n const send = sendRequest;\n\n return new Promise((resolve, reject) => {\n pending.set(id, { resolve, reject });\n send(id, method, params);\n });\n};\nconst settleRequest = (\n id: number,\n result: JSONValue | undefined,\n error?: { code: RequestErrorCode; message: string }\n): void => {\n const request = pending.get(id);\n\n if (!request) return;\n\n pending.delete(id);\n\n if (error) {\n request.reject(new ExtensionRequestError(error.code, error.message));\n } else {\n request.resolve(result);\n }\n};\n\nexport { ExtensionRequestError, request, setRequestSender, settleRequest };\n","import { createContext, useContext } from \"solid-js\";\nimport type { JSONValue } from \"./protocol\";\nimport { request } from \"./requests\";\n\n/** A document block in editor JSON, e.g. `{ type: \"paragraph\", content: [{ type: \"text\", text: \"Hi\" }] }`. */\ninterface ContentNode {\n type: string;\n attrs?: Record<string, JSONValue>;\n content?: ContentNode[];\n marks?: Array<{ type: string; attrs?: Record<string, JSONValue> }>;\n text?: string;\n}\ninterface ViewEditor {\n /** Block action views: replaces the blocks the action was opened for. */\n replace(content: ContentNode[]): Promise<void>;\n /** Block action views: inserts blocks after those the action was opened for. */\n insertAfter(content: ContentNode[]): Promise<void>;\n /** Element views: updates the element's props; call it from a user interaction. */\n setElementProps(props: Record<string, JSONValue>): Promise<void>;\n /** Block action views: closes the view. */\n close(): Promise<void>;\n}\n\nconst ViewContext = createContext<string>();\n\n/**\n * Edits the document from the current view. Andesine validates every edit and applies it as the\n * member's own edit; edits reject with `ExtensionRequestError` when they do not fit.\n */\nconst useEditor = (): ViewEditor => {\n const viewID = useContext(ViewContext) ?? \"\";\n const asJSON = (content: ContentNode[]) => content as unknown as JSONValue;\n\n return {\n replace: async (content) => {\n await request(\"editor.replace\", { viewID, content: asJSON(content) });\n },\n insertAfter: async (content) => {\n await request(\"editor.insertAfter\", { viewID, content: asJSON(content) });\n },\n setElementProps: async (props) => {\n await request(\"editor.setElementProps\", { viewID, props });\n },\n close: async () => {\n await request(\"view.close\", { viewID });\n }\n };\n};\n\nexport { ViewContext, useEditor };\nexport type { ContentNode, ViewEditor };\n","import { createSignal } from \"solid-js\";\nimport { type JSONValue } from \"./protocol\";\nimport { ExtensionRequestError, request } from \"./requests\";\n\ninterface ExtensionContext {\n permissions: string[];\n backend: string | null;\n configuration: Record<string, JSONValue>;\n}\ninterface SessionMessage {\n token: string | null;\n extensionID: string;\n instance: string;\n}\ninterface APIFetchResult {\n status: number;\n contentType: string;\n body: string;\n}\n\n/** The origin `apiFetch` accepts; use it as the SDK `baseURL`. */\nconst EXTENSION_API_URL = \"https://api.andesine.invalid\";\nconst SESSION_WAIT = 10_000;\nconst [permissions, setPermissions] = createSignal<string[]>([]);\nconst [backendURL, setBackendURL] = createSignal<string | null>(null);\nconst [session, setSession] = createSignal<string | null>(null);\nconst [configuration, setConfiguration] = createSignal<Record<string, JSONValue>>({});\n\nlet identity = { extensionID: \"\", instance: \"\" };\n\nconst updateContext = (context: ExtensionContext): void => {\n setPermissions(context.permissions);\n setBackendURL(context.backend);\n setConfiguration(context.configuration);\n};\nconst updateSession = (message: SessionMessage): void => {\n identity = { extensionID: message.extensionID, instance: message.instance };\n setSession(message.token);\n};\n/**\n * The configuration's non-secret values, with defaults for unset fields (reactive). Secret\n * fields are readable only by the extension backend.\n */\nconst useConfiguration = <T extends Record<string, JSONValue> = Record<string, JSONValue>>() => {\n return configuration as () => Partial<T>;\n};\n/** The member's effective permissions for this extension (reactive), e.g. `read:entries`. */\nconst usePermissions = () => permissions;\n/** Whether the effective permissions include `permission`; a write permission includes its read. */\nconst hasPermission = (permission: string): boolean => {\n const write = permission.startsWith(\"read:\") ? permission.slice(5) : null;\n\n return permissions().some((granted) => granted === permission || granted === write);\n};\n/**\n * A fetch implementation for the Andesine SDK: requests go through Andesine with the member's\n * session, within the effective permissions. Forbidden operations answer with HTTP 403.\n * @example createClient({ baseURL: EXTENSION_API_URL, fetch: apiFetch })\n */\nconst apiFetch: typeof fetch = async (input, init) => {\n const outgoing = new Request(input, init);\n const url = new URL(outgoing.url);\n const hasBody = ![\"GET\", \"HEAD\"].includes(outgoing.method);\n\n if (url.origin !== EXTENSION_API_URL) {\n throw new TypeError(`apiFetch only accepts ${EXTENSION_API_URL} URLs`);\n }\n\n try {\n const result = (await request(\"api.fetch\", {\n method: outgoing.method,\n path: `${url.pathname}${url.search}`,\n ...(hasBody && { body: await outgoing.text() })\n })) as unknown as APIFetchResult;\n\n return new Response(result.body, {\n status: result.status,\n headers: { \"content-type\": result.contentType }\n });\n } catch (error) {\n if (!(error instanceof ExtensionRequestError) || error.code !== \"forbidden\") throw error;\n\n return Response.json(\n { code: \"FORBIDDEN\", message: \"The extension cannot call this operation\" },\n { status: 403 }\n );\n }\n};\nconst waitForSession = (): Promise<string> => {\n return new Promise((resolve, reject) => {\n const started = Date.now();\n const check = () => {\n const token = session();\n\n if (token) {\n resolve(token);\n } else if (Date.now() - started > SESSION_WAIT) {\n reject(new ExtensionRequestError(\"unavailable\", \"No backend session is available\"));\n } else {\n setTimeout(check, 100);\n }\n };\n\n check();\n });\n};\n/** The extension's own backend, as declared in the manifest. */\nconst backend = {\n /**\n * Fetches a path of the backend URL with the member's session token in `Authorization`, and\n * the extension ID and instance API URL in `Andesine-Extension` and `Andesine-Instance`; the\n * backend verifies them with `verifySession` from `@andesine/sdk/extensions`. Its CORS\n * configuration must allow these headers.\n */\n async fetch(path: string, init?: RequestInit): Promise<Response> {\n const base = backendURL();\n\n if (!base) throw new ExtensionRequestError(\"unavailable\", \"The extension has no backend\");\n\n const root = base.endsWith(\"/\") ? base : `${base}/`;\n const url = new URL(path.replace(/^\\/+/, \"\"), root);\n\n if (!url.href.startsWith(root)) throw new TypeError(\"The path is outside the backend URL\");\n\n const headers = new Headers(init?.headers);\n\n headers.set(\"Authorization\", `Bearer ${await waitForSession()}`);\n headers.set(\"Andesine-Extension\", identity.extensionID);\n headers.set(\"Andesine-Instance\", identity.instance);\n\n return fetch(url, { ...init, headers, credentials: \"omit\", referrerPolicy: \"no-referrer\" });\n }\n};\n\nexport {\n EXTENSION_API_URL,\n apiFetch,\n backend,\n hasPermission,\n updateContext,\n updateSession,\n useConfiguration,\n usePermissions\n};\n","import { catchError, type Component } from \"solid-js\";\nimport { ViewContext } from \"./editor\";\nimport { createStore, reconcile } from \"solid-js/store\";\nimport {\n createComponent,\n createViewRoot,\n getCallback,\n removeViewRoot,\n render,\n setPatchSender,\n type ViewNode\n} from \"./renderer\";\nimport type { Envelope, HostMessageBody, WorkerMessageBody } from \"./protocol\";\nimport { setRequestSender, settleRequest } from \"./requests\";\nimport { updateContext, updateSession } from \"./context\";\n\ninterface RunningView {\n root: ViewNode;\n setProps(props: Record<string, unknown>): void;\n dispose(): void;\n}\n\ntype ExtensionViews = Record<string, Component<never>>;\n\nconst PROTOCOL: Envelope[\"protocol\"] = 1;\n// Context providers return their children, which are view nodes in this renderer.\nconst ViewProvider = ViewContext.Provider as unknown as (props: {\n value: string;\n children: ViewNode;\n}) => ViewNode;\nconst views = new Map<string, RunningView>();\n\nlet started = false;\n\nconst getErrorMessage = (error: unknown): string => {\n return String(error instanceof Error ? error.message : error).slice(0, 500);\n};\n/**\n * Starts the extension frontend in its sandbox worker. `entries` maps the view entries that the\n * manifest names to Solid components; each view receives its host props as reactive props.\n */\nconst startExtension = (entries: ExtensionViews): void => {\n if (started) return;\n\n started = true;\n globalThis.addEventListener(\"message\", function handleInit(event: MessageEvent) {\n const isInit = event.data?.type === \"andesine:init\" && event.ports.length === 1;\n\n if (!isInit) return;\n\n const port = event.ports[0];\n const generation = event.data.generation as number;\n\n let sequence = 0;\n\n const send = (body: WorkerMessageBody): void => {\n port.postMessage({ ...body, protocol: PROTOCOL, generation, sequence: sequence++ });\n };\n const sendError = (error: unknown, viewID?: string): void => {\n send({ type: \"error\", viewID, message: getErrorMessage(error) });\n };\n const createView = (viewID: string, entry: string, props: Record<string, unknown>): void => {\n // Solid components return JSX elements, which are view nodes in this renderer.\n const component = Object.hasOwn(entries, entry)\n ? (entries[entry] as unknown as (props: object) => ViewNode)\n : undefined;\n\n if (!component || views.has(viewID)) {\n sendError(`Unknown or duplicate view: ${entry}`, viewID);\n\n return;\n }\n\n const root = createViewRoot(viewID);\n const [viewProps, setViewProps] = createStore({ ...props });\n const dispose = render(() => {\n return catchError(\n () => {\n return createComponent(ViewProvider, {\n value: viewID,\n get children() {\n return createComponent(component, viewProps);\n }\n });\n },\n (error) => sendError(error, viewID)\n )!;\n }, root);\n\n views.set(viewID, {\n root,\n setProps: (next) => setViewProps(reconcile(next)),\n dispose\n });\n };\n const disposeView = (viewID: string): void => {\n const view = views.get(viewID);\n\n if (!view) return;\n\n views.delete(viewID);\n view.dispose();\n removeViewRoot(view.root);\n };\n const handleMessage = (message: HostMessageBody): void => {\n if (message.type === \"heartbeat\") {\n send({ type: \"heartbeat\", id: message.id });\n } else if (message.type === \"view.create\") {\n createView(message.viewID, message.entry, message.props);\n } else if (message.type === \"view.update\") {\n views.get(message.viewID)?.setProps(message.props);\n } else if (message.type === \"view.dispose\") {\n disposeView(message.viewID);\n } else if (message.type === \"context\") {\n updateContext(message);\n } else if (message.type === \"session\") {\n updateSession(message);\n } else if (message.type === \"response\") {\n settleRequest(message.id, message.result, message.error);\n } else if (message.type === \"callback\") {\n // Callbacks of removed nodes are released; late calls are ignored.\n getCallback(message.callback)?.(...message.args);\n } else if (message.type === \"dispose\") {\n [...views.keys()].forEach(disposeView);\n port.close();\n globalThis.close();\n }\n };\n\n globalThis.removeEventListener(\"message\", handleInit);\n globalThis.addEventListener(\"error\", (error) => sendError(error.message));\n globalThis.addEventListener(\"unhandledrejection\", (event) => sendError(event.reason));\n setPatchSender((patches) => send({ type: \"patch\", patches }));\n setRequestSender((id, method, params) => send({ type: \"request\", id, method, params }));\n port.addEventListener(\"message\", (event: MessageEvent<HostMessageBody>) => {\n try {\n handleMessage(event.data);\n } catch (error) {\n sendError(error);\n }\n });\n port.start();\n send({ type: \"ready\" });\n });\n};\n\nexport { startExtension };\n","import { request } from \"./requests\";\n\n// Host actions run only shortly after the user interacts with the extension's view, for example\n// from a button's `onClick`. They reject with `ExtensionRequestError`.\n\n/** Copies text; the host shows a fallback dialog if the browser refuses. */\nconst copyText = async (text: string): Promise<void> => {\n await request(\"host.copyText\", { text });\n};\n/** Downloads a text file, for example `downloadFile(\"andesine.config.ts\", source)`. */\nconst downloadFile = async (name: string, text: string, type?: string): Promise<void> => {\n await request(\"host.downloadFile\", type ? { name, text, type } : { name, text });\n};\n/** Opens an `https:` URL in a new tab; URLs outside the declared URLs need confirmation. */\nconst openURL = async (url: string): Promise<void> => {\n await request(\"host.openURL\", { url });\n};\n\nexport { copyText, downloadFile, openURL };\n","import { type JSONValue } from \"./protocol\";\nimport { request } from \"./requests\";\n\ninterface StorageEntry<T extends JSONValue = JSONValue> {\n key: string;\n value: T;\n /** An ISO date-time. */\n updatedAt: string;\n}\ninterface StorageListOptions {\n /** Only keys that start with the prefix. */\n prefix?: string;\n /** Entries after this key, from the previous page. */\n after?: string;\n /** At most 100 (the default). */\n limit?: number;\n}\ninterface StoragePage {\n data: StorageEntry[];\n hasMore: boolean;\n}\n\n/**\n * The extension's JSON key-value storage, shared with its backend. Keys have up to 256\n * characters; values up to 128 KiB; the storage up to 10,000 entries and 10 MiB. The last write\n * wins. Failed requests reject with `ExtensionRequestError`.\n */\nconst storage = {\n /** The entry, or null when the key is not set. */\n async get<T extends JSONValue = JSONValue>(key: string): Promise<StorageEntry<T> | null> {\n return ((await request(\"storage.get\", { key })) ?? null) as StorageEntry<T> | null;\n },\n /** Creates or replaces an entry; resolves with its update time. */\n async set(key: string, value: JSONValue): Promise<{ updatedAt: string }> {\n const { updatedAt } = (await request(\"storage.set\", { key, value })) as { updatedAt: string };\n\n return { updatedAt };\n },\n /** Resolves with whether the key was set. */\n async delete(key: string): Promise<boolean> {\n const { deleted } = (await request(\"storage.delete\", { key })) as { deleted: boolean };\n\n return deleted;\n },\n /** Entries in key order; a page can have fewer entries than `limit` when values are large. */\n async list(options: StorageListOptions = {}): Promise<StoragePage> {\n return (await request(\"storage.list\", { ...options })) as unknown as StoragePage;\n }\n};\n\nexport { storage };\nexport type { StorageEntry, StorageListOptions, StoragePage };\n"],"mappings":"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"}