diff --git a/code/lib/core-server/src/utils/__tests__/server-channel.test.ts b/code/lib/core-server/src/utils/__tests__/server-channel.test.ts new file mode 100644 index 000000000000..786370be0f99 --- /dev/null +++ b/code/lib/core-server/src/utils/__tests__/server-channel.test.ts @@ -0,0 +1,86 @@ +import type { Server } from 'http'; +import { Channel } from '@storybook/channels'; + +import { EventEmitter } from 'events'; +import { stringify } from 'telejson'; +import { getServerChannel, ServerChannelTransport } from '../get-server-channel'; + +describe('getServerChannel', () => { + test('should return a channel', () => { + const server = { on: jest.fn() } as any as Server; + const result = getServerChannel(server); + expect(result).toBeInstanceOf(Channel); + }); + + test('should attach to the http server', () => { + const server = { on: jest.fn() } as any as Server; + getServerChannel(server); + expect(server.on).toHaveBeenCalledWith('upgrade', expect.any(Function)); + }); +}); + +describe('ServerChannelTransport', () => { + test('parses simple JSON', () => { + const server = new EventEmitter() as any as Server; + const socket = new EventEmitter(); + const transport = new ServerChannelTransport(server); + const handler = jest.fn(); + transport.setHandler(handler); + + // @ts-expect-error (an internal API) + transport.socket.emit('connection', socket); + socket.emit('message', '"hello"'); + + expect(handler).toHaveBeenCalledWith('hello'); + }); + test('parses object JSON', () => { + const server = new EventEmitter() as any as Server; + const socket = new EventEmitter(); + const transport = new ServerChannelTransport(server); + const handler = jest.fn(); + transport.setHandler(handler); + + // @ts-expect-error (an internal API) + transport.socket.emit('connection', socket); + socket.emit('message', JSON.stringify({ type: 'hello' })); + + expect(handler).toHaveBeenCalledWith({ type: 'hello' }); + }); + test('supports telejson cyclical data', () => { + const server = new EventEmitter() as any as Server; + const socket = new EventEmitter(); + const transport = new ServerChannelTransport(server); + const handler = jest.fn(); + transport.setHandler(handler); + + // @ts-expect-error (an internal API) + transport.socket.emit('connection', socket); + + const input: any = { a: 1 }; + input.b = input; + socket.emit('message', stringify(input)); + + expect(handler.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "a": 1, + "b": [Circular], + } + `); + }); + test('skips telejson classes and functions in data', () => { + const server = new EventEmitter() as any as Server; + const socket = new EventEmitter(); + const transport = new ServerChannelTransport(server); + const handler = jest.fn(); + transport.setHandler(handler); + + // @ts-expect-error (an internal API) + transport.socket.emit('connection', socket); + + const input = { a() {}, b: class {} }; + socket.emit('message', stringify(input)); + + expect(handler.mock.calls[0][0].a).toEqual(expect.any(String)); + expect(handler.mock.calls[0][0].b).toEqual(expect.any(String)); + }); +}); diff --git a/code/lib/core-server/src/utils/get-server-channel.ts b/code/lib/core-server/src/utils/get-server-channel.ts index c44c51b36307..1a488afb06c9 100644 --- a/code/lib/core-server/src/utils/get-server-channel.ts +++ b/code/lib/core-server/src/utils/get-server-channel.ts @@ -27,7 +27,10 @@ export class ServerChannelTransport { this.socket.on('connection', (wss) => { wss.on('message', (raw) => { const data = raw.toString(); - const event = typeof data === 'string' && isJSON(data) ? parse(data) : data; + const event = + typeof data === 'string' && isJSON(data) + ? parse(data, { allowFunction: false, allowClass: false }) + : data; this.handler?.(event); }); }); @@ -38,7 +41,7 @@ export class ServerChannelTransport { } send(event: any) { - const data = stringify(event, { maxDepth: 15, allowFunction: true }); + const data = stringify(event, { maxDepth: 15, allowFunction: false, allowClass: false }); Array.from(this.socket.clients) .filter((c) => c.readyState === WebSocket.OPEN)