Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core: Integrate serverChannel into channel #22940

Merged
merged 23 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
bd812b4
Add an API for sending messages to the serverChannel from manager-api
ndelangen Jun 2, 2023
16bad71
wrap using a Channel class
ndelangen Jun 5, 2023
518c764
Merge branch 'next' into norbert/server-channel-for-addons
ndelangen Jun 5, 2023
91f632e
add serverChannel to FakeProvider
ndelangen Jun 5, 2023
c3ec2bd
Merge branch 'next' into norbert/server-channel-for-addons
ndelangen Jun 6, 2023
3a3f92f
no longer need hack with args being an array sometimes, convert Buffe…
ndelangen Jun 6, 2023
e73539f
rename to `onServer` and `emitServer`, prefix with `experimental_`
ndelangen Jun 6, 2023
7c64ac7
merge next into norbert/server-channel-for-addons
ndelangen Jun 6, 2023
513fe67
Merge branch 'next' into norbert/server-channel-for-addons
ndelangen Jun 6, 2023
4e21dab
Merge branch 'next' into norbert/server-channel-for-addons
ndelangen Jun 6, 2023
5625cac
experiment with universal channel
ndelangen Jun 6, 2023
0fa6639
stop connecting twice, just use main channel for everything, it conne…
ndelangen Jun 6, 2023
348a9b0
cleanup && fix tests
ndelangen Jun 6, 2023
55acaf9
simplify
ndelangen Jun 6, 2023
422703b
fixes
ndelangen Jun 6, 2023
9101c11
Merge pull request #22939 from storybookjs/norbert/experiment-merged-…
ndelangen Jun 6, 2023
860663f
add deprecations & cleanup
ndelangen Jun 6, 2023
7c415f1
more cleanup
ndelangen Jun 6, 2023
782ec33
add context/explanation, add experimental prefix, add same logic to r…
ndelangen Jun 7, 2023
abf05e3
Merge branch 'next' into norbert/server-channel-for-addons
ndelangen Jun 9, 2023
004e6da
Merge branch 'next' into norbert/server-channel-for-addons
ndelangen Jun 9, 2023
9f97490
Merge branch 'next' into norbert/server-channel-for-addons
ndelangen Jun 12, 2023
fd08096
Merge branch 'next' into norbert/server-channel-for-addons
ndelangen Jun 12, 2023
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
1 change: 0 additions & 1 deletion code/builders/builder-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
},
"dependencies": {
"@storybook/channel-postmessage": "7.1.0-alpha.29",
"@storybook/channel-websocket": "7.1.0-alpha.29",
"@storybook/client-logger": "7.1.0-alpha.29",
"@storybook/core-common": "7.1.0-alpha.29",
"@storybook/csf-plugin": "7.1.0-alpha.29",
Expand Down
5 changes: 1 addition & 4 deletions code/builders/builder-vite/src/codegen-set-addon-channel.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
export async function generateAddonSetupCode() {
return `
import { createChannel as createPostMessageChannel } from '@storybook/channel-postmessage';
import { createChannel as createWebSocketChannel } from '@storybook/channel-websocket';
import { addons } from '@storybook/preview-api';

const channel = createPostMessageChannel({ page: 'preview' });
addons.setChannel(channel);
window.__STORYBOOK_ADDONS_CHANNEL__ = channel;

if (window.CONFIG_TYPE === 'DEVELOPMENT'){
const serverChannel = createWebSocketChannel({});
addons.setServerChannel(serverChannel);
window.__STORYBOOK_SERVER_CHANNEL__ = serverChannel;
window.__STORYBOOK_SERVER_CHANNEL__ = channel;
}
`.trim();
}
1 change: 0 additions & 1 deletion code/builders/builder-webpack5/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
"@storybook/addons": "7.1.0-alpha.29",
"@storybook/api": "7.1.0-alpha.29",
"@storybook/channel-postmessage": "7.1.0-alpha.29",
"@storybook/channel-websocket": "7.1.0-alpha.29",
"@storybook/channels": "7.1.0-alpha.29",
"@storybook/client-api": "7.1.0-alpha.29",
"@storybook/client-logger": "7.1.0-alpha.29",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { global } from '@storybook/global';

import { ClientApi, PreviewWeb, addons, composeConfigs } from '@storybook/preview-api';
import { createChannel as createPostMessageChannel } from '@storybook/channel-postmessage';
import { createChannel as createWebSocketChannel } from '@storybook/channel-websocket';

import { importFn } from './{{storiesFilename}}';

Expand All @@ -13,9 +12,7 @@ const channel = createPostMessageChannel({ page: 'preview' });
addons.setChannel(channel);

if (global.CONFIG_TYPE === 'DEVELOPMENT'){
const serverChannel = createWebSocketChannel({});
addons.setServerChannel(serverChannel);
window.__STORYBOOK_SERVER_CHANNEL__ = serverChannel;
window.__STORYBOOK_SERVER_CHANNEL__ = channel;
}

const preview = new PreviewWeb();
Expand Down
1 change: 0 additions & 1 deletion code/frameworks/html-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
"@storybook/addons": "7.1.0-alpha.29",
"@storybook/builder-vite": "7.1.0-alpha.29",
"@storybook/channel-postmessage": "7.1.0-alpha.29",
"@storybook/channel-websocket": "7.1.0-alpha.29",
"@storybook/client-api": "7.1.0-alpha.29",
"@storybook/core-server": "7.1.0-alpha.29",
"@storybook/html": "7.1.0-alpha.29",
Expand Down
1 change: 1 addition & 0 deletions code/lib/channel-postmessage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"prep": "../../../scripts/prepare/bundle.ts"
},
"dependencies": {
"@storybook/channel-websocket": "7.1.0-alpha.29",
"@storybook/channels": "7.1.0-alpha.29",
"@storybook/client-logger": "7.1.0-alpha.29",
"@storybook/core-events": "7.1.0-alpha.29",
Expand Down
16 changes: 13 additions & 3 deletions code/lib/channel-postmessage/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { global } from '@storybook/global';
import * as EVENTS from '@storybook/core-events';
import { Channel } from '@storybook/channels';
import type { ChannelHandler, ChannelEvent, ChannelTransport } from '@storybook/channels';
import { WebsocketTransport } from '@storybook/channel-websocket';
import { logger, pretty } from '@storybook/client-logger';
import { isJSON, parse, stringify } from 'telejson';
import qs from 'qs';
import invariant from 'tiny-invariant';

const { document, location } = global;
const { document, location, CONFIG_TYPE } = global;

interface Config {
page: 'manager' | 'preview';
Expand Down Expand Up @@ -289,8 +290,17 @@ const getEventSourceUrl = (event: MessageEvent) => {
* Creates a channel which communicates with an iframe or child window.
*/
export function createChannel({ page }: Config): Channel {
const transport = new PostmsgTransport({ page });
return new Channel({ transport });
const transports: ChannelTransport[] = [new PostmsgTransport({ page })];

if (CONFIG_TYPE === 'DEVELOPMENT') {
const protocol = window.location.protocol === 'http:' ? 'ws' : 'wss';
const { hostname, port } = window.location;
const channelUrl = `${protocol}://${hostname}:${port}/storybook-server-channel`;

transports.push(new WebsocketTransport({ url: channelUrl, onError: () => {} }));
}

return new Channel({ transports });
}

// backwards compat with builder-vite
Expand Down
1 change: 1 addition & 0 deletions code/lib/channel-postmessage/src/typings.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
declare var CHANNEL_OPTIONS: any;
declare var CONFIG_TYPE: 'DEVELOPMENT' | 'PRODUCTION';
22 changes: 16 additions & 6 deletions code/lib/channel-websocket/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { global } from '@storybook/global';
import { Channel } from '@storybook/channels';
import type { ChannelHandler } from '@storybook/channels';
import type { ChannelHandler, ChannelTransport } from '@storybook/channels';
import { logger } from '@storybook/client-logger';
import { isJSON, parse, stringify } from 'telejson';
import invariant from 'tiny-invariant';

const { CONFIG_TYPE } = global;

const { WebSocket } = global;

type OnError = (message: Event) => void;
Expand Down Expand Up @@ -80,15 +82,23 @@ export function createChannel({
async = false,
onError = (err) => logger.warn(err),
}: CreateChannelArgs) {
let channelUrl = url;
if (!channelUrl) {
const transports: ChannelTransport[] = [];

if (url) {
transports.push(new WebsocketTransport({ url, onError }));
}

const isUrlServerChannel = !!url?.includes('storybook-server-channel');

if (CONFIG_TYPE === 'DEVELOPMENT' && isUrlServerChannel === false) {
const protocol = window.location.protocol === 'http:' ? 'ws' : 'wss';
const { hostname, port } = window.location;
channelUrl = `${protocol}://${hostname}:${port}/storybook-server-channel`;
const channelUrl = `${protocol}://${hostname}:${port}/storybook-server-channel`;

transports.push(new WebsocketTransport({ url: channelUrl, onError: () => {} }));
}

const transport = new WebsocketTransport({ url: channelUrl, onError });
return new Channel({ transport, async });
return new Channel({ transports, async });
}

// backwards compat with builder-vite
Expand Down
1 change: 1 addition & 0 deletions code/lib/channel-websocket/src/typings.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
declare module 'json-fn';
declare var CONFIG_TYPE: 'DEVELOPMENT' | 'PRODUCTION';
11 changes: 7 additions & 4 deletions code/lib/channels/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('Channel', () => {
});

it('should not set transport if not passed as an argument', () => {
channel = new Channel();
channel = new Channel({});
expect(channel.hasTransport).toBeFalsy();
});

Expand All @@ -29,7 +29,7 @@ describe('Channel', () => {
});

it('should set isAsync to false as default value', () => {
channel = new Channel();
channel = new Channel({});
expect(channel.isAsync).toBeFalsy();
});

Expand Down Expand Up @@ -104,8 +104,11 @@ describe('Channel', () => {
listenerOutputData = data;
});
const sendSpy = jest.fn();
// @ts-expect-error (Converted from ts-ignore)
channel.transport.send = sendSpy;
// @ts-expect-error (access private property for testing purposes)
channel.transports.forEach((t) => {
// eslint-disable-next-line no-param-reassign
t.send = sendSpy;
});
channel.emit(eventName, ...listenerInputData);
expect(listenerOutputData).toEqual(listenerInputData);
expect(sendSpy.mock.calls[0][1]).toEqual({ depth: 1 });
Expand Down
44 changes: 33 additions & 11 deletions code/lib/channels/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,20 @@ interface EventsKeyValue {
[key: string]: Listener[];
}

interface ChannelArgs {
type ChannelArgs = ChannelArgsSingle | ChannelArgsMulti;
interface ChannelArgsSingle {
transport?: ChannelTransport;
async?: boolean;
}
interface ChannelArgsMulti {
transports: ChannelTransport[];
async?: boolean;
}

const isMulti = (args: ChannelArgs): args is ChannelArgsMulti => {
// @ts-expect-error (we guard against this right here)
return args.transports !== undefined;
};

const generateRandomId = () => {
// generates a random 13 character string
Expand All @@ -40,18 +50,30 @@ export class Channel {

private data: Record<string, any> = {};

private readonly transport: ChannelTransport | undefined = undefined;
private readonly transports: ChannelTransport[] = [];

constructor(input: ChannelArgsMulti);
constructor(input: ChannelArgsSingle);
constructor(input: ChannelArgs = {}) {
this.isAsync = input.async || false;

constructor({ transport, async = false }: ChannelArgs = {}) {
this.isAsync = async;
if (transport) {
this.transport = transport;
this.transport.setHandler((event) => this.handleEvent(event));
if (isMulti(input)) {
this.transports = input.transports || [];

this.transports.forEach((t) => {
t.setHandler((event) => this.handleEvent(event));
});
} else {
this.transports = input.transport ? [input.transport] : [];
}

this.transports.forEach((t) => {
t.setHandler((event) => this.handleEvent(event));
});
}

get hasTransport() {
return !!this.transport;
return this.transports.length > 0;
}

addListener(eventName: string, listener: Listener) {
Expand All @@ -67,9 +89,9 @@ export class Channel {
}

const handler = () => {
if (this.transport) {
this.transport.send(event, options);
}
this.transports.forEach((t) => {
t.send(event, options);
});
this.handleEvent(event);
};

Expand Down
1 change: 1 addition & 0 deletions code/lib/core-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"@aw-web-design/x-default-browser": "1.4.88",
"@discoveryjs/json-ext": "^0.5.3",
"@storybook/builder-manager": "7.1.0-alpha.29",
"@storybook/channels": "7.1.0-alpha.29",
"@storybook/core-common": "7.1.0-alpha.29",
"@storybook/core-events": "7.1.0-alpha.29",
"@storybook/csf": "^0.1.0",
Expand Down
5 changes: 4 additions & 1 deletion code/lib/core-server/src/dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ export async function storybookDevServer(options: Options) {
options.presets.apply<CoreConfig>('core'),
]);

const serverChannel = getServerChannel(server);
const serverChannel = await options.presets.apply(
'experimental_serverChannel',
getServerChannel(server)
);

let indexError: Error;
// try get index generator, if failed, send telemetry without storyCount, then rethrow the error
Expand Down
44 changes: 34 additions & 10 deletions code/lib/core-server/src/utils/get-server-channel.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,56 @@
import WebSocket, { WebSocketServer } from 'ws';
import { stringify } from 'telejson';
import { isJSON, parse, stringify } from 'telejson';
import type { ChannelHandler } from '@storybook/channels';
import { Channel } from '@storybook/channels';

type Server = ConstructorParameters<typeof WebSocketServer>[0]['server'];

export class ServerChannel {
webSocketServer: WebSocketServer;
/**
* This class represents a channel transport that allows for a one-to-many relationship between the server and clients.
* Unlike other channels such as the postmessage and websocket channel implementations, this channel will receive from many clients and any events emitted will be sent out to all connected clients.
*/
export class ServerChannelTransport {
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
private socket: WebSocketServer;

private handler?: ChannelHandler;

constructor(server: Server) {
this.webSocketServer = new WebSocketServer({ noServer: true });
this.socket = new WebSocketServer({ noServer: true });

server.on('upgrade', (request, socket, head) => {
if (request.url === '/storybook-server-channel') {
this.webSocketServer.handleUpgrade(request, socket, head, (ws) => {
this.webSocketServer.emit('connection', ws, request);
this.socket.handleUpgrade(request, socket, head, (ws) => {
this.socket.emit('connection', ws, request);
});
}
});
this.socket.on('connection', (wss) => {
wss.on('message', (raw) => {
const data = raw.toString();
const event = typeof data === 'string' && isJSON(data) ? parse(data) : data;
this.handler(event);
});
});
}

setHandler(handler: ChannelHandler) {
this.handler = handler;
}

emit(type: string, args: any = []) {
const event = { type, args };
send(event: any) {
const data = stringify(event, { maxDepth: 15, allowFunction: true });
Array.from(this.webSocketServer.clients)

Array.from(this.socket.clients)
.filter((c) => c.readyState === WebSocket.OPEN)
.forEach((client) => client.send(data));
}
}

export function getServerChannel(server: Server) {
return new ServerChannel(server);
const transports = [new ServerChannelTransport(server)];

return new Channel({ transports, async: true });
}

// for backwards compatibility
export type ServerChannel = ReturnType<typeof getServerChannel>;
12 changes: 12 additions & 0 deletions code/lib/manager-api/src/lib/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export class AddonStore {

private channel: Channel | undefined;

/**
* @deprecated will be removed in 8.0
*/
private serverChannel: Channel | undefined;

private promise: any;
Expand All @@ -51,6 +54,9 @@ export class AddonStore {
return this.channel;
};

/**
* @deprecated will be removed in 8.0, use getChannel instead
*/
getServerChannel = (): Channel => {
if (!this.serverChannel) {
throw new Error('Accessing non-existent serverChannel');
Expand All @@ -63,13 +69,19 @@ export class AddonStore {

hasChannel = (): boolean => !!this.channel;

/**
* @deprecated will be removed in 8.0, please use the normal channel instead
*/
hasServerChannel = (): boolean => !!this.serverChannel;

setChannel = (channel: Channel): void => {
this.channel = channel;
this.resolve();
};

/**
* @deprecated will be removed in 8.0, please use the normal channel instead
*/
setServerChannel = (channel: Channel): void => {
this.serverChannel = channel;
};
Expand Down
1 change: 0 additions & 1 deletion code/lib/manager-api/src/modules/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export const init: ModuleFn<SubAPI, SubState> = ({ provider }) => {
}
provider.channel.emit(type, data, ...args);
},

collapseAll: () => {
provider.channel.emit(STORIES_COLLAPSE_ALL, {});
},
Expand Down
Loading