Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions packages/devframe/src/in-page-channel/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ const FUNCTION_METHOD_PREFIX = channelMethod('function', '')
*/
export function createLocalFunctionRegistry(codec: InPageChannelSerialization): {
register: (definition: InPageFunctionDefinitionAny) => void
registerInternal: (method: string, handler: (...args: unknown[]) => unknown) => void
on: (name: string, listener: (...args: unknown[]) => void) => () => void
resolve: (name: string) => ((...args: unknown[]) => unknown) | undefined
} {
Expand All @@ -183,6 +184,12 @@ export function createLocalFunctionRegistry(codec: InPageChannelSerialization):
register(definition) {
definitions.set(channelMethod(definition.type, definition.name), definition)
},
// The shared-state layer keys its handlers by their own fully-qualified
// wire methods (`devframe:in-page:page-state:*`, the panel-state events),
// so they register verbatim rather than through `channelMethod`.
registerInternal(method, handler) {
definitions.set(method, { name: method, handler })
},
on(name, listener) {
const key = channelMethod('event', name)
let registered = listeners.get(key)
Expand All @@ -200,14 +207,8 @@ export function createLocalFunctionRegistry(codec: InPageChannelSerialization):
resolve(name) {
const definition = definitions.get(name)
const registered = listeners.get(name)
if (!definition && !registered?.size) {
if (name.startsWith(FUNCTION_METHOD_PREFIX)) {
return () => {
throw diagnostics.DF0077({ name: name.slice(FUNCTION_METHOD_PREFIX.length) })
}
}
if (!definition && !registered?.size)
return undefined
}
return async (...rawArgs: unknown[]) => {
const args = codec.deserialize ? rawArgs.map(codec.deserialize) : rawArgs
if (definition?.jsonSerializable)
Expand All @@ -227,6 +228,31 @@ export function createLocalFunctionRegistry(codec: InPageChannelSerialization):
}
}

type LocalHandler = (...args: unknown[]) => unknown

/**
* Consult each registry in order and, only once none owns the name, fall back
* to the coded "not registered" error for a `function:` call (events stay
* silent). The fallback lives here, after every registry, so chaining a
* state registry ahead of the user registry never masks a real handler.
*/
export function resolveLocalHandler(
name: string,
registries: ((name: string) => LocalHandler | undefined)[],
): LocalHandler | undefined {
for (const registry of registries) {
const handler = registry(name)
if (handler)
return handler
}
if (name.startsWith(FUNCTION_METHOD_PREFIX)) {
return () => {
throw diagnostics.DF0077({ name: name.slice(FUNCTION_METHOD_PREFIX.length) })
}
}
return undefined
}

type RemoteFunctions = Record<string, (...args: any[]) => any>

export interface AttachChannelPortOptions {
Expand Down
7 changes: 4 additions & 3 deletions packages/devframe/src/in-page-channel/page-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
DEFAULT_CALL_TIMEOUT_MS,
deserializeResult,
resolveHeartbeat,
resolveLocalHandler,
serializeArgs,
warnOnce,
withCallDeadline,
Expand Down Expand Up @@ -106,10 +107,10 @@ export function createPageScriptChannel<P extends InPageChannelProtocol>(
},
})
const stateRegistry = createLocalFunctionRegistry(codec)
for (const [name, handler] of Object.entries(internal.internalHandlers))
stateRegistry.register({ name, handler })
for (const [method, handler] of Object.entries(internal.internalHandlers))
stateRegistry.registerInternal(method, handler)
internal.attached = attachChannelPort(port, {
resolveLocal: fnName => stateRegistry.resolve(fnName) ?? registry.resolve(fnName),
resolveLocal: fnName => resolveLocalHandler(fnName, [stateRegistry.resolve, registry.resolve]),
onControl: (kind) => {
if (kind === 'ping')
internal.attached.postControl('pong')
Expand Down
7 changes: 4 additions & 3 deletions packages/devframe/src/in-page-channel/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
deserializeResult,
InPageChannelError,
resolveHeartbeat,
resolveLocalHandler,
serializeArgs,
warnOnce,
withCallDeadline,
Expand Down Expand Up @@ -91,8 +92,8 @@ export function connectPanelChannel<P extends InPageChannelProtocol>(
call: (method, args) => enqueueCall(method, serializeArgs(codec, args)),
})
const stateRegistry = createLocalFunctionRegistry(codec)
for (const [name, handler] of Object.entries(stateHost.handlers))
stateRegistry.register({ name, handler })
for (const [method, handler] of Object.entries(stateHost.handlers))
stateRegistry.registerInternal(method, handler)

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