diff --git a/packages/server-runtime/src/middlewares/route.test.ts b/packages/server-runtime/src/middlewares/route.test.ts index 92d13c9ca0..5446f8442e 100644 --- a/packages/server-runtime/src/middlewares/route.test.ts +++ b/packages/server-runtime/src/middlewares/route.test.ts @@ -99,11 +99,11 @@ describe('route middleware', () => { expect(collectDestinations(event)).toEqual(['label:env=prod']) }) - it('respects explicit empty destinations as an override', () => { + it('treats an explicit empty route destination list as the override', () => { const event = createSparkNotifyEvent({ data: { - id: 'evt-3', - eventId: 'spark-3', + id: 'evt-override', + eventId: 'spark-override', kind: 'ping', urgency: 'soon', headline: 'hello', @@ -115,22 +115,36 @@ describe('route middleware', () => { expect(collectDestinations(event)).toEqual([]) }) - it('treats an explicit empty route destination list as the override', () => { + it('treats an explicit empty data destination list as the override', () => { const event = createSparkNotifyEvent({ data: { - id: 'evt-override', - eventId: 'spark-override', + id: 'evt-data-empty', + eventId: 'spark-data-empty', kind: 'ping', urgency: 'soon', headline: 'hello', - destinations: ['module:character'], + destinations: [], }, - route: { destinations: [] }, + route: undefined, }) expect(collectDestinations(event)).toEqual([]) }) + it('ignores primitive data payloads when checking destinations', () => { + const event = { + type: 'spark:notify', + data: 'not-an-object', + metadata: { + source: { kind: 'plugin', plugin: { id: 'server-runtime' }, id: 'test' }, + event: { id: 'evt-primitive' }, + }, + route: undefined, + } as unknown as WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify'], any> + + expect(collectDestinations(event)).toBeUndefined() + }) + it('matches destinations by label selector', () => { const peer = createPeer({ id: 'peer-2', diff --git a/packages/server-runtime/src/middlewares/route.ts b/packages/server-runtime/src/middlewares/route.ts index e0d671f114..76235e3328 100644 --- a/packages/server-runtime/src/middlewares/route.ts +++ b/packages/server-runtime/src/middlewares/route.ts @@ -25,6 +25,8 @@ export interface RouteContext { export type RouteMiddleware = (context: RouteContext) => RouteDecision | void +type DestinationList = Array + function getPeerLabels(peer: AuthenticatedPeer) { return { ...peer.identity?.plugin?.labels, @@ -118,25 +120,29 @@ export function createPolicyMiddleware(policy: RoutingPolicy): RouteMiddleware { } /** - * Resolves the destinations attached to an event. + * Collects explicit route destinations from the route envelope or event payload. * * Use when: - * - Route-level destinations should override payload-level destinations - * - Delivery logic needs to distinguish between "broadcast" and "explicitly send nowhere" + * - Routing middleware needs the effective destination override for a websocket event + * - Callers must preserve explicit empty destination lists instead of falling back to broadcast * * Expects: - * - An explicit empty `route.destinations` array is a meaningful override + * - A websocket event whose `route.destinations` or `data.destinations` may be present + * - `data.destinations` is only treated as valid when it is an array-shaped override * * Returns: - * - The route destinations, payload destinations, or `undefined` when the event is unrestricted + * - The explicit destination list when present + * - `undefined` when no destination override was provided */ -export function collectDestinations(event: WebSocketEvent | (Omit & Partial>)) { +export function collectDestinations( + event: WebSocketEvent | (Omit & Partial>), +): DestinationList | undefined { if (event.route && 'destinations' in event.route) { return event.route.destinations } - const data = event.data as { destinations?: Array } | undefined - if (data?.destinations?.length) { + const data = event.data as unknown + if (typeof data === 'object' && data !== null && 'destinations' in data && Array.isArray(data.destinations)) { return data.destinations } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 74d51ff216..c2c13388e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6243,6 +6243,7 @@ packages: '@hono/node-ws@1.3.0': resolution: {integrity: sha512-ju25YbbvLuXdqBCmLZLqnNYu1nbHIQjoyUqA8ApZOeL1k4skuiTcw5SW77/5SUYo2Xi2NVBJoVlfQurnKEp03Q==} engines: {node: '>=18.14.1'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: '@hono/node-server': ^1.19.2 hono: ^4.6.0