mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 16:09:43 +02:00
Merge pull request #402 from shenlvkang-collab/pr/webview-route-masking
fix(webview): let a proxied single-page app route on its own path, and recover a frame that reloads
This commit is contained in:
@@ -40,6 +40,7 @@ interface AppLike {
|
||||
openWebview(id: string, options?: { path?: string }): Promise<void>;
|
||||
_apiJson(path: string, opts?: { method?: string; body?: unknown }): Promise<unknown>;
|
||||
_updateActiveWebviewTab(): void;
|
||||
_installWebviewLostListener(): void;
|
||||
showToast?: (msg: string, kind: string) => void;
|
||||
}
|
||||
|
||||
@@ -315,3 +316,73 @@ describe('callers consult the hook first', () => {
|
||||
expect(section.indexOf('openLinkThroughWebTabIfLoopback?.(urlLink.href)')).toBeGreaterThan(urlHandler);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Lost-frame recovery: the server's recovery page posts `{type, path}` to the
|
||||
* parent; the tab that owns the frame remounts it inside the prefix at that path.
|
||||
*/
|
||||
describe('lost-frame recovery', () => {
|
||||
const lost = (win: Window, source: unknown, path: unknown) =>
|
||||
win.dispatchEvent(
|
||||
new (win as unknown as { MessageEvent: typeof MessageEvent }).MessageEvent('message', {
|
||||
data: { type: 'codeman:webview-lost', path },
|
||||
source: source as Window,
|
||||
})
|
||||
);
|
||||
const frameOf = (win: Window, id: string) =>
|
||||
win.document.querySelector(`.webview-frame[data-webview-id="${id}"] iframe`) as HTMLIFrameElement;
|
||||
|
||||
it('remounts the frame that sent the message at the path it lost, inside the prefix', async () => {
|
||||
const { win, app } = boot();
|
||||
app._installWebviewLostListener();
|
||||
await app.openWebview('dev');
|
||||
const frame = frameOf(win, 'dev');
|
||||
lost(win, frame.contentWindow, '/about?tab=2#top');
|
||||
await vi.waitFor(() => expect(frameSrc(win, 'dev')).toBe('/webview/cap-dev/about?tab=2#top'));
|
||||
expect(frameOf(win, 'dev')).toBe(frame);
|
||||
});
|
||||
|
||||
it('recovers to the landing page for a bare reload', async () => {
|
||||
const { win, app } = boot();
|
||||
app._installWebviewLostListener();
|
||||
await app.openWebview('dev');
|
||||
await app.openUrlInWebTab('http://localhost:5173/deep');
|
||||
expect(frameSrc(win, 'dev')).toBe('/webview/cap-dev/deep');
|
||||
lost(win, frameOf(win, 'dev').contentWindow, '/');
|
||||
await vi.waitFor(() => expect(frameSrc(win, 'dev')).toBe('/webview/cap-dev/'));
|
||||
});
|
||||
|
||||
it('ignores a message that did not come from one of its frames, or is malformed', async () => {
|
||||
const { win, app, calls } = boot();
|
||||
app._installWebviewLostListener();
|
||||
await app.openWebview('dev');
|
||||
const before = calls.length;
|
||||
lost(win, win, '/elsewhere');
|
||||
lost(win, frameOf(win, 'dev').contentWindow, 42);
|
||||
win.dispatchEvent(
|
||||
new (win as unknown as { MessageEvent: typeof MessageEvent }).MessageEvent('message', {
|
||||
data: 'codeman:webview-lost',
|
||||
source: frameOf(win, 'dev').contentWindow as Window,
|
||||
})
|
||||
);
|
||||
await new Promise((r) => setTimeout(r, 20));
|
||||
expect(calls.length).toBe(before);
|
||||
expect(frameSrc(win, 'dev')).toBe('/webview/cap-dev/');
|
||||
});
|
||||
|
||||
it('never lets the path jump the frame off the proxy, and bounds a reload loop', async () => {
|
||||
const { win, app, calls } = boot();
|
||||
app._installWebviewLostListener();
|
||||
await app.openWebview('dev');
|
||||
lost(win, frameOf(win, 'dev').contentWindow, '//evil.example/x');
|
||||
await vi.waitFor(() => expect(calls.filter((c) => c.path.endsWith('/open')).length).toBe(2));
|
||||
expect(frameSrc(win, 'dev')).toBe('/webview/cap-dev/');
|
||||
|
||||
const opensBefore = calls.filter((c) => c.path.endsWith('/open')).length;
|
||||
for (let i = 0; i < 10; i += 1) lost(win, frameOf(win, 'dev').contentWindow, `/spin-${i}`);
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
const opens = calls.filter((c) => c.path.endsWith('/open')).length - opensBefore;
|
||||
expect(opens).toBeLessThanOrEqual(5);
|
||||
expect(opens).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user