Skip to content

Commit b0683e7

Browse files
authored
fix: resolve in-page channel functions across chained registries (#378)
1 parent 2041f07 commit b0683e7

3 files changed

Lines changed: 41 additions & 13 deletions

File tree

packages/devframe/src/in-page-channel/internal.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ const FUNCTION_METHOD_PREFIX = channelMethod('function', '')
174174
*/
175175
export function createLocalFunctionRegistry(codec: InPageChannelSerialization): {
176176
register: (definition: InPageFunctionDefinitionAny) => void
177+
registerInternal: (method: string, handler: (...args: unknown[]) => unknown) => void
177178
on: (name: string, listener: (...args: unknown[]) => void) => () => void
178179
resolve: (name: string) => ((...args: unknown[]) => unknown) | undefined
179180
} {
@@ -183,6 +184,12 @@ export function createLocalFunctionRegistry(codec: InPageChannelSerialization):
183184
register(definition) {
184185
definitions.set(channelMethod(definition.type, definition.name), definition)
185186
},
187+
// The shared-state layer keys its handlers by their own fully-qualified
188+
// wire methods (`devframe:in-page:page-state:*`, the panel-state events),
189+
// so they register verbatim rather than through `channelMethod`.
190+
registerInternal(method, handler) {
191+
definitions.set(method, { name: method, handler })
192+
},
186193
on(name, listener) {
187194
const key = channelMethod('event', name)
188195
let registered = listeners.get(key)
@@ -200,14 +207,8 @@ export function createLocalFunctionRegistry(codec: InPageChannelSerialization):
200207
resolve(name) {
201208
const definition = definitions.get(name)
202209
const registered = listeners.get(name)
203-
if (!definition && !registered?.size) {
204-
if (name.startsWith(FUNCTION_METHOD_PREFIX)) {
205-
return () => {
206-
throw diagnostics.DF0077({ name: name.slice(FUNCTION_METHOD_PREFIX.length) })
207-
}
208-
}
210+
if (!definition && !registered?.size)
209211
return undefined
210-
}
211212
return async (...rawArgs: unknown[]) => {
212213
const args = codec.deserialize ? rawArgs.map(codec.deserialize) : rawArgs
213214
if (definition?.jsonSerializable)
@@ -227,6 +228,31 @@ export function createLocalFunctionRegistry(codec: InPageChannelSerialization):
227228
}
228229
}
229230

231+
type LocalHandler = (...args: unknown[]) => unknown
232+
233+
/**
234+
* Consult each registry in order and, only once none owns the name, fall back
235+
* to the coded "not registered" error for a `function:` call (events stay
236+
* silent). The fallback lives here, after every registry, so chaining a
237+
* state registry ahead of the user registry never masks a real handler.
238+
*/
239+
export function resolveLocalHandler(
240+
name: string,
241+
registries: ((name: string) => LocalHandler | undefined)[],
242+
): LocalHandler | undefined {
243+
for (const registry of registries) {
244+
const handler = registry(name)
245+
if (handler)
246+
return handler
247+
}
248+
if (name.startsWith(FUNCTION_METHOD_PREFIX)) {
249+
return () => {
250+
throw diagnostics.DF0077({ name: name.slice(FUNCTION_METHOD_PREFIX.length) })
251+
}
252+
}
253+
return undefined
254+
}
255+
230256
type RemoteFunctions = Record<string, (...args: any[]) => any>
231257

232258
export interface AttachChannelPortOptions {

packages/devframe/src/in-page-channel/page-script.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
DEFAULT_CALL_TIMEOUT_MS,
1616
deserializeResult,
1717
resolveHeartbeat,
18+
resolveLocalHandler,
1819
serializeArgs,
1920
warnOnce,
2021
withCallDeadline,
@@ -106,10 +107,10 @@ export function createPageScriptChannel<P extends InPageChannelProtocol>(
106107
},
107108
})
108109
const stateRegistry = createLocalFunctionRegistry(codec)
109-
for (const [name, handler] of Object.entries(internal.internalHandlers))
110-
stateRegistry.register({ name, handler })
110+
for (const [method, handler] of Object.entries(internal.internalHandlers))
111+
stateRegistry.registerInternal(method, handler)
111112
internal.attached = attachChannelPort(port, {
112-
resolveLocal: fnName => stateRegistry.resolve(fnName) ?? registry.resolve(fnName),
113+
resolveLocal: fnName => resolveLocalHandler(fnName, [stateRegistry.resolve, registry.resolve]),
113114
onControl: (kind) => {
114115
if (kind === 'ping')
115116
internal.attached.postControl('pong')

packages/devframe/src/in-page-channel/panel.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
deserializeResult,
1717
InPageChannelError,
1818
resolveHeartbeat,
19+
resolveLocalHandler,
1920
serializeArgs,
2021
warnOnce,
2122
withCallDeadline,
@@ -91,8 +92,8 @@ export function connectPanelChannel<P extends InPageChannelProtocol>(
9192
call: (method, args) => enqueueCall(method, serializeArgs(codec, args)),
9293
})
9394
const stateRegistry = createLocalFunctionRegistry(codec)
94-
for (const [name, handler] of Object.entries(stateHost.handlers))
95-
stateRegistry.register({ name, handler })
95+
for (const [method, handler] of Object.entries(stateHost.handlers))
96+
stateRegistry.registerInternal(method, handler)
9697

9798
function sendEventNow(method: string, args: unknown[]): void {
9899
void attached?.rpc.$callRaw({ method, args, event: true, optional: true }).catch(() => {})
@@ -144,7 +145,7 @@ export function connectPanelChannel<P extends InPageChannelProtocol>(
144145
// another instance the user pinned to) replaces the previous port.
145146
attached?.dispose({ bye: true, reason: 'the panel adopted a newer port' })
146147
attached = attachChannelPort(port, {
147-
resolveLocal: fnName => stateRegistry.resolve(fnName) ?? registry.resolve(fnName),
148+
resolveLocal: fnName => resolveLocalHandler(fnName, [stateRegistry.resolve, registry.resolve]),
148149
onControl: (kind) => {
149150
if (kind === 'ping')
150151
attached?.postControl('pong')

0 commit comments

Comments
 (0)