@conference-kit/signaling-server 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +15 -0
- package/src/index.ts +185 -0
- package/tsconfig.json +8 -0
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@conference-kit/signaling-server",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "bun run src/index.ts",
|
|
9
|
+
"build": "tsc -p tsconfig.json"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"typescript": "^5.9.3"
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
type Server<T> = {
|
|
2
|
+
upgrade(req: Request, options: { data: T }): boolean;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
type ServerWebSocket<T> = {
|
|
6
|
+
data: T;
|
|
7
|
+
subscribe(topic: string): void;
|
|
8
|
+
unsubscribe(topic: string): void;
|
|
9
|
+
publish(topic: string, data: string): void;
|
|
10
|
+
send(data: string): void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
declare const Bun: {
|
|
14
|
+
serve: <T>(options: {
|
|
15
|
+
hostname?: string;
|
|
16
|
+
port?: number;
|
|
17
|
+
fetch(req: Request, server: Server<T>): Response | void | undefined;
|
|
18
|
+
websocket: {
|
|
19
|
+
open(ws: ServerWebSocket<T>): void;
|
|
20
|
+
message(
|
|
21
|
+
ws: ServerWebSocket<T>,
|
|
22
|
+
message: string | ArrayBuffer | Uint8Array
|
|
23
|
+
): void;
|
|
24
|
+
close(ws: ServerWebSocket<T>): void;
|
|
25
|
+
};
|
|
26
|
+
}) => { port: number };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type ClientMeta = { peerId: string; room?: string | null };
|
|
30
|
+
|
|
31
|
+
type SignalPayload = {
|
|
32
|
+
type: "signal";
|
|
33
|
+
to: string;
|
|
34
|
+
data: unknown;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type BroadcastPayload = {
|
|
38
|
+
type: "broadcast";
|
|
39
|
+
data: unknown;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type IncomingMessage = SignalPayload | BroadcastPayload;
|
|
43
|
+
|
|
44
|
+
type OutgoingMessage =
|
|
45
|
+
| { type: "signal"; from: string; data: unknown }
|
|
46
|
+
| { type: "broadcast"; from: string; room?: string | null; data: unknown }
|
|
47
|
+
| PresenceMessage
|
|
48
|
+
| { type: "error"; message: string };
|
|
49
|
+
|
|
50
|
+
type PresenceMessage = {
|
|
51
|
+
type: "presence";
|
|
52
|
+
room?: string | null;
|
|
53
|
+
peerId: string;
|
|
54
|
+
peers: string[];
|
|
55
|
+
action: "join" | "leave";
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function parseMessage(raw: string): IncomingMessage | null {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
if (parsed && (parsed.type === "signal" || parsed.type === "broadcast"))
|
|
62
|
+
return parsed;
|
|
63
|
+
return null;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error("Failed to parse message", error);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const HOST = "0.0.0.0";
|
|
71
|
+
const PORT = 8787;
|
|
72
|
+
|
|
73
|
+
const roomMembers = new Map<string, Set<string>>();
|
|
74
|
+
|
|
75
|
+
// Signaling server with simple room presence
|
|
76
|
+
const liveServer = Bun.serve<ClientMeta>({
|
|
77
|
+
hostname: HOST,
|
|
78
|
+
port: PORT,
|
|
79
|
+
fetch(req: Request, bunServer: Server<ClientMeta>) {
|
|
80
|
+
const url = new URL(req.url);
|
|
81
|
+
const peerId = url.searchParams.get("peerId");
|
|
82
|
+
const room = url.searchParams.get("room");
|
|
83
|
+
|
|
84
|
+
if (!peerId) {
|
|
85
|
+
return new Response("peerId query param is required", { status: 400 });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const upgraded = bunServer.upgrade(req, { data: { peerId, room } });
|
|
89
|
+
if (!upgraded) {
|
|
90
|
+
return new Response("WebSocket upgrade failed", { status: 500 });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return undefined;
|
|
94
|
+
},
|
|
95
|
+
websocket: {
|
|
96
|
+
open(ws: ServerWebSocket<ClientMeta>) {
|
|
97
|
+
const { peerId, room } = ws.data;
|
|
98
|
+
ws.subscribe(`peer:${peerId}`);
|
|
99
|
+
if (room) {
|
|
100
|
+
ws.subscribe(`room:${room}`);
|
|
101
|
+
const set = roomMembers.get(room) ?? new Set<string>();
|
|
102
|
+
set.add(peerId);
|
|
103
|
+
roomMembers.set(room, set);
|
|
104
|
+
const presence: PresenceMessage = {
|
|
105
|
+
type: "presence",
|
|
106
|
+
room,
|
|
107
|
+
peerId,
|
|
108
|
+
peers: Array.from(set),
|
|
109
|
+
action: "join",
|
|
110
|
+
};
|
|
111
|
+
// Publish to everyone in the room and also send directly so the new peer gets an immediate roster snapshot.
|
|
112
|
+
ws.publish(`room:${room}`, JSON.stringify(presence));
|
|
113
|
+
ws.send(JSON.stringify(presence));
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
message(
|
|
117
|
+
ws: ServerWebSocket<ClientMeta>,
|
|
118
|
+
raw: string | ArrayBuffer | Uint8Array
|
|
119
|
+
) {
|
|
120
|
+
const payload = typeof raw === "string" ? raw : raw.toString();
|
|
121
|
+
const parsed = parseMessage(payload);
|
|
122
|
+
if (!parsed) {
|
|
123
|
+
ws.send(
|
|
124
|
+
JSON.stringify({
|
|
125
|
+
type: "error",
|
|
126
|
+
message: "Invalid message payload",
|
|
127
|
+
} satisfies OutgoingMessage)
|
|
128
|
+
);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (parsed.type === "signal") {
|
|
133
|
+
const outgoing: OutgoingMessage = {
|
|
134
|
+
type: "signal",
|
|
135
|
+
from: ws.data.peerId,
|
|
136
|
+
data: parsed.data,
|
|
137
|
+
};
|
|
138
|
+
ws.publish(`peer:${parsed.to}`, JSON.stringify(outgoing));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (parsed.type === "broadcast") {
|
|
143
|
+
const outgoing: OutgoingMessage = {
|
|
144
|
+
type: "broadcast",
|
|
145
|
+
from: ws.data.peerId,
|
|
146
|
+
room: ws.data.room,
|
|
147
|
+
data: parsed.data,
|
|
148
|
+
};
|
|
149
|
+
if (ws.data.room) {
|
|
150
|
+
ws.publish(`room:${ws.data.room}`, JSON.stringify(outgoing));
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
close(ws: ServerWebSocket<ClientMeta>) {
|
|
156
|
+
const { peerId, room } = ws.data;
|
|
157
|
+
ws.unsubscribe(`peer:${peerId}`);
|
|
158
|
+
if (room) {
|
|
159
|
+
ws.unsubscribe(`room:${room}`);
|
|
160
|
+
const set = roomMembers.get(room);
|
|
161
|
+
if (set) {
|
|
162
|
+
set.delete(peerId);
|
|
163
|
+
if (set.size === 0) {
|
|
164
|
+
roomMembers.delete(room);
|
|
165
|
+
} else {
|
|
166
|
+
roomMembers.set(room, set);
|
|
167
|
+
}
|
|
168
|
+
const presence: PresenceMessage = {
|
|
169
|
+
type: "presence",
|
|
170
|
+
room,
|
|
171
|
+
peerId,
|
|
172
|
+
peers: Array.from(set),
|
|
173
|
+
action: "leave",
|
|
174
|
+
};
|
|
175
|
+
ws.publish(`room:${room}`, JSON.stringify(presence));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
console.log(`Signaling server running on ws://localhost:${liveServer.port}`);
|
|
183
|
+
console.log(
|
|
184
|
+
`LAN access (if allowed by firewall): ws://<your-lan-ip>:${liveServer.port}`
|
|
185
|
+
);
|