fix(vscode): skip unsupported OpenChamber event stream
Shared session-list subscriptions opened server-only event and URL-token routes against the webview origin, causing repeated 403 responses. Skip the control-stream subscription in VS Code while preserving OpenCode bridge sync and polling. Validated with regression tests, UI type-check and lint, and the VS Code webview build. The reported gray-screen crash remains unconfirmed pending user logs.
This commit is contained in:
@@ -1,12 +1,4 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test';
|
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
||||||
|
|
||||||
mock.module('./runtime-url', () => ({
|
|
||||||
getRuntimeUrlResolver: () => ({ sse: (path: string) => `http://runtime.test${path}` }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
mock.module('./runtime-switch', () => ({
|
|
||||||
subscribeRuntimeEndpointChanged: () => () => undefined,
|
|
||||||
}));
|
|
||||||
|
|
||||||
class MockEventSource {
|
class MockEventSource {
|
||||||
static CLOSED = 2;
|
static CLOSED = 2;
|
||||||
@@ -29,20 +21,37 @@ class MockEventSource {
|
|||||||
describe('openchamber events', () => {
|
describe('openchamber events', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
MockEventSource.instances = [];
|
MockEventSource.instances = [];
|
||||||
globalThis.window = {} as Window & typeof globalThis;
|
Object.defineProperty(globalThis, 'window', {
|
||||||
globalThis.EventSource = MockEventSource as unknown as typeof EventSource;
|
value: Object.assign(new EventTarget(), { location: new URL('http://runtime.test') }),
|
||||||
|
configurable: true,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
Object.defineProperty(globalThis, 'EventSource', { value: MockEventSource, configurable: true, writable: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
delete (globalThis as { window?: unknown }).window;
|
Reflect.deleteProperty(globalThis, 'window');
|
||||||
delete (globalThis as { EventSource?: unknown }).EventSource;
|
Reflect.deleteProperty(globalThis, 'EventSource');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not open the server-only event stream in VS Code', async () => {
|
||||||
|
Object.defineProperty(window, '__VSCODE_CONFIG__', {
|
||||||
|
value: { workspaceFolder: 'C:/repo', workspaceFolders: [] },
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
const { subscribeOpenchamberEvents } = await import('./openchamberEvents');
|
||||||
|
const unsubscribe = subscribeOpenchamberEvents(() => undefined);
|
||||||
|
try {
|
||||||
|
expect(MockEventSource.instances).toHaveLength(0);
|
||||||
|
} finally {
|
||||||
|
unsubscribe();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('dispatches externally created session events', async () => {
|
test('dispatches externally created session events', async () => {
|
||||||
const { subscribeOpenchamberEvents } = await import('./openchamberEvents');
|
const { subscribeOpenchamberEvents } = await import('./openchamberEvents');
|
||||||
const events: unknown[] = [];
|
const events: unknown[] = [];
|
||||||
const listener = (event: unknown) => events.push(event);
|
const unsubscribe = subscribeOpenchamberEvents((event) => events.push(event));
|
||||||
const unsubscribe = subscribeOpenchamberEvents(listener);
|
|
||||||
const source = MockEventSource.instances[0];
|
const source = MockEventSource.instances[0];
|
||||||
|
|
||||||
source.onmessage?.({
|
source.onmessage?.({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { getRuntimeUrlResolver } from './runtime-url';
|
import { getRuntimeUrlResolver } from './runtime-url';
|
||||||
import { subscribeRuntimeEndpointChanged } from './runtime-switch';
|
import { subscribeRuntimeEndpointChanged } from './runtime-switch';
|
||||||
|
import { isVSCodeRuntime } from './desktop';
|
||||||
|
|
||||||
type ScheduledTaskRanEvent = {
|
type ScheduledTaskRanEvent = {
|
||||||
type: 'scheduled-task-ran';
|
type: 'scheduled-task-ran';
|
||||||
@@ -284,6 +285,10 @@ const cleanupRuntimeChangeSubscription = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const subscribeOpenchamberEvents = (listener: Listener): (() => void) => {
|
export const subscribeOpenchamberEvents = (listener: Listener): (() => void) => {
|
||||||
|
// VS Code runs OpenCode through its bridge, not the OpenChamber server that
|
||||||
|
// owns this stream. Opening it here retries against vscode-webview:// forever.
|
||||||
|
if (isVSCodeRuntime()) return () => undefined;
|
||||||
|
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
ensureRuntimeChangeSubscription();
|
ensureRuntimeChangeSubscription();
|
||||||
connect();
|
connect();
|
||||||
|
|||||||
@@ -103,6 +103,13 @@ The webview build emits each worker as one self-contained file. VS Code webviews
|
|||||||
|
|
||||||
Message and part ordering is owned by [`packages/ui/src/sync/DOCUMENTATION.md`](../../ui/src/sync/DOCUMENTATION.md#session-message-loading). The VS Code webview consumes that shared sync implementation; bridge and proxy runtimes pass OpenCode records through without adding runtime-specific ordering.
|
Message and part ordering is owned by [`packages/ui/src/sync/DOCUMENTATION.md`](../../ui/src/sync/DOCUMENTATION.md#session-message-loading). The VS Code webview consumes that shared sync implementation; bridge and proxy runtimes pass OpenCode records through without adding runtime-specific ordering.
|
||||||
|
|
||||||
|
The OpenChamber control stream (`/api/openchamber/events`) requires the
|
||||||
|
OpenChamber server, which the extension does not run. `subscribeOpenchamberEvents`
|
||||||
|
therefore returns a no-op subscription in VS Code before resolving URLs or
|
||||||
|
opening a connection. Session sync still uses the OpenCode SSE bridge and
|
||||||
|
global session polling. Sending the control stream to the webview origin caused
|
||||||
|
repeated `403` responses and URL-token requests to `/auth/url-token`.
|
||||||
|
|
||||||
## Extension guideline
|
## Extension guideline
|
||||||
|
|
||||||
When adding new bridge route families:
|
When adding new bridge route families:
|
||||||
|
|||||||
Reference in New Issue
Block a user