Skip to content

Commit a3bea6c

Browse files
committed
fix: restrict in-page listeners to events
1 parent 5c026a0 commit a3bea6c

3 files changed

Lines changed: 52 additions & 10 deletions

File tree

‎packages/devframe/src/in-page-channel/types.test-d.ts‎

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ interface PageScriptOnlyProtocol {
2121
panel: Record<string, never>
2222
}
2323

24+
interface MixedPanelProtocol {
25+
pageScript: Record<string, never>
26+
panel: {
27+
confirm: (message: string) => boolean
28+
notify: (message: string) => void
29+
}
30+
}
31+
2432
describe('Channel function definitions', () => {
2533
it('distinguishes event, query, and action definitions', () => {
2634
defineChannelFunction({ name: 'notify', type: 'event' })
@@ -177,16 +185,18 @@ describe('In-page script channel', () => {
177185
})
178186

179187
describe('Event checking', () => {
180-
it('types runtime subscriptions to page-script functions', () => {
181-
const unsubscribe = channel.on('echo', (value) => {
188+
it('types runtime subscriptions to page-script events', () => {
189+
const unsubscribe = channel.on('save', (value) => {
182190
expectTypeOf(value).toEqualTypeOf<string>()
183191
})
184192

185193
expectTypeOf(unsubscribe).toEqualTypeOf<() => void>()
186194
// @ts-expect-error Panel functions cannot be handled by the page script.
187195
channel.on('notify', () => {})
188-
// @ts-expect-error `echo` listeners receive a string.
189-
channel.on('echo', (value: number) => void value)
196+
// @ts-expect-error Query functions cannot be handled as events.
197+
channel.on('echo', () => {})
198+
// @ts-expect-error `save` listeners receive a string.
199+
channel.on('save', (value: number) => void value)
190200
})
191201

192202
it('types panel connection events', () => {
@@ -353,6 +363,20 @@ describe('Panel channel', () => {
353363
channel.on('notify', (message: number) => void message)
354364
})
355365

366+
it('rejects runtime subscriptions to panel queries', () => {
367+
const mixedChannel = connectPanelChannel<MixedPanelProtocol>({
368+
name: 'devframes:mixed-panel',
369+
functions: {
370+
confirm: { handler: () => true },
371+
notify: { type: 'event' },
372+
},
373+
})
374+
375+
mixedChannel.on('notify', () => {})
376+
// @ts-expect-error Query functions cannot be handled as events.
377+
mixedChannel.on('confirm', () => {})
378+
})
379+
356380
it('types status events', () => {
357381
const unsubscribe = channel.events.on('status:updated', (status) => {
358382
expectTypeOf(status).toEqualTypeOf<'connecting' | 'connected' | 'closed'>()

‎packages/devframe/src/in-page-channel/types.ts‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@ type SharedStates<P extends InPageChannelProtocol>
3131
type FnArgs<F> = F extends (...args: infer A) => any ? A : never
3232
type FnReturn<F> = F extends (...args: any[]) => infer R ? Awaited<R> : never
3333

34+
/**
35+
* Page-script functions whose resolved return type marks an event.
36+
* @internal
37+
*/
38+
type PageScriptFunctionsEvents<P extends InPageChannelProtocol> = {
39+
[K in keyof PageScriptFunctions<P> as FnReturn<PageScriptFunctions<P>[K]> extends void ? K : never]: PageScriptFunctions<P>[K]
40+
}
41+
42+
/**
43+
* Panel functions whose resolved return type marks an event.
44+
* @internal
45+
*/
46+
type PanelFunctionsEvents<P extends InPageChannelProtocol> = {
47+
[K in keyof PanelFunctions<P> as FnReturn<PanelFunctions<P>[K]> extends void ? K : never]: PanelFunctions<P>[K]
48+
}
49+
3450
/**
3551
* Converts a protocol function to its accepted endpoint handler.
3652
*
@@ -340,9 +356,9 @@ export interface PageScriptChannel<P extends InPageChannelProtocol> {
340356
...args: FnArgs<PanelFunctions<P>[K]>
341357
) => void
342358
/** Subscribe to an event emitted by a panel. Returns an unsubscribe function. */
343-
on: <K extends keyof PageScriptFunctions<P> & string>(
359+
on: <K extends keyof PageScriptFunctionsEvents<P> & string>(
344360
name: K,
345-
listener: (...args: FnArgs<PageScriptFunctions<P>[K]>) => void,
361+
listener: (...args: FnArgs<PageScriptFunctionsEvents<P>[K]>) => void,
346362
) => () => void
347363
/** Page-script-authoritative shared states, replayed to joining panels. */
348364
readonly sharedState: InPageSharedStateHost<P>
@@ -397,9 +413,9 @@ export interface PanelChannel<P extends InPageChannelProtocol> {
397413
...args: FnArgs<PageScriptFunctions<P>[K]>
398414
) => void
399415
/** Subscribe to an event emitted by the page script. Returns an unsubscribe function. */
400-
on: <K extends keyof PanelFunctions<P> & string>(
416+
on: <K extends keyof PanelFunctionsEvents<P> & string>(
401417
name: K,
402-
listener: (...args: FnArgs<PanelFunctions<P>[K]>) => void,
418+
listener: (...args: FnArgs<PanelFunctionsEvents<P>[K]>) => void,
403419
) => () => void
404420
/** Shared states mirrored from the page-script authority. */
405421
readonly sharedState: InPageSharedStateHost<P>

‎tests/__snapshots__/tsnapi/devframe/in-page-channel.snapshot.d.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface PageScriptChannel<P extends InPageChannelProtocol> {
2727
readonly events: Pick<EventEmitter<PageScriptChannelEvents<P>>, 'on' | 'once'>;
2828
emit: <K extends keyof PanelFunctions<P> & string>(_: K, ..._: FnArgs<PanelFunctions<P>[K]>) => void;
2929
callEvent: <K extends keyof PanelFunctions<P> & string>(_: K, ..._: FnArgs<PanelFunctions<P>[K]>) => void;
30-
on: <K extends keyof PageScriptFunctions<P> & string>(_: K, _: (..._: FnArgs<PageScriptFunctions<P>[K]>) => void) => () => void;
30+
on: <K extends keyof PageScriptFunctionsEvents<P> & string>(_: K, _: (..._: FnArgs<PageScriptFunctionsEvents<P>[K]>) => void) => () => void;
3131
readonly sharedState: InPageSharedStateHost<P>;
3232
addPanelPort: (_: MessagePort) => PanelPeer<P>;
3333
close: () => void;
@@ -43,7 +43,7 @@ export interface PanelChannel<P extends InPageChannelProtocol> {
4343
call: <K extends keyof PageScriptFunctions<P> & string>(_: K, ..._: FnArgs<PageScriptFunctions<P>[K]>) => Promise<FnReturn<PageScriptFunctions<P>[K]>>;
4444
emit: <K extends keyof PageScriptFunctions<P> & string>(_: K, ..._: FnArgs<PageScriptFunctions<P>[K]>) => void;
4545
callEvent: <K extends keyof PageScriptFunctions<P> & string>(_: K, ..._: FnArgs<PageScriptFunctions<P>[K]>) => void;
46-
on: <K extends keyof PanelFunctions<P> & string>(_: K, _: (..._: FnArgs<PanelFunctions<P>[K]>) => void) => () => void;
46+
on: <K extends keyof PanelFunctionsEvents<P> & string>(_: K, _: (..._: FnArgs<PanelFunctionsEvents<P>[K]>) => void) => () => void;
4747
readonly sharedState: InPageSharedStateHost<P>;
4848
close: () => void;
4949
}
@@ -117,8 +117,10 @@ interface PageScriptChannelEvents<P extends InPageChannelProtocol> {
117117
'panel:disconnected': (_: PanelPeer<P>) => void;
118118
}
119119
type PageScriptFunctions<P extends InPageChannelProtocol> = SideFunctions<NonNullable<P['pageScript']>>;
120+
type PageScriptFunctionsEvents<P extends InPageChannelProtocol> = { [K in keyof PageScriptFunctions<P> as FnReturn<PageScriptFunctions<P>[K]> extends void ? K : never]: PageScriptFunctions<P>[K]; };
120121
interface PanelChannelEvents {
121122
'status:updated': (_: InPageChannelStatus) => void;
122123
}
123124
type PanelFunctions<P extends InPageChannelProtocol> = SideFunctions<NonNullable<P['panel']>>;
125+
type PanelFunctionsEvents<P extends InPageChannelProtocol> = { [K in keyof PanelFunctions<P> as FnReturn<PanelFunctions<P>[K]> extends void ? K : never]: PanelFunctions<P>[K]; };
124126
// #endregion

0 commit comments

Comments
 (0)