@junctionpanel/relay 0.1.16
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/LICENSE +671 -0
- package/dist/base64.d.ts +3 -0
- package/dist/base64.d.ts.map +1 -0
- package/dist/base64.js +17 -0
- package/dist/base64.js.map +1 -0
- package/dist/cloudflare-adapter.d.ts +73 -0
- package/dist/cloudflare-adapter.d.ts.map +1 -0
- package/dist/cloudflare-adapter.js +420 -0
- package/dist/cloudflare-adapter.js.map +1 -0
- package/dist/crypto.d.ts +30 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +126 -0
- package/dist/crypto.js.map +1 -0
- package/dist/e2ee.d.ts +5 -0
- package/dist/e2ee.d.ts.map +1 -0
- package/dist/e2ee.js +3 -0
- package/dist/e2ee.js.map +1 -0
- package/dist/encrypted-channel.d.ts +75 -0
- package/dist/encrypted-channel.d.ts.map +1 -0
- package/dist/encrypted-channel.js +323 -0
- package/dist/encrypted-channel.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +48 -0
package/dist/base64.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { fromByteArray, toByteArray } from "base64-js";
|
|
2
|
+
export function arrayBufferToBase64(buffer) {
|
|
3
|
+
return fromByteArray(new Uint8Array(buffer));
|
|
4
|
+
}
|
|
5
|
+
export function base64ToArrayBuffer(base64) {
|
|
6
|
+
const normalized = (() => {
|
|
7
|
+
const trimmed = base64.trim();
|
|
8
|
+
const standard = trimmed.replace(/-/g, "+").replace(/_/g, "/");
|
|
9
|
+
const padLen = (4 - (standard.length % 4)) % 4;
|
|
10
|
+
return standard + "=".repeat(padLen);
|
|
11
|
+
})();
|
|
12
|
+
const bytes = toByteArray(normalized);
|
|
13
|
+
const out = new Uint8Array(bytes.byteLength);
|
|
14
|
+
out.set(bytes);
|
|
15
|
+
return out.buffer;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=base64.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64.js","sourceRoot":"","sources":["../src/base64.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEvD,MAAM,UAAU,mBAAmB,CAAC,MAAmB;IACrD,OAAO,aAAa,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE;QACvB,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC/C,OAAO,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC,CAAC,EAAE,CAAC;IAEL,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC7C,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACf,OAAO,GAAG,CAAC,MAAM,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Durable Objects adapter for the relay.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a Durable Object class that can be deployed to
|
|
5
|
+
* Cloudflare Workers. It uses WebSocket hibernation for cost efficiency.
|
|
6
|
+
*
|
|
7
|
+
* Each session gets its own Durable Object instance, identified by session ID.
|
|
8
|
+
*
|
|
9
|
+
* Wrangler config:
|
|
10
|
+
* ```jsonc
|
|
11
|
+
* {
|
|
12
|
+
* "durable_objects": {
|
|
13
|
+
* "bindings": [{ "name": "RELAY", "class_name": "RelayDurableObject" }]
|
|
14
|
+
* },
|
|
15
|
+
* "migrations": [{ "tag": "v1", "new_classes": ["RelayDurableObject"] }]
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
interface DurableObjectState {
|
|
20
|
+
acceptWebSocket(ws: WebSocket, tags?: string[]): void;
|
|
21
|
+
getWebSockets(tag?: string): WebSocket[];
|
|
22
|
+
}
|
|
23
|
+
interface Env {
|
|
24
|
+
RELAY: DurableObjectNamespace;
|
|
25
|
+
}
|
|
26
|
+
interface DurableObjectNamespace {
|
|
27
|
+
idFromName(name: string): DurableObjectId;
|
|
28
|
+
get(id: DurableObjectId): DurableObjectStub;
|
|
29
|
+
}
|
|
30
|
+
interface DurableObjectId {
|
|
31
|
+
toString(): string;
|
|
32
|
+
}
|
|
33
|
+
interface DurableObjectStub {
|
|
34
|
+
fetch(request: Request): Promise<Response>;
|
|
35
|
+
}
|
|
36
|
+
export declare class RelayDurableObject {
|
|
37
|
+
private state;
|
|
38
|
+
private pendingFrames;
|
|
39
|
+
constructor(state: DurableObjectState);
|
|
40
|
+
private createWebSocketPair;
|
|
41
|
+
private requireWebSocketUpgrade;
|
|
42
|
+
private asSwitchingProtocolsResponse;
|
|
43
|
+
private hasServerDataSocket;
|
|
44
|
+
private hasClientSocket;
|
|
45
|
+
private nudgeOrResetControlForConnection;
|
|
46
|
+
private bufferFrame;
|
|
47
|
+
private flushFrames;
|
|
48
|
+
private listConnectedConnectionIds;
|
|
49
|
+
private notifyControls;
|
|
50
|
+
private fetchV1;
|
|
51
|
+
private fetchV2;
|
|
52
|
+
fetch(request: Request): Promise<Response>;
|
|
53
|
+
/**
|
|
54
|
+
* Called when a WebSocket message is received (wakes from hibernation).
|
|
55
|
+
*/
|
|
56
|
+
webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): void;
|
|
57
|
+
/**
|
|
58
|
+
* Called when a WebSocket closes (wakes from hibernation).
|
|
59
|
+
*/
|
|
60
|
+
webSocketClose(ws: WebSocket, code: number, reason: string, _wasClean: boolean): void;
|
|
61
|
+
/**
|
|
62
|
+
* Called on WebSocket error.
|
|
63
|
+
*/
|
|
64
|
+
webSocketError(ws: WebSocket, error: unknown): void;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Worker entry point that routes requests to the appropriate Durable Object.
|
|
68
|
+
*/
|
|
69
|
+
declare const _default: {
|
|
70
|
+
fetch(request: Request, env: Env): Promise<Response>;
|
|
71
|
+
};
|
|
72
|
+
export default _default;
|
|
73
|
+
//# sourceMappingURL=cloudflare-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare-adapter.d.ts","sourceRoot":"","sources":["../src/cloudflare-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAwBH,UAAU,kBAAkB;IAC1B,eAAe,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACtD,aAAa,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;CAC1C;AAOD,UAAU,GAAG;IACX,KAAK,EAAE,sBAAsB,CAAC;CAC/B;AAED,UAAU,sBAAsB;IAC9B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAAC;IAC1C,GAAG,CAAC,EAAE,EAAE,eAAe,GAAG,iBAAiB,CAAC;CAC7C;AAED,UAAU,eAAe;IACvB,QAAQ,IAAI,MAAM,CAAC;CACpB;AAED,UAAU,iBAAiB;IACzB,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC5C;AAkBD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,aAAa,CAAkD;gBAE3D,KAAK,EAAE,kBAAkB;IAIrC,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,uBAAuB;IAQ/B,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,gCAAgC;IAiCxC,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,0BAA0B;IAelC,OAAO,CAAC,cAAc;IAgBtB,OAAO,CAAC,OAAO;IAyBf,OAAO,CAAC,OAAO;IA+ET,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IA2BhD;;OAEG;IACH,gBAAgB,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAqEpE;;OAEG;IACH,cAAc,CACZ,EAAE,EAAE,SAAS,EACb,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,OAAO,GACjB,IAAI;IA8CP;;OAEG;IACH,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;CAOpD;AAED;;GAEG;;mBAEoB,OAAO,OAAO,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC;;AAD5D,wBAmCE"}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Durable Objects adapter for the relay.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a Durable Object class that can be deployed to
|
|
5
|
+
* Cloudflare Workers. It uses WebSocket hibernation for cost efficiency.
|
|
6
|
+
*
|
|
7
|
+
* Each session gets its own Durable Object instance, identified by session ID.
|
|
8
|
+
*
|
|
9
|
+
* Wrangler config:
|
|
10
|
+
* ```jsonc
|
|
11
|
+
* {
|
|
12
|
+
* "durable_objects": {
|
|
13
|
+
* "bindings": [{ "name": "RELAY", "class_name": "RelayDurableObject" }]
|
|
14
|
+
* },
|
|
15
|
+
* "migrations": [{ "tag": "v1", "new_classes": ["RelayDurableObject"] }]
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
const LEGACY_RELAY_VERSION = "1";
|
|
20
|
+
const CURRENT_RELAY_VERSION = "2";
|
|
21
|
+
function resolveRelayVersion(rawValue) {
|
|
22
|
+
if (rawValue == null)
|
|
23
|
+
return LEGACY_RELAY_VERSION;
|
|
24
|
+
const value = rawValue.trim();
|
|
25
|
+
if (!value)
|
|
26
|
+
return LEGACY_RELAY_VERSION;
|
|
27
|
+
if (value === LEGACY_RELAY_VERSION || value === CURRENT_RELAY_VERSION) {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
export class RelayDurableObject {
|
|
33
|
+
constructor(state) {
|
|
34
|
+
this.pendingFrames = new Map();
|
|
35
|
+
this.state = state;
|
|
36
|
+
}
|
|
37
|
+
createWebSocketPair() {
|
|
38
|
+
const pair = new globalThis.WebSocketPair();
|
|
39
|
+
return [pair[0], pair[1]];
|
|
40
|
+
}
|
|
41
|
+
requireWebSocketUpgrade(request) {
|
|
42
|
+
const upgradeHeader = request.headers.get("Upgrade");
|
|
43
|
+
if (!upgradeHeader || upgradeHeader.toLowerCase() !== "websocket") {
|
|
44
|
+
return new Response("Expected WebSocket upgrade", { status: 426 });
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
asSwitchingProtocolsResponse(client) {
|
|
49
|
+
return new Response(null, {
|
|
50
|
+
status: 101,
|
|
51
|
+
webSocket: client,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
hasServerDataSocket(connectionId) {
|
|
55
|
+
try {
|
|
56
|
+
return this.state.getWebSockets(`server:${connectionId}`).length > 0;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
hasClientSocket(connectionId) {
|
|
63
|
+
try {
|
|
64
|
+
return this.state.getWebSockets(`client:${connectionId}`).length > 0;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
nudgeOrResetControlForConnection(connectionId) {
|
|
71
|
+
// If the daemon's control WS becomes half-open, the DO can't reliably detect it via ws.send errors
|
|
72
|
+
// (Cloudflare may accept writes even if the other side is no longer reading).
|
|
73
|
+
//
|
|
74
|
+
// Instead, observe whether the daemon reacts by opening the per-connection server-data socket.
|
|
75
|
+
// If it doesn't, nudge with a sync message; if still no reaction, force-close the control
|
|
76
|
+
// socket(s) so the daemon reconnects.
|
|
77
|
+
const initialDelayMs = 10000;
|
|
78
|
+
const secondDelayMs = 5000;
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
if (!this.hasClientSocket(connectionId))
|
|
81
|
+
return;
|
|
82
|
+
if (this.hasServerDataSocket(connectionId))
|
|
83
|
+
return;
|
|
84
|
+
// First nudge: send a full sync list.
|
|
85
|
+
this.notifyControls({ type: "sync", connectionIds: this.listConnectedConnectionIds() });
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
if (!this.hasClientSocket(connectionId))
|
|
88
|
+
return;
|
|
89
|
+
if (this.hasServerDataSocket(connectionId))
|
|
90
|
+
return;
|
|
91
|
+
// Still nothing: assume control is stuck and force a reconnect.
|
|
92
|
+
for (const ws of this.state.getWebSockets("server-control")) {
|
|
93
|
+
try {
|
|
94
|
+
ws.close(1011, "Control unresponsive");
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// ignore
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}, secondDelayMs);
|
|
101
|
+
}, initialDelayMs);
|
|
102
|
+
}
|
|
103
|
+
bufferFrame(connectionId, message) {
|
|
104
|
+
const existing = this.pendingFrames.get(connectionId) ?? [];
|
|
105
|
+
existing.push(message);
|
|
106
|
+
// Prevent unbounded memory growth if a daemon never connects.
|
|
107
|
+
if (existing.length > 200) {
|
|
108
|
+
existing.splice(0, existing.length - 200);
|
|
109
|
+
}
|
|
110
|
+
this.pendingFrames.set(connectionId, existing);
|
|
111
|
+
}
|
|
112
|
+
flushFrames(connectionId, serverWs) {
|
|
113
|
+
const frames = this.pendingFrames.get(connectionId);
|
|
114
|
+
if (!frames || frames.length === 0)
|
|
115
|
+
return;
|
|
116
|
+
this.pendingFrames.delete(connectionId);
|
|
117
|
+
for (const frame of frames) {
|
|
118
|
+
try {
|
|
119
|
+
serverWs.send(frame);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// If we can't flush, re-buffer and let the daemon re-establish.
|
|
123
|
+
this.bufferFrame(connectionId, frame);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
listConnectedConnectionIds() {
|
|
129
|
+
const out = new Set();
|
|
130
|
+
for (const ws of this.state.getWebSockets("client")) {
|
|
131
|
+
try {
|
|
132
|
+
const attachment = ws.deserializeAttachment();
|
|
133
|
+
if (attachment?.role === "client" && typeof attachment.connectionId === "string" && attachment.connectionId) {
|
|
134
|
+
out.add(attachment.connectionId);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// ignore
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return Array.from(out);
|
|
142
|
+
}
|
|
143
|
+
notifyControls(message) {
|
|
144
|
+
const text = JSON.stringify(message);
|
|
145
|
+
for (const ws of this.state.getWebSockets("server-control")) {
|
|
146
|
+
try {
|
|
147
|
+
ws.send(text);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// If the control socket is dead, close it so the daemon can reconnect.
|
|
151
|
+
try {
|
|
152
|
+
ws.close(1011, "Control send failed");
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// ignore
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
fetchV1(request, role, serverId) {
|
|
161
|
+
const upgradeError = this.requireWebSocketUpgrade(request);
|
|
162
|
+
if (upgradeError)
|
|
163
|
+
return upgradeError;
|
|
164
|
+
for (const ws of this.state.getWebSockets(role)) {
|
|
165
|
+
ws.close(1008, "Replaced by new connection");
|
|
166
|
+
}
|
|
167
|
+
const [client, server] = this.createWebSocketPair();
|
|
168
|
+
this.state.acceptWebSocket(server, [role]);
|
|
169
|
+
const attachment = {
|
|
170
|
+
serverId,
|
|
171
|
+
role,
|
|
172
|
+
version: LEGACY_RELAY_VERSION,
|
|
173
|
+
connectionId: null,
|
|
174
|
+
createdAt: Date.now(),
|
|
175
|
+
};
|
|
176
|
+
server.serializeAttachment(attachment);
|
|
177
|
+
console.log(`[Relay DO] v1:${role} connected to session ${serverId}`);
|
|
178
|
+
return this.asSwitchingProtocolsResponse(client);
|
|
179
|
+
}
|
|
180
|
+
fetchV2(request, role, serverId, connectionId) {
|
|
181
|
+
const upgradeError = this.requireWebSocketUpgrade(request);
|
|
182
|
+
if (upgradeError)
|
|
183
|
+
return upgradeError;
|
|
184
|
+
// If a client didn't provide a connectionId, the relay assigns one for routing.
|
|
185
|
+
const resolvedConnectionId = role === "client" && !connectionId
|
|
186
|
+
? `conn_${crypto.randomUUID().replace(/-/g, "").slice(0, 16)}`
|
|
187
|
+
: connectionId;
|
|
188
|
+
const isServerControl = role === "server" && !resolvedConnectionId;
|
|
189
|
+
const isServerData = role === "server" && !!resolvedConnectionId;
|
|
190
|
+
// Close any existing server-side connection with the same identity.
|
|
191
|
+
// - server-control: single per serverId
|
|
192
|
+
// - server-data: single per connectionId
|
|
193
|
+
// - client: many sockets per connectionId are allowed
|
|
194
|
+
if (isServerControl) {
|
|
195
|
+
for (const ws of this.state.getWebSockets("server-control")) {
|
|
196
|
+
ws.close(1008, "Replaced by new connection");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else if (isServerData) {
|
|
200
|
+
for (const ws of this.state.getWebSockets(`server:${resolvedConnectionId}`)) {
|
|
201
|
+
ws.close(1008, "Replaced by new connection");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const [client, server] = this.createWebSocketPair();
|
|
205
|
+
const tags = [];
|
|
206
|
+
if (role === "client") {
|
|
207
|
+
tags.push("client", `client:${resolvedConnectionId}`);
|
|
208
|
+
}
|
|
209
|
+
else if (isServerControl) {
|
|
210
|
+
tags.push("server-control");
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
tags.push("server", `server:${resolvedConnectionId}`);
|
|
214
|
+
}
|
|
215
|
+
this.state.acceptWebSocket(server, tags);
|
|
216
|
+
const attachment = {
|
|
217
|
+
serverId,
|
|
218
|
+
role,
|
|
219
|
+
version: CURRENT_RELAY_VERSION,
|
|
220
|
+
connectionId: resolvedConnectionId || null,
|
|
221
|
+
createdAt: Date.now(),
|
|
222
|
+
};
|
|
223
|
+
server.serializeAttachment(attachment);
|
|
224
|
+
console.log(`[Relay DO] v2:${role}${isServerControl ? "(control)" : ""}${isServerData ? `(data:${resolvedConnectionId})` : role === "client" ? `(${resolvedConnectionId})` : ""} connected to session ${serverId}`);
|
|
225
|
+
if (role === "client") {
|
|
226
|
+
this.notifyControls({ type: "connected", connectionId: resolvedConnectionId });
|
|
227
|
+
this.nudgeOrResetControlForConnection(resolvedConnectionId);
|
|
228
|
+
}
|
|
229
|
+
if (isServerControl) {
|
|
230
|
+
// Send current connection list so the daemon can attach existing connections.
|
|
231
|
+
try {
|
|
232
|
+
server.send(JSON.stringify({ type: "sync", connectionIds: this.listConnectedConnectionIds() }));
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// ignore
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (isServerData && resolvedConnectionId) {
|
|
239
|
+
this.flushFrames(resolvedConnectionId, server);
|
|
240
|
+
}
|
|
241
|
+
return this.asSwitchingProtocolsResponse(client);
|
|
242
|
+
}
|
|
243
|
+
async fetch(request) {
|
|
244
|
+
const url = new URL(request.url);
|
|
245
|
+
const role = url.searchParams.get("role");
|
|
246
|
+
const serverId = url.searchParams.get("serverId");
|
|
247
|
+
const connectionIdRaw = url.searchParams.get("connectionId");
|
|
248
|
+
const connectionId = typeof connectionIdRaw === "string" ? connectionIdRaw.trim() : "";
|
|
249
|
+
const version = resolveRelayVersion(url.searchParams.get("v"));
|
|
250
|
+
if (!role || (role !== "server" && role !== "client")) {
|
|
251
|
+
return new Response("Missing or invalid role parameter", { status: 400 });
|
|
252
|
+
}
|
|
253
|
+
if (!serverId) {
|
|
254
|
+
return new Response("Missing serverId parameter", { status: 400 });
|
|
255
|
+
}
|
|
256
|
+
if (!version) {
|
|
257
|
+
return new Response("Invalid v parameter (expected 1 or 2)", { status: 400 });
|
|
258
|
+
}
|
|
259
|
+
if (version === LEGACY_RELAY_VERSION) {
|
|
260
|
+
return this.fetchV1(request, role, serverId);
|
|
261
|
+
}
|
|
262
|
+
return this.fetchV2(request, role, serverId, connectionId);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Called when a WebSocket message is received (wakes from hibernation).
|
|
266
|
+
*/
|
|
267
|
+
webSocketMessage(ws, message) {
|
|
268
|
+
const attachment = ws.deserializeAttachment();
|
|
269
|
+
if (!attachment) {
|
|
270
|
+
console.error("[Relay DO] Message from WebSocket without attachment");
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const version = attachment.version ?? LEGACY_RELAY_VERSION;
|
|
274
|
+
if (version === LEGACY_RELAY_VERSION) {
|
|
275
|
+
const targetRole = attachment.role === "server" ? "client" : "server";
|
|
276
|
+
const targets = this.state.getWebSockets(targetRole);
|
|
277
|
+
for (const target of targets) {
|
|
278
|
+
try {
|
|
279
|
+
target.send(message);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
console.error(`[Relay DO] Failed to forward to ${targetRole}:`, error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const { role, connectionId } = attachment;
|
|
288
|
+
if (!connectionId) {
|
|
289
|
+
// Control channel: support simple app-level keepalive.
|
|
290
|
+
if (typeof message === "string") {
|
|
291
|
+
try {
|
|
292
|
+
const parsed = JSON.parse(message);
|
|
293
|
+
if (parsed?.type === "ping") {
|
|
294
|
+
try {
|
|
295
|
+
ws.send(JSON.stringify({ type: "pong", ts: Date.now() }));
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
// ignore
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
// ignore non-JSON control payloads
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (role === "client") {
|
|
309
|
+
const servers = this.state.getWebSockets(`server:${connectionId}`);
|
|
310
|
+
if (servers.length === 0) {
|
|
311
|
+
this.bufferFrame(connectionId, message);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
for (const target of servers) {
|
|
315
|
+
try {
|
|
316
|
+
target.send(message);
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
console.error(`[Relay DO] Failed to forward client->server(${connectionId}):`, error);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
// server data socket -> client
|
|
325
|
+
const targets = this.state.getWebSockets(`client:${connectionId}`);
|
|
326
|
+
for (const target of targets) {
|
|
327
|
+
try {
|
|
328
|
+
target.send(message);
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
console.error(`[Relay DO] Failed to forward server->client(${connectionId}):`, error);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Called when a WebSocket closes (wakes from hibernation).
|
|
337
|
+
*/
|
|
338
|
+
webSocketClose(ws, code, reason, _wasClean) {
|
|
339
|
+
const attachment = ws.deserializeAttachment();
|
|
340
|
+
if (!attachment)
|
|
341
|
+
return;
|
|
342
|
+
const version = attachment.version ?? LEGACY_RELAY_VERSION;
|
|
343
|
+
console.log(`[Relay DO] v${version}:${attachment.role}${attachment.connectionId ? `(${attachment.connectionId})` : ""} disconnected from session ${attachment.serverId} (${code}: ${reason})`);
|
|
344
|
+
if (version === LEGACY_RELAY_VERSION) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (attachment.role === "client" && attachment.connectionId) {
|
|
348
|
+
const remainingClientSockets = this.state
|
|
349
|
+
.getWebSockets(`client:${attachment.connectionId}`)
|
|
350
|
+
.some((socket) => socket !== ws);
|
|
351
|
+
if (remainingClientSockets) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
this.pendingFrames.delete(attachment.connectionId);
|
|
355
|
+
// Last socket for this session closed: now clean up matching server-data socket.
|
|
356
|
+
for (const serverWs of this.state.getWebSockets(`server:${attachment.connectionId}`)) {
|
|
357
|
+
try {
|
|
358
|
+
serverWs.close(1001, "Client disconnected");
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
// ignore
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
this.notifyControls({ type: "disconnected", connectionId: attachment.connectionId });
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (attachment.role === "server" && attachment.connectionId) {
|
|
368
|
+
// Force the client to reconnect and re-handshake when the daemon side drops.
|
|
369
|
+
for (const clientWs of this.state.getWebSockets(`client:${attachment.connectionId}`)) {
|
|
370
|
+
try {
|
|
371
|
+
clientWs.close(1012, "Server disconnected");
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
// ignore
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Called on WebSocket error.
|
|
381
|
+
*/
|
|
382
|
+
webSocketError(ws, error) {
|
|
383
|
+
const attachment = ws.deserializeAttachment();
|
|
384
|
+
console.error(`[Relay DO] WebSocket error for ${attachment?.role ?? "unknown"}:`, error);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Worker entry point that routes requests to the appropriate Durable Object.
|
|
389
|
+
*/
|
|
390
|
+
export default {
|
|
391
|
+
async fetch(request, env) {
|
|
392
|
+
const url = new URL(request.url);
|
|
393
|
+
// Health check
|
|
394
|
+
if (url.pathname === "/health") {
|
|
395
|
+
return new Response(JSON.stringify({ status: "ok" }), {
|
|
396
|
+
headers: { "Content-Type": "application/json" },
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
// Relay endpoint
|
|
400
|
+
if (url.pathname === "/ws") {
|
|
401
|
+
const serverId = url.searchParams.get("serverId");
|
|
402
|
+
if (!serverId) {
|
|
403
|
+
return new Response("Missing serverId parameter", { status: 400 });
|
|
404
|
+
}
|
|
405
|
+
const version = resolveRelayVersion(url.searchParams.get("v"));
|
|
406
|
+
if (!version) {
|
|
407
|
+
return new Response("Invalid v parameter (expected 1 or 2)", { status: 400 });
|
|
408
|
+
}
|
|
409
|
+
// Route to a version-isolated Durable Object instance.
|
|
410
|
+
const id = env.RELAY.idFromName(`relay-v${version}:${serverId}`);
|
|
411
|
+
const stub = env.RELAY.get(id);
|
|
412
|
+
const normalizedUrl = new URL(request.url);
|
|
413
|
+
normalizedUrl.searchParams.set("v", version);
|
|
414
|
+
const normalizedRequest = new Request(normalizedUrl.toString(), request);
|
|
415
|
+
return stub.fetch(normalizedRequest);
|
|
416
|
+
}
|
|
417
|
+
return new Response("Not found", { status: 404 });
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
//# sourceMappingURL=cloudflare-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare-adapter.js","sourceRoot":"","sources":["../src/cloudflare-adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2EE crypto primitives using NaCl (tweetnacl).
|
|
3
|
+
*
|
|
4
|
+
* - Key exchange: Curve25519 (nacl.box.before)
|
|
5
|
+
* - Encryption: XSalsa20-Poly1305 (nacl.box.after / open.after)
|
|
6
|
+
*
|
|
7
|
+
* Bundle format (binary):
|
|
8
|
+
* [nonce (24 bytes)] [ciphertext...]
|
|
9
|
+
*
|
|
10
|
+
* Transport format:
|
|
11
|
+
* The encrypted-channel sends the bundle as base64 text over WebSocket.
|
|
12
|
+
*/
|
|
13
|
+
export type KeyPair = {
|
|
14
|
+
publicKey: Uint8Array;
|
|
15
|
+
secretKey: Uint8Array;
|
|
16
|
+
};
|
|
17
|
+
export type SharedKey = Uint8Array;
|
|
18
|
+
export declare function generateKeyPair(): KeyPair;
|
|
19
|
+
export declare function exportPublicKey(publicKey: Uint8Array): string;
|
|
20
|
+
export declare function importPublicKey(base64: string): Uint8Array;
|
|
21
|
+
export declare function exportSecretKey(secretKey: Uint8Array): string;
|
|
22
|
+
export declare function importSecretKey(base64: string): Uint8Array;
|
|
23
|
+
export declare function deriveSharedKey(ourSecretKey: Uint8Array, peerPublicKey: Uint8Array): SharedKey;
|
|
24
|
+
/**
|
|
25
|
+
* Encrypts data and returns the binary bundle:
|
|
26
|
+
* [nonce (24)] [ciphertext...]
|
|
27
|
+
*/
|
|
28
|
+
export declare function encrypt(sharedKey: SharedKey, data: string | ArrayBuffer): ArrayBuffer;
|
|
29
|
+
export declare function decrypt(sharedKey: SharedKey, data: ArrayBuffer): string | ArrayBuffer;
|
|
30
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;GAWG;AAKH,MAAM,MAAM,OAAO,GAAG;IACpB,SAAS,EAAE,UAAU,CAAC;IACtB,SAAS,EAAE,UAAU,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC;AA+CnC,wBAAgB,eAAe,IAAI,OAAO,CAIzC;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,UAAU,GAAG,MAAM,CAK7D;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAM1D;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,UAAU,GAAG,MAAM,CAK7D;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAM1D;AAED,wBAAgB,eAAe,CAC7B,YAAY,EAAE,UAAU,EACxB,aAAa,EAAE,UAAU,GACxB,SAAS,CAQX;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,WAAW,CASrF;AAED,wBAAgB,OAAO,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,WAAW,CAmBrF"}
|