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