From b49cf9275a08c468ca44fc1ca0e79ae364062ac5 Mon Sep 17 00:00:00 2001 From: Neko Ayaka Date: Sat, 7 Feb 2026 03:31:05 +0800 Subject: [PATCH] fix(plugin-sdk,server-*): type mismatch --- apps/stage-tamagotchi/electron.vite.config.ts | 5 ++ .../testdata/test-no-connect-plugin.ts | 2 +- packages/server-runtime/src/index.ts | 47 +++++++++++++++++-- .../src/middlewares/route.test.ts | 4 +- .../server-runtime/src/middlewares/route.ts | 17 +++++-- .../src/middlewares/route/match-expression.ts | 32 ++++++++----- packages/server-sdk/src/client.ts | 2 + .../src/types/websocket/events.ts | 10 ++++ packages/stage-ui/src/utils/event-source.ts | 5 +- .../src/background/client.ts | 9 ++-- 10 files changed, 106 insertions(+), 27 deletions(-) diff --git a/apps/stage-tamagotchi/electron.vite.config.ts b/apps/stage-tamagotchi/electron.vite.config.ts index 2a4323f95..105201c76 100644 --- a/apps/stage-tamagotchi/electron.vite.config.ts +++ b/apps/stage-tamagotchi/electron.vite.config.ts @@ -38,6 +38,7 @@ export default defineConfig({ // Thanks to [@Maqsyo](https://github.com/Maqsyo) // https://github.com/alex8088/electron-vite/issues/99#issuecomment-1862671727 base: './', + build: { rolldownOptions: { input: { @@ -46,6 +47,7 @@ export default defineConfig({ }, }, }, + optimizeDeps: { exclude: [ // Internal Packages @@ -74,6 +76,7 @@ export default defineConfig({ '@framework/model/cubismmoc', ], }, + resolve: { alias: { '@proj-airi/server-sdk': resolve(join(import.meta.dirname, '..', '..', 'packages', 'server-sdk', 'src')), @@ -83,6 +86,7 @@ export default defineConfig({ '@proj-airi/stage-shared': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-shared', 'src')), }, }, + server: { warmup: { clientFiles: [ @@ -91,6 +95,7 @@ export default defineConfig({ ], }, }, + worker: { format: 'es', rollupOptions: { diff --git a/packages/plugin-sdk/src/plugin-host/testdata/test-no-connect-plugin.ts b/packages/plugin-sdk/src/plugin-host/testdata/test-no-connect-plugin.ts index 24d2f862c..dfe9f1140 100644 --- a/packages/plugin-sdk/src/plugin-host/testdata/test-no-connect-plugin.ts +++ b/packages/plugin-sdk/src/plugin-host/testdata/test-no-connect-plugin.ts @@ -1,4 +1,4 @@ -import type { ContextInit } from '../../apis/plugin/shared' +import type { ContextInit } from '../../plugin/shared' export async function init(_initContext: ContextInit) { return false diff --git a/packages/server-runtime/src/index.ts b/packages/server-runtime/src/index.ts index c0487b622..25a2c5267 100644 --- a/packages/server-runtime/src/index.ts +++ b/packages/server-runtime/src/index.ts @@ -23,16 +23,19 @@ import { matchesDestinations, } from './middlewares' -function createServerEventMetadata(serverInstanceId: string, parentId?: string) { +function createServerEventMetadata(serverInstanceId: string, parentId?: string): { source: MetadataEventSource, event: { id: string, parentId?: string } } { return { event: { id: nanoid(), parentId, }, source: { - plugin: WebSocketEventSource.Server, - instanceId: serverInstanceId, - version: packageJSON.version, + kind: 'plugin', + plugin: { + id: WebSocketEventSource.Server, + version: packageJSON.version, + }, + id: serverInstanceId, }, } } @@ -160,6 +163,24 @@ export function setupApp(options?: { } } + function listKnownModules() { + return Array.from(peers.values()) + .filter(peerInfo => peerInfo.name && peerInfo.identity) + .map(peerInfo => ({ + name: peerInfo.name, + index: peerInfo.index, + identity: peerInfo.identity!, + })) + } + + function sendRegistrySync(peer: Peer, parentId?: string) { + send(peer, { + type: 'registry:modules:sync', + data: { modules: listKnownModules() }, + metadata: createServerEventMetadata(instanceId, parentId), + }) + } + app.get('/ws', defineWebSocketHandler({ open: (peer) => { if (authToken) { @@ -168,6 +189,7 @@ export function setupApp(options?: { else { peer.send(RESPONSES.authenticated) peers.set(peer.id, { peer, authenticated: true, name: '', lastHeartbeatAt: Date.now() }) + sendRegistrySync(peer) } logger.withFields({ peer: peer.id, activePeers: peers.size }).log('connected') @@ -228,6 +250,8 @@ export function setupApp(options?: { p.authenticated = true } + sendRegistrySync(peer, event.metadata?.event.id) + return } @@ -253,6 +277,11 @@ export function setupApp(options?: { return } } + if (!identity || identity.kind !== 'plugin' || !identity.plugin?.id) { + send(peer, RESPONSES.error('module identity must include kind=plugin and a plugin id for event \'module:announce\'', instanceId)) + + return + } if (authToken && !p.authenticated) { send(peer, RESPONSES.error('must authenticate before announcing', instanceId)) @@ -271,7 +300,15 @@ export function setupApp(options?: { } case 'ui:configure': { - const { moduleName, moduleIndex, config } = event.data + const data = event.data as { + moduleName?: string + moduleIndex?: number + identity?: MetadataEventSource + config?: Record + } + const moduleName = data.moduleName ?? data.identity?.plugin?.id ?? '' + const moduleIndex = data.moduleIndex + const config = data.config if (moduleName === '') { send(peer, RESPONSES.error('the field \'moduleName\' can\'t be empty for event \'ui:configure\'', instanceId)) diff --git a/packages/server-runtime/src/middlewares/route.test.ts b/packages/server-runtime/src/middlewares/route.test.ts index 7ef6a8eaf..b8defad44 100644 --- a/packages/server-runtime/src/middlewares/route.test.ts +++ b/packages/server-runtime/src/middlewares/route.test.ts @@ -19,7 +19,7 @@ function createPeer(options: { authenticated: true, name: options.name, identity: options.plugin && options.instanceId - ? { plugin: options.plugin, instanceId: options.instanceId, labels: options.labels } + ? { kind: 'plugin', plugin: { id: options.plugin }, id: options.instanceId, labels: options.labels } : undefined, } } @@ -39,7 +39,7 @@ function createSparkNotifyEvent(overrides: Partial RouteDecision | void +function getPeerLabels(peer: AuthenticatedPeer) { + return { + ...peer.identity?.plugin?.labels, + ...peer.identity?.labels, + } +} + export function isDevtoolsPeer(peer: AuthenticatedPeer) { - const devtoolsLabel = peer.identity?.labels?.devtools + const devtoolsLabel = getPeerLabels(peer).devtools const isDevtoolsLabel = devtoolsLabel === 'true' || devtoolsLabel === '1' return Boolean(isDevtoolsLabel || peer.name.includes('devtools')) } export function peerMatchesPolicy(peer: AuthenticatedPeer, policy: RoutingPolicy) { - if (policy.allowPlugins?.length && !policy.allowPlugins.includes(peer.identity?.plugin ?? '')) { + const pluginId = peer.identity?.plugin?.id ?? '' + + if (policy.allowPlugins?.length && !policy.allowPlugins.includes(pluginId)) { return false } - if (policy.denyPlugins?.length && policy.denyPlugins.includes(peer.identity?.plugin ?? '')) { + if (policy.denyPlugins?.length && policy.denyPlugins.includes(pluginId)) { return false } - const labels = peer.identity?.labels ?? {} + const labels = getPeerLabels(peer) if (policy.allowLabels?.length && !matchesLabelSelectors(policy.allowLabels, labels)) { return false } diff --git a/packages/server-runtime/src/middlewares/route/match-expression.ts b/packages/server-runtime/src/middlewares/route/match-expression.ts index 491324e7b..a1d6201fb 100644 --- a/packages/server-runtime/src/middlewares/route/match-expression.ts +++ b/packages/server-runtime/src/middlewares/route/match-expression.ts @@ -33,6 +33,13 @@ export function matchesLabelSelectors(selectors: string[], labels: Record matchesLabelSelector(selector, labels)) } +function getPeerLabels(peer: AuthenticatedPeer) { + return { + ...peer.identity?.plugin?.labels, + ...peer.identity?.labels, + } +} + export function matchesRouteExpression(expression: RouteTargetExpression, peer: AuthenticatedPeer) { switch (expression.type) { case 'and': @@ -40,9 +47,10 @@ export function matchesRouteExpression(expression: RouteTargetExpression, peer: case 'or': return expression.any.some(expr => matchesRouteExpression(expr, peer)) case 'glob': { + const pluginId = peer.identity?.plugin?.id const matched = matchesGlob(expression.glob, peer.name) - || matchesGlob(expression.glob, peer.identity?.plugin) - || matchesGlob(expression.glob, peer.identity?.instanceId) + || matchesGlob(expression.glob, pluginId) + || matchesGlob(expression.glob, peer.identity?.id) return expression.inverted ? !matched : matched } @@ -51,15 +59,15 @@ export function matchesRouteExpression(expression: RouteTargetExpression, peer: return expression.inverted ? !matched : matched } case 'plugin': { - const matched = expression.plugins.includes(peer.identity?.plugin ?? '') + const matched = expression.plugins.includes(peer.identity?.plugin?.id ?? '') return expression.inverted ? !matched : matched } case 'instance': { - const matched = expression.instances.includes(peer.identity?.instanceId ?? '') + const matched = expression.instances.includes(peer.identity?.id ?? '') return expression.inverted ? !matched : matched } case 'label': { - const matched = matchesLabelSelectors(expression.selectors, peer.identity?.labels ?? {}) + const matched = matchesLabelSelectors(expression.selectors, getPeerLabels(peer)) return expression.inverted ? !matched : matched } case 'module': { @@ -89,21 +97,23 @@ export function matchesDestination(destination: string | RouteTargetExpression, switch (prefix) { case 'plugin': - return peer.identity?.plugin === value + return peer.identity?.plugin?.id === value case 'instance': - return peer.identity?.instanceId === value + return peer.identity?.id === value case 'label': - return matchesLabelSelectors([value], peer.identity?.labels ?? {}) + return matchesLabelSelectors([value], getPeerLabels(peer)) case 'peer': return peer.peer.id === value case 'module': return peer.name === value case 'source': return peer.name === value - default: + default: { + const pluginId = peer.identity?.plugin?.id return matchesGlob(destination, peer.name) - || matchesGlob(destination, peer.identity?.plugin) - || matchesGlob(destination, peer.identity?.instanceId) + || matchesGlob(destination, pluginId) + || matchesGlob(destination, peer.identity?.id) + } } } diff --git a/packages/server-sdk/src/client.ts b/packages/server-sdk/src/client.ts index 9fa68da87..9e9afeaba 100644 --- a/packages/server-sdk/src/client.ts +++ b/packages/server-sdk/src/client.ts @@ -74,6 +74,8 @@ export class Client { onAnyMessage: () => {}, onAnySend: () => {}, possibleEvents: [], + dependencies: [], + configSchema: undefined, onError: () => {}, onClose: () => {}, autoConnect: true, diff --git a/packages/server-shared/src/types/websocket/events.ts b/packages/server-shared/src/types/websocket/events.ts index ecb3dea17..231e4a64e 100644 --- a/packages/server-shared/src/types/websocket/events.ts +++ b/packages/server-shared/src/types/websocket/events.ts @@ -708,6 +708,10 @@ interface ModuleStatusChangeEvent { details?: Record } +interface ModuleConfigureEvent { + config: C | Record +} + interface UiConfigureEvent { moduleName: string moduleIndex?: number @@ -845,6 +849,8 @@ export const moduleContributeCapabilityActivated = defineEventa('module:status:change') +export const moduleConfigure = defineEventa('module:configure') + export const uiConfigure = defineEventa('ui:configure') export const inputText = defineEventa('input:text') @@ -953,6 +959,10 @@ export interface WebSocketEvents { * Request a phase transition (module → host). */ 'module:status:change': ModuleStatusChangeEvent + /** + * Push configuration down to module (host → module). + */ + 'module:configure': ModuleConfigureEvent 'ui:configure': UiConfigureEvent diff --git a/packages/stage-ui/src/utils/event-source.ts b/packages/stage-ui/src/utils/event-source.ts index cc59a2eea..b6190877a 100644 --- a/packages/stage-ui/src/utils/event-source.ts +++ b/packages/stage-ui/src/utils/event-source.ts @@ -10,7 +10,10 @@ function formatMetadataSource(source?: MetadataEventSource) { if (!source?.plugin) return undefined - return source.instanceId ? `${source.plugin}:${source.instanceId}` : source.plugin + const pluginId = source.plugin.id + const instanceId = source.id + + return instanceId ? `${pluginId}:${instanceId}` : pluginId } export function getEventSourceKey(event: EventSourcePayload, fallback = 'unknown') { diff --git a/plugins/airi-plugin-web-extension/src/background/client.ts b/plugins/airi-plugin-web-extension/src/background/client.ts index 3cd137ccf..d7d99e3d0 100644 --- a/plugins/airi-plugin-web-extension/src/background/client.ts +++ b/plugins/airi-plugin-web-extension/src/background/client.ts @@ -28,9 +28,12 @@ export function createClientState(): ClientState { function createIdentity() { return { - plugin: PLUGIN_NAME, - instanceId: nanoid(), - version: typeof packageJSON.version === 'string' ? packageJSON.version : undefined, + kind: 'plugin', + plugin: { + id: PLUGIN_NAME, + version: typeof packageJSON.version === 'string' ? packageJSON.version : undefined, + }, + id: nanoid(), labels: { runtime: 'web-extension', },