@evolu/nodejs 1.0.1-preview.1 → 1.0.1-preview.11
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/Crypto.d.ts +3 -0
- package/dist/Crypto.d.ts.map +1 -0
- package/dist/Crypto.js +2 -0
- package/dist/Evolu/Relay.d.ts +17 -2
- package/dist/Evolu/Relay.d.ts.map +1 -1
- package/dist/Evolu/Relay.js +118 -37
- package/package.json +9 -7
- package/src/Crypto.ts +4 -0
- package/src/Evolu/Relay.ts +168 -61
package/dist/Crypto.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Crypto.d.ts","sourceRoot":"","sources":["../src/Crypto.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,eAAO,MAAM,qBAAqB,QAAO,eAAkC,CAAC"}
|
package/dist/Crypto.js
ADDED
package/dist/Evolu/Relay.d.ts
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
import { ConsoleDep } from "@evolu/common";
|
|
1
|
+
import { ConsoleDep, CreateSqliteDriverDep, Result, SqliteError } from "@evolu/common";
|
|
2
2
|
import { Relay, RelayConfig } from "@evolu/common/evolu";
|
|
3
3
|
export interface NodeJsRelayConfig extends RelayConfig {
|
|
4
|
+
/** The port number for the HTTP server. */
|
|
4
5
|
readonly port?: number;
|
|
5
6
|
}
|
|
6
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Creates an Evolu relay server.
|
|
9
|
+
*
|
|
10
|
+
* This implementation uses Node.js and better-sqlite3. Additional relay
|
|
11
|
+
* implementations will be provided for other platforms (Bun, Deno, Cloudflare
|
|
12
|
+
* Workers, Vercel Edge, etc.).
|
|
13
|
+
*/
|
|
14
|
+
export declare const createNodeJsRelay: (deps: ConsoleDep) => (config: NodeJsRelayConfig) => Promise<Result<Relay, SqliteError>>;
|
|
15
|
+
/**
|
|
16
|
+
* Creates an Evolu relay server with a custom SQLite driver.
|
|
17
|
+
*
|
|
18
|
+
* Use this when you need to provide a different SQLite driver implementation
|
|
19
|
+
* (e.g., using alternative SQLite libraries).
|
|
20
|
+
*/
|
|
21
|
+
export declare const createNodeJsRelayWithSqliteDriver: (deps: ConsoleDep & CreateSqliteDriverDep) => (config: NodeJsRelayConfig) => Promise<Result<Relay, SqliteError>>;
|
|
7
22
|
//# sourceMappingURL=Relay.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../src/Evolu/Relay.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,
|
|
1
|
+
{"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../src/Evolu/Relay.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAIV,qBAAqB,EAKrB,MAAM,EAEN,WAAW,EAGZ,MAAM,eAAe,CAAC;AACvB,OAAO,EASL,KAAK,EACL,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAO7B,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC3B,MAAM,UAAU,MAChB,QAAQ,iBAAiB,KAAG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CAInD,CAAC;AAEf;;;;;GAKG;AACH,eAAO,MAAM,iCAAiC,GAC3C,MAAM,UAAU,GAAG,qBAAqB,MACxC,QAAQ,iBAAiB,KAAG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CAKnD,CAAC"}
|
package/dist/Evolu/Relay.js
CHANGED
|
@@ -1,80 +1,161 @@
|
|
|
1
|
-
import { createManyToManyMap, createRandom, createSqlite,
|
|
2
|
-
import { applyProtocolMessageAsRelay,
|
|
1
|
+
import { createManyToManyMap, createRandom, createSqlite, isAsync, ok, SimpleName, Uint8Array, } from "@evolu/common";
|
|
2
|
+
import { applyProtocolMessageAsRelay, createBaseSqliteStorageTables, createRelayLogger, createRelaySqliteStorage, createRelayStorageTables, defaultProtocolMessageMaxSize, parseOwnerIdFromOwnerWebSocketTransportUrl, } from "@evolu/common/evolu";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { createServer } from "http";
|
|
3
5
|
import { WebSocket, WebSocketServer } from "ws";
|
|
4
6
|
import { createBetterSqliteDriver } from "../BetterSqliteDriver.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
7
|
+
import { createTimingSafeEqual } from "../Crypto.js";
|
|
8
|
+
/**
|
|
9
|
+
* Creates an Evolu relay server.
|
|
10
|
+
*
|
|
11
|
+
* This implementation uses Node.js and better-sqlite3. Additional relay
|
|
12
|
+
* implementations will be provided for other platforms (Bun, Deno, Cloudflare
|
|
13
|
+
* Workers, Vercel Edge, etc.).
|
|
14
|
+
*/
|
|
15
|
+
export const createNodeJsRelay = (deps) => (config) => createNodeJsRelayWithSqliteDriver({
|
|
16
|
+
...deps,
|
|
17
|
+
createSqliteDriver: createBetterSqliteDriver,
|
|
18
|
+
})(config);
|
|
19
|
+
/**
|
|
20
|
+
* Creates an Evolu relay server with a custom SQLite driver.
|
|
21
|
+
*
|
|
22
|
+
* Use this when you need to provide a different SQLite driver implementation
|
|
23
|
+
* (e.g., using alternative SQLite libraries).
|
|
24
|
+
*/
|
|
25
|
+
export const createNodeJsRelayWithSqliteDriver = (deps) => (config) => createNodeJsRelayWithDeps({
|
|
26
|
+
...deps,
|
|
27
|
+
random: createRandom(),
|
|
28
|
+
timingSafeEqual: createTimingSafeEqual(),
|
|
29
|
+
})(config);
|
|
30
|
+
const createNodeJsRelayWithDeps = (deps) => async ({ port = 443, name = SimpleName.orThrow("evolu-relay"), enableLogging = false, isOwnerAllowed, isOwnerWithinQuota, }) => {
|
|
31
|
+
const log = createRelayLogger(deps);
|
|
32
|
+
log.started(enableLogging, port);
|
|
33
|
+
const dbFileExists = existsSync(`${name}.db`);
|
|
34
|
+
const sqlite = await createSqlite(deps)(name);
|
|
35
|
+
if (!sqlite.ok)
|
|
36
|
+
return sqlite;
|
|
37
|
+
const depsWithSqlite = { ...deps, sqlite: sqlite.value };
|
|
38
|
+
if (!dbFileExists) {
|
|
39
|
+
const baseTables = createBaseSqliteStorageTables(depsWithSqlite);
|
|
40
|
+
if (!baseTables.ok)
|
|
41
|
+
return baseTables;
|
|
42
|
+
const relayTables = createRelayStorageTables(depsWithSqlite);
|
|
43
|
+
if (!relayTables.ok)
|
|
44
|
+
return relayTables;
|
|
45
|
+
}
|
|
46
|
+
const storage = createRelaySqliteStorage(depsWithSqlite)({
|
|
47
|
+
onStorageError: log.storageError,
|
|
48
|
+
...(isOwnerWithinQuota && { isOwnerWithinQuota }),
|
|
49
|
+
});
|
|
50
|
+
const server = createServer();
|
|
23
51
|
const wss = new WebSocketServer({
|
|
24
|
-
maxPayload:
|
|
25
|
-
|
|
52
|
+
maxPayload: defaultProtocolMessageMaxSize,
|
|
53
|
+
noServer: true,
|
|
26
54
|
});
|
|
27
55
|
const ownerSocketsMap = createManyToManyMap();
|
|
56
|
+
server.on("upgrade", (request, socket, head) => {
|
|
57
|
+
socket.on("error", log.upgradeSocketError);
|
|
58
|
+
const completeUpgrade = () => {
|
|
59
|
+
socket.removeListener("error", log.upgradeSocketError);
|
|
60
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
61
|
+
wss.emit("connection", ws, request);
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
if (!isOwnerAllowed) {
|
|
65
|
+
completeUpgrade();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const ownerId = parseOwnerIdFromOwnerWebSocketTransportUrl(request.url ?? "");
|
|
69
|
+
if (!ownerId) {
|
|
70
|
+
log.invalidOrMissingOwnerIdInUrl(request.url);
|
|
71
|
+
socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
72
|
+
socket.destroy();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
void (async () => {
|
|
76
|
+
const result = isOwnerAllowed(ownerId);
|
|
77
|
+
const isAllowed = isAsync(result) ? await result : result;
|
|
78
|
+
if (!isAllowed) {
|
|
79
|
+
log.unauthorizedOwner(ownerId);
|
|
80
|
+
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
81
|
+
socket.destroy();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
completeUpgrade();
|
|
85
|
+
})();
|
|
86
|
+
});
|
|
28
87
|
wss.on("connection", (ws) => {
|
|
29
|
-
|
|
88
|
+
log.connectionEstablished(wss.clients.size);
|
|
89
|
+
ws.on("error", (error) => {
|
|
90
|
+
log.connectionWebSocketError(error);
|
|
91
|
+
});
|
|
30
92
|
const options = {
|
|
31
93
|
subscribe: (ownerId) => {
|
|
32
94
|
ownerSocketsMap.add(ownerId, ws);
|
|
95
|
+
log.relayOptionSubscribe(ownerId, () => ownerSocketsMap.getValues(ownerId)?.size ?? 0);
|
|
96
|
+
},
|
|
97
|
+
unsubscribe: (ownerId) => {
|
|
98
|
+
ownerSocketsMap.remove(ownerId, ws);
|
|
99
|
+
log.relayOptionUnsubscribe(ownerId, () => ownerSocketsMap.getValues(ownerId)?.size ?? 0);
|
|
33
100
|
},
|
|
34
101
|
broadcast: (ownerId, message) => {
|
|
35
102
|
const sockets = ownerSocketsMap.getValues(ownerId);
|
|
103
|
+
if (!sockets)
|
|
104
|
+
return;
|
|
105
|
+
let broadcastCount = 0;
|
|
36
106
|
for (const socket of sockets) {
|
|
37
107
|
if (socket !== ws && socket.readyState === WebSocket.OPEN) {
|
|
38
108
|
socket.send(message, { binary: true });
|
|
109
|
+
broadcastCount++;
|
|
39
110
|
}
|
|
40
111
|
}
|
|
112
|
+
log.relayOptionBroadcast(ownerId, broadcastCount, sockets.size);
|
|
41
113
|
},
|
|
42
114
|
};
|
|
43
115
|
ws.on("message", (message) => {
|
|
44
116
|
if (!Uint8Array.is(message))
|
|
45
117
|
return;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
118
|
+
log.messageLength(message.length);
|
|
119
|
+
applyProtocolMessageAsRelay({ storage })(message, options)
|
|
120
|
+
.then((response) => {
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
log.applyProtocolMessageAsRelayError(response.error);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
ws.send(response.value.message, { binary: true });
|
|
126
|
+
log.responseLength(response.value.message.length);
|
|
127
|
+
})
|
|
128
|
+
.catch(log.applyProtocolMessageAsRelayUnknownError);
|
|
54
129
|
});
|
|
55
130
|
ws.on("close", () => {
|
|
56
131
|
ownerSocketsMap.deleteValue(ws);
|
|
132
|
+
log.connectionClosed(wss.clients.size);
|
|
57
133
|
});
|
|
58
134
|
});
|
|
59
|
-
|
|
60
|
-
|
|
135
|
+
server.listen(port);
|
|
136
|
+
const dispose = () => {
|
|
137
|
+
log.shuttingDown();
|
|
61
138
|
wss.clients.forEach((client) => {
|
|
62
139
|
if (client.readyState === WebSocket.OPEN) {
|
|
63
140
|
client.close(1000, "Evolu Relay shutting down");
|
|
64
141
|
}
|
|
65
142
|
});
|
|
66
143
|
wss.close(() => {
|
|
67
|
-
|
|
144
|
+
log.webSocketServerDisposed();
|
|
145
|
+
});
|
|
146
|
+
server.close(() => {
|
|
147
|
+
log.httpServerDisposed();
|
|
68
148
|
});
|
|
69
149
|
};
|
|
70
150
|
let isDisposed = false;
|
|
71
|
-
|
|
151
|
+
const relay = {
|
|
72
152
|
[Symbol.dispose]: () => {
|
|
73
153
|
if (isDisposed)
|
|
74
154
|
return;
|
|
75
155
|
isDisposed = true;
|
|
76
|
-
sqlite[Symbol.dispose]();
|
|
77
|
-
|
|
156
|
+
sqlite.value[Symbol.dispose]();
|
|
157
|
+
dispose();
|
|
78
158
|
},
|
|
79
159
|
};
|
|
160
|
+
return ok(relay);
|
|
80
161
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evolu/nodejs",
|
|
3
|
-
"version": "1.0.1-preview.
|
|
3
|
+
"version": "1.0.1-preview.11",
|
|
4
4
|
"description": "Evolu for Node.js",
|
|
5
5
|
"author": "Daniel Steigerwald <daniel@steigerwald.cz>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,19 +20,21 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"better-sqlite3": "^
|
|
23
|
+
"better-sqlite3": "^12.1.1",
|
|
24
24
|
"ws": "^8.18.2"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/better-sqlite3": "^7.6.13",
|
|
28
|
-
"@types/node": "^22.
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
28
|
+
"@types/node": "^22.17.1",
|
|
29
|
+
"@types/ws": "^8.18.1",
|
|
30
|
+
"shx": "^0.4.0",
|
|
31
|
+
"typescript": "^5.9.2",
|
|
32
|
+
"vitest": "^4.0.4",
|
|
33
|
+
"@evolu/common": "6.0.1-preview.29",
|
|
32
34
|
"@evolu/tsconfig": "0.0.2"
|
|
33
35
|
},
|
|
34
36
|
"peerDependencies": {
|
|
35
|
-
"@evolu/common": "^6.0.1-preview.
|
|
37
|
+
"@evolu/common": "^6.0.1-preview.29"
|
|
36
38
|
},
|
|
37
39
|
"engines": {
|
|
38
40
|
"node": ">=22.0.0"
|
package/src/Crypto.ts
ADDED
package/src/Evolu/Relay.ts
CHANGED
|
@@ -3,114 +3,215 @@ import {
|
|
|
3
3
|
createManyToManyMap,
|
|
4
4
|
createRandom,
|
|
5
5
|
createSqlite,
|
|
6
|
-
|
|
6
|
+
CreateSqliteDriverDep,
|
|
7
|
+
isAsync,
|
|
8
|
+
ok,
|
|
7
9
|
OwnerId,
|
|
10
|
+
RandomDep,
|
|
11
|
+
Result,
|
|
8
12
|
SimpleName,
|
|
13
|
+
SqliteError,
|
|
14
|
+
TimingSafeEqualDep,
|
|
9
15
|
Uint8Array,
|
|
10
16
|
} from "@evolu/common";
|
|
11
17
|
import {
|
|
12
18
|
applyProtocolMessageAsRelay,
|
|
13
19
|
ApplyProtocolMessageAsRelayOptions,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
createBaseSqliteStorageTables,
|
|
21
|
+
createRelayLogger,
|
|
22
|
+
createRelaySqliteStorage,
|
|
23
|
+
createRelayStorageTables,
|
|
24
|
+
defaultProtocolMessageMaxSize,
|
|
25
|
+
parseOwnerIdFromOwnerWebSocketTransportUrl,
|
|
17
26
|
Relay,
|
|
18
27
|
RelayConfig,
|
|
19
|
-
SqliteStorageDeps,
|
|
20
28
|
} from "@evolu/common/evolu";
|
|
29
|
+
import { existsSync } from "fs";
|
|
30
|
+
import { createServer } from "http";
|
|
21
31
|
import { WebSocket, WebSocketServer } from "ws";
|
|
22
32
|
import { createBetterSqliteDriver } from "../BetterSqliteDriver.js";
|
|
33
|
+
import { createTimingSafeEqual } from "../Crypto.js";
|
|
23
34
|
|
|
24
35
|
export interface NodeJsRelayConfig extends RelayConfig {
|
|
36
|
+
/** The port number for the HTTP server. */
|
|
25
37
|
readonly port?: number;
|
|
26
38
|
}
|
|
27
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Creates an Evolu relay server.
|
|
42
|
+
*
|
|
43
|
+
* This implementation uses Node.js and better-sqlite3. Additional relay
|
|
44
|
+
* implementations will be provided for other platforms (Bun, Deno, Cloudflare
|
|
45
|
+
* Workers, Vercel Edge, etc.).
|
|
46
|
+
*/
|
|
28
47
|
export const createNodeJsRelay =
|
|
29
48
|
(deps: ConsoleDep) =>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
...deps,
|
|
48
|
-
createSqliteDriver: createBetterSqliteDriver,
|
|
49
|
-
})(versionedName),
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
const sqliteStorageDeps: SqliteStorageDeps = {
|
|
53
|
-
sqlite,
|
|
49
|
+
(config: NodeJsRelayConfig): Promise<Result<Relay, SqliteError>> =>
|
|
50
|
+
createNodeJsRelayWithSqliteDriver({
|
|
51
|
+
...deps,
|
|
52
|
+
createSqliteDriver: createBetterSqliteDriver,
|
|
53
|
+
})(config);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Creates an Evolu relay server with a custom SQLite driver.
|
|
57
|
+
*
|
|
58
|
+
* Use this when you need to provide a different SQLite driver implementation
|
|
59
|
+
* (e.g., using alternative SQLite libraries).
|
|
60
|
+
*/
|
|
61
|
+
export const createNodeJsRelayWithSqliteDriver =
|
|
62
|
+
(deps: ConsoleDep & CreateSqliteDriverDep) =>
|
|
63
|
+
(config: NodeJsRelayConfig): Promise<Result<Relay, SqliteError>> =>
|
|
64
|
+
createNodeJsRelayWithDeps({
|
|
65
|
+
...deps,
|
|
54
66
|
random: createRandom(),
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
timingSafeEqual: createTimingSafeEqual(),
|
|
68
|
+
})(config);
|
|
69
|
+
|
|
70
|
+
const createNodeJsRelayWithDeps =
|
|
71
|
+
(deps: ConsoleDep & CreateSqliteDriverDep & RandomDep & TimingSafeEqualDep) =>
|
|
72
|
+
async ({
|
|
73
|
+
port = 443,
|
|
74
|
+
name = SimpleName.orThrow("evolu-relay"),
|
|
75
|
+
enableLogging = false,
|
|
76
|
+
isOwnerAllowed,
|
|
77
|
+
isOwnerWithinQuota,
|
|
78
|
+
}: NodeJsRelayConfig): Promise<Result<Relay, SqliteError>> => {
|
|
79
|
+
const log = createRelayLogger(deps);
|
|
80
|
+
log.started(enableLogging, port);
|
|
81
|
+
|
|
82
|
+
const dbFileExists = existsSync(`${name}.db`);
|
|
83
|
+
|
|
84
|
+
const sqlite = await createSqlite(deps)(name);
|
|
85
|
+
if (!sqlite.ok) return sqlite;
|
|
86
|
+
|
|
87
|
+
const depsWithSqlite = { ...deps, sqlite: sqlite.value };
|
|
88
|
+
|
|
89
|
+
if (!dbFileExists) {
|
|
90
|
+
const baseTables = createBaseSqliteStorageTables(depsWithSqlite);
|
|
91
|
+
if (!baseTables.ok) return baseTables;
|
|
92
|
+
|
|
93
|
+
const relayTables = createRelayStorageTables(depsWithSqlite);
|
|
94
|
+
if (!relayTables.ok) return relayTables;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const storage = createRelaySqliteStorage(depsWithSqlite)({
|
|
98
|
+
onStorageError: log.storageError,
|
|
99
|
+
...(isOwnerWithinQuota && { isOwnerWithinQuota }),
|
|
100
|
+
});
|
|
64
101
|
|
|
102
|
+
const server = createServer();
|
|
65
103
|
const wss = new WebSocketServer({
|
|
66
|
-
maxPayload:
|
|
67
|
-
|
|
104
|
+
maxPayload: defaultProtocolMessageMaxSize,
|
|
105
|
+
noServer: true,
|
|
68
106
|
});
|
|
69
107
|
|
|
70
108
|
const ownerSocketsMap = createManyToManyMap<OwnerId, WebSocket>();
|
|
71
109
|
|
|
110
|
+
server.on("upgrade", (request, socket, head) => {
|
|
111
|
+
socket.on("error", log.upgradeSocketError);
|
|
112
|
+
|
|
113
|
+
const completeUpgrade = () => {
|
|
114
|
+
socket.removeListener("error", log.upgradeSocketError);
|
|
115
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
116
|
+
wss.emit("connection", ws, request);
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (!isOwnerAllowed) {
|
|
121
|
+
completeUpgrade();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const ownerId = parseOwnerIdFromOwnerWebSocketTransportUrl(
|
|
126
|
+
request.url ?? "",
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (!ownerId) {
|
|
130
|
+
log.invalidOrMissingOwnerIdInUrl(request.url);
|
|
131
|
+
socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
132
|
+
socket.destroy();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
void (async () => {
|
|
137
|
+
const result = isOwnerAllowed(ownerId);
|
|
138
|
+
const isAllowed = isAsync(result) ? await result : result;
|
|
139
|
+
if (!isAllowed) {
|
|
140
|
+
log.unauthorizedOwner(ownerId);
|
|
141
|
+
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
142
|
+
socket.destroy();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
completeUpgrade();
|
|
146
|
+
})();
|
|
147
|
+
});
|
|
148
|
+
|
|
72
149
|
wss.on("connection", (ws) => {
|
|
73
|
-
|
|
150
|
+
log.connectionEstablished(wss.clients.size);
|
|
151
|
+
|
|
152
|
+
ws.on("error", (error) => {
|
|
153
|
+
log.connectionWebSocketError(error);
|
|
154
|
+
});
|
|
74
155
|
|
|
75
156
|
const options: ApplyProtocolMessageAsRelayOptions = {
|
|
76
157
|
subscribe: (ownerId) => {
|
|
77
158
|
ownerSocketsMap.add(ownerId, ws);
|
|
159
|
+
log.relayOptionSubscribe(
|
|
160
|
+
ownerId,
|
|
161
|
+
() => ownerSocketsMap.getValues(ownerId)?.size ?? 0,
|
|
162
|
+
);
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
unsubscribe: (ownerId) => {
|
|
166
|
+
ownerSocketsMap.remove(ownerId, ws);
|
|
167
|
+
log.relayOptionUnsubscribe(
|
|
168
|
+
ownerId,
|
|
169
|
+
() => ownerSocketsMap.getValues(ownerId)?.size ?? 0,
|
|
170
|
+
);
|
|
78
171
|
},
|
|
172
|
+
|
|
79
173
|
broadcast: (ownerId, message) => {
|
|
80
|
-
const sockets = ownerSocketsMap.getValues(ownerId)
|
|
174
|
+
const sockets = ownerSocketsMap.getValues(ownerId);
|
|
175
|
+
if (!sockets) return;
|
|
176
|
+
|
|
177
|
+
let broadcastCount = 0;
|
|
81
178
|
for (const socket of sockets) {
|
|
82
179
|
if (socket !== ws && socket.readyState === WebSocket.OPEN) {
|
|
83
180
|
socket.send(message, { binary: true });
|
|
181
|
+
broadcastCount++;
|
|
84
182
|
}
|
|
85
183
|
}
|
|
184
|
+
|
|
185
|
+
log.relayOptionBroadcast(ownerId, broadcastCount, sockets.size);
|
|
86
186
|
},
|
|
87
187
|
};
|
|
88
188
|
|
|
89
189
|
ws.on("message", (message) => {
|
|
90
190
|
if (!Uint8Array.is(message)) return;
|
|
191
|
+
log.messageLength(message.length);
|
|
91
192
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (response.value) {
|
|
103
|
-
ws.send(response.value, { binary: true });
|
|
104
|
-
}
|
|
193
|
+
applyProtocolMessageAsRelay({ storage })(message, options)
|
|
194
|
+
.then((response) => {
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
log.applyProtocolMessageAsRelayError(response.error);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
ws.send(response.value.message, { binary: true });
|
|
200
|
+
log.responseLength(response.value.message.length);
|
|
201
|
+
})
|
|
202
|
+
.catch(log.applyProtocolMessageAsRelayUnknownError);
|
|
105
203
|
});
|
|
106
204
|
|
|
107
205
|
ws.on("close", () => {
|
|
108
206
|
ownerSocketsMap.deleteValue(ws);
|
|
207
|
+
log.connectionClosed(wss.clients.size);
|
|
109
208
|
});
|
|
110
209
|
});
|
|
111
210
|
|
|
112
|
-
|
|
113
|
-
|
|
211
|
+
server.listen(port);
|
|
212
|
+
|
|
213
|
+
const dispose = () => {
|
|
214
|
+
log.shuttingDown();
|
|
114
215
|
|
|
115
216
|
wss.clients.forEach((client) => {
|
|
116
217
|
if (client.readyState === WebSocket.OPEN) {
|
|
@@ -119,18 +220,24 @@ export const createNodeJsRelay =
|
|
|
119
220
|
});
|
|
120
221
|
|
|
121
222
|
wss.close(() => {
|
|
122
|
-
|
|
223
|
+
log.webSocketServerDisposed();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
server.close(() => {
|
|
227
|
+
log.httpServerDisposed();
|
|
123
228
|
});
|
|
124
229
|
};
|
|
125
230
|
|
|
126
231
|
let isDisposed = false;
|
|
127
232
|
|
|
128
|
-
|
|
233
|
+
const relay: Relay = {
|
|
129
234
|
[Symbol.dispose]: () => {
|
|
130
235
|
if (isDisposed) return;
|
|
131
236
|
isDisposed = true;
|
|
132
|
-
sqlite[Symbol.dispose]();
|
|
133
|
-
|
|
237
|
+
sqlite.value[Symbol.dispose]();
|
|
238
|
+
dispose();
|
|
134
239
|
},
|
|
135
240
|
};
|
|
241
|
+
|
|
242
|
+
return ok(relay);
|
|
136
243
|
};
|