@cocreate/server-mesh 1.2.0 → 1.2.2

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/src/index.js CHANGED
@@ -6,727 +6,429 @@
6
6
  * by the Free Software Foundation, either version 3 of the License, or
7
7
  * (at your option) any later version.
8
8
  *
9
- * This program is distributed in the hope that it will be useful,
10
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- * GNU Affero General Public License for more details.
9
+ * This program is distributed in commercials or open source under GNU AGPLv3.
13
10
  *
14
11
  * You should have received a copy of the GNU Affero General Public License
15
12
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
13
  *
17
14
  ********************************************************************************/
18
15
 
19
- import { WebSocketServer, WebSocket } from 'ws';
20
- import https from 'node:https';
21
- import fs from 'node:fs';
22
- import path from 'node:path';
23
- import os from 'node:os';
24
- import cluster from 'node:cluster';
25
- import { execSync } from 'node:child_process';
26
16
  import { EventEmitter } from 'node:events';
27
17
  import crypto from 'node:crypto';
28
-
29
- const activeConnections = new Map(); // serverId/IP -> WebSocket P2P tunnels
30
- const retryTimers = new Map(); // peerIp -> Timeout ID
31
- const pruneTimers = new Map(); // peerIp -> Timeout ID
32
- const orgWorkers = new Map(); // organization_id -> Set(workerId)
33
- const orgServers = new Map(); // organization_id -> Set(WebSocket)
34
-
35
- let isGracefulExitRunning = false;
36
- let wsPingInterval = null;
37
-
38
- let serverCtx = null;
39
- const MESH_PORT = process.env.MESH_PORT || 8090;
40
- let meshWss = null;
41
- let secureMeshServer = null;
42
-
43
- /**
44
- * Safely loops and sends JSON payloads to one or multiple sockets.
45
- * Wraps individual operations in independent try-catch layers to avoid process instability.
46
- *
47
- * @param {WebSocket|WebSocket[]} sockets - Target WebSocket(s)
48
- * @param {Object} data - Payload content to dispatch
49
- */
50
- function socketSend(sockets, data) {
51
- const targetSockets = Array.isArray(sockets) ? sockets : [sockets];
52
- const dataStr = typeof data === 'string' ? data : JSON.stringify(data);
53
-
54
- for (const socket of targetSockets) {
55
- try {
56
- if (socket && socket.readyState === WebSocket.OPEN) {
57
- socket.send(dataStr);
58
- }
59
- } catch (err) {
60
- console.error(`[@cocreate/server-mesh] Error executing socketSend transaction:`, err.message);
61
- }
62
- }
63
- }
18
+ import { ServerMeshInbound } from './inbound.js';
19
+ import { ServerMeshOutbound } from './outbound.js';
64
20
 
65
21
  /**
66
- * ServerMesh acts as a pure transport fabric. It blindly dispatches
67
- * all inbound network payloads as standard EventEmitter events.
22
+ * ServerMesh orchestrates multi-tenant routing, unified IPC event forwarding,
23
+ * shared active connection pools, and coordinates ServerMeshInbound and ServerMeshOutbound.
68
24
  */
