@evolu/nodejs 1.0.1-preview.9 → 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.map +1 -1
- package/dist/Evolu/Relay.js +34 -30
- package/package.json +3 -3
- package/src/Evolu/Relay.ts +47 -34
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../src/Evolu/Relay.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAIV,qBAAqB,
|
|
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,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { applyProtocolMessageAsRelay, createRelayLogger, createRelaySqliteStorage, defaultProtocolMessageMaxSize,
|
|
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";
|
|
3
4
|
import { createServer } from "http";
|
|
4
5
|
import { WebSocket, WebSocketServer } from "ws";
|
|
5
6
|
import { createBetterSqliteDriver } from "../BetterSqliteDriver.js";
|
|
@@ -26,25 +27,32 @@ export const createNodeJsRelayWithSqliteDriver = (deps) => (config) => createNod
|
|
|
26
27
|
random: createRandom(),
|
|
27
28
|
timingSafeEqual: createTimingSafeEqual(),
|
|
28
29
|
})(config);
|
|
29
|
-
const createNodeJsRelayWithDeps = (deps) => async ({ port = 443, name = SimpleName.orThrow("evolu-relay"), enableLogging = false,
|
|
30
|
+
const createNodeJsRelayWithDeps = (deps) => async ({ port = 443, name = SimpleName.orThrow("evolu-relay"), enableLogging = false, isOwnerAllowed, isOwnerWithinQuota, }) => {
|
|
30
31
|
const log = createRelayLogger(deps);
|
|
31
32
|
log.started(enableLogging, port);
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
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)({
|
|
37
47
|
onStorageError: log.storageError,
|
|
48
|
+
isOwnerWithinQuota,
|
|
38
49
|
});
|
|
39
|
-
if (!storageResult.ok)
|
|
40
|
-
return storageResult;
|
|
41
|
-
const storage = storageResult.value;
|
|
42
50
|
const server = createServer();
|
|
43
51
|
const wss = new WebSocketServer({
|
|
44
52
|
maxPayload: defaultProtocolMessageMaxSize,
|
|
45
53
|
noServer: true,
|
|
46
54
|
});
|
|
47
|
-
const
|
|
55
|
+
const ownerSocketRelation = createRelation();
|
|
48
56
|
server.on("upgrade", (request, socket, head) => {
|
|
49
57
|
socket.on("error", log.upgradeSocketError);
|
|
50
58
|
const completeUpgrade = () => {
|
|
@@ -53,32 +61,28 @@ const createNodeJsRelayWithDeps = (deps) => async ({ port = 443, name = SimpleNa
|
|
|
53
61
|
wss.emit("connection", ws, request);
|
|
54
62
|
});
|
|
55
63
|
};
|
|
56
|
-
if (!
|
|
64
|
+
if (!isOwnerAllowed) {
|
|
57
65
|
completeUpgrade();
|
|
58
66
|
return;
|
|
59
67
|
}
|
|
60
|
-
const ownerId =
|
|
68
|
+
const ownerId = parseOwnerIdFromOwnerWebSocketTransportUrl(request.url ?? "");
|
|
61
69
|
if (!ownerId) {
|
|
62
70
|
log.invalidOrMissingOwnerIdInUrl(request.url);
|
|
63
71
|
socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
64
72
|
socket.destroy();
|
|
65
73
|
return;
|
|
66
74
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
void (async () => {
|
|
76
|
+
const result = isOwnerAllowed(ownerId);
|
|
77
|
+
const isAllowed = isAsync(result) ? await result : result;
|
|
78
|
+
if (!isAllowed) {
|
|
70
79
|
log.unauthorizedOwner(ownerId);
|
|
71
80
|
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
72
81
|
socket.destroy();
|
|
73
82
|
return;
|
|
74
83
|
}
|
|
75
84
|
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
|
-
});
|
|
85
|
+
})();
|
|
82
86
|
});
|
|
83
87
|
wss.on("connection", (ws) => {
|
|
84
88
|
log.connectionEstablished(wss.clients.size);
|
|
@@ -87,15 +91,15 @@ const createNodeJsRelayWithDeps = (deps) => async ({ port = 443, name = SimpleNa
|
|
|
87
91
|
});
|
|
88
92
|
const options = {
|
|
89
93
|
subscribe: (ownerId) => {
|
|
90
|
-
|
|
91
|
-
log.relayOptionSubscribe(ownerId, () =>
|
|
94
|
+
ownerSocketRelation.add(ownerId, ws);
|
|
95
|
+
log.relayOptionSubscribe(ownerId, () => ownerSocketRelation.getB(ownerId)?.size ?? 0);
|
|
92
96
|
},
|
|
93
97
|
unsubscribe: (ownerId) => {
|
|
94
|
-
|
|
95
|
-
log.relayOptionUnsubscribe(ownerId, () =>
|
|
98
|
+
ownerSocketRelation.remove(ownerId, ws);
|
|
99
|
+
log.relayOptionUnsubscribe(ownerId, () => ownerSocketRelation.getB(ownerId)?.size ?? 0);
|
|
96
100
|
},
|
|
97
101
|
broadcast: (ownerId, message) => {
|
|
98
|
-
const sockets =
|
|
102
|
+
const sockets = ownerSocketRelation.getB(ownerId);
|
|
99
103
|
if (!sockets)
|
|
100
104
|
return;
|
|
101
105
|
let broadcastCount = 0;
|
|
@@ -124,7 +128,7 @@ const createNodeJsRelayWithDeps = (deps) => async ({ port = 443, name = SimpleNa
|
|
|
124
128
|
.catch(log.applyProtocolMessageAsRelayUnknownError);
|
|
125
129
|
});
|
|
126
130
|
ws.on("close", () => {
|
|
127
|
-
|
|
131
|
+
ownerSocketRelation.deleteB(ws);
|
|
128
132
|
log.connectionClosed(wss.clients.size);
|
|
129
133
|
});
|
|
130
134
|
});
|
|
@@ -149,7 +153,7 @@ const createNodeJsRelayWithDeps = (deps) => async ({ port = 443, name = SimpleNa
|
|
|
149
153
|
if (isDisposed)
|
|
150
154
|
return;
|
|
151
155
|
isDisposed = true;
|
|
152
|
-
sqlite[Symbol.dispose]();
|
|
156
|
+
sqlite.value[Symbol.dispose]();
|
|
153
157
|
dispose();
|
|
154
158
|
},
|
|
155
159
|
};
|
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,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ConsoleDep,
|
|
3
|
-
|
|
3
|
+
createRelation,
|
|
4
4
|
createRandom,
|
|
5
5
|
createSqlite,
|
|
6
6
|
CreateSqliteDriverDep,
|
|
7
|
+
isAsync,
|
|
7
8
|
ok,
|
|
8
9
|
OwnerId,
|
|
9
10
|
RandomDep,
|
|
@@ -16,13 +17,16 @@ import {
|
|
|
16
17
|
import {
|
|
17
18
|
applyProtocolMessageAsRelay,
|
|
18
19
|
ApplyProtocolMessageAsRelayOptions,
|
|
20
|
+
createBaseSqliteStorageTables,
|
|
19
21
|
createRelayLogger,
|
|
20
22
|
createRelaySqliteStorage,
|
|
23
|
+
createRelayStorageTables,
|
|
21
24
|
defaultProtocolMessageMaxSize,
|
|
22
|
-
|
|
25
|
+
parseOwnerIdFromOwnerWebSocketTransportUrl,
|
|
23
26
|
Relay,
|
|
24
27
|
RelayConfig,
|
|
25
28
|
} from "@evolu/common/evolu";
|
|
29
|
+
import { existsSync } from "fs";
|
|
26
30
|
import { createServer } from "http";
|
|
27
31
|
import { WebSocket, WebSocketServer } from "ws";
|
|
28
32
|
import { createBetterSqliteDriver } from "../BetterSqliteDriver.js";
|
|
@@ -69,29 +73,39 @@ const createNodeJsRelayWithDeps =
|
|
|
69
73
|
port = 443,
|
|
70
74
|
name = SimpleName.orThrow("evolu-relay"),
|
|
71
75
|
enableLogging = false,
|
|
72
|
-
|
|
76
|
+
isOwnerAllowed,
|
|
77
|
+
isOwnerWithinQuota,
|
|
73
78
|
}: NodeJsRelayConfig): Promise<Result<Relay, SqliteError>> => {
|
|
74
79
|
const log = createRelayLogger(deps);
|
|
75
80
|
log.started(enableLogging, port);
|
|
76
81
|
|
|
77
|
-
const
|
|
78
|
-
if (!sqliteResult.ok) return sqliteResult;
|
|
79
|
-
const sqlite = sqliteResult.value;
|
|
82
|
+
const dbFileExists = existsSync(`${name}.db`);
|
|
80
83
|
|
|
81
|
-
const
|
|
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)({
|
|
82
98
|
onStorageError: log.storageError,
|
|
99
|
+
isOwnerWithinQuota,
|
|
83
100
|
});
|
|
84
101
|
|
|
85
|
-
if (!storageResult.ok) return storageResult;
|
|
86
|
-
const storage = storageResult.value;
|
|
87
|
-
|
|
88
102
|
const server = createServer();
|
|
89
103
|
const wss = new WebSocketServer({
|
|
90
104
|
maxPayload: defaultProtocolMessageMaxSize,
|
|
91
105
|
noServer: true,
|
|
92
106
|
});
|
|
93
107
|
|
|
94
|
-
const
|
|
108
|
+
const ownerSocketRelation = createRelation<OwnerId, WebSocket>();
|
|
95
109
|
|
|
96
110
|
server.on("upgrade", (request, socket, head) => {
|
|
97
111
|
socket.on("error", log.upgradeSocketError);
|
|
@@ -103,12 +117,15 @@ const createNodeJsRelayWithDeps =
|
|
|
103
117
|
});
|
|
104
118
|
};
|
|
105
119
|
|
|
106
|
-
if (!
|
|
120
|
+
if (!isOwnerAllowed) {
|
|
107
121
|
completeUpgrade();
|
|
108
122
|
return;
|
|
109
123
|
}
|
|
110
124
|
|
|
111
|
-
const ownerId =
|
|
125
|
+
const ownerId = parseOwnerIdFromOwnerWebSocketTransportUrl(
|
|
126
|
+
request.url ?? "",
|
|
127
|
+
);
|
|
128
|
+
|
|
112
129
|
if (!ownerId) {
|
|
113
130
|
log.invalidOrMissingOwnerIdInUrl(request.url);
|
|
114
131
|
socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
@@ -116,21 +133,17 @@ const createNodeJsRelayWithDeps =
|
|
|
116
133
|
return;
|
|
117
134
|
}
|
|
118
135
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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");
|
|
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");
|
|
132
142
|
socket.destroy();
|
|
133
|
-
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
completeUpgrade();
|
|
146
|
+
})();
|
|
134
147
|
});
|
|
135
148
|
|
|
136
149
|
wss.on("connection", (ws) => {
|
|
@@ -142,23 +155,23 @@ const createNodeJsRelayWithDeps =
|
|
|
142
155
|
|
|
143
156
|
const options: ApplyProtocolMessageAsRelayOptions = {
|
|
144
157
|
subscribe: (ownerId) => {
|
|
145
|
-
|
|
158
|
+
ownerSocketRelation.add(ownerId, ws);
|
|
146
159
|
log.relayOptionSubscribe(
|
|
147
160
|
ownerId,
|
|
148
|
-
() =>
|
|
161
|
+
() => ownerSocketRelation.getB(ownerId)?.size ?? 0,
|
|
149
162
|
);
|
|
150
163
|
},
|
|
151
164
|
|
|
152
165
|
unsubscribe: (ownerId) => {
|
|
153
|
-
|
|
166
|
+
ownerSocketRelation.remove(ownerId, ws);
|
|
154
167
|
log.relayOptionUnsubscribe(
|
|
155
168
|
ownerId,
|
|
156
|
-
() =>
|
|
169
|
+
() => ownerSocketRelation.getB(ownerId)?.size ?? 0,
|
|
157
170
|
);
|
|
158
171
|
},
|
|
159
172
|
|
|
160
173
|
broadcast: (ownerId, message) => {
|
|
161
|
-
const sockets =
|
|
174
|
+
const sockets = ownerSocketRelation.getB(ownerId);
|
|
162
175
|
if (!sockets) return;
|
|
163
176
|
|
|
164
177
|
let broadcastCount = 0;
|
|
@@ -190,7 +203,7 @@ const createNodeJsRelayWithDeps =
|
|
|
190
203
|
});
|
|
191
204
|
|
|
192
205
|
ws.on("close", () => {
|
|
193
|
-
|
|
206
|
+
ownerSocketRelation.deleteB(ws);
|
|
194
207
|
log.connectionClosed(wss.clients.size);
|
|
195
208
|
});
|
|
196
209
|
});
|
|
@@ -221,7 +234,7 @@ const createNodeJsRelayWithDeps =
|
|
|
221
234
|
[Symbol.dispose]: () => {
|
|
222
235
|
if (isDisposed) return;
|
|
223
236
|
isDisposed = true;
|
|
224
|
-
sqlite[Symbol.dispose]();
|
|
237
|
+
sqlite.value[Symbol.dispose]();
|
|
225
238
|
dispose();
|
|
226
239
|
},
|
|
227
240
|
};
|