Skip to content

Commit 2148fbb

Browse files
committed
Add websocket callbacks
1 parent 312c6e3 commit 2148fbb

25 files changed

Lines changed: 1965 additions & 17 deletions
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Dash websocket worker
2+
3+
Worker for websocket based callbacks.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "@plotly/dash-websocket-worker",
3+
"version": "1.0.0",
4+
"description": "SharedWorker for WebSocket-based Dash callbacks",
5+
"main": "dist/index.js",
6+
"types": "dist/index.d.ts",
7+
"scripts": {
8+
"build": "webpack --mode production",
9+
"build:dev": "webpack --mode development",
10+
"watch": "webpack --mode development --watch",
11+
"clean": "rm -rf dist"
12+
},
13+
"files": [
14+
"dist"
15+
],
16+
"keywords": [
17+
"dash",
18+
"websocket",
19+
"sharedworker"
20+
],
21+
"author": "Plotly",
22+
"license": "MIT",
23+
"devDependencies": {
24+
"typescript": "^5.0.0",
25+
"webpack": "^5.0.0",
26+
"webpack-cli": "^5.0.0",
27+
"ts-loader": "^9.0.0"
28+
}
29+
}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import {
2+
WorkerMessageType,
3+
WorkerMessage,
4+
CallbackRequestMessage,
5+
GetPropsResponseMessage,
6+
SetPropsMessage,
7+
GetPropsRequestMessage,
8+
CallbackResponseMessage
9+
} from './types';
10+
11+
/**
12+
* Routes messages between renderers (via MessagePorts) and the WebSocket server.
13+
*/
14+
export class MessageRouter {
15+
/** Map of renderer IDs to their MessagePorts */
16+
private renderers: Map<string, MessagePort> = new Map();
17+
18+
/** Callback to send messages to the WebSocket server */
19+
public sendToServer: ((message: unknown) => void) | null = null;
20+
21+
/**
22+
* Register a renderer with its MessagePort.
23+
* @param rendererId Unique identifier for the renderer
24+
* @param port The MessagePort for communication
25+
*/
26+
public registerRenderer(rendererId: string, port: MessagePort): void {
27+
this.renderers.set(rendererId, port);
28+
}
29+
30+
/**
31+
* Unregister a renderer.
32+
* @param rendererId The renderer to unregister
33+
*/
34+
public unregisterRenderer(rendererId: string): void {
35+
this.renderers.delete(rendererId);
36+
}
37+
38+
/**
39+
* Get the number of connected renderers.
40+
*/
41+
public get rendererCount(): number {
42+
return this.renderers.size;
43+
}
44+
45+
/**
46+
* Handle a message from a renderer.
47+
* @param rendererId The ID of the renderer that sent the message
48+
* @param message The message from the renderer
49+
*/
50+
public handleRendererMessage(rendererId: string, message: WorkerMessage): void {
51+
switch (message.type) {
52+
case WorkerMessageType.CALLBACK_REQUEST:
53+
this.forwardCallbackRequest(rendererId, message as CallbackRequestMessage);
54+
break;
55+
56+
case WorkerMessageType.GET_PROPS_RESPONSE:
57+
this.forwardGetPropsResponse(rendererId, message as GetPropsResponseMessage);
58+
break;
59+
60+
default:
61+
console.warn(`Unknown message type from renderer: ${message.type}`);
62+
}
63+
}
64+
65+
/**
66+
* Handle a message from the WebSocket server.
67+
* @param message The message from the server
68+
*/
69+
public handleServerMessage(message: unknown): void {
70+
const msg = message as WorkerMessage;
71+
const rendererId = msg.rendererId;
72+
73+
switch (msg.type) {
74+
case WorkerMessageType.CALLBACK_RESPONSE:
75+
this.forwardToRenderer(rendererId, msg as CallbackResponseMessage);
76+
break;
77+
78+
case WorkerMessageType.SET_PROPS:
79+
this.forwardSetProps(rendererId, msg as SetPropsMessage);
80+
break;
81+
82+
case WorkerMessageType.GET_PROPS_REQUEST:
83+
this.forwardGetPropsRequest(rendererId, msg as GetPropsRequestMessage);
84+
break;
85+
86+
case WorkerMessageType.ERROR:
87+
this.forwardToRenderer(rendererId, msg);
88+
break;
89+
90+
default:
91+
console.warn(`Unknown message type from server: ${msg.type}`);
92+
}
93+
}
94+
95+
/**
96+
* Send a message to all connected renderers.
97+
* @param message The message to broadcast
98+
*/
99+
public broadcastToRenderers(message: WorkerMessage): void {
100+
for (const [, port] of this.renderers) {
101+
port.postMessage(message);
102+
}
103+
}
104+
105+
/**
106+
* Send a connected notification to a specific renderer.
107+
* @param rendererId The renderer to notify
108+
*/
109+
public notifyConnected(rendererId: string): void {
110+
const port = this.renderers.get(rendererId);
111+
if (port) {
112+
port.postMessage({
113+
type: WorkerMessageType.CONNECTED,
114+
rendererId
115+
});
116+
}
117+
}
118+
119+
/**
120+
* Send a disconnected notification to all renderers.
121+
* @param reason Optional reason for disconnection
122+
*/
123+
public notifyDisconnected(reason?: string): void {
124+
this.broadcastToRenderers({
125+
type: WorkerMessageType.DISCONNECTED,
126+
rendererId: '',
127+
payload: { reason }
128+
});
129+
}
130+
131+
/**
132+
* Send an error notification to a specific renderer.
133+
* @param rendererId The renderer to notify
134+
* @param message Error message
135+
* @param code Optional error code
136+
*/
137+
public notifyError(rendererId: string, message: string, code?: string): void {
138+
const port = this.renderers.get(rendererId);
139+
if (port) {
140+
port.postMessage({
141+
type: WorkerMessageType.ERROR,
142+
rendererId,
143+
payload: { message, code }
144+
});
145+
}
146+
}
147+
148+
private forwardCallbackRequest(rendererId: string, message: CallbackRequestMessage): void {
149+
if (this.sendToServer) {
150+
this.sendToServer({
151+
type: WorkerMessageType.CALLBACK_REQUEST,
152+
rendererId,
153+
requestId: message.requestId,
154+
payload: message.payload
155+
});
156+
}
157+
}
158+
159+
private forwardGetPropsResponse(rendererId: string, message: GetPropsResponseMessage): void {
160+
if (this.sendToServer) {
161+
this.sendToServer({
162+
type: WorkerMessageType.GET_PROPS_RESPONSE,
163+
rendererId,
164+
requestId: message.requestId,
165+
payload: message.payload
166+
});
167+
}
168+
}
169+
170+
private forwardToRenderer(rendererId: string, message: WorkerMessage): void {
171+
const port = this.renderers.get(rendererId);
172+
if (port) {
173+
port.postMessage(message);
174+
} else {
175+
console.warn(`Renderer ${rendererId} not found for message`);
176+
}
177+
}
178+
179+
private forwardSetProps(rendererId: string, message: SetPropsMessage): void {
180+
this.forwardToRenderer(rendererId, message);
181+
}
182+
183+
private forwardGetPropsRequest(rendererId: string, message: GetPropsRequestMessage): void {
184+
this.forwardToRenderer(rendererId, message);
185+
}
186+
}

0 commit comments

Comments
 (0)