@persistica/flux-mesh 0.0.1 → 0.0.4
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/README.md +9 -0
- package/main.js +6 -0
- package/package.json +24 -21
- package/build.js +0 -21
- package/message-routing.excalidraw +0 -2433
- package/project.json +0 -80
- package/src/_classes/web-rtc-client-interface.class.ts +0 -79
- package/src/_routes/auth/network-authority.post.route.ts +0 -74
- package/src/_routes/auth/network-client.post.route.ts +0 -169
- package/src/_routes/options.route.ts +0 -9
- package/src/auth/auth.ts +0 -29
- package/src/business-logic/channels/channel-manager.class.ts +0 -209
- package/src/index.ts +0 -3
- package/src/main.ts +0 -680
- package/src/orchestrators/facilitate-webrtc-connection.class.ts +0 -23
- package/src/register/network-client-manager.class.ts +0 -175
- package/src/register/register-network-authority.class.ts +0 -123
- package/src/routing/addressing.utils.ts +0 -34
- package/src/routing/global-channel/global-channel-pubsub.class.ts +0 -76
- package/src/routing/incomming-message-router.class.ts +0 -47
- package/src/routing/outgoing-message-router.class.ts +0 -72
- package/src/routing/process-message-router.class.ts +0 -85
- package/src/routing/redis/hash/network-agent.redis.sorted-set.ts +0 -60
- package/src/routing/redis/network-agent.redis.ts +0 -135
- package/src/routing/redis/redis-connection.class.ts +0 -261
- package/src/routing/rpc/core/global-rpc-client.class.ts +0 -215
- package/src/test.spec.ts +0 -7
- package/tsconfig.json +0 -23
- package/tsconfig.lib.json +0 -20
package/src/main.ts
DELETED
|
@@ -1,680 +0,0 @@
|
|
|
1
|
-
// Make TypeScript happy
|
|
2
|
-
declare global {
|
|
3
|
-
var meshLoadCount: number | null;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
globalThis.meshLoadCount ??= 0;
|
|
7
|
-
globalThis.meshLoadCount++;
|
|
8
|
-
|
|
9
|
-
console.log(`[flux-mesh] Reloaded ${globalThis.meshLoadCount} time(s)`);
|
|
10
|
-
|
|
11
|
-
// import { Elysia } from 'elysia';
|
|
12
|
-
// import { swagger } from '@elysiajs/swagger';
|
|
13
|
-
|
|
14
|
-
// if (!process.env['FLUX_MESH_JWT_KEY']) {
|
|
15
|
-
// throw new Error('Missing FLUX_MESH_JWT_KEY in .env');
|
|
16
|
-
// }
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
type TNetworkId_S,
|
|
20
|
-
type TAddress,
|
|
21
|
-
type TChannelName,
|
|
22
|
-
type TClientId,
|
|
23
|
-
type TMachineAddress,
|
|
24
|
-
type TProcessAddress,
|
|
25
|
-
type TProcessId,
|
|
26
|
-
type TAgentOwnUId,
|
|
27
|
-
UnknownClientError,
|
|
28
|
-
CONNECT_TO_CLIENT,
|
|
29
|
-
SUBSCRIBE_NETWORK_CHANNEL_NAME,
|
|
30
|
-
ERROR,
|
|
31
|
-
RPC_RESPONSE,
|
|
32
|
-
SUBSCRIBED_NETWORK_CHANNEL_NAME,
|
|
33
|
-
NETWORK_CHANNEL_PUBLISH,
|
|
34
|
-
validateChannelNameOrThrow,
|
|
35
|
-
ON_NETWORK_CHANNEL_PUBLISH,
|
|
36
|
-
AUTHORITY_CHANNEL_SUBSCRIBE,
|
|
37
|
-
UNSUBSCRIBE_NETWORK_CHANNEL_NAME,
|
|
38
|
-
UNSUBSCRIBED_NETWORK_CHANNEL_NAME,
|
|
39
|
-
AUTHORITY_DISCONNECT_AGENT,
|
|
40
|
-
} from '@flux/shared/types';
|
|
41
|
-
import * as Bun from 'bun';
|
|
42
|
-
import { nanoid } from 'nanoid';
|
|
43
|
-
import { OutgoingMessageRouter } from './routing/outgoing-message-router.class';
|
|
44
|
-
import { NetworkAuthorityManager } from './register/register-network-authority.class';
|
|
45
|
-
import { verifyTokenOrThrow } from './auth/auth';
|
|
46
|
-
import { GlobalRPCClient } from './routing/rpc/core/global-rpc-client.class';
|
|
47
|
-
import { ProcessMessageRouter } from './routing/process-message-router.class';
|
|
48
|
-
import {
|
|
49
|
-
GlobalWebRTCClient,
|
|
50
|
-
WebRTCClient,
|
|
51
|
-
} from './_classes/web-rtc-client-interface.class';
|
|
52
|
-
import { facilitateWebRTCConnection } from './orchestrators/facilitate-webrtc-connection.class';
|
|
53
|
-
import {
|
|
54
|
-
readMachineAddress,
|
|
55
|
-
readProcessAddress,
|
|
56
|
-
readProcessId,
|
|
57
|
-
} from './routing/addressing.utils';
|
|
58
|
-
import { NetworkAgentManager } from './register/network-client-manager.class';
|
|
59
|
-
import { OPTIONS_RESPONSE } from './_routes/options.route';
|
|
60
|
-
import { authorizeNetworkAuthority } from './_routes/auth/network-authority.post.route';
|
|
61
|
-
import { authorizeNetworkAgent } from './_routes/auth/network-client.post.route';
|
|
62
|
-
import type {
|
|
63
|
-
RPCClient,
|
|
64
|
-
RPCResponse,
|
|
65
|
-
TRPCResponseCallbackFunction,
|
|
66
|
-
} from '@flux/shared/ws';
|
|
67
|
-
import * as nodeURL from 'node:url';
|
|
68
|
-
import {
|
|
69
|
-
type RedisConnection,
|
|
70
|
-
getMeshRedisConnection,
|
|
71
|
-
} from './routing/redis/redis-connection.class';
|
|
72
|
-
import { GlobalChannelPubsub } from './routing/global-channel/global-channel-pubsub.class';
|
|
73
|
-
import { NetworkChannelManager } from './business-logic/channels/channel-manager.class';
|
|
74
|
-
import { isNanoId } from 'libs/flux/shared/types/src/lib/client-id.type';
|
|
75
|
-
import { PicoLogger } from '@utils/pico-logger';
|
|
76
|
-
import type { TFluxClientUID } from '@flux/shared/utils';
|
|
77
|
-
|
|
78
|
-
PicoLogger.configure({
|
|
79
|
-
allowScopes: '*',
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
export type TConnectedClientSocket = Bun.ServerWebSocket<{
|
|
83
|
-
ip: Bun.SocketAddress | null;
|
|
84
|
-
id: TClientId;
|
|
85
|
-
uid?: TAgentOwnUId;
|
|
86
|
-
address: TAddress;
|
|
87
|
-
networkId: TNetworkId_S;
|
|
88
|
-
isAuthority?: boolean;
|
|
89
|
-
rtcClient?: WebRTCClient;
|
|
90
|
-
claim?: string;
|
|
91
|
-
rpcClient: RPCClient<'channel'>;
|
|
92
|
-
channelNames: Set<TChannelName>;
|
|
93
|
-
machineUID?: TFluxClientUID,
|
|
94
|
-
|
|
95
|
-
// The amount of data sent
|
|
96
|
-
throughput: {
|
|
97
|
-
bytes: number;
|
|
98
|
-
packets: number;
|
|
99
|
-
};
|
|
100
|
-
}>;
|
|
101
|
-
|
|
102
|
-
const clientMap: Map<TClientId, TConnectedClientSocket> = new Map();
|
|
103
|
-
const processId: TProcessId = readProcessId();
|
|
104
|
-
const machineAddress: TMachineAddress = readMachineAddress();
|
|
105
|
-
const processAddress: TProcessAddress = readProcessAddress();
|
|
106
|
-
|
|
107
|
-
const clientRPCResponseCallbacks: Map<
|
|
108
|
-
TClientId,
|
|
109
|
-
Set<TRPCResponseCallbackFunction>
|
|
110
|
-
> = new Map();
|
|
111
|
-
|
|
112
|
-
// const incommingClientMessageRouter: IncommingClientMessageRouter = new IncommingClientMessageRouter(
|
|
113
|
-
// (
|
|
114
|
-
// clientId: TClientId,
|
|
115
|
-
// onMessage: TCallbackFunction,
|
|
116
|
-
// ) => {
|
|
117
|
-
// clientCallbacks.set(clientId, [onMessage]);
|
|
118
|
-
// },
|
|
119
|
-
// );
|
|
120
|
-
|
|
121
|
-
export class FluxMeshServer {
|
|
122
|
-
|
|
123
|
-
private readonly redisConnection: RedisConnection = getMeshRedisConnection();
|
|
124
|
-
private readonly onReadyListeners: Set<() => void> = new Set();
|
|
125
|
-
private readonly bunServer: Bun.Server;
|
|
126
|
-
private readonly globalChannelPubsub: GlobalChannelPubsub;
|
|
127
|
-
private readonly channelManager: NetworkChannelManager;
|
|
128
|
-
|
|
129
|
-
constructor(
|
|
130
|
-
private readonly options?: {
|
|
131
|
-
port?: number,
|
|
132
|
-
redisConnectionString?: string;
|
|
133
|
-
},
|
|
134
|
-
) {
|
|
135
|
-
this.options = {
|
|
136
|
-
// Defaults
|
|
137
|
-
port: 5100,
|
|
138
|
-
redisConnectionString: '',
|
|
139
|
-
// Override with provided options
|
|
140
|
-
...this.options,
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const networkAuthorityManager: NetworkAuthorityManager = new NetworkAuthorityManager();
|
|
144
|
-
const networkAgentManager: NetworkAgentManager = new NetworkAgentManager();
|
|
145
|
-
|
|
146
|
-
const outgoingMessageRouter: OutgoingMessageRouter = new OutgoingMessageRouter(
|
|
147
|
-
// passToLocalClient:
|
|
148
|
-
(
|
|
149
|
-
clientId: TClientId,
|
|
150
|
-
message: string,
|
|
151
|
-
) => {
|
|
152
|
-
const client: TConnectedClientSocket | undefined =
|
|
153
|
-
clientMap.get(clientId);
|
|
154
|
-
|
|
155
|
-
if (!client) {
|
|
156
|
-
throw new UnknownClientError(clientId, processAddress);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
client.send(message);
|
|
160
|
-
}
|
|
161
|
-
);
|
|
162
|
-
const processMessageRouter: ProcessMessageRouter = new ProcessMessageRouter();
|
|
163
|
-
|
|
164
|
-
const globalRPCClient: GlobalRPCClient<
|
|
165
|
-
'authorize' | 'authorizeNetworkChannel'
|
|
166
|
-
> = new GlobalRPCClient(outgoingMessageRouter, processMessageRouter);
|
|
167
|
-
|
|
168
|
-
this.bunServer = Bun.serve({
|
|
169
|
-
port: this.options.port,
|
|
170
|
-
idleTimeout: 0, // deactivate timeout
|
|
171
|
-
routes: {
|
|
172
|
-
// ****************************************************************************
|
|
173
|
-
// *** Authenticate Network Authority
|
|
174
|
-
// ****************************************************************************
|
|
175
|
-
'/auth/network-authority': {
|
|
176
|
-
OPTIONS: OPTIONS_RESPONSE,
|
|
177
|
-
POST: authorizeNetworkAuthority,
|
|
178
|
-
},
|
|
179
|
-
|
|
180
|
-
// ****************************************************************************
|
|
181
|
-
// *** Authenticate Client on Network
|
|
182
|
-
// ****************************************************************************
|
|
183
|
-
'/auth/network-client': {
|
|
184
|
-
OPTIONS: OPTIONS_RESPONSE,
|
|
185
|
-
POST: (request: Bun.BunRequest) =>
|
|
186
|
-
authorizeNetworkAgent(
|
|
187
|
-
request,
|
|
188
|
-
networkAuthorityManager,
|
|
189
|
-
globalRPCClient,
|
|
190
|
-
),
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
|
|
194
|
-
async fetch(
|
|
195
|
-
request: Request,
|
|
196
|
-
server: Bun.Server,
|
|
197
|
-
) {
|
|
198
|
-
// Upgrade the request to a WebSocket
|
|
199
|
-
|
|
200
|
-
// const cookies = request.headers.get('Cookie');
|
|
201
|
-
// const token = cookies['X-Token'];
|
|
202
|
-
// console.log('CVookies: ', cookies);
|
|
203
|
-
|
|
204
|
-
const token: string | string[] | undefined = nodeURL.parse(request.url, true)
|
|
205
|
-
.query['token'];
|
|
206
|
-
|
|
207
|
-
try {
|
|
208
|
-
const decodedToken: {
|
|
209
|
-
networkId: TNetworkId_S;
|
|
210
|
-
claim?: string;
|
|
211
|
-
isAuthority?: boolean;
|
|
212
|
-
agentUID?: string,
|
|
213
|
-
machineUID?: TFluxClientUID,
|
|
214
|
-
} = verifyTokenOrThrow(token) as any;
|
|
215
|
-
|
|
216
|
-
const socketId: TClientId = nanoid() as TClientId;
|
|
217
|
-
|
|
218
|
-
if (
|
|
219
|
-
server.upgrade(request, {
|
|
220
|
-
data: {
|
|
221
|
-
ip: server.requestIP(request),
|
|
222
|
-
id: socketId,
|
|
223
|
-
networkId: decodedToken.networkId,
|
|
224
|
-
isAuthority: decodedToken.isAuthority,
|
|
225
|
-
address: `${machineAddress}/${processId}/${socketId}`,
|
|
226
|
-
claim: decodedToken.claim,
|
|
227
|
-
uid: decodedToken.agentUID,
|
|
228
|
-
machineUID: decodedToken.machineUID,
|
|
229
|
-
channelNames: new Set(),
|
|
230
|
-
throughput: {
|
|
231
|
-
bytes: 0,
|
|
232
|
-
packets: 0,
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
})
|
|
236
|
-
) {
|
|
237
|
-
// Do not return a Response
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
} catch {
|
|
241
|
-
console.error('Token verification failed');
|
|
242
|
-
|
|
243
|
-
return new Response('Token verification failed', {
|
|
244
|
-
status: 500,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return new Response('Upgrade failed', { status: 500 });
|
|
249
|
-
},
|
|
250
|
-
|
|
251
|
-
websocket: {
|
|
252
|
-
perMessageDeflate: true,
|
|
253
|
-
maxPayloadLength: 1024 * 1024, // 1 MB
|
|
254
|
-
// publishToSelf: true,
|
|
255
|
-
|
|
256
|
-
// A socket is opened, validate it
|
|
257
|
-
async open(
|
|
258
|
-
_ws: TConnectedClientSocket,
|
|
259
|
-
): Promise<void> {
|
|
260
|
-
clientMap.set(_ws.data.id, _ws);
|
|
261
|
-
|
|
262
|
-
if (_ws.data.isAuthority) {
|
|
263
|
-
PicoLogger.log(`👮 Authority connected at address: '${_ws.data.address}'`, 'ws-connection');
|
|
264
|
-
|
|
265
|
-
await networkAuthorityManager
|
|
266
|
-
.register(
|
|
267
|
-
_ws.data.networkId,
|
|
268
|
-
_ws.data.id,
|
|
269
|
-
_ws.data.machineUID,
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
// Let the client detect readyState. Regular ping cannot be detected by the WebSocket client in the browser
|
|
273
|
-
_ws.send('isReady');
|
|
274
|
-
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
PicoLogger.log(`🤵 Agent connected: ${_ws.data.id}`, 'ws-connection');
|
|
279
|
-
|
|
280
|
-
await networkAgentManager
|
|
281
|
-
.registerAgent(
|
|
282
|
-
_ws.data.networkId,
|
|
283
|
-
_ws.data.id,
|
|
284
|
-
_ws.data.ip,
|
|
285
|
-
_ws.data.address,
|
|
286
|
-
_ws.data.throughput,
|
|
287
|
-
_ws.data.uid,
|
|
288
|
-
_ws.data.machineUID,
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
_ws.data.rtcClient = new WebRTCClient(
|
|
292
|
-
processAddress,
|
|
293
|
-
_ws.send.bind(_ws),
|
|
294
|
-
(cb: TRPCResponseCallbackFunction) => {
|
|
295
|
-
const cbs:
|
|
296
|
-
| Set<TRPCResponseCallbackFunction>
|
|
297
|
-
| undefined = clientRPCResponseCallbacks.get(
|
|
298
|
-
_ws.data.id
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
clientRPCResponseCallbacks.set(
|
|
302
|
-
_ws.data.id,
|
|
303
|
-
cbs === undefined ? new Set([cb]) : cbs.add(cb)
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
);
|
|
307
|
-
|
|
308
|
-
// Let the client detect readyState. Regular ping cannot be detected by the WebSocket client in the browser
|
|
309
|
-
_ws.send('isReady');
|
|
310
|
-
// This will make the client retry: _ws.terminate();
|
|
311
|
-
// ws.close(1001, 'Client not validated'); // ! Check correct error code
|
|
312
|
-
},
|
|
313
|
-
|
|
314
|
-
message: async (
|
|
315
|
-
ws: TConnectedClientSocket,
|
|
316
|
-
message_: string | Buffer,
|
|
317
|
-
) => {
|
|
318
|
-
|
|
319
|
-
if (typeof message_ !== 'string') {
|
|
320
|
-
throw new Error('Message is not a string');
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Calculate the throughput
|
|
324
|
-
const packageSize: number = new TextEncoder().encode(message_).length;
|
|
325
|
-
ws.data.throughput.bytes = ws.data.throughput.bytes + packageSize;
|
|
326
|
-
ws.data.throughput.packets++;
|
|
327
|
-
|
|
328
|
-
const packageType: string | undefined = message_.split(':')[0];
|
|
329
|
-
|
|
330
|
-
switch (packageType) {
|
|
331
|
-
case AUTHORITY_DISCONNECT_AGENT: {
|
|
332
|
-
if (!ws.data.isAuthority) {
|
|
333
|
-
ws.close(4000, 'Bad behavior');
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const agentClientId: string = message_.substring(message_.indexOf(':') + 1);
|
|
338
|
-
|
|
339
|
-
if (!isNanoId(agentClientId)) {
|
|
340
|
-
ws.send(`${ERROR}:Invalid agent ID`);
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Attempt to get the client
|
|
345
|
-
const connectedClientSocket: TConnectedClientSocket | undefined = clientMap.get(agentClientId);
|
|
346
|
-
|
|
347
|
-
if (
|
|
348
|
-
!connectedClientSocket ||
|
|
349
|
-
(connectedClientSocket.data.networkId !== ws.data.networkId) ||
|
|
350
|
-
(connectedClientSocket.data.isAuthority)
|
|
351
|
-
) {
|
|
352
|
-
ws.send(`${ERROR}:Cannot kick agent`);
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
connectedClientSocket.close(1002, 'Kicked by authority');
|
|
357
|
-
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
case AUTHORITY_CHANNEL_SUBSCRIBE: {
|
|
362
|
-
if (!ws.data.isAuthority) {
|
|
363
|
-
ws.close(4000, 'Bad behavior');
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Subscribe to the events
|
|
368
|
-
ws.subscribe(`~/networks/${ws.data.networkId}/channel-created`);
|
|
369
|
-
ws.subscribe(`~/networks/${ws.data.networkId}/channel-empty`);
|
|
370
|
-
|
|
371
|
-
break;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
case NETWORK_CHANNEL_PUBLISH: {
|
|
375
|
-
const firstColon = message_.indexOf(':');
|
|
376
|
-
const secondColon = message_.indexOf(':', firstColon + 1);
|
|
377
|
-
|
|
378
|
-
const channelName: string = message_.slice(
|
|
379
|
-
firstColon + 1,
|
|
380
|
-
secondColon
|
|
381
|
-
);
|
|
382
|
-
const data: string = message_.slice(secondColon + 1);
|
|
383
|
-
|
|
384
|
-
if (validateChannelNameOrThrow(channelName)) {
|
|
385
|
-
if (ws.data.channelNames.has(channelName)) {
|
|
386
|
-
// Don't publish to self
|
|
387
|
-
this.globalChannelPubsub.publish(
|
|
388
|
-
`networks/${ws.data.networkId}/channels/${channelName}`,
|
|
389
|
-
`${ON_NETWORK_CHANNEL_PUBLISH}:${channelName}:${data}`,
|
|
390
|
-
ws,
|
|
391
|
-
);
|
|
392
|
-
|
|
393
|
-
// Add the usage to the channel
|
|
394
|
-
this.channelManager
|
|
395
|
-
.increaseUsageCount(
|
|
396
|
-
ws.data.networkId,
|
|
397
|
-
channelName,
|
|
398
|
-
packageSize,
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
break;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
case SUBSCRIBE_NETWORK_CHANNEL_NAME: {
|
|
407
|
-
const channelNameString: string = message_.substring(message_.indexOf(':') + 1);
|
|
408
|
-
|
|
409
|
-
try {
|
|
410
|
-
validateChannelNameOrThrow(channelNameString);
|
|
411
|
-
} catch {
|
|
412
|
-
ws.send(`${ERROR}:Not authorized`);
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const channelName: TChannelName = channelNameString as TChannelName;
|
|
417
|
-
|
|
418
|
-
if (ws.data.channelNames.has(channelName)) {
|
|
419
|
-
ws.send(`${ERROR}:Agent is already subscribed to channnel`);
|
|
420
|
-
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
try {
|
|
425
|
-
const networkAuthorityAddress: TAddress = await networkAuthorityManager
|
|
426
|
-
.resolveNetworkAuthorityAddressOrThrow(
|
|
427
|
-
ws.data.networkId,
|
|
428
|
-
);
|
|
429
|
-
|
|
430
|
-
const canHaveMembers = await this.channelManager.canHaveMembers(
|
|
431
|
-
ws.data.networkId,
|
|
432
|
-
channelName,
|
|
433
|
-
);
|
|
434
|
-
|
|
435
|
-
if (!canHaveMembers) {
|
|
436
|
-
ws.send(`${ERROR}:Channel limit is reached`);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
try {
|
|
440
|
-
const authorize: boolean = await globalRPCClient.call(
|
|
441
|
-
networkAuthorityAddress,
|
|
442
|
-
'authorizeNetworkChannel',
|
|
443
|
-
channelName,
|
|
444
|
-
ws.data.claim
|
|
445
|
-
);
|
|
446
|
-
|
|
447
|
-
if (authorize) {
|
|
448
|
-
ws.subscribe(`networks/${ws.data.networkId}/channels/${channelName}`);
|
|
449
|
-
ws.send(`${SUBSCRIBED_NETWORK_CHANNEL_NAME}:${channelName}`);
|
|
450
|
-
ws.data.channelNames.add(channelName);
|
|
451
|
-
PicoLogger.log(`🎉 Client was authorized on channel name '${channelName}'`, 'authorized');
|
|
452
|
-
|
|
453
|
-
this.channelManager
|
|
454
|
-
.joinNetworkChannel(
|
|
455
|
-
ws.data.networkId,
|
|
456
|
-
channelName,
|
|
457
|
-
ws.data.address,
|
|
458
|
-
);
|
|
459
|
-
} else {
|
|
460
|
-
console.error(`Client was not authorized to connect to channel name '${channelName}'`);
|
|
461
|
-
ws.send(`${ERROR}:Not authorized`);
|
|
462
|
-
}
|
|
463
|
-
} catch (error) {
|
|
464
|
-
if (error instanceof Error) {
|
|
465
|
-
ws.send(`${ERROR}:${error.message}`);
|
|
466
|
-
break;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
console.error('Unknown error', error);
|
|
470
|
-
ws.send(`${ERROR}:Unknown error`);
|
|
471
|
-
}
|
|
472
|
-
} catch {
|
|
473
|
-
ws.send(`${ERROR}:Not netrowk authority found`);
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
case UNSUBSCRIBE_NETWORK_CHANNEL_NAME: {
|
|
480
|
-
const channelNameString: string = message_.substring(message_.indexOf(':') + 1);
|
|
481
|
-
|
|
482
|
-
try {
|
|
483
|
-
validateChannelNameOrThrow(channelNameString);
|
|
484
|
-
} catch {
|
|
485
|
-
ws.send(`${ERROR}:Not authorized`);
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
const channelName: TChannelName = channelNameString as TChannelName;
|
|
490
|
-
|
|
491
|
-
if (!ws.data.channelNames.has(channelName)) {
|
|
492
|
-
ws.send(`${ERROR}:Cannot unsubscribe. Agent is not connected to the channnel`);
|
|
493
|
-
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
try {
|
|
498
|
-
ws.unsubscribe(`networks/${ws.data.networkId}/channels/${channelName}`);
|
|
499
|
-
ws.send(`${UNSUBSCRIBED_NETWORK_CHANNEL_NAME}:${channelName}`);
|
|
500
|
-
ws.data.channelNames.delete(channelName);
|
|
501
|
-
console.log(`🚪 Client left channel name '${channelName}'`);
|
|
502
|
-
|
|
503
|
-
this.channelManager
|
|
504
|
-
.leaveNetworkChannel(
|
|
505
|
-
ws.data.networkId,
|
|
506
|
-
channelName,
|
|
507
|
-
ws.data.address,
|
|
508
|
-
);
|
|
509
|
-
|
|
510
|
-
} catch (error) {
|
|
511
|
-
if (error instanceof Error) {
|
|
512
|
-
ws.send(`${ERROR}:${error.message}`);
|
|
513
|
-
break;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
console.error('Unknown error', error);
|
|
517
|
-
ws.send(`${ERROR}:Unknown error`);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
break;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
case RPC_RESPONSE: {
|
|
524
|
-
const rpcResponseMessage: RPCResponse = JSON.parse(
|
|
525
|
-
message_.substring(message_.indexOf(':') + 1)
|
|
526
|
-
) as RPCResponse;
|
|
527
|
-
|
|
528
|
-
PicoLogger.log('📡 Received RPC response.', 'rpc');
|
|
529
|
-
|
|
530
|
-
processMessageRouter.message(
|
|
531
|
-
rpcResponseMessage.rpcProcessAddress,
|
|
532
|
-
message_
|
|
533
|
-
);
|
|
534
|
-
|
|
535
|
-
const cbs: Set<TRPCResponseCallbackFunction> | undefined =
|
|
536
|
-
clientRPCResponseCallbacks.get(ws.data.id);
|
|
537
|
-
|
|
538
|
-
for (const clientCallback of cbs ?? []) {
|
|
539
|
-
clientCallback(rpcResponseMessage);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
break;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
case CONNECT_TO_CLIENT: {
|
|
546
|
-
const initiatingClient: WebRTCClient | undefined =
|
|
547
|
-
ws.data.rtcClient;
|
|
548
|
-
|
|
549
|
-
if (!initiatingClient) {
|
|
550
|
-
console.warn('Initiating client could not be resolved');
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const clientOwnUId: TAgentOwnUId = message_.substring(
|
|
554
|
-
message_.indexOf(':') + 1
|
|
555
|
-
) as TAgentOwnUId;
|
|
556
|
-
const networkClientAddress: TAddress =
|
|
557
|
-
await networkAgentManager.resolveNetworkClientAddressByUid(
|
|
558
|
-
ws.data.networkId,
|
|
559
|
-
clientOwnUId
|
|
560
|
-
);
|
|
561
|
-
|
|
562
|
-
const remoteClient: GlobalWebRTCClient | undefined =
|
|
563
|
-
new GlobalWebRTCClient(
|
|
564
|
-
networkClientAddress,
|
|
565
|
-
outgoingMessageRouter,
|
|
566
|
-
processMessageRouter
|
|
567
|
-
);
|
|
568
|
-
|
|
569
|
-
if (!remoteClient) {
|
|
570
|
-
console.warn('Remote client could not be resolved');
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
if (initiatingClient && remoteClient) {
|
|
574
|
-
facilitateWebRTCConnection(
|
|
575
|
-
initiatingClient,
|
|
576
|
-
remoteClient,
|
|
577
|
-
);
|
|
578
|
-
} else {
|
|
579
|
-
ws.send(`${ERROR}:RPC clients could not be resolved`);
|
|
580
|
-
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
break;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
},
|
|
588
|
-
|
|
589
|
-
// A socket is closed
|
|
590
|
-
close: (
|
|
591
|
-
ws: TConnectedClientSocket,
|
|
592
|
-
code: number,
|
|
593
|
-
) => {
|
|
594
|
-
clientMap.delete(ws.data.id);
|
|
595
|
-
|
|
596
|
-
if (ws.data.isAuthority) {
|
|
597
|
-
PicoLogger.log(`🛑 Authority socket disconnecting ${code} ${ws.data.id}`, 'ws-disconnect'); // 1001
|
|
598
|
-
|
|
599
|
-
networkAuthorityManager.unregister(
|
|
600
|
-
ws.data.networkId,
|
|
601
|
-
ws.data.address,
|
|
602
|
-
);
|
|
603
|
-
} else {
|
|
604
|
-
PicoLogger.log(`🛑🤵 Agent socket disconnecting ${code} ${ws.data.id}`, 'ws-disconnect'); // 1001
|
|
605
|
-
// Unsubscribe from topics
|
|
606
|
-
|
|
607
|
-
for (const channelName of (ws.data.channelNames ?? [])) {
|
|
608
|
-
ws.unsubscribe(
|
|
609
|
-
`networks/${ws.data.networkId}/channels/${channelName}`
|
|
610
|
-
);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// * Leave all channels
|
|
614
|
-
if (ws.data.channelNames.size > 0) {
|
|
615
|
-
this.channelManager
|
|
616
|
-
.leaveAllNetworkChannels(
|
|
617
|
-
ws.data.networkId,
|
|
618
|
-
ws.data.address,
|
|
619
|
-
ws.data.channelNames,
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
networkAgentManager.unregisterNetworkAgent(
|
|
624
|
-
ws.data.networkId,
|
|
625
|
-
ws.data.id,
|
|
626
|
-
ws.data.uid,
|
|
627
|
-
);
|
|
628
|
-
}
|
|
629
|
-
},
|
|
630
|
-
|
|
631
|
-
drain(_ws: Bun.ServerWebSocket<unknown>) {
|
|
632
|
-
console.log('drain');
|
|
633
|
-
}, // the socket is ready to receive more data
|
|
634
|
-
},
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
this.globalChannelPubsub = new GlobalChannelPubsub(
|
|
638
|
-
this.redisConnection,
|
|
639
|
-
this.bunServer,
|
|
640
|
-
processAddress,
|
|
641
|
-
);
|
|
642
|
-
|
|
643
|
-
this.channelManager = new NetworkChannelManager(this.globalChannelPubsub);
|
|
644
|
-
|
|
645
|
-
// TODO: DETECT WHEN READY
|
|
646
|
-
setTimeout(() => {
|
|
647
|
-
console.log(`Reloaded ${(globalThis as any).meshLoadCount} time(s)`);
|
|
648
|
-
console.log(`🚀 Flux mesh server running on localhost:${this.options?.port}`);
|
|
649
|
-
|
|
650
|
-
setInterval(() => {
|
|
651
|
-
this.redisConnection.setConnected(`${machineAddress}/${processId}`);
|
|
652
|
-
}, 3_000);
|
|
653
|
-
|
|
654
|
-
for (const cb of this.onReadyListeners) {
|
|
655
|
-
cb();
|
|
656
|
-
}
|
|
657
|
-
}, 50);
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
public onReady(
|
|
661
|
-
fn: () => void,
|
|
662
|
-
): void {
|
|
663
|
-
this.onReadyListeners.add(fn);
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/**
|
|
667
|
-
* Gracefully shuts down the server.
|
|
668
|
-
*
|
|
669
|
-
* @returns { Promise<void> }
|
|
670
|
-
*/
|
|
671
|
-
public async stop(
|
|
672
|
-
): Promise<void> {
|
|
673
|
-
clientMap.clear();
|
|
674
|
-
|
|
675
|
-
// 'true': Force stop and close all active connections
|
|
676
|
-
await this.bunServer.stop(true);
|
|
677
|
-
await this.redisConnection.setDisconnected(`${machineAddress}/${processId}`);
|
|
678
|
-
this.redisConnection.disconnect();
|
|
679
|
-
}
|
|
680
|
-
}
|