69
- class ServerMesh extends EventEmitter {
25
+ export class ServerMesh extends EventEmitter {
70
26
  constructor() {
71
27
  super();
28
+ this.server = null;
29
+
30
+ // Sub-module transport managers
31
+ this.inboundManager = new ServerMeshInbound(this);
32
+ this.outboundManager = new ServerMeshOutbound(this);
33
+
34
+ // Shared Connection & Multi-tenant Routing Tables
35
+ this.activeConnections = new Map(); // serverId / IP -> Network Socket
36
+ this.orgWorkers = new Map(); // organization_id -> Set(workerId)
37
+ this.orgServers = new Map(); // organization_id -> Set(Network Socket)
38
+
39
+ this.isClosed = false;
40
+ this.handleServerEvent = this.handleServerEvent.bind(this);
72
41
  }
73
42
 
74
43
  /**
75
- * Sends a payload across the secure network mesh.
76
- * Automatically appends a unique ID (uid) to guarantee end-to-end tracking.
44
+ * Initializes the ServerMesh orchestrator with the CoCreateServer instance context.
77
45
  *
78
- * @param {Object} payload - Object to transmit containing a required 'method'
46
+ * @param {Object} server - CoCreateServer instance
47
+ * @returns {Promise<ServerMesh>}
79
48
  */
80
- send(payload) {
81
- if (!payload || !payload.method) {
82
- console.error("[@cocreate/server-mesh] [MESH ERROR] Cannot transmit mesh payload without a defined 'method' key.");
83
- return;
49
+ async init(server) {
50
+ if (!server) {
51
+ throw new Error('[@cocreate/server-mesh] Failed to initialize: server instance is required.');
84
52
  }
85
53
 
86
- // Standardize: Every outgoing message must have a unique identifier (uid)
87
- if (!payload.uid) {
88
- payload.uid = crypto.randomUUID();
89
- }
54
+ this.server = server;
55
+ this.server.mesh = this; // Attach mesh instance directly to server context
56
+ this.isClosed = false;
90
57
 
91
- const isPrimary = serverCtx?.isPrimary;
58
+ // Register IPC listeners on server context
59
+ this.setupServerListeners();
92
60
 
93
- if (!isPrimary) {
94
- // Worker Thread: Forward to Master process via native child IPC to relay
95
- if (typeof process.send === 'function') {
96
- process.send({
97
- method: 'mesh.outbound_mesh_relay',
98
- payload: payload
99
- });
100
- }
101
- } else {
102
- // Master Process: Determine target sockets dynamically based on payload directives
103
- const targets = [];
61
+ const isPrimary = this.server.isPrimary ?? true;
104
62
 
105
- if (payload.broadcast === true || !payload.servers) {
106
- // Symmetrical Cluster Broadcast: Gather all active socket tunnels
107
- targets.push(...activeConnections.values());
108
- } else {
109
- // Multicast/Unicast Routing: Handle polymorphic servers filter (single string or array)
110
- const serversList = Array.isArray(payload.servers)
111
- ? payload.servers
112
- : [payload.servers];
63
+ if (!isPrimary) {
64
+ console.log(`[@cocreate/server-mesh] Initialized mesh layer in worker process context`);
65
+ return this;
66
+ }
113
67
 
114
- for (const id of serversList) {
115
- const socket = activeConnections.get(id);
116
- if (socket) {
117
- targets.push(socket);
118
- }
119
- }
120
- }
68
+ // Initialize inbound and outbound sub-modules for Primary node
69
+ await this.inboundManager.init(this.server);
70
+ await this.outboundManager.init(this.server);
121
71
 
122
- // Deduplicate target sockets since activeConnections maps both serverId and IP to the same socket
123
- const uniqueSockets = Array.from(new Set(targets));
124
-
125
- // Dispatch safely using standard wrapper loop
126
- socketSend(uniqueSockets, payload);
127
- }
72
+ console.log(`[@cocreate/server-mesh] Initialized server mesh layer for node ${this.server.id || 'unassigned'}`);
73
+ return this;
128
74
  }
129
75
 
130
76
  /**
131
- * Coordinates the graceful shutdown sequence. Called directly from the Master server class.
132
- * @returns {Promise<void>} Resolves when all connections have dropped and port binds are freed
77
+ * Binds IPC message and broadcasting event handlers to this.server.
133
78
  */
134
- async close() {
135
- return closeMesh();
136
- }
137
- }
138
-
139
- const serverMeshInstance = new ServerMesh();
140
-
141
- // Listener A: Handle peer registration of an organization subscription
142
- serverMeshInstance.on('mesh.orgAdded', (message, ws) => {
143
- if (!ws) return;
144
- const organization_id = message.organization_id;
145
- const ip = ws.remoteAddress || ws._socket?.remoteAddress;
146
- const peerId = message.senderId || ip;
147
-
148
- if (!orgServers.has(organization_id)) {
149
- orgServers.set(organization_id, new Set());
150
- }
151
- orgServers.get(organization_id).add(ws);
152
- console.log(`[@cocreate/server-mesh] Peer ${peerId} registered subscription for org "${organization_id}"`);
153
- });
154
-
155
- // Listener B: Handle peer de-registration of an organization subscription
156
- serverMeshInstance.on('mesh.orgDeleted', (message, ws) => {
157
- if (!ws) return;
158
- const organization_id = message.organization_id;
159
- const ip = ws.remoteAddress || ws._socket?.remoteAddress;
160
- const peerId = message.senderId || ip;
161
-
162
- const sockets = orgServers.get(organization_id);
163
- if (sockets) {
164
- sockets.delete(ws);
165
- if (sockets.size === 0) {
166
- orgServers.delete(organization_id);
167
- }
168
- console.log(`[@cocreate/server-mesh] Peer ${peerId} de-registered subscription from org "${organization_id}"`);
169
- }
170
- });
171
-
172
- // Listener C: Handle incoming real-time network unicast transaction data
173
- serverMeshInstance.on('mesh.orgData', (message) => {
174
- const { organization_id, data } = message;
175
-
176
- // Local Unicast: Relay directly to local child worker threads hosting this tenant
177
- const activeWorkerIds = orgWorkers.get(organization_id);
178
- if (activeWorkerIds && activeWorkerIds.size > 0 && serverCtx) {
179
- try {
180
- serverCtx.send({
181
- method: 'mesh.inbound',
182
- workers: Array.from(activeWorkerIds),
183
- master: false,
184
- data: typeof data === 'string' ? data : JSON.stringify(data)
185
- });
186
- } catch (err) {
187
- console.error(`[@cocreate/server-mesh] Failed to relay incoming orgData to workers of org "${organization_id}":`, err.message);
188
- }
189
- }
190
- });
191
-
192
- // Listener D: Handle cluster coordination to cancel DB prune schedules on peer consensus
193
- serverMeshInstance.on('mesh.peer_pruned', (message) => {
194
- if (message.deadIp) {
195
- const activeTimerId = pruneTimers.get(message.deadIp);
196
- if (activeTimerId) {
197
- clearTimeout(activeTimerId);
198
- pruneTimers.delete(message.deadIp);
199
- console.log(`[@cocreate/server-mesh] Cancelled DB prune for ${message.deadIp} due to peer consensus.`);
200
- }
201
- }
202
- });
79
+ setupServerListeners() {
80
+ if (!this.server || typeof this.server.on !== 'function') return;
203
81
 
204
- // Listener E: Handle fallback of unhandled incoming network packets
205
- serverMeshInstance.on('mesh.raw_message', (message) => {
206
- if (serverCtx) {
207
- try {
208
- serverCtx.send({
209
- method: 'mesh.inbound',
210
- broadcast: true,
211
- master: false,
212
- data: JSON.stringify(message)
213
- });
214
- } catch (err) {
215
- console.error("[@cocreate/server-mesh] Failed to relay raw fallback message to workers:", err.message);
216
- }
217
- }
218
- });
82
+ // Main unified IPC entry point
83
+ this.server.on('mesh', (payload) => {
84
+ this.handleIPCPayload(payload);
85
+ });
219
86
 
220
- /**
221
- * Extracts raw dataset arrays from standard database responses.
222
- */
223
- function normalizePeersList(result) {
224
- if (!result) return [];
225
- if (Array.isArray(result)) return result;
226
- if (Array.isArray(result.object)) return result.object;
227
- if (Array.isArray(result.data)) return result.data;
228
- return [];
229
- }
87
+ // Direct mesh dispatch listener
88
+ this.server.on('mesh.send', (payload) => {
89
+ this.send(payload);
90
+ });
230
91
 
231
- /**
232
- * Generates secure, ephemeral TLS credentials on-the-fly for inter-node communication.
233
- */
234
- function generateTLSCredentials() {
235
- const tmpDir = os.tmpdir();
236
- const keyPath = path.join(tmpDir, `mesh-key-${Date.now()}-${crypto.randomBytes(4).toString('hex')}.pem`);
237
- const certPath = path.join(tmpDir, `mesh-cert-${Date.now()}-${crypto.randomBytes(4).toString('hex')}.pem`);
238
-
239
- try {
240
- execSync(`openssl req -x509 -newkey rsa:2048 -keyout "${keyPath}" -out "${certPath}" -days 1 -nodes -subj "/CN=localhost"`, { stdio: 'ignore' });
241
- const key = fs.readFileSync(keyPath);
242
- const cert = fs.readFileSync(certPath);
243
-
244
- // Cleanup generated temporary certificate files
245
- try { fs.unlinkSync(keyPath); } catch {}
246
- try { fs.unlinkSync(certPath); } catch {}
247
-
248
- return { key, cert };
249
- } catch (err) {
250
- console.error('[@cocreate/server-mesh] Failed to generate ephemeral TLS credentials:', err.message);
251
- return null;
92
+ // Register protocol routing logic
93
+ this.setupProtocolListeners();
252
94
  }
253
- }
254
95
 
255
- /**
256
- * Safely removes a closed socket from all organization maps.
257
- */
258
- function removeSocketFromAllOrgServers(ws) {
259
- for (const [orgId, sockets] of orgServers.entries()) {
260
- if (sockets.has(ws)) {
261
- sockets.delete(ws);
262
- if (sockets.size === 0) {
263
- orgServers.delete(orgId);
96
+ /**
97
+ * Handles inbound IPC payloads from server.on('mesh').
98
+ *
99
+ * @param {Object} payload - IPC message containing 'method' property
100
+ */
101
+ handleIPCPayload(payload) {
102
+ if (!payload || typeof payload !== 'object' || !payload.method) return;
103
+
104
+ const { method } = payload;
105
+
106
+ if (method === 'mesh.send' || method === 'mesh.outbound_mesh_relay') {
107
+ const dataToTransmit = payload.payload ? payload.payload : payload;
108
+ this.send(dataToTransmit);
109
+ }
110
+ else if (method === 'mesh.registerOrg' || method === 'mesh.orgAdded') {
111
+ const { organization_id, workerId } = payload;
112
+ let isNewOrg = false;
113
+
114
+ if (!this.orgWorkers.has(organization_id)) {
115
+ this.orgWorkers.set(organization_id, new Set());
116
+ isNewOrg = true;
264
117
  }
265
- }
266
- }
267
- }
268
-
269
- /**
270
- * Boots the socket mesh and configures routing tunnels.
271
- *
272
- * @param {Object} server - CoCreateServer instance context
273
- */
274
- export async function init(server) {
275
- serverCtx = server;
276
-
277
- const isPrimary = serverCtx.isPrimary;
278
118
 
279
- if (!isPrimary) {
280
- // --- WORKER EVENT PIPELINE ---
281
- // Monitor process IPC from Primary
282
- process.on('message', (message) => {
283
- if (message && message.method) {
284
- serverMeshInstance.emit(message.method, message);
119
+ if (workerId) {
120
+ this.orgWorkers.get(organization_id).add(workerId);
285
121
  }
286
- });
287
- return;
288
- }
289
-
290
- // --- MASTER TUNNEL & UNICAST ROUTING PIPELINE ---
291
- // Handle incoming IPC registrations from child workers
292
- cluster.on('message', (workerRef, message) => {
293
- if (message && message.method) {
294
- if (message.method === 'mesh.registerOrg') {
295
- const { organization_id, workerId } = message;
296
- let isNewOrg = false;
297
-
298
- if (!orgWorkers.has(organization_id)) {
299
- orgWorkers.set(organization_id, new Set());
300
- isNewOrg = true;
301
- }
302
-
303
- orgWorkers.get(organization_id).add(workerId);
304
- console.log(`[@cocreate/server-mesh] [LOCAL-MAP] Tenant "${organization_id}" registered on Worker ${workerId}`);
305
122
 
306
- if (isNewOrg) {
307
- serverMeshInstance.send({
308
- method: 'mesh.orgAdded',
123
+ if (isNewOrg) {
124
+ this.send({
125
+ method: 'mesh.orgAdded',
126
+ organization_id,
127
+ senderId: this.server.id
128
+ });
129
+ }
130
+ }
131
+ else if (method === 'mesh.deregisterOrg' || method === 'mesh.orgDeleted') {
132
+ const { organization_id, workerId } = payload;
133
+ const workersSet = this.orgWorkers.get(organization_id);
134
+
135
+ if (workersSet) {
136
+ if (workerId) workersSet.delete(workerId);
137
+ if (!workerId || workersSet.size === 0) {
138
+ this.orgWorkers.delete(organization_id);
139
+ this.send({
140
+ method: 'mesh.orgDeleted',
309
141
  organization_id,
310
- senderId: serverCtx.id
142
+ senderId: this.server.id
311
143
  });
312
144
  }
313
- }
314
- else if (message.method === 'mesh.deregisterOrg') {
315
- const { organization_id, workerId } = message;
316
- const workersSet = orgWorkers.get(organization_id);
317
-
318
- if (workersSet) {
319
- workersSet.delete(workerId);
320
- if (workersSet.size === 0) {
321
- orgWorkers.delete(organization_id);
322
- console.log(`[@cocreate/server-mesh] [LOCAL-MAP] Tenant "${organization_id}" evicted. No remaining local workers.`);
323
- serverMeshInstance.send({
324
- method: 'mesh.orgDeleted',
325
- organization_id,
326
- senderId: serverCtx.id
145
+ }
146
+ }
147
+ else if (method === 'mesh.orgData') {
148
+ const { organization_id, data, senderWorkerId } = payload;
149
+
150
+ // Relay locally to sibling workers hosting tenant
151
+ const activeWorkerIds = this.orgWorkers.get(organization_id);
152
+ if (activeWorkerIds && activeWorkerIds.size > 0 && this.server) {
153
+ const siblingWorkerIds = senderWorkerId
154
+ ? Array.from(activeWorkerIds).filter(wId => wId !== senderWorkerId)
155
+ : Array.from(activeWorkerIds);
156
+
157
+ if (siblingWorkerIds.length > 0) {
158
+ try {
159
+ this.server.send({
160
+ method: 'mesh.inbound',
161
+ workers: siblingWorkerIds,
162
+ master: false,
163
+ data: typeof data === 'string' ? data : JSON.stringify(data)
327
164
  });
165
+ } catch (err) {
166
+ console.error("[@cocreate/server-mesh] Failed to relay local orgData:", err.message);
328
167
  }
329
168
  }
330
- }
331
- else if (message.method === 'mesh.orgData') {
332
- const { organization_id, data, senderWorkerId } = message;
333
-
334
- // Local Unicast: Relay directly to child worker cores hosting the tenant (ignoring sender)
335
- const activeWorkerIds = orgWorkers.get(organization_id);
336
- if (activeWorkerIds && activeWorkerIds.size > 0) {
337
- const siblingWorkerIds = Array.from(activeWorkerIds).filter(wId => wId !== senderWorkerId);
338
- if (siblingWorkerIds.length > 0) {
339
- try {
340
- serverCtx.send({
341
- method: 'mesh.inbound',
342
- workers: siblingWorkerIds,
343
- master: false,
344
- data: typeof data === 'string' ? data : JSON.stringify(data)
345
- });
346
- } catch (err) {
347
- console.error("[@cocreate/server-mesh] Failed to propagate orgData to local siblings:", err.message);
348
- }
349
- }
350
- }
169
+ }
351
170
 
352
- // Network Unicast: Relay over secure tunnels to peer servers hosting the tenant
353
- const targetSockets = orgServers.get(organization_id);
354
- if (targetSockets && targetSockets.size > 0) {
355
- const envelope = {
356
- method: 'mesh.orgData',
357
- organization_id,
358
- data
359
- };
360
- socketSend(Array.from(targetSockets), envelope);
361
- }
362
- }
363
- else if (message.method === 'mesh.outbound_mesh_relay') {
364
- // Relay child worker message out to peer nodes
365
- if (message.payload) {
366
- serverMeshInstance.send(message.payload);
171
+ // Relay to network peer sockets subscribed to tenant
172
+ const targetSockets = this.orgServers.get(organization_id);
173
+ if (targetSockets && targetSockets.size > 0) {
174
+ this.socketSend(Array.from(targetSockets), {
175
+ method: 'mesh.orgData',
176
+ organization_id,
177
+ data
178
+ });
179
+ }
180
+ }
181
+ else {
182
+ const targetMethod = method.replace(/^mesh\./, '');
183
+ const systemEvents = ['restart', 'reboot', 'shutdown', 'cordon', 'shed', 'drain'];
184
+
185
+ if (systemEvents.includes(targetMethod)) {
186
+ const actionPayload = { ...payload, method: targetMethod };
187
+ if (typeof this.server.send === 'function') {
188
+ this.server.send(actionPayload);
367
189
  }
190
+ this.emit(targetMethod, actionPayload);
191
+ } else {
192
+ this.emit(method, payload);
368
193
  }
369
-
370
- // Mirror all incoming process IPC commands to local listeners
371
- serverMeshInstance.emit(message.method, message, workerRef);
372
194
  }
373
- });
374
-
375
- // Boot HTTPS Secure Server
376
- const credentials = generateTLSCredentials();
377
- if (credentials) {
378
- secureMeshServer = https.createServer(credentials);
379
- secureMeshServer.on('error', (err) => {
380
- if (err.code === 'EADDRINUSE') {
381
- console.error(`\n[@cocreate/server-mesh] [FATAL] Port ${MESH_PORT} is already in use!`);
382
- process.exit(1);
383
- } else {
384
- console.error(`[@cocreate/server-mesh] Mesh server error:`, err);
195
+ }
196
+
197
+ /**
198
+ * Registers protocol handlers for multi-tenant subscriptions and consensus events.
199
+ */
200
+ setupProtocolListeners() {
201
+ this.on('mesh.orgAdded', (message, ws) => {
202
+ if (!ws) return;
203
+ const organization_id = message.organization_id;
204
+ const ip = ws.remoteAddress || ws._socket?.remoteAddress;
205
+ const peerId = message.senderId || ip;
206
+
207
+ if (!this.orgServers.has(organization_id)) {
208
+ this.orgServers.set(organization_id, new Set());
385
209
  }
210
+ this.orgServers.get(organization_id).add(ws);
211
+ console.log(`[@cocreate/server-mesh] Peer ${peerId} registered subscription for org "${organization_id}"`);
386
212
  });
387
213
 
388
- secureMeshServer.listen(MESH_PORT, '0.0.0.0', () => {
389
- console.log(`[@cocreate/server-mesh] Mesh securely listening on 0.0.0.0:${MESH_PORT}`);
214
+ this.on('mesh.orgDeleted', (message, ws) => {
215
+ if (!ws) return;
216
+ const organization_id = message.organization_id;
217
+ const ip = ws.remoteAddress || ws._socket?.remoteAddress;
218
+ const peerId = message.senderId || ip;
219
+
220
+ const sockets = this.orgServers.get(organization_id);
221
+ if (sockets) {
222
+ sockets.delete(ws);
223
+ if (sockets.size === 0) {
224
+ this.orgServers.delete(organization_id);
225
+ }
226
+ console.log(`[@cocreate/server-mesh] Peer ${peerId} de-registered subscription from org "${organization_id}"`);
227
+ }
390
228
  });
391
- } else {
392
- throw new Error("Unable to boot secure mesh: TLS credential generation failed.");
393
- }
394
229
 
395
- meshWss = new WebSocketServer({ server: secureMeshServer });
230
+ this.on('mesh.orgData', (message) => {
231
+ const { organization_id, data } = message;
396
232
 
397
- meshWss.on('connection', (ws, req) => {
398
- const ip = req.socket.remoteAddress;
399
- let remoteServerId = null;
400
-
401
- const protocolHeader = req.headers['sec-websocket-protocol'];
402
- if (protocolHeader) {
403
- try {
404
- const parsed = JSON.parse(decodeURIComponent(protocolHeader));
405
- remoteServerId = parsed.id;
406
- } catch (err) {
407
- // Ignore decoding anomalies gracefully
233
+ const activeWorkerIds = this.orgWorkers.get(organization_id);
234
+ if (activeWorkerIds && activeWorkerIds.size > 0 && this.server) {
235
+ try {
236
+ this.server.send({
237
+ method: 'mesh.inbound',
238
+ workers: Array.from(activeWorkerIds),
239
+ master: false,
240
+ data: typeof data === 'string' ? data : JSON.stringify(data)
241
+ });
242
+ } catch (err) {
243
+ console.error(`[@cocreate/server-mesh] Failed to relay incoming orgData for org "${organization_id}":`, err.message);
244
+ }
408
245
  }
409
- }
410
-
411
- const identifier = remoteServerId || ip;
412
- console.log(`[@cocreate/server-mesh] Secure peer connection established: ${identifier}`);
413
-
414
- ws.isAlive = true;
415
- ws.on('pong', () => { ws.isAlive = true; });
246
+ });
416
247
 
417
- // Inlined Blind Event-Dispatch Loop: If no method, do nothing.
418
- ws.on('message', (rawData) => {
419
- try {
420
- const message = JSON.parse(rawData.toString());
421
- if (message && message.method) {
422
- serverMeshInstance.emit(message.method, message, ws);
248
+ this.on('mesh.peer_pruned', (message) => {
249
+ if (message.deadIp && this.outboundManager) {
250
+ const activeTimerId = this.outboundManager.pruneTimers.get(message.deadIp);
251
+ if (activeTimerId) {
252
+ clearTimeout(activeTimerId);
253
+ this.outboundManager.pruneTimers.delete(message.deadIp);
254
+ console.log(`[@cocreate/server-mesh] Cancelled prune schedule for ${message.deadIp} on peer consensus.`);
423
255
  }
424
- } catch (err) {
425
- // Safely catch parsing failures
426
256
  }
427
257
  });
428
258
 
429
- ws.on('close', () => {
430
- console.log(`[@cocreate/server-mesh] Peer connection ${identifier} dropped.`);
431
- activeConnections.delete(identifier);
432
- if (remoteServerId) activeConnections.delete(ip);
433
- removeSocketFromAllOrgServers(ws);
434
- });
435
-
436
- ws.on('error', (err) => {
437
- console.error(`[@cocreate/server-mesh] Connection error (${identifier}):`, err.message);
259
+ this.on('mesh.raw_message', (message) => {
260
+ if (this.server) {
261
+ try {
262
+ this.server.send({
263
+ method: 'mesh.inbound',
264
+ broadcast: true,
265
+ master: false,
266
+ data: JSON.stringify(message)
267
+ });
268
+ } catch (err) {
269
+ console.error("[@cocreate/server-mesh] Failed to relay raw fallback message:", err.message);
270
+ }
271
+ }
438
272
  });
439
-
440
- activeConnections.set(identifier, ws);
441
- if (remoteServerId) activeConnections.set(ip, ws);
442
- });
443
-
444
- try {
445
- await getServers();
446
- } catch (err) {
447
- console.error("[@cocreate/server-mesh] Peer discovery reconciliation cycle failed on start:", err.message);
448
273
  }
449
-
450
- wsPingInterval = setInterval(pingActiveWebsockets, 30000);
451
- }
452
274
 
453
- /**
454
- * Registers this server instance once on startup to allow discovery.
455
- */
456
- async function registerServer() {
457
- if (isGracefulExitRunning || !serverCtx?.crud) return;
458
- const instanceId = serverCtx.infrastructure?.instanceId;
459
- if (instanceId) {
275
+ /**
276
+ * Parses incoming network socket payloads and dispatches events.
277
+ *
278
+ * @param {string|Buffer} rawData
279
+ * @param {Object} ws - Active network socket reference
280
+ */
281
+ handleIncomingMessage(rawData, ws) {
282
+ let message;
460
283
  try {
461
- await serverCtx.crud.send({
462
- method: 'object.delete',
463
- array: 'servers',
464
- $filter: {
465
- query: { 'infrastructure.instanceId': instanceId },
466
- limit: 0
467
- },
468
- organization_id: serverCtx.organization_id,
469
- host: serverCtx.host
470
- });
284
+ message = JSON.parse(rawData.toString());
471
285
  } catch (err) {
472
- console.warn("[@cocreate/server-mesh] Startup instance-eviction run failed/skipped:", err.message);
286
+ return;
473
287
  }
474
- }
475
288
 
476
- const registerPayload = {
477
- method: 'object.create',
478
- array: 'servers',
479
- object: {
480
- _id: serverCtx.id,
481
- ip: serverCtx.ip,
482
- host: serverCtx.host,
483
- status: 'active',
484
- infrastructure: serverCtx.infrastructure
485
- },
486
- organization_id: serverCtx.organization_id,
487
- host: serverCtx.host
488
- };
489
-
490
- try {
491
- const registerResult = await serverCtx.crud.send(registerPayload);
492
- if (registerResult && registerResult.error) {
493
- console.error(`[@cocreate/server-mesh] Startup registration rejected: ${JSON.stringify(registerResult.error)}`);
289
+ if (!message || typeof message !== 'object') return;
290
+
291
+ if (message.server_id && message.server_id === this.server?.id) {
292
+ return;
293
+ }
294
+
295
+ const method = message.method;
296
+ if (!method) return;
297
+
298
+ const targetMethod = method.replace(/^mesh\./, '');
299
+ const systemEvents = ['restart', 'reboot', 'shutdown', 'cordon', 'shed', 'drain'];
300
+
301
+ if (systemEvents.includes(targetMethod)) {
302
+ const ipcPayload = { ...message, method: targetMethod };
303
+
304
+ if (this.server && typeof this.server.send === 'function') {
305
+ this.server.send(ipcPayload);
306
+ }
307
+
308
+ this.emit(targetMethod, ipcPayload, ws);
494
309
  } else {
495
- console.log(`[@cocreate/server-mesh] Successfully registered server profile in db.`);
310
+ this.emit(method, message, ws);
496
311
  }
497
- } catch (err) {
498
- console.error("[@cocreate/server-mesh] Startup registration network error:", err.message);
499
312
  }
500
- }
501
313
 
502
- /**
503
- * Monitors responsiveness of active WebSocket connections.
504
- */
505
- function pingActiveWebsockets() {
506
- if (isGracefulExitRunning || !meshWss) return;
507
-
508
- meshWss.clients.forEach((ws) => {
509
- if (ws.isAlive === false) {
510
- console.warn(`[@cocreate/server-mesh] Client socket unresponsive. Terminating connection.`);
511
- return ws.terminate();
314
+ /**
315
+ * Transmits payload across active network socket tunnels or targeted servers.
316
+ *
317
+ * @param {Object} payload
318
+ */
319
+ send(payload) {
320
+ if (!payload || typeof payload !== 'object' || !payload.method) {
321
+ console.error("[@cocreate/server-mesh] Cannot transmit mesh payload without a defined 'method' key.");
322
+ return;
512
323
  }
513
- ws.isAlive = false;
514
- try {
515
- ws.ping();
516
- } catch (err) {
517
- console.error("[@cocreate/server-mesh] Error during websocket ping:", err.message);
324
+
325
+ if (!payload.uuid) {
326
+ payload.uuid = crypto.randomUUID();
518
327
  }
519
- });
520
- }
521
328
 
522
- /**
523
- * Cleanly handles node evictions and releases network bindings.
524
- * Rewritten to support fully coordinated async invocation from Primary Server controller without process.exit
525
- * @returns {Promise<void>} Resolves when the HTTPS mesh server port is successfully released.
526
- */
527
- async function closeMesh() {
528
- if (isGracefulExitRunning) return;
529
- isGracefulExitRunning = true;
530
-
531
- console.log(`[@cocreate/server-mesh] Coordinated secure mesh shutdown triggered. Evicting state...`);
329
+ const isPrimary = this.server?.isPrimary ?? true;
532
330
 
533
- clearInterval(wsPingInterval);
331
+ if (!isPrimary) {
332
+ if (typeof this.server?.send === 'function') {
333
+ this.server.send({
334
+ method: 'mesh.outbound_mesh_relay',
335
+ payload: payload
336
+ });
337
+ }
338
+ } else {
339
+ const targets = [];
534
340
 
535
- if (serverCtx?.crud && serverCtx.id && serverCtx.organization_id) {
536
- try {
537
- await serverCtx.crud.send({
538
- method: 'object.delete',
539
- array: 'servers',
540
- object: { _id: serverCtx.id },
541
- organization_id: serverCtx.organization_id,
542
- host: serverCtx.host
543
- });
544
- console.log(`[@cocreate/server-mesh] Server record wiped from database.`);
545
- } catch (err) {
546
- console.error("[@cocreate/server-mesh] Failed to wipe server status cleanly from DB:", err.message);
341
+ if (payload.broadcast === true || !payload.servers) {
342
+ targets.push(...this.activeConnections.values());
343
+ } else {
344
+ const serversList = Array.isArray(payload.servers) ? payload.servers : [payload.servers];
345
+ for (const id of serversList) {
346
+ const socket = this.activeConnections.get(id);
347
+ if (socket) {
348
+ targets.push(socket);
349
+ }
350
+ }
351
+ }
352
+
353
+ const uniqueSockets = Array.from(new Set(targets));
354
+ this.socketSend(uniqueSockets, payload);
547
355
  }
548
356
  }
549
357
 
550
- for (const timerId of retryTimers.values()) clearTimeout(timerId);
551
- for (const timerId of pruneTimers.values()) clearTimeout(timerId);
552
- retryTimers.clear();
553
- pruneTimers.clear();
358
+ /**
359
+ * Safely serializes and sends payload data across network sockets without direct library coupling.
360
+ *
361
+ * @param {Object|Object[]} sockets - Target socket reference(s)
362
+ * @param {Object} data
363
+ */
364
+ socketSend(sockets, data) {
365
+ const targetSockets = Array.isArray(sockets) ? sockets : [sockets];
366
+ const dataStr = typeof data === 'string' ? data : JSON.stringify(data);
554
367
 
555
- const uniqueSockets = new Set(activeConnections.values());
556
- for (const ws of uniqueSockets) {
557
- if (ws && ws.readyState === WebSocket.OPEN) {
558
- try { ws.close(); } catch {}
368
+ for (const socket of targetSockets) {
369
+ try {
370
+ // 1 corresponds to OPEN state in WebSocket standard protocol implementations
371
+ if (socket && socket.readyState === 1) {
372
+ socket.send(dataStr);
373
+ }
374
+ } catch (err) {
375
+ console.error(`[@cocreate/server-mesh] Error executing socketSend transaction:`, err.message);
376
+ }
559
377
  }
560
378
  }
561
- activeConnections.clear();
562
-
563
- if (meshWss) {
564
- try { meshWss.close(); } catch {}
565
- }
566
379
 
567
- return new Promise((resolve) => {
568
- if (secureMeshServer) {
569
- secureMeshServer.close(() => {
570
- console.log(`[@cocreate/server-mesh] Secure inter-node port ${MESH_PORT} successfully released.`);
571
- resolve();
572
- });
573
- } else {
574
- resolve();
380
+ /**
381
+ * Safely removes a closed socket connection from tenant routing maps.
382
+ *
383
+ * @param {Object} ws
384
+ */
385
+ removeSocketFromAllOrgServers(ws) {
386
+ for (const [orgId, sockets] of this.orgServers.entries()) {
387
+ if (sockets.has(ws)) {
388
+ sockets.delete(ws);
389
+ if (sockets.size === 0) {
390
+ this.orgServers.delete(orgId);
391
+ }
392
+ }
575
393
  }
576
- });
577
- }
394
+ }
578
395
 
579
- /**
580
- * Scans for peer nodes, establishes direct connections, and schedules lazy prunes.
581
- */
582
- async function getServers() {
583
- if (isGracefulExitRunning || !serverCtx?.crud) return;
584
-
585
- // Register profile once on boot
586
- await registerServer();
587
-
588
- const readPayload = {
589
- method: 'object.read',
590
- array: 'servers',
591
- $filter: {
592
- query: { _id: { $lt: serverCtx.id } },
593
- sort: { _id: 1 },
594
- limit: 0
595
- },
596
- organization_id: serverCtx.organization_id,
597
- host: serverCtx.host
598
- };
599
-
600
- let readResult;
601
- try {
602
- readResult = await serverCtx.crud.send(readPayload);
603
- } catch (err) {
604
- console.error("[@cocreate/server-mesh] Failed to fetch active peer server records:", err.message);
605
- return;
396
+ /**
397
+ * Internal server event handler callback.
398
+ */
399
+ handleServerEvent(payload) {
400
+ this.send(payload);
606
401
  }
607
402
 
608
- const peers = normalizePeersList(readResult);
609
-
610
- for (const targetPeer of peers) {
611
- const targetIp = targetPeer.ip;
612
-
613
- if (targetIp === serverCtx.ip) continue;
614
- if (activeConnections.has(targetIp)) continue;
615
-
616
- console.log(`[@cocreate/server-mesh] Initiating secure connection stream to target: ${targetIp}`);
617
-
618
- let retryCount = 0;
619
- const maxRetries = 10;
620
-
621
- const connectToPeer = () => {
622
- if (isGracefulExitRunning) return;
623
-
624
- const targetUrl = `wss://${targetIp}:${MESH_PORT}/${serverCtx.organization_id}`;
625
- const clientSocket = new WebSocket(targetUrl, [
626
- encodeURIComponent(JSON.stringify({
627
- type: 'peer',
628
- ip: serverCtx.ip,
629
- id: serverCtx.id,
630
- token: process.env.CLUSTER_SECRET,
631
- sniHost: serverCtx.host
632
- }))
633
- ], {
634
- servername: serverCtx.host,
635
- rejectUnauthorized: false
636
- });
637
-
638
- clientSocket.isAlive = true;
639
- clientSocket.on('pong', () => { clientSocket.isAlive = true; });
640
- clientSocket.on('ping', () => { clientSocket.pong(); });
641
-
642
- clientSocket.on('open', () => {
643
- console.log(`[@cocreate/server-mesh] Secure link complete to peer: ${targetIp}`);
644
- retryCount = 0;
645
-
646
- if (retryTimers.has(targetIp)) clearTimeout(retryTimers.get(targetIp));
647
- if (pruneTimers.has(targetIp)) clearTimeout(pruneTimers.get(targetIp));
648
-
649
- retryTimers.delete(targetIp);
650
- pruneTimers.delete(targetIp);
651
-
652
- clientSocket.isPeer = true;
653
- clientSocket.isPlatformTunnel = true;
654
-
655
- activeConnections.set(targetPeer._id, clientSocket);
656
- activeConnections.set(targetIp, clientSocket);
657
- });
658
-
659
- clientSocket.on('close', () => {
660
- activeConnections.delete(targetPeer._id);
661
- activeConnections.delete(targetIp);
662
- retryTimers.delete(targetIp);
663
- removeSocketFromAllOrgServers(clientSocket);
664
-
665
- if (isGracefulExitRunning) return;
666
-
667
- if (retryCount < maxRetries) {
668
- retryCount++;
669
- const delay = 5000 + Math.floor(Math.random() * 2000);
670
- console.warn(`[@cocreate/server-mesh] Peer disconnected. Retrying in ${delay}ms...`);
671
- const timerId = setTimeout(connectToPeer, delay);
672
- retryTimers.set(targetIp, timerId);
673
- } else {
674
- const staggerDelay = peers.length * 1000;
675
- console.warn(`[@cocreate/server-mesh] Peer dead. Scheduling db prune in ${staggerDelay}ms...`);
676
-
677
- const pruneTimerId = setTimeout(async () => {
678
- console.error(`[@cocreate/server-mesh] Pruning unresponsive node: ${targetIp}`);
679
-
680
- try {
681
- await serverCtx.crud.send({
682
- method: 'object.delete',
683
- array: 'servers',
684
- object: { _id: targetPeer._id },
685
- organization_id: serverCtx.organization_id,
686
- host: serverCtx.host
687
- });
688
- } catch (err) {
689
- console.error(`[@cocreate/server-mesh] Failed to prune node ${targetIp} from database:`, err.message);
690
- }
691
-
692
- // Symmetrical Consensus Broadcast: Emits with standard uuid tracking
693
- serverMeshInstance.send({
694
- method: 'mesh.peer_pruned',
695
- deadIp: targetIp,
696
- isForwarded: true,
697
- senderId: serverCtx.id
698
- });
699
-
700
- pruneTimers.delete(targetIp);
701
- }, staggerDelay);
702
-
703
- pruneTimers.set(targetIp, pruneTimerId);
704
- }
705
- });
403
+ /**
404
+ * Initiates coordinated mesh shutdown, closing server ports and outbound timers.
405
+ *
406
+ * @returns {Promise<void>}
407
+ */
408
+ async close() {
409
+ if (this.isClosed) return;
410
+ this.isClosed = true;
706
411
 
707
- // Inlined Blind Event-Dispatch Loop
708
- clientSocket.on('message', (rawData) => {
709
- try {
710
- const message = JSON.parse(rawData.toString());
711
- if (message && message.method) {
712
- serverMeshInstance.emit(message.method, message, clientSocket);
713
- }
714
- } catch (err) {
715
- // Safely catch parsing failures
716
- }
717
- });
412
+ console.log(`[@cocreate/server-mesh] Coordinated secure mesh shutdown triggered.`);
718
413
 
719
- clientSocket.on('error', (err) => {
720
- console.log(`[@cocreate/server-mesh] Socket transport failure: ${err.message}`);
721
- });
722
- };
414
+ if (this.outboundManager) {
415
+ this.outboundManager.close();
416
+ }
417
+
418
+ const uniqueSockets = new Set(this.activeConnections.values());
419
+ for (const ws of uniqueSockets) {
420
+ if (ws && ws.readyState === 1) {
421
+ try { ws.close(); } catch {}
422
+ }
423
+ }
424
+ this.activeConnections.clear();
723
425
 
724
- connectToPeer();
426
+ if (this.inboundManager) {
427
+ await this.inboundManager.close();
428
+ }
725
429
  }
726
430
  }
727
431
 
728
- // Attach init directly to the instance to allow standard default-import invocation
729
- serverMeshInstance.init = init;
730
-
731
- // Default export matches standard functional entry contract
732
- export default serverMeshInstance;
432
+ // Export singleton instance as default export and named export
433
+ export const serverMesh = new ServerMesh();
434
+ export default serverMesh;