@evolu/nodejs 1.0.1-preview.8 → 2.0.0
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 +106 -66
- package/package.json +3 -3
- package/src/Evolu/Relay.ts +149 -79
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,57 +1,105 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { applyProtocolMessageAsRelay,
|
|
1
|
+
import { createRelation, 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
7
|
import { createTimingSafeEqual } from "../Crypto.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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,
|
|
49
|
+
});
|
|
50
|
+
const server = createServer();
|
|
25
51
|
const wss = new WebSocketServer({
|
|
26
|
-
maxPayload:
|
|
27
|
-
|
|
52
|
+
maxPayload: defaultProtocolMessageMaxSize,
|
|
53
|
+
noServer: true,
|
|
54
|
+
});
|
|
55
|
+
const ownerSocketRelation = createRelation();
|
|
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
|
+
})();
|
|
28
86
|
});
|
|
29
|
-
const ownerSocketsMap = createManyToManyMap();
|
|
30
87
|
wss.on("connection", (ws) => {
|
|
31
|
-
|
|
32
|
-
clientCount: wss.clients.size,
|
|
33
|
-
});
|
|
88
|
+
log.connectionEstablished(wss.clients.size);
|
|
34
89
|
ws.on("error", (error) => {
|
|
35
|
-
|
|
36
|
-
deps.console.error(error);
|
|
90
|
+
log.connectionWebSocketError(error);
|
|
37
91
|
});
|
|
38
92
|
const options = {
|
|
39
93
|
subscribe: (ownerId) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
ownerId,
|
|
43
|
-
subscriberCount: ownerSocketsMap.getValues(ownerId)?.size ?? 0,
|
|
44
|
-
});
|
|
94
|
+
ownerSocketRelation.add(ownerId, ws);
|
|
95
|
+
log.relayOptionSubscribe(ownerId, () => ownerSocketRelation.getB(ownerId)?.size ?? 0);
|
|
45
96
|
},
|
|
46
97
|
unsubscribe: (ownerId) => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
ownerId,
|
|
50
|
-
subscriberCount: ownerSocketsMap.getValues(ownerId)?.size ?? 0,
|
|
51
|
-
});
|
|
98
|
+
ownerSocketRelation.remove(ownerId, ws);
|
|
99
|
+
log.relayOptionUnsubscribe(ownerId, () => ownerSocketRelation.getB(ownerId)?.size ?? 0);
|
|
52
100
|
},
|
|
53
101
|
broadcast: (ownerId, message) => {
|
|
54
|
-
const sockets =
|
|
102
|
+
const sockets = ownerSocketRelation.getB(ownerId);
|
|
55
103
|
if (!sockets)
|
|
56
104
|
return;
|
|
57
105
|
let broadcastCount = 0;
|
|
@@ -61,61 +109,53 @@ export const createNodeJsRelay = (deps) => async (config) => {
|
|
|
61
109
|
broadcastCount++;
|
|
62
110
|
}
|
|
63
111
|
}
|
|
64
|
-
|
|
65
|
-
ownerId,
|
|
66
|
-
broadcastCount,
|
|
67
|
-
totalSubscribers: sockets.size,
|
|
68
|
-
});
|
|
112
|
+
log.relayOptionBroadcast(ownerId, broadcastCount, sockets.size);
|
|
69
113
|
},
|
|
70
114
|
};
|
|
71
115
|
ws.on("message", (message) => {
|
|
72
116
|
if (!Uint8Array.is(message))
|
|
73
117
|
return;
|
|
74
|
-
|
|
75
|
-
messageSize: message.length,
|
|
76
|
-
});
|
|
118
|
+
log.messageLength(message.length);
|
|
77
119
|
applyProtocolMessageAsRelay({ storage })(message, options)
|
|
78
120
|
.then((response) => {
|
|
79
121
|
if (!response.ok) {
|
|
80
|
-
|
|
81
|
-
deps.console.error(response.error);
|
|
122
|
+
log.applyProtocolMessageAsRelayError(response.error);
|
|
82
123
|
return;
|
|
83
124
|
}
|
|
84
125
|
ws.send(response.value.message, { binary: true });
|
|
85
|
-
|
|
86
|
-
responseSize: response.value.message.length,
|
|
87
|
-
});
|
|
126
|
+
log.responseLength(response.value.message.length);
|
|
88
127
|
})
|
|
89
|
-
.catch(
|
|
90
|
-
deps.console.error("[relay]", "applyProtocolMessageAsRelay", error);
|
|
91
|
-
});
|
|
128
|
+
.catch(log.applyProtocolMessageAsRelayUnknownError);
|
|
92
129
|
});
|
|
93
130
|
ws.on("close", () => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
clientCount: wss.clients.size,
|
|
97
|
-
});
|
|
131
|
+
ownerSocketRelation.deleteB(ws);
|
|
132
|
+
log.connectionClosed(wss.clients.size);
|
|
98
133
|
});
|
|
99
134
|
});
|
|
100
|
-
|
|
101
|
-
|
|
135
|
+
server.listen(port);
|
|
136
|
+
const dispose = () => {
|
|
137
|
+
log.shuttingDown();
|
|
102
138
|
wss.clients.forEach((client) => {
|
|
103
139
|
if (client.readyState === WebSocket.OPEN) {
|
|
104
140
|
client.close(1000, "Evolu Relay shutting down");
|
|
105
141
|
}
|
|
106
142
|
});
|
|
107
143
|
wss.close(() => {
|
|
108
|
-
|
|
144
|
+
log.webSocketServerDisposed();
|
|
145
|
+
});
|
|
146
|
+
server.close(() => {
|
|
147
|
+
log.httpServerDisposed();
|
|
109
148
|
});
|
|
110
149
|
};
|
|
111
150
|
let isDisposed = false;
|
|
112
|
-
|
|
151
|
+
const relay = {
|
|
113
152
|
[Symbol.dispose]: () => {
|
|
114
153
|
if (isDisposed)
|
|
115
154
|
return;
|
|
116
155
|
isDisposed = true;
|
|
117
|
-
sqlite[Symbol.dispose]();
|
|
118
|
-
|
|
156
|
+
sqlite.value[Symbol.dispose]();
|
|
157
|
+
dispose();
|
|
119
158
|
},
|
|
120
159
|
};
|
|
160
|
+
return ok(relay);
|
|
121
161
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evolu/nodejs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Evolu for Node.js",
|
|
5
5
|
"author": "Daniel Steigerwald <daniel@steigerwald.cz>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"shx": "^0.4.0",
|
|
31
31
|
"typescript": "^5.9.2",
|
|
32
32
|
"vitest": "^4.0.4",
|
|
33
|
-
"@evolu/common": "
|
|
33
|
+
"@evolu/common": "7.0.0",
|
|
34
34
|
"@evolu/tsconfig": "0.0.2"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@evolu/common": "^
|
|
37
|
+
"@evolu/common": "^7.0.0"
|
|
38
38
|
},
|
|
39
39
|
"engines": {
|
|
40
40
|
"node": ">=22.0.0"
|
package/src/Evolu/Relay.ts
CHANGED
|
@@ -1,100 +1,177 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ConsoleDep,
|
|
3
|
-
|
|
3
|
+
createRelation,
|
|
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
|
-
|
|
20
|
+
createBaseSqliteStorageTables,
|
|
21
|
+
createRelayLogger,
|
|
22
|
+
createRelaySqliteStorage,
|
|
23
|
+
createRelayStorageTables,
|
|
24
|
+
defaultProtocolMessageMaxSize,
|
|
25
|
+
parseOwnerIdFromOwnerWebSocketTransportUrl,
|
|
16
26
|
Relay,
|
|
17
27
|
RelayConfig,
|
|
18
|
-
RelaySqliteStorageDeps,
|
|
19
28
|
} from "@evolu/common/evolu";
|
|
29
|
+
import { existsSync } from "fs";
|
|
30
|
+
import { createServer } from "http";
|
|
20
31
|
import { WebSocket, WebSocketServer } from "ws";
|
|
21
32
|
import { createBetterSqliteDriver } from "../BetterSqliteDriver.js";
|
|
22
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
|
-
|
|
48
|
-
const relaySqliteStorageDeps: RelaySqliteStorageDeps = {
|
|
49
|
-
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,
|
|
50
66
|
random: createRandom(),
|
|
51
67
|
timingSafeEqual: createTimingSafeEqual(),
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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,
|
|
100
|
+
});
|
|
61
101
|
|
|
102
|
+
const server = createServer();
|
|
62
103
|
const wss = new WebSocketServer({
|
|
63
|
-
maxPayload:
|
|
64
|
-
|
|
104
|
+
maxPayload: defaultProtocolMessageMaxSize,
|
|
105
|
+
noServer: true,
|
|
65
106
|
});
|
|
66
107
|
|
|
67
|
-
const
|
|
108
|
+
const ownerSocketRelation = createRelation<OwnerId, WebSocket>();
|
|
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
|
+
});
|
|
68
148
|
|
|
69
149
|
wss.on("connection", (ws) => {
|
|
70
|
-
|
|
71
|
-
clientCount: wss.clients.size,
|
|
72
|
-
});
|
|
150
|
+
log.connectionEstablished(wss.clients.size);
|
|
73
151
|
|
|
74
152
|
ws.on("error", (error) => {
|
|
75
|
-
|
|
76
|
-
deps.console.error(error);
|
|
153
|
+
log.connectionWebSocketError(error);
|
|
77
154
|
});
|
|
78
155
|
|
|
79
156
|
const options: ApplyProtocolMessageAsRelayOptions = {
|
|
80
157
|
subscribe: (ownerId) => {
|
|
81
|
-
|
|
82
|
-
|
|
158
|
+
ownerSocketRelation.add(ownerId, ws);
|
|
159
|
+
log.relayOptionSubscribe(
|
|
83
160
|
ownerId,
|
|
84
|
-
|
|
85
|
-
|
|
161
|
+
() => ownerSocketRelation.getB(ownerId)?.size ?? 0,
|
|
162
|
+
);
|
|
86
163
|
},
|
|
87
164
|
|
|
88
165
|
unsubscribe: (ownerId) => {
|
|
89
|
-
|
|
90
|
-
|
|
166
|
+
ownerSocketRelation.remove(ownerId, ws);
|
|
167
|
+
log.relayOptionUnsubscribe(
|
|
91
168
|
ownerId,
|
|
92
|
-
|
|
93
|
-
|
|
169
|
+
() => ownerSocketRelation.getB(ownerId)?.size ?? 0,
|
|
170
|
+
);
|
|
94
171
|
},
|
|
95
172
|
|
|
96
173
|
broadcast: (ownerId, message) => {
|
|
97
|
-
const sockets =
|
|
174
|
+
const sockets = ownerSocketRelation.getB(ownerId);
|
|
98
175
|
if (!sockets) return;
|
|
99
176
|
|
|
100
177
|
let broadcastCount = 0;
|
|
@@ -105,49 +182,36 @@ export const createNodeJsRelay =
|
|
|
105
182
|
}
|
|
106
183
|
}
|
|
107
184
|
|
|
108
|
-
|
|
109
|
-
ownerId,
|
|
110
|
-
broadcastCount,
|
|
111
|
-
totalSubscribers: sockets.size,
|
|
112
|
-
});
|
|
185
|
+
log.relayOptionBroadcast(ownerId, broadcastCount, sockets.size);
|
|
113
186
|
},
|
|
114
187
|
};
|
|
115
188
|
|
|
116
189
|
ws.on("message", (message) => {
|
|
117
190
|
if (!Uint8Array.is(message)) return;
|
|
118
|
-
|
|
119
|
-
deps.console.log("[relay]", "on message", {
|
|
120
|
-
messageSize: message.length,
|
|
121
|
-
});
|
|
191
|
+
log.messageLength(message.length);
|
|
122
192
|
|
|
123
193
|
applyProtocolMessageAsRelay({ storage })(message, options)
|
|
124
194
|
.then((response) => {
|
|
125
195
|
if (!response.ok) {
|
|
126
|
-
|
|
127
|
-
deps.console.error(response.error);
|
|
196
|
+
log.applyProtocolMessageAsRelayError(response.error);
|
|
128
197
|
return;
|
|
129
198
|
}
|
|
130
|
-
|
|
131
199
|
ws.send(response.value.message, { binary: true });
|
|
132
|
-
|
|
133
|
-
responseSize: response.value.message.length,
|
|
134
|
-
});
|
|
200
|
+
log.responseLength(response.value.message.length);
|
|
135
201
|
})
|
|
136
|
-
.catch(
|
|
137
|
-
deps.console.error("[relay]", "applyProtocolMessageAsRelay", error);
|
|
138
|
-
});
|
|
202
|
+
.catch(log.applyProtocolMessageAsRelayUnknownError);
|
|
139
203
|
});
|
|
140
204
|
|
|
141
205
|
ws.on("close", () => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
clientCount: wss.clients.size,
|
|
145
|
-
});
|
|
206
|
+
ownerSocketRelation.deleteB(ws);
|
|
207
|
+
log.connectionClosed(wss.clients.size);
|
|
146
208
|
});
|
|
147
209
|
});
|
|
148
210
|
|
|
149
|
-
|
|
150
|
-
|
|
211
|
+
server.listen(port);
|
|
212
|
+
|
|
213
|
+
const dispose = () => {
|
|
214
|
+
log.shuttingDown();
|
|
151
215
|
|
|
152
216
|
wss.clients.forEach((client) => {
|
|
153
217
|
if (client.readyState === WebSocket.OPEN) {
|
|
@@ -156,18 +220,24 @@ export const createNodeJsRelay =
|
|
|
156
220
|
});
|
|
157
221
|
|
|
158
222
|
wss.close(() => {
|
|
159
|
-
|
|
223
|
+
log.webSocketServerDisposed();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
server.close(() => {
|
|
227
|
+
log.httpServerDisposed();
|
|
160
228
|
});
|
|
161
229
|
};
|
|
162
230
|
|
|
163
231
|
let isDisposed = false;
|
|
164
232
|
|
|
165
|
-
|
|
233
|
+
const relay: Relay = {
|
|
166
234
|
[Symbol.dispose]: () => {
|
|
167
235
|
if (isDisposed) return;
|
|
168
236
|
isDisposed = true;
|
|
169
|
-
sqlite[Symbol.dispose]();
|
|
170
|
-
|
|
237
|
+
sqlite.value[Symbol.dispose]();
|
|
238
|
+
dispose();
|
|
171
239
|
},
|
|
172
240
|
};
|
|
241
|
+
|
|
242
|
+
return ok(relay);
|
|
173
243
|
};
|