@immediately-run/sdk 0.1.5 → 0.2.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/contribute.cjs +32 -0
- package/dist/contribute.cjs.map +1 -0
- package/dist/contribute.d.cts +100 -0
- package/dist/contribute.d.ts +100 -0
- package/dist/contribute.js +8 -0
- package/dist/contribute.js.map +1 -0
- package/dist/editorContext.cjs +46 -0
- package/dist/editorContext.cjs.map +1 -0
- package/dist/editorContext.d.cts +32 -0
- package/dist/editorContext.d.ts +32 -0
- package/dist/editorContext.js +20 -0
- package/dist/editorContext.js.map +1 -0
- package/dist/formFactor.cjs +46 -0
- package/dist/formFactor.cjs.map +1 -0
- package/dist/formFactor.d.cts +29 -0
- package/dist/formFactor.d.ts +29 -0
- package/dist/formFactor.js +20 -0
- package/dist/formFactor.js.map +1 -0
- package/dist/index.cjs +10 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/mounts.cjs +42 -6
- package/dist/mounts.cjs.map +1 -1
- package/dist/mounts.d.cts +57 -3
- package/dist/mounts.d.ts +57 -3
- package/dist/mounts.js +33 -6
- package/dist/mounts.js.map +1 -1
- package/dist/protocolStream.cjs +87 -0
- package/dist/protocolStream.cjs.map +1 -0
- package/dist/protocolStream.d.cts +44 -0
- package/dist/protocolStream.d.ts +44 -0
- package/dist/protocolStream.js +61 -0
- package/dist/protocolStream.js.map +1 -0
- package/dist/theme.cjs +57 -0
- package/dist/theme.cjs.map +1 -0
- package/dist/theme.d.cts +38 -0
- package/dist/theme.d.ts +38 -0
- package/dist/theme.js +30 -0
- package/dist/theme.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { addListener, sendMessage } from "./sandboxUtils";
|
|
2
|
+
class StreamError extends Error {
|
|
3
|
+
constructor(code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "StreamError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
let streamCounter = 0;
|
|
10
|
+
const nextMsgId = () => {
|
|
11
|
+
streamCounter = (streamCounter + 1) % Number.MAX_SAFE_INTEGER;
|
|
12
|
+
return streamCounter;
|
|
13
|
+
};
|
|
14
|
+
async function* consumeStream(transport, type, method, params, msgId = nextMsgId()) {
|
|
15
|
+
const queue = [];
|
|
16
|
+
let wake = null;
|
|
17
|
+
const push = (frame) => {
|
|
18
|
+
queue.push(frame);
|
|
19
|
+
const w = wake;
|
|
20
|
+
wake = null;
|
|
21
|
+
w?.();
|
|
22
|
+
};
|
|
23
|
+
const unsubscribe = transport.subscribe(type, (msg) => {
|
|
24
|
+
if (msg.msgId !== msgId || !msg.stream) return;
|
|
25
|
+
push(msg.stream);
|
|
26
|
+
});
|
|
27
|
+
try {
|
|
28
|
+
transport.send({ type, method, params, msgId, stream: true });
|
|
29
|
+
while (true) {
|
|
30
|
+
if (queue.length === 0) {
|
|
31
|
+
await new Promise((resolve) => {
|
|
32
|
+
wake = resolve;
|
|
33
|
+
});
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const frame = queue.shift();
|
|
37
|
+
if (frame.kind === "event") {
|
|
38
|
+
yield frame.value;
|
|
39
|
+
} else if (frame.kind === "done") {
|
|
40
|
+
return frame.value;
|
|
41
|
+
} else {
|
|
42
|
+
throw new StreamError(frame.code, frame.message);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} finally {
|
|
46
|
+
unsubscribe();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const bundlerTransport = {
|
|
50
|
+
send: (msg) => sendMessage(msg.type, msg),
|
|
51
|
+
subscribe: (type, handler) => addListener(type, (msg) => handler(msg))
|
|
52
|
+
};
|
|
53
|
+
function protocolStream(protocolName, method, params) {
|
|
54
|
+
return consumeStream(bundlerTransport, protocolName, method, params);
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
StreamError,
|
|
58
|
+
consumeStream,
|
|
59
|
+
protocolStream
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=protocolStream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/protocolStream.ts"],"sourcesContent":["// SDK-side consumer for the host streaming transport (UI_AS_APPS_SPEC §5.1).\n//\n// The host `pumpGenerator` emits, per request msgId, a run of `stream.event`\n// frames terminated by one `stream.done` (with the return value) or `stream.error`\n// frame. This reassembles that run into an AsyncGenerator: each `event` is a\n// `yield`, the `done` value is the generator's `return`, an `error` is a `throw`.\n//\n// `consumeStream` takes an injected `StreamTransport` so it's unit-tested with a\n// fake send/subscribe — no bundler. `protocolStream`/`contribute` below wire it to\n// the real sandbox messageBus via sandboxUtils.\nimport { addListener, sendMessage } from './sandboxUtils';\n\nexport type StreamFrame =\n | { kind: 'event'; value: unknown }\n | { kind: 'done'; value: unknown }\n | { kind: 'error'; code: string; message: string };\n\nexport interface StreamTransport {\n // Fire the request that starts the stream. The host replies with frames tagged\n // by the same `msgId`.\n send: (msg: { type: string; method: string; params: unknown[]; msgId: number; stream: true }) => void;\n // Subscribe to inbound frames for `type`; returns an unsubscribe.\n subscribe: (\n type: string,\n handler: (msg: { msgId?: number; stream?: StreamFrame }) => void\n ) => () => void;\n}\n\nexport class StreamError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'StreamError';\n this.code = code;\n }\n}\n\nlet streamCounter = 0;\nconst nextMsgId = (): number => {\n // Distinct from the bundler's own protocolRequest counter space is unnecessary —\n // frames are filtered by (type, msgId, stream) so a collision with a one-shot\n // reply (which has `result`, not `stream`) can't be misread.\n streamCounter = (streamCounter + 1) % Number.MAX_SAFE_INTEGER;\n return streamCounter;\n};\n\n/**\n * Drive one streamed request to completion over an injected transport.\n *\n * Yields each event value; returns the `done` value; throws `StreamError` on an\n * error frame. Always unsubscribes (via the generator's `finally`) so an early\n * `break` in the consumer doesn't leak the listener.\n */\nexport async function* consumeStream<T = unknown, R = unknown>(\n transport: StreamTransport,\n type: string,\n method: string,\n params: unknown[],\n msgId: number = nextMsgId()\n): AsyncGenerator<T, R, void> {\n const queue: StreamFrame[] = [];\n let wake: (() => void) | null = null;\n const push = (frame: StreamFrame) => {\n queue.push(frame);\n const w = wake;\n wake = null;\n w?.();\n };\n\n const unsubscribe = transport.subscribe(type, (msg) => {\n if (msg.msgId !== msgId || !msg.stream) return;\n push(msg.stream);\n });\n\n try {\n transport.send({ type, method, params, msgId, stream: true });\n while (true) {\n if (queue.length === 0) {\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n continue;\n }\n const frame = queue.shift() as StreamFrame;\n if (frame.kind === 'event') {\n yield frame.value as T;\n } else if (frame.kind === 'done') {\n return frame.value as R;\n } else {\n throw new StreamError(frame.code, frame.message);\n }\n }\n } finally {\n unsubscribe();\n }\n}\n\n// The real sandbox transport, built from the bundler messageBus helpers.\nconst bundlerTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n};\n\n/**\n * Consume an elevated streaming protocol method from app code.\n *\n * `for await (const ev of protocolStream('protocol-contribute', 'run', [opts])) …`\n */\nexport function protocolStream<T = unknown, R = unknown>(\n protocolName: string,\n method: string,\n params: unknown[]\n): AsyncGenerator<T, R, void> {\n return consumeStream<T, R>(bundlerTransport, protocolName, method, params);\n}\n"],"mappings":"AAUA,SAAS,aAAa,mBAAmB;AAkBlC,MAAM,oBAAoB,MAAM;AAAA,EAErC,YAAY,MAAc,SAAiB;AACzC,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAI,gBAAgB;AACpB,MAAM,YAAY,MAAc;AAI9B,mBAAiB,gBAAgB,KAAK,OAAO;AAC7C,SAAO;AACT;AASA,gBAAuB,cACrB,WACA,MACA,QACA,QACA,QAAgB,UAAU,GACE;AAC5B,QAAM,QAAuB,CAAC;AAC9B,MAAI,OAA4B;AAChC,QAAM,OAAO,CAAC,UAAuB;AACnC,UAAM,KAAK,KAAK;AAChB,UAAM,IAAI;AACV,WAAO;AACP,QAAI;AAAA,EACN;AAEA,QAAM,cAAc,UAAU,UAAU,MAAM,CAAC,QAAQ;AACrD,QAAI,IAAI,UAAU,SAAS,CAAC,IAAI,OAAQ;AACxC,SAAK,IAAI,MAAM;AAAA,EACjB,CAAC;AAED,MAAI;AACF,cAAU,KAAK,EAAE,MAAM,QAAQ,QAAQ,OAAO,QAAQ,KAAK,CAAC;AAC5D,WAAO,MAAM;AACX,UAAI,MAAM,WAAW,GAAG;AACtB,cAAM,IAAI,QAAc,CAAC,YAAY;AACnC,iBAAO;AAAA,QACT,CAAC;AACD;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,MAAM;AAC1B,UAAI,MAAM,SAAS,SAAS;AAC1B,cAAM,MAAM;AAAA,MACd,WAAW,MAAM,SAAS,QAAQ;AAChC,eAAO,MAAM;AAAA,MACf,OAAO;AACL,cAAM,IAAI,YAAY,MAAM,MAAM,MAAM,OAAO;AAAA,MACjD;AAAA,IACF;AAAA,EACF,UAAE;AACA,gBAAY;AAAA,EACd;AACF;AAGA,MAAM,mBAAoC;AAAA,EACxC,MAAM,CAAC,QAAQ,YAAY,IAAI,MAAM,GAAyC;AAAA,EAC9E,WAAW,CAAC,MAAM,YAChB,YAAY,MAAM,CAAC,QAAQ,QAAQ,GAA+C,CAAC;AACvF;AAOO,SAAS,eACd,cACA,QACA,QAC4B;AAC5B,SAAO,cAAoB,kBAAkB,cAAc,QAAQ,MAAM;AAC3E;","names":[]}
|
package/dist/theme.cjs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
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 theme_exports = {};
|
|
20
|
+
__export(theme_exports, {
|
|
21
|
+
getHostTheme: () => getHostTheme,
|
|
22
|
+
onHostThemeChange: () => onHostThemeChange,
|
|
23
|
+
setHostTheme: () => setHostTheme,
|
|
24
|
+
useHostTheme: () => useHostTheme
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(theme_exports);
|
|
27
|
+
var import_react = require("react");
|
|
28
|
+
var import_sandboxUtils = require("./sandboxUtils");
|
|
29
|
+
const themeService = () => {
|
|
30
|
+
return module.evaluation.module.bundler.theme;
|
|
31
|
+
};
|
|
32
|
+
const getHostTheme = () => themeService().getTheme();
|
|
33
|
+
const onHostThemeChange = (listener) => {
|
|
34
|
+
const disposable = themeService().onChange(listener);
|
|
35
|
+
return () => disposable.dispose();
|
|
36
|
+
};
|
|
37
|
+
const useHostTheme = () => {
|
|
38
|
+
const [theme, setTheme] = (0, import_react.useState)(getHostTheme);
|
|
39
|
+
(0, import_react.useEffect)(() => onHostThemeChange(setTheme), []);
|
|
40
|
+
return theme;
|
|
41
|
+
};
|
|
42
|
+
const setHostTheme = async (theme) => {
|
|
43
|
+
const res = await (0, import_sandboxUtils.protocolRequest)("theme", "set", [{ theme }]);
|
|
44
|
+
if (!res || res.ok !== true) {
|
|
45
|
+
const err = new Error(res?.message ?? "setHostTheme failed");
|
|
46
|
+
err.code = (res && "code" in res ? res.code : void 0) ?? "unknown";
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
51
|
+
0 && (module.exports = {
|
|
52
|
+
getHostTheme,
|
|
53
|
+
onHostThemeChange,
|
|
54
|
+
setHostTheme,
|
|
55
|
+
useHostTheme
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=theme.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/theme.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { protocolRequest } from './sandboxUtils';\n\n/**\n * The host UI theme, mirrored from the immediately.run host window into the\n * sandbox. Your app can read this to render in step with the host chrome\n * (light / dark).\n *\n * This is the baseline `theme:read` capability — every app may read it. Changing\n * the host theme is a separate, elevated action (`theme:set`), available only to\n * the theme-toggle system app.\n */\nexport type HostTheme = 'light' | 'dark';\n\ninterface ThemeService {\n getTheme(): HostTheme;\n onChange(listener: (theme: HostTheme) => void): { dispose(): void };\n}\n\n// `module.evaluation.module.bundler.theme` is the sandbox bundler injected into\n// the evaluation context (same path the other SDK helpers use for `auth`).\nconst themeService = (): ThemeService => {\n // @ts-ignore - injected by the sandbox runtime\n return module.evaluation.module.bundler.theme;\n};\n\n/**\n * Returns the current host theme. Poll this for a one-off read; use\n * {@link onHostThemeChange} or {@link useHostTheme} to react to changes.\n */\nexport const getHostTheme = (): HostTheme => themeService().getTheme();\n\n/**\n * Subscribe to host theme changes. The listener is invoked immediately with the\n * current theme, then again on every change. Returns an unsubscribe fn.\n */\nexport const onHostThemeChange = (\n listener: (theme: HostTheme) => void,\n): (() => void) => {\n const disposable = themeService().onChange(listener);\n return () => disposable.dispose();\n};\n\n/**\n * React hook returning the current host theme, re-rendering when it changes.\n * The recommended way to implement an app's own `useTheme`: follow the host,\n * allow a local override.\n */\nexport const useHostTheme = (): HostTheme => {\n const [theme, setTheme] = useState<HostTheme>(getHostTheme);\n useEffect(() => onHostThemeChange(setTheme), []);\n return theme;\n};\n\n/**\n * Set the host UI theme — the ELEVATED `theme:set` action (§8.5). The host\n * applies it and re-pushes the new value to every `theme:read` iframe, so your\n * own {@link useHostTheme} confirms the change (the loop closes with no special\n * case). Only a grant holding `theme:set` (e.g. the theme-toggle system app) may\n * call this; any other app is rejected host-side with a `forbidden`\n * {@link Error} (carrying `.code`), regardless of what the app claims. Update\n * optimistically and let the re-push confirm.\n */\nexport const setHostTheme = async (theme: HostTheme): Promise<void> => {\n const res = (await protocolRequest('theme', 'set', [{ theme }])) as\n | { ok: true; data?: unknown }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'setHostTheme failed') as Error & {\n code?: string;\n };\n err.code = (res && 'code' in res ? res.code : undefined) ?? 'unknown';\n throw err;\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAoC;AACpC,0BAAgC;AAoBhC,MAAM,eAAe,MAAoB;AAEvC,SAAO,OAAO,WAAW,OAAO,QAAQ;AAC1C;AAMO,MAAM,eAAe,MAAiB,aAAa,EAAE,SAAS;AAM9D,MAAM,oBAAoB,CAC/B,aACiB;AACjB,QAAM,aAAa,aAAa,EAAE,SAAS,QAAQ;AACnD,SAAO,MAAM,WAAW,QAAQ;AAClC;AAOO,MAAM,eAAe,MAAiB;AAC3C,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAoB,YAAY;AAC1D,8BAAU,MAAM,kBAAkB,QAAQ,GAAG,CAAC,CAAC;AAC/C,SAAO;AACT;AAWO,MAAM,eAAe,OAAO,UAAoC;AACrE,QAAM,MAAO,UAAM,qCAAgB,SAAS,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;AAI9D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,qBAAqB;AAG3D,QAAI,QAAQ,OAAO,UAAU,MAAM,IAAI,OAAO,WAAc;AAC5D,UAAM;AAAA,EACR;AACF;","names":[]}
|
package/dist/theme.d.cts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host UI theme, mirrored from the immediately.run host window into the
|
|
3
|
+
* sandbox. Your app can read this to render in step with the host chrome
|
|
4
|
+
* (light / dark).
|
|
5
|
+
*
|
|
6
|
+
* This is the baseline `theme:read` capability — every app may read it. Changing
|
|
7
|
+
* the host theme is a separate, elevated action (`theme:set`), available only to
|
|
8
|
+
* the theme-toggle system app.
|
|
9
|
+
*/
|
|
10
|
+
type HostTheme = 'light' | 'dark';
|
|
11
|
+
/**
|
|
12
|
+
* Returns the current host theme. Poll this for a one-off read; use
|
|
13
|
+
* {@link onHostThemeChange} or {@link useHostTheme} to react to changes.
|
|
14
|
+
*/
|
|
15
|
+
declare const getHostTheme: () => HostTheme;
|
|
16
|
+
/**
|
|
17
|
+
* Subscribe to host theme changes. The listener is invoked immediately with the
|
|
18
|
+
* current theme, then again on every change. Returns an unsubscribe fn.
|
|
19
|
+
*/
|
|
20
|
+
declare const onHostThemeChange: (listener: (theme: HostTheme) => void) => (() => void);
|
|
21
|
+
/**
|
|
22
|
+
* React hook returning the current host theme, re-rendering when it changes.
|
|
23
|
+
* The recommended way to implement an app's own `useTheme`: follow the host,
|
|
24
|
+
* allow a local override.
|
|
25
|
+
*/
|
|
26
|
+
declare const useHostTheme: () => HostTheme;
|
|
27
|
+
/**
|
|
28
|
+
* Set the host UI theme — the ELEVATED `theme:set` action (§8.5). The host
|
|
29
|
+
* applies it and re-pushes the new value to every `theme:read` iframe, so your
|
|
30
|
+
* own {@link useHostTheme} confirms the change (the loop closes with no special
|
|
31
|
+
* case). Only a grant holding `theme:set` (e.g. the theme-toggle system app) may
|
|
32
|
+
* call this; any other app is rejected host-side with a `forbidden`
|
|
33
|
+
* {@link Error} (carrying `.code`), regardless of what the app claims. Update
|
|
34
|
+
* optimistically and let the re-push confirm.
|
|
35
|
+
*/
|
|
36
|
+
declare const setHostTheme: (theme: HostTheme) => Promise<void>;
|
|
37
|
+
|
|
38
|
+
export { type HostTheme, getHostTheme, onHostThemeChange, setHostTheme, useHostTheme };
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host UI theme, mirrored from the immediately.run host window into the
|
|
3
|
+
* sandbox. Your app can read this to render in step with the host chrome
|
|
4
|
+
* (light / dark).
|
|
5
|
+
*
|
|
6
|
+
* This is the baseline `theme:read` capability — every app may read it. Changing
|
|
7
|
+
* the host theme is a separate, elevated action (`theme:set`), available only to
|
|
8
|
+
* the theme-toggle system app.
|
|
9
|
+
*/
|
|
10
|
+
type HostTheme = 'light' | 'dark';
|
|
11
|
+
/**
|
|
12
|
+
* Returns the current host theme. Poll this for a one-off read; use
|
|
13
|
+
* {@link onHostThemeChange} or {@link useHostTheme} to react to changes.
|
|
14
|
+
*/
|
|
15
|
+
declare const getHostTheme: () => HostTheme;
|
|
16
|
+
/**
|
|
17
|
+
* Subscribe to host theme changes. The listener is invoked immediately with the
|
|
18
|
+
* current theme, then again on every change. Returns an unsubscribe fn.
|
|
19
|
+
*/
|
|
20
|
+
declare const onHostThemeChange: (listener: (theme: HostTheme) => void) => (() => void);
|
|
21
|
+
/**
|
|
22
|
+
* React hook returning the current host theme, re-rendering when it changes.
|
|
23
|
+
* The recommended way to implement an app's own `useTheme`: follow the host,
|
|
24
|
+
* allow a local override.
|
|
25
|
+
*/
|
|
26
|
+
declare const useHostTheme: () => HostTheme;
|
|
27
|
+
/**
|
|
28
|
+
* Set the host UI theme — the ELEVATED `theme:set` action (§8.5). The host
|
|
29
|
+
* applies it and re-pushes the new value to every `theme:read` iframe, so your
|
|
30
|
+
* own {@link useHostTheme} confirms the change (the loop closes with no special
|
|
31
|
+
* case). Only a grant holding `theme:set` (e.g. the theme-toggle system app) may
|
|
32
|
+
* call this; any other app is rejected host-side with a `forbidden`
|
|
33
|
+
* {@link Error} (carrying `.code`), regardless of what the app claims. Update
|
|
34
|
+
* optimistically and let the re-push confirm.
|
|
35
|
+
*/
|
|
36
|
+
declare const setHostTheme: (theme: HostTheme) => Promise<void>;
|
|
37
|
+
|
|
38
|
+
export { type HostTheme, getHostTheme, onHostThemeChange, setHostTheme, useHostTheme };
|
package/dist/theme.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { protocolRequest } from "./sandboxUtils";
|
|
3
|
+
const themeService = () => {
|
|
4
|
+
return module.evaluation.module.bundler.theme;
|
|
5
|
+
};
|
|
6
|
+
const getHostTheme = () => themeService().getTheme();
|
|
7
|
+
const onHostThemeChange = (listener) => {
|
|
8
|
+
const disposable = themeService().onChange(listener);
|
|
9
|
+
return () => disposable.dispose();
|
|
10
|
+
};
|
|
11
|
+
const useHostTheme = () => {
|
|
12
|
+
const [theme, setTheme] = useState(getHostTheme);
|
|
13
|
+
useEffect(() => onHostThemeChange(setTheme), []);
|
|
14
|
+
return theme;
|
|
15
|
+
};
|
|
16
|
+
const setHostTheme = async (theme) => {
|
|
17
|
+
const res = await protocolRequest("theme", "set", [{ theme }]);
|
|
18
|
+
if (!res || res.ok !== true) {
|
|
19
|
+
const err = new Error(res?.message ?? "setHostTheme failed");
|
|
20
|
+
err.code = (res && "code" in res ? res.code : void 0) ?? "unknown";
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
export {
|
|
25
|
+
getHostTheme,
|
|
26
|
+
onHostThemeChange,
|
|
27
|
+
setHostTheme,
|
|
28
|
+
useHostTheme
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/theme.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { protocolRequest } from './sandboxUtils';\n\n/**\n * The host UI theme, mirrored from the immediately.run host window into the\n * sandbox. Your app can read this to render in step with the host chrome\n * (light / dark).\n *\n * This is the baseline `theme:read` capability — every app may read it. Changing\n * the host theme is a separate, elevated action (`theme:set`), available only to\n * the theme-toggle system app.\n */\nexport type HostTheme = 'light' | 'dark';\n\ninterface ThemeService {\n getTheme(): HostTheme;\n onChange(listener: (theme: HostTheme) => void): { dispose(): void };\n}\n\n// `module.evaluation.module.bundler.theme` is the sandbox bundler injected into\n// the evaluation context (same path the other SDK helpers use for `auth`).\nconst themeService = (): ThemeService => {\n // @ts-ignore - injected by the sandbox runtime\n return module.evaluation.module.bundler.theme;\n};\n\n/**\n * Returns the current host theme. Poll this for a one-off read; use\n * {@link onHostThemeChange} or {@link useHostTheme} to react to changes.\n */\nexport const getHostTheme = (): HostTheme => themeService().getTheme();\n\n/**\n * Subscribe to host theme changes. The listener is invoked immediately with the\n * current theme, then again on every change. Returns an unsubscribe fn.\n */\nexport const onHostThemeChange = (\n listener: (theme: HostTheme) => void,\n): (() => void) => {\n const disposable = themeService().onChange(listener);\n return () => disposable.dispose();\n};\n\n/**\n * React hook returning the current host theme, re-rendering when it changes.\n * The recommended way to implement an app's own `useTheme`: follow the host,\n * allow a local override.\n */\nexport const useHostTheme = (): HostTheme => {\n const [theme, setTheme] = useState<HostTheme>(getHostTheme);\n useEffect(() => onHostThemeChange(setTheme), []);\n return theme;\n};\n\n/**\n * Set the host UI theme — the ELEVATED `theme:set` action (§8.5). The host\n * applies it and re-pushes the new value to every `theme:read` iframe, so your\n * own {@link useHostTheme} confirms the change (the loop closes with no special\n * case). Only a grant holding `theme:set` (e.g. the theme-toggle system app) may\n * call this; any other app is rejected host-side with a `forbidden`\n * {@link Error} (carrying `.code`), regardless of what the app claims. Update\n * optimistically and let the re-push confirm.\n */\nexport const setHostTheme = async (theme: HostTheme): Promise<void> => {\n const res = (await protocolRequest('theme', 'set', [{ theme }])) as\n | { ok: true; data?: unknown }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'setHostTheme failed') as Error & {\n code?: string;\n };\n err.code = (res && 'code' in res ? res.code : undefined) ?? 'unknown';\n throw err;\n }\n};\n"],"mappings":"AAAA,SAAS,WAAW,gBAAgB;AACpC,SAAS,uBAAuB;AAoBhC,MAAM,eAAe,MAAoB;AAEvC,SAAO,OAAO,WAAW,OAAO,QAAQ;AAC1C;AAMO,MAAM,eAAe,MAAiB,aAAa,EAAE,SAAS;AAM9D,MAAM,oBAAoB,CAC/B,aACiB;AACjB,QAAM,aAAa,aAAa,EAAE,SAAS,QAAQ;AACnD,SAAO,MAAM,WAAW,QAAQ;AAClC;AAOO,MAAM,eAAe,MAAiB;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAoB,YAAY;AAC1D,YAAU,MAAM,kBAAkB,QAAQ,GAAG,CAAC,CAAC;AAC/C,SAAO;AACT;AAWO,MAAM,eAAe,OAAO,UAAoC;AACrE,QAAM,MAAO,MAAM,gBAAgB,SAAS,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;AAI9D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,qBAAqB;AAG3D,QAAI,QAAQ,OAAO,UAAU,MAAM,IAAI,OAAO,WAAc;AAC5D,UAAM;AAAA,EACR;AACF;","names":[]}
|
package/package.json
CHANGED