@getpaseo/relay 0.1.2
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/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 +67 -0
- package/dist/cloudflare-adapter.d.ts.map +1 -0
- package/dist/cloudflare-adapter.js +330 -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 +305 -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 +21 -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 +49 -0
package/dist/base64.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64.d.ts","sourceRoot":"","sources":["../src/base64.ts"],"names":[],"mappings":"AAEA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAE/D;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAY/D"}
|
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,67 @@
|
|
|
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 pendingClientFrames;
|
|
39
|
+
constructor(state: DurableObjectState);
|
|
40
|
+
private hasServerDataSocket;
|
|
41
|
+
private nudgeOrResetControlForClient;
|
|
42
|
+
private bufferClientFrame;
|
|
43
|
+
private flushClientFrames;
|
|
44
|
+
private listConnectedClientIds;
|
|
45
|
+
private notifyControls;
|
|
46
|
+
fetch(request: Request): Promise<Response>;
|
|
47
|
+
/**
|
|
48
|
+
* Called when a WebSocket message is received (wakes from hibernation).
|
|
49
|
+
*/
|
|
50
|
+
webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): void;
|
|
51
|
+
/**
|
|
52
|
+
* Called when a WebSocket closes (wakes from hibernation).
|
|
53
|
+
*/
|
|
54
|
+
webSocketClose(ws: WebSocket, code: number, reason: string, _wasClean: boolean): void;
|
|
55
|
+
/**
|
|
56
|
+
* Called on WebSocket error.
|
|
57
|
+
*/
|
|
58
|
+
webSocketError(ws: WebSocket, error: unknown): void;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Worker entry point that routes requests to the appropriate Durable Object.
|
|
62
|
+
*/
|
|
63
|
+
declare const _default: {
|
|
64
|
+
fetch(request: Request, env: Env): Promise<Response>;
|
|
65
|
+
};
|
|
66
|
+
export default _default;
|
|
67
|
+
//# 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;AASH,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;AAiBD,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,mBAAmB,CAAkD;gBAEjE,KAAK,EAAE,kBAAkB;IAKrC,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,4BAA4B;IA+BpC,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,cAAc;IAgBhB,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAoGhD;;OAEG;IACH,gBAAgB,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAsDpE;;OAEG;IACH,cAAc,CACZ,EAAE,EAAE,SAAS,EACb,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,OAAO,GACjB,IAAI;IAkCP;;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,wBA0BE"}
|
|
@@ -0,0 +1,330 @@
|
|
|
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
|
+
export class RelayDurableObject {
|
|
20
|
+
constructor(state) {
|
|
21
|
+
this.pendingClientFrames = new Map();
|
|
22
|
+
this.state = state;
|
|
23
|
+
}
|
|
24
|
+
hasServerDataSocket(clientId) {
|
|
25
|
+
try {
|
|
26
|
+
return this.state.getWebSockets(`server:${clientId}`).length > 0;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
nudgeOrResetControlForClient(clientId) {
|
|
33
|
+
// If the daemon's control WS becomes half-open, the DO can't reliably detect it via ws.send errors
|
|
34
|
+
// (Cloudflare may accept writes even if the other side is no longer reading).
|
|
35
|
+
//
|
|
36
|
+
// Instead, observe whether the daemon reacts by opening the per-client server-data socket.
|
|
37
|
+
// If it doesn't, nudge with a sync message; if still no reaction, force-close the control
|
|
38
|
+
// socket(s) so the daemon reconnects.
|
|
39
|
+
const initialDelayMs = 10000;
|
|
40
|
+
const secondDelayMs = 5000;
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
if (this.hasServerDataSocket(clientId))
|
|
43
|
+
return;
|
|
44
|
+
// First nudge: send a full sync list.
|
|
45
|
+
this.notifyControls({ type: "sync", clientIds: this.listConnectedClientIds() });
|
|
46
|
+
setTimeout(() => {
|
|
47
|
+
if (this.hasServerDataSocket(clientId))
|
|
48
|
+
return;
|
|
49
|
+
// Still nothing: assume control is stuck and force a reconnect.
|
|
50
|
+
for (const ws of this.state.getWebSockets("server-control")) {
|
|
51
|
+
try {
|
|
52
|
+
ws.close(1011, "Control unresponsive");
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// ignore
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}, secondDelayMs);
|
|
59
|
+
}, initialDelayMs);
|
|
60
|
+
}
|
|
61
|
+
bufferClientFrame(clientId, message) {
|
|
62
|
+
const existing = this.pendingClientFrames.get(clientId) ?? [];
|
|
63
|
+
existing.push(message);
|
|
64
|
+
// Prevent unbounded memory growth if a daemon never connects.
|
|
65
|
+
if (existing.length > 200) {
|
|
66
|
+
existing.splice(0, existing.length - 200);
|
|
67
|
+
}
|
|
68
|
+
this.pendingClientFrames.set(clientId, existing);
|
|
69
|
+
}
|
|
70
|
+
flushClientFrames(clientId, serverWs) {
|
|
71
|
+
const frames = this.pendingClientFrames.get(clientId);
|
|
72
|
+
if (!frames || frames.length === 0)
|
|
73
|
+
return;
|
|
74
|
+
this.pendingClientFrames.delete(clientId);
|
|
75
|
+
for (const frame of frames) {
|
|
76
|
+
try {
|
|
77
|
+
serverWs.send(frame);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// If we can't flush, re-buffer and let the daemon re-establish.
|
|
81
|
+
this.bufferClientFrame(clientId, frame);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
listConnectedClientIds() {
|
|
87
|
+
const out = new Set();
|
|
88
|
+
for (const ws of this.state.getWebSockets("client")) {
|
|
89
|
+
try {
|
|
90
|
+
const attachment = ws.deserializeAttachment();
|
|
91
|
+
if (attachment?.role === "client" && typeof attachment.clientId === "string" && attachment.clientId) {
|
|
92
|
+
out.add(attachment.clientId);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// ignore
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return Array.from(out);
|
|
100
|
+
}
|
|
101
|
+
notifyControls(message) {
|
|
102
|
+
const text = JSON.stringify(message);
|
|
103
|
+
for (const ws of this.state.getWebSockets("server-control")) {
|
|
104
|
+
try {
|
|
105
|
+
ws.send(text);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// If the control socket is dead, close it so the daemon can reconnect.
|
|
109
|
+
try {
|
|
110
|
+
ws.close(1011, "Control send failed");
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// ignore
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async fetch(request) {
|
|
119
|
+
const url = new URL(request.url);
|
|
120
|
+
const role = url.searchParams.get("role");
|
|
121
|
+
const serverId = url.searchParams.get("serverId");
|
|
122
|
+
const clientIdRaw = url.searchParams.get("clientId");
|
|
123
|
+
const clientId = typeof clientIdRaw === "string" ? clientIdRaw.trim() : "";
|
|
124
|
+
if (!role || (role !== "server" && role !== "client")) {
|
|
125
|
+
return new Response("Missing or invalid role parameter", { status: 400 });
|
|
126
|
+
}
|
|
127
|
+
if (!serverId) {
|
|
128
|
+
return new Response("Missing serverId parameter", { status: 400 });
|
|
129
|
+
}
|
|
130
|
+
// Clients must provide a clientId so the daemon can create an independent
|
|
131
|
+
// E2EE channel per client connection.
|
|
132
|
+
if (role === "client" && !clientId) {
|
|
133
|
+
return new Response("Missing clientId parameter", { status: 400 });
|
|
134
|
+
}
|
|
135
|
+
const upgradeHeader = request.headers.get("Upgrade");
|
|
136
|
+
if (!upgradeHeader || upgradeHeader.toLowerCase() !== "websocket") {
|
|
137
|
+
return new Response("Expected WebSocket upgrade", { status: 426 });
|
|
138
|
+
}
|
|
139
|
+
const isServerControl = role === "server" && !clientId;
|
|
140
|
+
const isServerData = role === "server" && !!clientId;
|
|
141
|
+
// Close any existing connection with the same identity.
|
|
142
|
+
// - server-control: single per serverId
|
|
143
|
+
// - server-data: single per clientId
|
|
144
|
+
// - client: single per clientId
|
|
145
|
+
if (isServerControl) {
|
|
146
|
+
for (const ws of this.state.getWebSockets("server-control")) {
|
|
147
|
+
ws.close(1008, "Replaced by new connection");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (isServerData) {
|
|
151
|
+
for (const ws of this.state.getWebSockets(`server:${clientId}`)) {
|
|
152
|
+
ws.close(1008, "Replaced by new connection");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
for (const ws of this.state.getWebSockets(`client:${clientId}`)) {
|
|
157
|
+
ws.close(1008, "Replaced by new connection");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Create WebSocket pair
|
|
161
|
+
const pair = new globalThis.WebSocketPair();
|
|
162
|
+
const [client, server] = [pair[0], pair[1]];
|
|
163
|
+
const tags = [];
|
|
164
|
+
if (role === "client") {
|
|
165
|
+
tags.push("client", `client:${clientId}`);
|
|
166
|
+
}
|
|
167
|
+
else if (isServerControl) {
|
|
168
|
+
tags.push("server-control");
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
tags.push("server", `server:${clientId}`);
|
|
172
|
+
}
|
|
173
|
+
// Accept with hibernation support, tagged for lookup.
|
|
174
|
+
this.state.acceptWebSocket(server, tags);
|
|
175
|
+
// Store attachment for hibernation recovery
|
|
176
|
+
const attachment = {
|
|
177
|
+
serverId,
|
|
178
|
+
role,
|
|
179
|
+
clientId: clientId || null,
|
|
180
|
+
createdAt: Date.now(),
|
|
181
|
+
};
|
|
182
|
+
server.serializeAttachment(attachment);
|
|
183
|
+
console.log(`[Relay DO] ${role}${isServerControl ? "(control)" : ""}${isServerData ? `(data:${clientId})` : role === "client" ? `(${clientId})` : ""} connected to session ${serverId}`);
|
|
184
|
+
if (role === "client") {
|
|
185
|
+
this.notifyControls({ type: "client_connected", clientId });
|
|
186
|
+
this.nudgeOrResetControlForClient(clientId);
|
|
187
|
+
}
|
|
188
|
+
if (isServerControl) {
|
|
189
|
+
// Send current client list so the daemon can attach existing clients.
|
|
190
|
+
try {
|
|
191
|
+
server.send(JSON.stringify({ type: "sync", clientIds: this.listConnectedClientIds() }));
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// ignore
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (isServerData && clientId) {
|
|
198
|
+
this.flushClientFrames(clientId, server);
|
|
199
|
+
}
|
|
200
|
+
return new Response(null, {
|
|
201
|
+
status: 101,
|
|
202
|
+
webSocket: client,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Called when a WebSocket message is received (wakes from hibernation).
|
|
207
|
+
*/
|
|
208
|
+
webSocketMessage(ws, message) {
|
|
209
|
+
const attachment = ws.deserializeAttachment();
|
|
210
|
+
if (!attachment) {
|
|
211
|
+
console.error("[Relay DO] Message from WebSocket without attachment");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const { role, clientId } = attachment;
|
|
215
|
+
if (!clientId) {
|
|
216
|
+
// Control channel: support simple app-level keepalive.
|
|
217
|
+
if (typeof message === "string") {
|
|
218
|
+
try {
|
|
219
|
+
const parsed = JSON.parse(message);
|
|
220
|
+
if (parsed?.type === "ping") {
|
|
221
|
+
try {
|
|
222
|
+
ws.send(JSON.stringify({ type: "pong", ts: Date.now() }));
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// ignore
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// ignore non-JSON control payloads
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (role === "client") {
|
|
236
|
+
const servers = this.state.getWebSockets(`server:${clientId}`);
|
|
237
|
+
if (servers.length === 0) {
|
|
238
|
+
this.bufferClientFrame(clientId, message);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
for (const target of servers) {
|
|
242
|
+
try {
|
|
243
|
+
target.send(message);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
console.error(`[Relay DO] Failed to forward client->server(${clientId}):`, error);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// server data socket -> client
|
|
252
|
+
const targets = this.state.getWebSockets(`client:${clientId}`);
|
|
253
|
+
for (const target of targets) {
|
|
254
|
+
try {
|
|
255
|
+
target.send(message);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
console.error(`[Relay DO] Failed to forward server->client(${clientId}):`, error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Called when a WebSocket closes (wakes from hibernation).
|
|
264
|
+
*/
|
|
265
|
+
webSocketClose(ws, code, reason, _wasClean) {
|
|
266
|
+
const attachment = ws.deserializeAttachment();
|
|
267
|
+
if (!attachment)
|
|
268
|
+
return;
|
|
269
|
+
console.log(`[Relay DO] ${attachment.role}${attachment.clientId ? `(${attachment.clientId})` : ""} disconnected from session ${attachment.serverId} (${code}: ${reason})`);
|
|
270
|
+
if (attachment.role === "client" && attachment.clientId) {
|
|
271
|
+
this.pendingClientFrames.delete(attachment.clientId);
|
|
272
|
+
// Close the matching server-data socket so the daemon can clean up quickly.
|
|
273
|
+
for (const serverWs of this.state.getWebSockets(`server:${attachment.clientId}`)) {
|
|
274
|
+
try {
|
|
275
|
+
serverWs.close(1001, "Client disconnected");
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
// ignore
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
this.notifyControls({ type: "client_disconnected", clientId: attachment.clientId });
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (attachment.role === "server" && attachment.clientId) {
|
|
285
|
+
// Force the client to reconnect and re-handshake when the daemon side drops.
|
|
286
|
+
for (const clientWs of this.state.getWebSockets(`client:${attachment.clientId}`)) {
|
|
287
|
+
try {
|
|
288
|
+
clientWs.close(1012, "Server disconnected");
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
// ignore
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Called on WebSocket error.
|
|
298
|
+
*/
|
|
299
|
+
webSocketError(ws, error) {
|
|
300
|
+
const attachment = ws.deserializeAttachment();
|
|
301
|
+
console.error(`[Relay DO] WebSocket error for ${attachment?.role ?? "unknown"}:`, error);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Worker entry point that routes requests to the appropriate Durable Object.
|
|
306
|
+
*/
|
|
307
|
+
export default {
|
|
308
|
+
async fetch(request, env) {
|
|
309
|
+
const url = new URL(request.url);
|
|
310
|
+
// Health check
|
|
311
|
+
if (url.pathname === "/health") {
|
|
312
|
+
return new Response(JSON.stringify({ status: "ok" }), {
|
|
313
|
+
headers: { "Content-Type": "application/json" },
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
// Relay endpoint
|
|
317
|
+
if (url.pathname === "/ws") {
|
|
318
|
+
const serverId = url.searchParams.get("serverId");
|
|
319
|
+
if (!serverId) {
|
|
320
|
+
return new Response("Missing serverId parameter", { status: 400 });
|
|
321
|
+
}
|
|
322
|
+
// Route to Durable Object instance for this session
|
|
323
|
+
const id = env.RELAY.idFromName(serverId);
|
|
324
|
+
const stub = env.RELAY.get(id);
|
|
325
|
+
return stub.fetch(request);
|
|
326
|
+
}
|
|
327
|
+
return new Response("Not found", { status: 404 });
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
//# 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"}
|
package/dist/crypto.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* E2EE crypto primitives using NaCl (tweetnacl).
|
|
4
|
+
*
|
|
5
|
+
* - Key exchange: Curve25519 (nacl.box.before)
|
|
6
|
+
* - Encryption: XSalsa20-Poly1305 (nacl.box.after / open.after)
|
|
7
|
+
*
|
|
8
|
+
* Bundle format (binary):
|
|
9
|
+
* [nonce (24 bytes)] [ciphertext...]
|
|
10
|
+
*
|
|
11
|
+
* Transport format:
|
|
12
|
+
* The encrypted-channel sends the bundle as base64 text over WebSocket.
|
|
13
|
+
*/
|
|
14
|
+
import nacl from "tweetnacl";
|
|
15
|
+
import { fromByteArray, toByteArray } from "base64-js";
|
|
16
|
+
const NONCE_LENGTH = nacl.box.nonceLength; // 24
|
|
17
|
+
let prngReady = false;
|
|
18
|
+
function ensurePrng() {
|
|
19
|
+
if (prngReady)
|
|
20
|
+
return;
|
|
21
|
+
try {
|
|
22
|
+
nacl.randomBytes(1);
|
|
23
|
+
prngReady = true;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// fallthrough
|
|
28
|
+
}
|
|
29
|
+
const cryptoObj = globalThis.crypto;
|
|
30
|
+
if (cryptoObj?.getRandomValues) {
|
|
31
|
+
nacl.setPRNG((x, n) => {
|
|
32
|
+
cryptoObj.getRandomValues(x.subarray(0, n));
|
|
33
|
+
});
|
|
34
|
+
prngReady = true;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
throw new Error("No secure PRNG available for tweetnacl (missing crypto.getRandomValues)");
|
|
38
|
+
}
|
|
39
|
+
function encodeBase64(bytes) {
|
|
40
|
+
return fromByteArray(bytes);
|
|
41
|
+
}
|
|
42
|
+
function decodeBase64(base64) {
|
|
43
|
+
return toByteArray(base64);
|
|
44
|
+
}
|
|
45
|
+
function toUint8(data) {
|
|
46
|
+
return typeof data === "string" ? new TextEncoder().encode(data) : new Uint8Array(data);
|
|
47
|
+
}
|
|
48
|
+
function toArrayBuffer(bytes) {
|
|
49
|
+
const out = new Uint8Array(bytes.byteLength);
|
|
50
|
+
out.set(bytes);
|
|
51
|
+
return out.buffer;
|
|
52
|
+
}
|
|
53
|
+
export function generateKeyPair() {
|
|
54
|
+
ensurePrng();
|
|
55
|
+
const { publicKey, secretKey } = nacl.box.keyPair();
|
|
56
|
+
return { publicKey, secretKey };
|
|
57
|
+
}
|
|
58
|
+
export function exportPublicKey(publicKey) {
|
|
59
|
+
if (!(publicKey instanceof Uint8Array) || publicKey.byteLength !== nacl.box.publicKeyLength) {
|
|
60
|
+
throw new Error(`Invalid public key length (expected ${nacl.box.publicKeyLength})`);
|
|
61
|
+
}
|
|
62
|
+
return encodeBase64(publicKey);
|
|
63
|
+
}
|
|
64
|
+
export function importPublicKey(base64) {
|
|
65
|
+
const bytes = decodeBase64(base64);
|
|
66
|
+
if (bytes.byteLength !== nacl.box.publicKeyLength) {
|
|
67
|
+
throw new Error(`Invalid public key length (expected ${nacl.box.publicKeyLength})`);
|
|
68
|
+
}
|
|
69
|
+
return bytes;
|
|
70
|
+
}
|
|
71
|
+
export function exportSecretKey(secretKey) {
|
|
72
|
+
if (!(secretKey instanceof Uint8Array) || secretKey.byteLength !== nacl.box.secretKeyLength) {
|
|
73
|
+
throw new Error(`Invalid secret key length (expected ${nacl.box.secretKeyLength})`);
|
|
74
|
+
}
|
|
75
|
+
return encodeBase64(secretKey);
|
|
76
|
+
}
|
|
77
|
+
export function importSecretKey(base64) {
|
|
78
|
+
const bytes = decodeBase64(base64);
|
|
79
|
+
if (bytes.byteLength !== nacl.box.secretKeyLength) {
|
|
80
|
+
throw new Error(`Invalid secret key length (expected ${nacl.box.secretKeyLength})`);
|
|
81
|
+
}
|
|
82
|
+
return bytes;
|
|
83
|
+
}
|
|
84
|
+
export function deriveSharedKey(ourSecretKey, peerPublicKey) {
|
|
85
|
+
if (ourSecretKey.byteLength !== nacl.box.secretKeyLength) {
|
|
86
|
+
throw new Error(`Invalid secret key length (expected ${nacl.box.secretKeyLength})`);
|
|
87
|
+
}
|
|
88
|
+
if (peerPublicKey.byteLength !== nacl.box.publicKeyLength) {
|
|
89
|
+
throw new Error(`Invalid peer public key length (expected ${nacl.box.publicKeyLength})`);
|
|
90
|
+
}
|
|
91
|
+
return nacl.box.before(peerPublicKey, ourSecretKey);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Encrypts data and returns the binary bundle:
|
|
95
|
+
* [nonce (24)] [ciphertext...]
|
|
96
|
+
*/
|
|
97
|
+
export function encrypt(sharedKey, data) {
|
|
98
|
+
ensurePrng();
|
|
99
|
+
const nonce = nacl.randomBytes(NONCE_LENGTH);
|
|
100
|
+
const plaintext = toUint8(data);
|
|
101
|
+
const ciphertext = nacl.box.after(plaintext, nonce, sharedKey);
|
|
102
|
+
const out = new Uint8Array(nonce.byteLength + ciphertext.byteLength);
|
|
103
|
+
out.set(nonce, 0);
|
|
104
|
+
out.set(ciphertext, nonce.byteLength);
|
|
105
|
+
return toArrayBuffer(out);
|
|
106
|
+
}
|
|
107
|
+
export function decrypt(sharedKey, data) {
|
|
108
|
+
const bytes = new Uint8Array(data);
|
|
109
|
+
if (bytes.byteLength < NONCE_LENGTH) {
|
|
110
|
+
throw new Error("Ciphertext bundle too short");
|
|
111
|
+
}
|
|
112
|
+
const nonce = bytes.slice(0, NONCE_LENGTH);
|
|
113
|
+
const ciphertext = bytes.slice(NONCE_LENGTH);
|
|
114
|
+
const opened = nacl.box.open.after(ciphertext, nonce, sharedKey);
|
|
115
|
+
if (!opened) {
|
|
116
|
+
throw new Error("Decryption failed");
|
|
117
|
+
}
|
|
118
|
+
const plaintext = toArrayBuffer(opened);
|
|
119
|
+
try {
|
|
120
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(plaintext);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return plaintext;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
|
package/dist/e2ee.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createClientChannel, createDaemonChannel, EncryptedChannel, } from "./encrypted-channel.js";
|
|
2
|
+
export type { Transport, EncryptedChannelEvents } from "./encrypted-channel.js";
|
|
3
|
+
export { generateKeyPair, exportPublicKey, importPublicKey, exportSecretKey, importSecretKey, } from "./crypto.js";
|
|
4
|
+
export type { KeyPair, SharedKey } from "./crypto.js";
|
|
5
|
+
//# sourceMappingURL=e2ee.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"e2ee.d.ts","sourceRoot":"","sources":["../src/e2ee.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEhF,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/e2ee.js
ADDED
package/dist/e2ee.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"e2ee.js","sourceRoot":"","sources":["../src/e2ee.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,GAChB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypted channel that wraps a WebSocket-like transport.
|
|
3
|
+
*
|
|
4
|
+
* Handles ECDH handshake and encrypts/decrypts all messages.
|
|
5
|
+
* Works identically for daemon and client sides.
|
|
6
|
+
*/
|
|
7
|
+
import { type KeyPair, type SharedKey } from "./crypto.js";
|
|
8
|
+
export interface Transport {
|
|
9
|
+
send(data: string | ArrayBuffer): void;
|
|
10
|
+
close(code?: number, reason?: string): void;
|
|
11
|
+
onmessage: ((data: string | ArrayBuffer) => void) | null;
|
|
12
|
+
onclose: ((code: number, reason: string) => void) | null;
|
|
13
|
+
onerror: ((error: Error) => void) | null;
|
|
14
|
+
}
|
|
15
|
+
export interface EncryptedChannelEvents {
|
|
16
|
+
onopen?: () => void;
|
|
17
|
+
onmessage?: (data: string | ArrayBuffer) => void;
|
|
18
|
+
onclose?: (code: number, reason: string) => void;
|
|
19
|
+
onerror?: (error: Error) => void;
|
|
20
|
+
}
|
|
21
|
+
type ChannelState = "connecting" | "handshaking" | "open" | "closed";
|
|
22
|
+
type EncryptedChannelOptions = {
|
|
23
|
+
/**
|
|
24
|
+
* If set, the channel can validate repeated plaintext `{type:"hello"}`
|
|
25
|
+
* messages even after it is open.
|
|
26
|
+
*
|
|
27
|
+
* This is useful for robustness when the client retries the handshake
|
|
28
|
+
* (e.g., it didn't observe the daemon's `{type:"ready"}` yet). In that case,
|
|
29
|
+
* the daemon should re-send `{type:"ready"}` without changing keys.
|
|
30
|
+
*/
|
|
31
|
+
daemonKeyPair?: KeyPair;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Creates an encrypted channel as the initiator (client).
|
|
35
|
+
*
|
|
36
|
+
* The client:
|
|
37
|
+
* 1. Receives daemon's public key via QR code
|
|
38
|
+
* 2. Generates own keypair
|
|
39
|
+
* 3. Sends hello with own public key
|
|
40
|
+
* 4. Derives shared key and starts encrypted communication
|
|
41
|
+
*/
|
|
42
|
+
export declare function createClientChannel(transport: Transport, daemonPublicKeyB64: string, events?: EncryptedChannelEvents): Promise<EncryptedChannel>;
|
|
43
|
+
/**
|
|
44
|
+
* Creates an encrypted channel as the responder (daemon).
|
|
45
|
+
*
|
|
46
|
+
* The daemon:
|
|
47
|
+
* 1. Has pre-generated keypair (public key was in QR)
|
|
48
|
+
* 2. Waits for client's hello with their public key
|
|
49
|
+
* 3. Derives shared key and starts encrypted communication
|
|
50
|
+
*/
|
|
51
|
+
export declare function createDaemonChannel(transport: Transport, daemonKeyPair: KeyPair, events?: EncryptedChannelEvents): Promise<EncryptedChannel>;
|
|
52
|
+
/**
|
|
53
|
+
* Encrypted channel that wraps a transport with E2EE.
|
|
54
|
+
*/
|
|
55
|
+
export declare class EncryptedChannel {
|
|
56
|
+
private transport;
|
|
57
|
+
private sharedKey;
|
|
58
|
+
private state;
|
|
59
|
+
private events;
|
|
60
|
+
private options;
|
|
61
|
+
private pendingSends;
|
|
62
|
+
private onOpenCallbacks;
|
|
63
|
+
private onCloseCallbacks;
|
|
64
|
+
constructor(transport: Transport, sharedKey: SharedKey, events?: EncryptedChannelEvents, options?: EncryptedChannelOptions);
|
|
65
|
+
setState(state: ChannelState): void;
|
|
66
|
+
private handleMessage;
|
|
67
|
+
send(data: string | ArrayBuffer): Promise<void>;
|
|
68
|
+
private flushPendingSends;
|
|
69
|
+
close(code?: number, reason?: string): void;
|
|
70
|
+
isOpen(): boolean;
|
|
71
|
+
onTransitionToOpen(cb: () => void): void;
|
|
72
|
+
onClose(cb: () => void): void;
|
|
73
|
+
}
|
|
74
|
+
export {};
|
|
75
|
+
//# sourceMappingURL=encrypted-channel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encrypted-channel.d.ts","sourceRoot":"","sources":["../src/encrypted-channel.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH,OAAO,EAOL,KAAK,OAAO,EACZ,KAAK,SAAS,EACf,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAAC;IACvC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,KAAK,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,KAAK,YAAY,GAAG,YAAY,GAAG,aAAa,GAAG,MAAM,GAAG,QAAQ,CAAC;AAErE,KAAK,uBAAuB,GAAG;IAC7B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAcF;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,SAAS,EACpB,kBAAkB,EAAE,MAAM,EAC1B,MAAM,GAAE,sBAA2B,GAClC,OAAO,CAAC,gBAAgB,CAAC,CAkD3B;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,OAAO,EACtB,MAAM,GAAE,sBAA2B,GAClC,OAAO,CAAC,gBAAgB,CAAC,CA2D3B;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,YAAY,CAAmC;IACvD,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,gBAAgB,CAAyB;gBAG/C,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,MAAM,GAAE,sBAA2B,EACnC,OAAO,GAAE,uBAA4B;IAkBvC,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;YAIrB,aAAa;IAmHrB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;YAkBvC,iBAAiB;IAS/B,KAAK,CAAC,IAAI,SAAO,EAAE,MAAM,SAAmB,GAAG,IAAI;IAKnD,MAAM,IAAI,OAAO;IAIjB,kBAAkB,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;IAIxC,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI;CAG9B"}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* Encrypted channel that wraps a WebSocket-like transport.
|
|
4
|
+
*
|
|
5
|
+
* Handles ECDH handshake and encrypts/decrypts all messages.
|
|
6
|
+
* Works identically for daemon and client sides.
|
|
7
|
+
*/
|
|
8
|
+
import { generateKeyPair, exportPublicKey, importPublicKey, deriveSharedKey, encrypt, decrypt, } from "./crypto.js";
|
|
9
|
+
import { arrayBufferToBase64, base64ToArrayBuffer } from "./base64.js";
|
|
10
|
+
const HANDSHAKE_RETRY_MS = 1000;
|
|
11
|
+
const MAX_PENDING_SENDS = 200;
|
|
12
|
+
/**
|
|
13
|
+
* Creates an encrypted channel as the initiator (client).
|
|
14
|
+
*
|
|
15
|
+
* The client:
|
|
16
|
+
* 1. Receives daemon's public key via QR code
|
|
17
|
+
* 2. Generates own keypair
|
|
18
|
+
* 3. Sends hello with own public key
|
|
19
|
+
* 4. Derives shared key and starts encrypted communication
|
|
20
|
+
*/
|
|
21
|
+
export async function createClientChannel(transport, daemonPublicKeyB64, events = {}) {
|
|
22
|
+
const keyPair = generateKeyPair();
|
|
23
|
+
const daemonPublicKey = importPublicKey(daemonPublicKeyB64);
|
|
24
|
+
const sharedKey = deriveSharedKey(keyPair.secretKey, daemonPublicKey);
|
|
25
|
+
const channel = new EncryptedChannel(transport, sharedKey, events);
|
|
26
|
+
// Send hello with our public key
|
|
27
|
+
const ourPublicKeyB64 = exportPublicKey(keyPair.publicKey);
|
|
28
|
+
const hello = { type: "hello", key: ourPublicKeyB64 };
|
|
29
|
+
const helloText = JSON.stringify(hello);
|
|
30
|
+
let retry = null;
|
|
31
|
+
const emitSendError = (error) => {
|
|
32
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
33
|
+
events.onerror?.(err);
|
|
34
|
+
};
|
|
35
|
+
const sendHello = () => {
|
|
36
|
+
try {
|
|
37
|
+
transport.send(helloText);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
// This can happen during daemon restarts while the socket transitions
|
|
42
|
+
// through CLOSING/CLOSED states. Report it but do not throw from timers.
|
|
43
|
+
emitSendError(error);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const clearRetry = () => {
|
|
48
|
+
if (retry) {
|
|
49
|
+
clearInterval(retry);
|
|
50
|
+
retry = null;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
channel.onTransitionToOpen(() => clearRetry());
|
|
54
|
+
channel.onClose(() => clearRetry());
|
|
55
|
+
sendHello();
|
|
56
|
+
retry = setInterval(() => {
|
|
57
|
+
if (channel.isOpen()) {
|
|
58
|
+
clearRetry();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
sendHello();
|
|
62
|
+
}, HANDSHAKE_RETRY_MS);
|
|
63
|
+
// Avoid keeping Node processes alive (e.g. tests) if the handshake is stuck.
|
|
64
|
+
retry.unref?.();
|
|
65
|
+
return channel;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Creates an encrypted channel as the responder (daemon).
|
|
69
|
+
*
|
|
70
|
+
* The daemon:
|
|
71
|
+
* 1. Has pre-generated keypair (public key was in QR)
|
|
72
|
+
* 2. Waits for client's hello with their public key
|
|
73
|
+
* 3. Derives shared key and starts encrypted communication
|
|
74
|
+
*/
|
|
75
|
+
export async function createDaemonChannel(transport, daemonKeyPair, events = {}) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const bufferedMessages = [];
|
|
78
|
+
const shouldIgnorePostHelloPlaintext = (data) => {
|
|
79
|
+
try {
|
|
80
|
+
const text = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
81
|
+
const parsed = JSON.parse(text);
|
|
82
|
+
return parsed.type === "hello" || parsed.type === "ready";
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
transport.onmessage = async (data) => {
|
|
89
|
+
try {
|
|
90
|
+
const helloText = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
91
|
+
const msg = JSON.parse(helloText);
|
|
92
|
+
if (msg.type !== "hello" || !msg.key) {
|
|
93
|
+
throw new Error("Invalid hello message");
|
|
94
|
+
}
|
|
95
|
+
// Buffer any subsequent messages that arrive while we're doing async
|
|
96
|
+
// WebCrypto work to derive the shared key. Without this, it's possible
|
|
97
|
+
// for the next message (already encrypted) to be misinterpreted as a
|
|
98
|
+
// second hello, causing the handshake to fail.
|
|
99
|
+
transport.onmessage = (next) => {
|
|
100
|
+
bufferedMessages.push(next);
|
|
101
|
+
};
|
|
102
|
+
const clientPublicKey = importPublicKey(msg.key);
|
|
103
|
+
const sharedKey = deriveSharedKey(daemonKeyPair.secretKey, clientPublicKey);
|
|
104
|
+
const channel = new EncryptedChannel(transport, sharedKey, events, { daemonKeyPair });
|
|
105
|
+
transport.send(JSON.stringify({ type: "ready" }));
|
|
106
|
+
channel.setState("open");
|
|
107
|
+
events.onopen?.();
|
|
108
|
+
for (const buffered of bufferedMessages) {
|
|
109
|
+
if (shouldIgnorePostHelloPlaintext(buffered))
|
|
110
|
+
continue;
|
|
111
|
+
transport.onmessage?.(buffered);
|
|
112
|
+
}
|
|
113
|
+
resolve(channel);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
reject(error);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
transport.onerror = (error) => {
|
|
120
|
+
reject(error);
|
|
121
|
+
};
|
|
122
|
+
transport.onclose = (code, reason) => {
|
|
123
|
+
reject(new Error(`Connection closed during handshake: ${code} ${reason}`));
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Encrypted channel that wraps a transport with E2EE.
|
|
129
|
+
*/
|
|
130
|
+
export class EncryptedChannel {
|
|
131
|
+
constructor(transport, sharedKey, events = {}, options = {}) {
|
|
132
|
+
this.state = "handshaking";
|
|
133
|
+
this.pendingSends = [];
|
|
134
|
+
this.onOpenCallbacks = [];
|
|
135
|
+
this.onCloseCallbacks = [];
|
|
136
|
+
this.transport = transport;
|
|
137
|
+
this.sharedKey = sharedKey;
|
|
138
|
+
this.events = events;
|
|
139
|
+
this.options = options;
|
|
140
|
+
transport.onmessage = (data) => this.handleMessage(data);
|
|
141
|
+
transport.onclose = (code, reason) => {
|
|
142
|
+
this.state = "closed";
|
|
143
|
+
this.events.onclose?.(code, reason);
|
|
144
|
+
for (const cb of this.onCloseCallbacks)
|
|
145
|
+
cb();
|
|
146
|
+
};
|
|
147
|
+
transport.onerror = (error) => {
|
|
148
|
+
this.events.onerror?.(error);
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
setState(state) {
|
|
152
|
+
this.state = state;
|
|
153
|
+
}
|
|
154
|
+
async handleMessage(data) {
|
|
155
|
+
if (this.state === "handshaking") {
|
|
156
|
+
try {
|
|
157
|
+
const text = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
158
|
+
const msg = JSON.parse(text);
|
|
159
|
+
if (msg.type === "ready") {
|
|
160
|
+
this.state = "open";
|
|
161
|
+
this.events.onopen?.();
|
|
162
|
+
for (const cb of this.onOpenCallbacks)
|
|
163
|
+
cb();
|
|
164
|
+
await this.flushPendingSends();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// ignore non-ready handshake traffic
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (this.state !== "open")
|
|
173
|
+
return;
|
|
174
|
+
try {
|
|
175
|
+
const ciphertext = await (async () => {
|
|
176
|
+
// Handle (or ignore) any stray plaintext handshake traffic.
|
|
177
|
+
try {
|
|
178
|
+
const text = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
179
|
+
if (text.trim().startsWith("{")) {
|
|
180
|
+
const parsed = JSON.parse(text);
|
|
181
|
+
if (parsed.type === "hello" && typeof parsed.key === "string") {
|
|
182
|
+
if (this.options.daemonKeyPair) {
|
|
183
|
+
try {
|
|
184
|
+
const clientPublicKey = importPublicKey(parsed.key);
|
|
185
|
+
const nextSharedKey = deriveSharedKey(this.options.daemonKeyPair.secretKey, clientPublicKey);
|
|
186
|
+
// If it's the same client key (handshake retry), re-send
|
|
187
|
+
// "ready" but do not re-key. Re-keying here would desync
|
|
188
|
+
// the channel and cause decrypt failures.
|
|
189
|
+
if (keysEqual(nextSharedKey, this.sharedKey)) {
|
|
190
|
+
this.transport.send(JSON.stringify({ type: "ready" }));
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
// Different key implies a new client connection (common with relays
|
|
194
|
+
// where the daemon's socket stays open while the client reconnects).
|
|
195
|
+
// Re-key and re-send "ready". Drop any queued sends to avoid leaking
|
|
196
|
+
// messages between logical client sessions.
|
|
197
|
+
this.state = "handshaking";
|
|
198
|
+
this.sharedKey = nextSharedKey;
|
|
199
|
+
this.pendingSends = [];
|
|
200
|
+
this.transport.send(JSON.stringify({ type: "ready" }));
|
|
201
|
+
this.state = "open";
|
|
202
|
+
await this.flushPendingSends();
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
if (parsed.type === "ready") {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
// Any other JSON-looking payload is plaintext app traffic, which
|
|
215
|
+
// means the peer is not encrypting (or we are out of sync).
|
|
216
|
+
throw new Error("Received plaintext frame on encrypted channel");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
// If we detected plaintext protocol mismatch, fail hard.
|
|
221
|
+
if (error instanceof Error && error.message.includes("plaintext frame")) {
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
// Otherwise ignore JSON parse/TextDecoder failures and fall back to
|
|
225
|
+
// decoding ciphertext below.
|
|
226
|
+
}
|
|
227
|
+
if (typeof data === "string") {
|
|
228
|
+
return base64ToArrayBuffer(data);
|
|
229
|
+
}
|
|
230
|
+
// Some WebSocket implementations deliver text frames as ArrayBuffer.
|
|
231
|
+
// Our protocol always transmits ciphertext as base64 text.
|
|
232
|
+
try {
|
|
233
|
+
const decoded = new TextDecoder().decode(data);
|
|
234
|
+
return base64ToArrayBuffer(decoded);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return data;
|
|
238
|
+
}
|
|
239
|
+
})();
|
|
240
|
+
if (ciphertext) {
|
|
241
|
+
const plaintext = await decrypt(this.sharedKey, ciphertext);
|
|
242
|
+
this.events.onmessage?.(plaintext);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
247
|
+
// Treat decryption/protocol errors as fatal so the peer can reconnect and
|
|
248
|
+
// re-handshake. Emitting an error event here can cause higher-level code
|
|
249
|
+
// to tear down the session without triggering a clean reconnect.
|
|
250
|
+
try {
|
|
251
|
+
this.transport.close(1011, err.message);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
// ignore
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async send(data) {
|
|
259
|
+
if (this.state === "handshaking") {
|
|
260
|
+
if (this.pendingSends.length >= MAX_PENDING_SENDS) {
|
|
261
|
+
this.pendingSends.shift();
|
|
262
|
+
}
|
|
263
|
+
this.pendingSends.push(data);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (this.state !== "open") {
|
|
267
|
+
throw new Error("Channel not open");
|
|
268
|
+
}
|
|
269
|
+
const ciphertext = await encrypt(this.sharedKey, data);
|
|
270
|
+
// Send as base64 for WebSocket text compatibility
|
|
271
|
+
this.transport.send(arrayBufferToBase64(ciphertext));
|
|
272
|
+
}
|
|
273
|
+
async flushPendingSends() {
|
|
274
|
+
if (this.state !== "open")
|
|
275
|
+
return;
|
|
276
|
+
const pending = this.pendingSends;
|
|
277
|
+
this.pendingSends = [];
|
|
278
|
+
for (const item of pending) {
|
|
279
|
+
await this.send(item);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
close(code = 1000, reason = "Normal closure") {
|
|
283
|
+
this.state = "closed";
|
|
284
|
+
this.transport.close(code, reason);
|
|
285
|
+
}
|
|
286
|
+
isOpen() {
|
|
287
|
+
return this.state === "open";
|
|
288
|
+
}
|
|
289
|
+
onTransitionToOpen(cb) {
|
|
290
|
+
this.onOpenCallbacks.push(cb);
|
|
291
|
+
}
|
|
292
|
+
onClose(cb) {
|
|
293
|
+
this.onCloseCallbacks.push(cb);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function keysEqual(a, b) {
|
|
297
|
+
if (a.byteLength !== b.byteLength)
|
|
298
|
+
return false;
|
|
299
|
+
for (let i = 0; i < a.byteLength; i += 1) {
|
|
300
|
+
if (a[i] !== b[i])
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=encrypted-channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encrypted-channel.js","sourceRoot":"","sources":["../src/encrypted-channel.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { ConnectionRole, RelaySessionAttachment, } from "./types.js";
|
|
2
|
+
export { generateKeyPair, exportPublicKey, importPublicKey, deriveSharedKey, encrypt, decrypt, } from "./crypto.js";
|
|
3
|
+
export { createClientChannel, createDaemonChannel, EncryptedChannel, } from "./encrypted-channel.js";
|
|
4
|
+
export type { Transport, EncryptedChannelEvents } from "./encrypted-channel.js";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,OAAO,EACP,OAAO,GACR,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,OAAO,EACP,OAAO,GACR,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay connection types and interfaces.
|
|
3
|
+
*
|
|
4
|
+
* The relay bridges two WebSocket connections:
|
|
5
|
+
* - Server (daemon): The Paseo server connecting to the relay
|
|
6
|
+
* - Client (app): The mobile/web app connecting to the relay
|
|
7
|
+
*
|
|
8
|
+
* Messages are forwarded bidirectionally without modification.
|
|
9
|
+
*/
|
|
10
|
+
export type ConnectionRole = "server" | "client";
|
|
11
|
+
export interface RelaySessionAttachment {
|
|
12
|
+
serverId: string;
|
|
13
|
+
role: ConnectionRole;
|
|
14
|
+
/**
|
|
15
|
+
* Unique id for the client connection. Allows the daemon to create an
|
|
16
|
+
* independent socket + E2EE channel per connected client.
|
|
17
|
+
*/
|
|
18
|
+
clientId?: string | null;
|
|
19
|
+
createdAt: number;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEjD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,cAAc,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay connection types and interfaces.
|
|
3
|
+
*
|
|
4
|
+
* The relay bridges two WebSocket connections:
|
|
5
|
+
* - Server (daemon): The Paseo server connecting to the relay
|
|
6
|
+
* - Client (app): The mobile/web app connecting to the relay
|
|
7
|
+
*
|
|
8
|
+
* Messages are forwarded bidirectionally without modification.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@getpaseo/relay",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Paseo relay for bridging daemon and client connections",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"node": "./dist/index.js",
|
|
17
|
+
"default": "./src/index.ts"
|
|
18
|
+
},
|
|
19
|
+
"./e2ee": {
|
|
20
|
+
"types": "./dist/e2ee.d.ts",
|
|
21
|
+
"node": "./dist/e2ee.js",
|
|
22
|
+
"default": "./src/e2ee.ts"
|
|
23
|
+
},
|
|
24
|
+
"./cloudflare": {
|
|
25
|
+
"types": "./dist/cloudflare-adapter.d.ts",
|
|
26
|
+
"node": "./dist/cloudflare-adapter.js",
|
|
27
|
+
"default": "./src/cloudflare-adapter.ts"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && tsc -p tsconfig.json --incremental false",
|
|
32
|
+
"prepack": "npm run build",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"base64-js": "^1.5.1",
|
|
39
|
+
"tweetnacl": "^1.0.3",
|
|
40
|
+
"ws": "^8.14.2"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@cloudflare/workers-types": "^4.20241230.0",
|
|
44
|
+
"@types/node": "^20.9.0",
|
|
45
|
+
"@types/ws": "^8.5.8",
|
|
46
|
+
"typescript": "^5.2.2",
|
|
47
|
+
"vitest": "^3.2.4"
|
|
48
|
+
}
|
|
49
|
+
}
|