feat(plugin): add shared stream bridge
This commit is contained in:
3 files changed
+103
No files matched your search
@@ -325,6 +325,7 @@ describe("PluginHostBridge", () => {
|
||||
|
||||
const closed = pluginSandboxDocument("<html><head></head><body></body></html>", ["host.events"]);
|
||||
expect(closed).toContain("connect-src 'none';");
|
||||
expect(closed).toContain("host.stream.chunk");
|
||||
});
|
||||
|
||||
it("sends the theme in init and pushes theme updates through env messages", () => {
|
||||
|
||||
@@ -342,6 +342,56 @@ function pluginSdkSource(): string {
|
||||
for (const byte of bytes) binary += String.fromCharCode(byte);
|
||||
return btoa(binary);
|
||||
};
|
||||
const stream = async (method, params = {}, options = {}) => {
|
||||
const streamId = options.streamId || (globalThis.crypto?.randomUUID?.() || 'stream-' + Date.now() + '-' + (++sequence));
|
||||
const closeMethod = options.closeMethod || 'filesystem/stream/close';
|
||||
let removeListener;
|
||||
let closeRequested = false;
|
||||
let resolveOpen;
|
||||
let rejectOpen;
|
||||
const metadata = {};
|
||||
const opened = new Promise((resolve, reject) => { resolveOpen = resolve; rejectOpen = reject; });
|
||||
const readable = new ReadableStream({
|
||||
start(controller) {
|
||||
const onEvent = (message) => {
|
||||
if (message?.method !== 'host.stream.chunk' && message?.method !== 'host.stream.end' && message?.method !== 'host.stream.error') return;
|
||||
const event = message.params || {};
|
||||
if (event.streamId !== streamId) return;
|
||||
if (message.method === 'host.stream.chunk') {
|
||||
try { controller.enqueue(decode(event.dataBase64 || '')); } catch (error) { controller.error(error); }
|
||||
return;
|
||||
}
|
||||
removeListener?.();
|
||||
removeListener = undefined;
|
||||
if (message.method === 'host.stream.error') {
|
||||
const error = new Error(event.message || 'Plugin stream failed');
|
||||
rejectOpen(error);
|
||||
controller.error(error);
|
||||
} else {
|
||||
Object.assign(metadata, event);
|
||||
controller.close();
|
||||
}
|
||||
};
|
||||
removeListener = () => listeners.event.delete(onEvent);
|
||||
listeners.event.add(onEvent);
|
||||
request('backend.invoke', { method, params: { ...(params || {}), streamId }, timeoutMs: options.timeoutMs }).then(resolveOpen, (error) => {
|
||||
removeListener?.();
|
||||
removeListener = undefined;
|
||||
rejectOpen(error);
|
||||
controller.error(error);
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
removeListener?.();
|
||||
removeListener = undefined;
|
||||
if (closeRequested) return undefined;
|
||||
closeRequested = true;
|
||||
return request('backend.invoke', { method: closeMethod, params: { streamId } }).catch(() => undefined);
|
||||
},
|
||||
});
|
||||
Object.assign(metadata, await opened);
|
||||
return { stream: readable, metadata };
|
||||
};
|
||||
window.dbxPlugin = Object.freeze({
|
||||
ready,
|
||||
get context() { return context; },
|
||||
@@ -349,6 +399,7 @@ function pluginSdkSource(): string {
|
||||
get theme() { return theme; },
|
||||
request,
|
||||
invoke: (method, params, options = {}) => request('backend.invoke', { method, params, timeoutMs: options.timeoutMs }),
|
||||
stream,
|
||||
notify: (method, params) => request('backend.notify', { method, params }),
|
||||
sendBinary: (channel, data) => {
|
||||
if (typeof data === 'string') return request('backend.sendBinary', { channel, dataBase64: data });
|
||||
|
||||
Reference in new issue
Block a user