@cocreate/server-mesh 1.2.1 → 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/README.md +326 -36
- package/package.json +14 -10
- package/src/inbound.js +226 -0
- package/src/index.js +333 -631
- package/src/outbound.js +222 -0
package/README.md
CHANGED
|
@@ -1,65 +1,355 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @cocreate/server-mesh
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A high-performance, self-healing, peer-to-peer (P2P) encrypted transport fabric built for distributed cluster orchestration, real-time tenant routing, and inter-node event synchronization across the CoCreate ecosystem.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-

|
|
7
|
-

|
|
8
|
-

|
|
5
|
+
---
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
## Documentation
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
For complete API references, deployment guides, and architecture documentation, visit the CoCreate Server Mesh documentation.
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
## Table of Contents
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
- [Overview](#overview)
|
|
14
|
+
- [Key Features](#key-features)
|
|
15
|
+
- [Architecture & Message Pipeline](#architecture--message-pipeline)
|
|
16
|
+
- [Installation](#installation)
|
|
17
|
+
- [Quick Start](#quick-start)
|
|
18
|
+
- [Routing Directives & Targeting Matrix](#routing-directives--targeting-matrix)
|
|
19
|
+
- [Tenant-Aware Routing Protocols](#tenant-aware-routing-protocols)
|
|
20
|
+
- [Self-Healing & Reconnection Logic](#self-healing--reconnection-logic)
|
|
21
|
+
- [Programmatic API Reference](#programmatic-api-reference)
|
|
22
|
+
- [Security & TLS Specification](#security--tls-specification)
|
|
23
|
+
- [Environment Variables](#environment-variables)
|
|
24
|
+
- [Announcements](#announcements)
|
|
25
|
+
- [Roadmap](#roadmap)
|
|
26
|
+
- [How to Contribute](#how-to-contribute)
|
|
27
|
+
- [About](#about)
|
|
28
|
+
- [License](#license)
|
|
17
29
|
|
|
18
|
-
|
|
30
|
+
---
|
|
19
31
|
|
|
20
|
-
|
|
21
|
-
- [Announcements](#announcements)
|
|
22
|
-
- [Roadmap](#roadmap)
|
|
23
|
-
- [How to Contribute](#how-to-contribute)
|
|
24
|
-
- [About](#about)
|
|
25
|
-
- [License](#license)
|
|
32
|
+
## Overview
|
|
26
33
|
|
|
27
|
-
|
|
34
|
+
`@cocreate/server-mesh` serves as the real-time inter-server transport plane for `@cocreate/server`. It establishes zero-trust, mutual P2P encrypted WebSocket tunnels between cluster nodes, enabling seamless multi-tenant data propagation, worker thread relays, and distributed cluster command execution.
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
Designed as a pure transport fabric, `@cocreate/server-mesh` blindly routes and emits network payloads as standard `EventEmitter` events across the cluster without introducing business-logic overhead.
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
---
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
## Key Features
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
- **Zero-Trust Ephemeral TLS:** Automatically generates temporary TLS credentials for secure node-to-node communication over the mesh network.
|
|
43
|
+
- **Polymorphic Target Directives:** Unicast, multicast, broadcast, worker-specific, or tenant-aware routing using a unified payload format.
|
|
44
|
+
- **Cryptographic Traceability:** Automatically stamps every outgoing frame with a unique `crypto.randomUUID()` identifier for distributed tracing and replay protection.
|
|
45
|
+
- **Tenant Isolation:** Dynamically tracks tenant-to-worker and tenant-to-server mappings so messages are only delivered to nodes actively serving an organization.
|
|
46
|
+
- **Self-Healing Reconnection:** Uses exponential backoff with randomized jitter to prevent reconnection storms after network interruptions.
|
|
47
|
+
- **Cluster IPC Integration:** Transparently bridges Node.js Primary and Worker processes while extending communication across distributed servers.
|
|
48
|
+
- **EventEmitter API:** Receive mesh messages using familiar Node.js event listeners.
|
|
36
49
|
|
|
37
|
-
|
|
50
|
+
---
|
|
38
51
|
|
|
39
|
-
|
|
52
|
+
## Architecture & Message Pipeline
|
|
40
53
|
|
|
41
|
-
|
|
54
|
+
When a payload is dispatched through `@cocreate/server-mesh`, the routing engine evaluates execution context (Primary vs Worker), determines the target destination, and forwards the payload through IPC or encrypted peer connections.
|
|
42
55
|
|
|
43
|
-
|
|
56
|
+
$$
|
|
57
|
+
\text{Worker Thread}
|
|
58
|
+
\xrightarrow{\text{IPC}}
|
|
59
|
+
\text{Primary Process}
|
|
60
|
+
\xrightarrow{\text{Route Resolution}}
|
|
61
|
+
\begin{cases}
|
|
62
|
+
\text{Local Workers} \\
|
|
63
|
+
\text{Remote Peer Servers}
|
|
64
|
+
\end{cases}
|
|
65
|
+
$$
|
|
44
66
|
|
|
45
|
-
|
|
67
|
+
### Worker Context
|
|
46
68
|
|
|
47
|
-
|
|
69
|
+
Worker processes:
|
|
48
70
|
|
|
49
|
-
|
|
71
|
+
- Send outbound messages through native `process.send()`
|
|
72
|
+
- Receive inbound mesh traffic through IPC
|
|
73
|
+
- Never establish direct peer connections
|
|
50
74
|
|
|
51
|
-
|
|
75
|
+
### Primary Context
|
|
52
76
|
|
|
53
|
-
|
|
77
|
+
The Primary process:
|
|
54
78
|
|
|
55
|
-
|
|
79
|
+
- Hosts the secure mesh WebSocket server
|
|
80
|
+
- Maintains encrypted peer connections
|
|
81
|
+
- Tracks tenant routing information
|
|
82
|
+
- Routes messages between workers and remote servers
|
|
56
83
|
|
|
57
|
-
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Installation
|
|
87
|
+
|
|
88
|
+
### NPM
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm install @cocreate/server-mesh
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Yarn
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
yarn add @cocreate/server-mesh
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
> **Note**
|
|
101
|
+
>
|
|
102
|
+
> `@cocreate/server-mesh` integrates directly with `@cocreate/server`, but it can also be initialized independently.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Quick Start
|
|
107
|
+
|
|
108
|
+
### Initialize
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
import CoCreateServer from "@cocreate/server";
|
|
112
|
+
import ServerMesh from "@cocreate/server-mesh";
|
|
113
|
+
|
|
114
|
+
await ServerMesh.init(CoCreateServer);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Send a Message
|
|
118
|
+
|
|
119
|
+
Unicast to a single server:
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
ServerMesh.send({
|
|
123
|
+
method: "cache.flush",
|
|
124
|
+
server: "srv_64f8a1bc90d1",
|
|
125
|
+
data: {
|
|
126
|
+
cache: "users"
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Multicast to multiple servers:
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
ServerMesh.send({
|
|
135
|
+
method: "worker.task",
|
|
136
|
+
server: ["srv_64f8a1bc90d1", "srv_90e3b2ac81f4"],
|
|
137
|
+
worker: 2,
|
|
138
|
+
data: {
|
|
139
|
+
taskId: 1042
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Broadcast to every node:
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
ServerMesh.send({
|
|
148
|
+
broadcast: true,
|
|
149
|
+
method: "reload.configuration"
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Listen for Messages
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
ServerMesh.on("cache.flush", (message) => {
|
|
157
|
+
console.log(message.data);
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Routing Directives & Targeting Matrix
|
|
164
|
+
|
|
165
|
+
| Target | Payload | Description |
|
|
166
|
+
| --- | --- | --- |
|
|
167
|
+
| Single Server | `{ server: "srv1" }` | Routes only to the specified server. |
|
|
168
|
+
| Multiple Servers | `{ server: ["srv1","srv2"] }` | Sends to every listed server. |
|
|
169
|
+
| Worker | `{ server: "srv1", worker: 2 }` | Routes to a specific worker on the target server. |
|
|
170
|
+
| Local Workers | `{ worker: [1,3] }` | Sends only to selected local workers. |
|
|
171
|
+
| Broadcast | `{ broadcast: true }` | Delivers to every connected server and local worker. |
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Tenant-Aware Routing Protocols
|
|
176
|
+
|
|
177
|
+
The mesh minimizes unnecessary network traffic by maintaining live organization routing maps.
|
|
178
|
+
|
|
179
|
+
```text
|
|
180
|
+
Client Connect
|
|
181
|
+
│
|
|
182
|
+
▼
|
|
183
|
+
mesh.registerOrg
|
|
184
|
+
│
|
|
185
|
+
▼
|
|
186
|
+
Map Organization → Worker
|
|
187
|
+
│
|
|
188
|
+
▼
|
|
189
|
+
Client Disconnect
|
|
190
|
+
│
|
|
191
|
+
▼
|
|
192
|
+
60 Second Grace Timer
|
|
193
|
+
│
|
|
194
|
+
▼
|
|
195
|
+
mesh.deregisterOrg
|
|
196
|
+
│
|
|
197
|
+
▼
|
|
198
|
+
mesh.orgDeleted Broadcast
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Organization Events
|
|
202
|
+
|
|
203
|
+
| Event | Description |
|
|
204
|
+
| --- | --- |
|
|
205
|
+
| `mesh.registerOrg` | Registers a worker serving an organization. |
|
|
206
|
+
| `mesh.deregisterOrg` | Removes a worker's organization mapping after all connections close. |
|
|
207
|
+
| `mesh.orgAdded` | Broadcast to peer servers when an organization becomes active. |
|
|
208
|
+
| `mesh.orgDeleted` | Broadcast when an organization is no longer active on a server. |
|
|
209
|
+
| `mesh.orgData` | Routes organization-specific payloads only to subscribed servers and workers. |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Self-Healing & Reconnection Logic
|
|
214
|
+
|
|
215
|
+
When peer servers disconnect, reconnection attempts use exponential backoff combined with randomized jitter to prevent synchronized reconnect storms.
|
|
216
|
+
|
|
217
|
+
$$
|
|
218
|
+
T_{delay}
|
|
219
|
+
=
|
|
220
|
+
\min\left(30000,\,
|
|
221
|
+
1000\times1.5^{attempt}
|
|
222
|
+
\right)
|
|
223
|
+
+
|
|
224
|
+
random(500,4500)
|
|
225
|
+
$$
|
|
226
|
+
|
|
227
|
+
Connections automatically retry until:
|
|
228
|
+
|
|
229
|
+
- the peer becomes available,
|
|
230
|
+
- the server shuts down,
|
|
231
|
+
- or the connection is intentionally closed.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Programmatic API Reference
|
|
236
|
+
|
|
237
|
+
### `ServerMesh.init(server)`
|
|
238
|
+
|
|
239
|
+
Initializes the mesh networking layer.
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
await ServerMesh.init(server);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
### `ServerMesh.send(payload)`
|
|
248
|
+
|
|
249
|
+
Routes a payload across the cluster.
|
|
250
|
+
|
|
251
|
+
```javascript
|
|
252
|
+
ServerMesh.send({
|
|
253
|
+
method: "custom.command",
|
|
254
|
+
server: "srv123",
|
|
255
|
+
data: {
|
|
256
|
+
key: "value"
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
### `ServerMesh.close()`
|
|
264
|
+
|
|
265
|
+
Gracefully closes all mesh connections and releases resources.
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
await ServerMesh.close();
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
### Events
|
|
274
|
+
|
|
275
|
+
```javascript
|
|
276
|
+
ServerMesh.on("mesh.orgAdded", handler);
|
|
277
|
+
|
|
278
|
+
ServerMesh.on("mesh.orgDeleted", handler);
|
|
279
|
+
|
|
280
|
+
ServerMesh.on("mesh.orgData", handler);
|
|
281
|
+
|
|
282
|
+
ServerMesh.on("mesh.raw_message", handler);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Security & TLS Specification
|
|
288
|
+
|
|
289
|
+
- **Encrypted Transport:** All peer communication occurs over mutually authenticated encrypted WebSocket connections.
|
|
290
|
+
- **Dedicated Mesh Port:** Mesh traffic is isolated from public HTTP and HTTPS traffic.
|
|
291
|
+
- **Ephemeral Certificates:** Temporary TLS certificates are generated during startup and automatically replaced on restart.
|
|
292
|
+
- **Server Authentication:** Incoming peers must successfully complete server identity validation before joining the mesh.
|
|
293
|
+
- **UUID Tracing:** Every transmitted frame receives a unique identifier for distributed tracing and replay protection.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Environment Variables
|
|
298
|
+
|
|
299
|
+
| Variable | Default | Description |
|
|
300
|
+
| --- | --- | --- |
|
|
301
|
+
| `MESH_PORT` | `8090` | Port used for mesh communication. |
|
|
302
|
+
| `CLUSTER_SECRET` | — | Shared secret used during mesh authentication. |
|
|
303
|
+
| `SERVER_ID` | Auto Generated | Persistent identifier for the current server node. |
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Announcements
|
|
308
|
+
|
|
309
|
+
Release notes, performance improvements, protocol updates, and networking enhancements are published with every release. Follow the repository to stay informed about new features and compatibility changes.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Roadmap
|
|
314
|
+
|
|
315
|
+
Upcoming enhancements include:
|
|
316
|
+
|
|
317
|
+
- Native QUIC / HTTP/3 transport
|
|
318
|
+
- Adaptive routing based on latency measurements
|
|
319
|
+
- Automatic peer discovery across local networks
|
|
320
|
+
- Streaming binary frame optimizations
|
|
321
|
+
- Dynamic topology-aware routing
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## How to Contribute
|
|
326
|
+
|
|
327
|
+
We welcome pull requests, bug reports, documentation improvements, performance optimizations, and security reviews.
|
|
328
|
+
|
|
329
|
+
Please review the project's `CONTRIBUTING.md` before submitting changes, and use the GitHub Issues page to report bugs or request new features.
|
|
330
|
+
|
|
331
|
+
Community feedback helps prioritize development and improve the platform.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## About
|
|
336
|
+
|
|
337
|
+
`@cocreate/server-mesh` is designed, built, and maintained by the CoCreate Developer Experience Team.
|
|
338
|
+
|
|
339
|
+
It serves as the distributed communication backbone of the CoCreate ecosystem, providing secure, low-latency messaging between clustered servers.
|
|
340
|
+
|
|
341
|
+
For questions, architecture discussions, or deployment guidance, visit the project's GitHub Discussions or join the CoCreate community.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## License
|
|
58
346
|
|
|
59
347
|
This software is dual-licensed under the GNU Affero General Public License version 3 (AGPLv3) and a commercial license.
|
|
60
348
|
|
|
61
|
-
|
|
349
|
+
### Open Source
|
|
350
|
+
|
|
351
|
+
For open-source projects and non-commercial use, this software is available under the AGPLv3. See the `LICENSE` file for the complete license text.
|
|
62
352
|
|
|
63
|
-
|
|
353
|
+
### Commercial
|
|
64
354
|
|
|
65
|
-
|
|
355
|
+
Organizations requiring proprietary use may obtain a commercial license from CoCreate.
|
package/package.json
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/server-mesh",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.2.2",
|
|
4
|
+
"description": "Decentralized, event-driven P2P server mesh and transport fabric for CoCreate Server. Optimizes multi-tenant cluster concurrency with dynamic, tenant-isolated WebSocket data tunnels.",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"cocreate
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
6
|
+
"cocreate",
|
|
7
|
+
"cocreate-server",
|
|
8
|
+
"websocket-clustering",
|
|
9
|
+
"p2p-mesh",
|
|
10
|
+
"multi-tenancy",
|
|
11
|
+
"realtime-collaboration",
|
|
12
|
+
"state-synchronization",
|
|
13
|
+
"distributed-systems",
|
|
14
|
+
"service-mesh",
|
|
15
|
+
"headless-server",
|
|
16
|
+
"crdt",
|
|
17
|
+
"zero-dependency"
|
|
14
18
|
],
|
|
15
19
|
"publishConfig": {
|
|
16
20
|
"access": "public"
|
package/src/inbound.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { WebSocketServer } from 'ws';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
import crypto from 'node:crypto';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ServerMeshInbound manages inbound HTTPS and WebSocket secure listening transports,
|
|
11
|
+
* ephemeral TLS certificate generation, inbound socket heartbeats, and inbound message processing.
|
|
12
|
+
*/
|
|
13
|
+
export class ServerMeshInbound {
|
|
14
|
+
constructor(mesh) {
|
|
15
|
+
this.mesh = mesh;
|
|
16
|
+
this.server = null;
|
|
17
|
+
this.meshPort = process.env.MESH_PORT || 8090;
|
|
18
|
+
this.secureMeshServer = null;
|
|
19
|
+
this.meshWss = null;
|
|
20
|
+
this.heartbeatInterval = null;
|
|
21
|
+
this.heartbeatMs = 30000;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Initializes the inbound server transport with host server context and IPC listeners.
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} server - CoCreateServer instance context
|
|
28
|
+
* @returns {Promise<void>}
|
|
29
|
+
*/
|
|
30
|
+
async init(server) {
|
|
31
|
+
this.server = server;
|
|
32
|
+
|
|
33
|
+
if (this.server && typeof this.server.on === 'function') {
|
|
34
|
+
this.server.on('mesh.inbound.status', () => {
|
|
35
|
+
console.log(`[@cocreate/server-mesh/inbound] Inbound transport status: ${this.secureMeshServer ? 'listening' : 'stopped'}`);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Fallback listener for legacy event name
|
|
39
|
+
this.server.on('mesh.server.status', () => {
|
|
40
|
+
console.log(`[@cocreate/server-mesh/inbound] Inbound transport status: ${this.secureMeshServer ? 'listening' : 'stopped'}`);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
this.server.on('mesh.inbound.relay', (payload) => {
|
|
44
|
+
if (payload && payload.data) {
|
|
45
|
+
this.broadcastInboundLocal(payload);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await this.startServer();
|
|
51
|
+
this.startHeartbeat();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generates short-lived RSA-2048 X.509 TLS credentials in temporary storage.
|
|
56
|
+
*
|
|
57
|
+
* @returns {{key: Buffer, cert: Buffer}|null}
|
|
58
|
+
*/
|
|
59
|
+
generateTLSCredentials() {
|
|
60
|
+
const tmpDir = os.tmpdir();
|
|
61
|
+
const keyPath = path.join(tmpDir, `mesh-key-${Date.now()}-${crypto.randomBytes(4).toString('hex')}.pem`);
|
|
62
|
+
const certPath = path.join(tmpDir, `mesh-cert-${Date.now()}-${crypto.randomBytes(4).toString('hex')}.pem`);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
execSync(`openssl req -x509 -newkey rsa:2048 -keyout "${keyPath}" -out "${certPath}" -days 1 -nodes -subj "/CN=localhost"`, { stdio: 'ignore' });
|
|
66
|
+
const key = fs.readFileSync(keyPath);
|
|
67
|
+
const cert = fs.readFileSync(certPath);
|
|
68
|
+
|
|
69
|
+
try { fs.unlinkSync(keyPath); } catch {}
|
|
70
|
+
try { fs.unlinkSync(certPath); } catch {}
|
|
71
|
+
|
|
72
|
+
return { key, cert };
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error('[@cocreate/server-mesh/inbound] Failed to generate ephemeral TLS credentials:', err.message);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Binds HTTPS and WebSocketServer listeners on MESH_PORT and configures inbound socket handlers.
|
|
81
|
+
*/
|
|
82
|
+
async startServer() {
|
|
83
|
+
const credentials = this.generateTLSCredentials();
|
|
84
|
+
if (!credentials) {
|
|
85
|
+
throw new Error("Unable to boot secure mesh server: TLS credential generation failed.");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.secureMeshServer = https.createServer(credentials);
|
|
89
|
+
|
|
90
|
+
this.secureMeshServer.on('error', (err) => {
|
|
91
|
+
if (err.code === 'EADDRINUSE') {
|
|
92
|
+
console.error(`\n[@cocreate/server-mesh/inbound] [FATAL] Port ${this.meshPort} is already in use!`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
} else {
|
|
95
|
+
console.error(`[@cocreate/server-mesh/inbound] HTTPS transport error:`, err);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
await new Promise((resolve) => {
|
|
100
|
+
this.secureMeshServer.listen(this.meshPort, '0.0.0.0', () => {
|
|
101
|
+
console.log(`[@cocreate/server-mesh/inbound] [PRIMARY] Inbound mesh listening on 0.0.0.0:${this.meshPort}`);
|
|
102
|
+
resolve();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
this.meshWss = new WebSocketServer({ server: this.secureMeshServer });
|
|
107
|
+
|
|
108
|
+
this.meshWss.on('connection', (ws, req) => {
|
|
109
|
+
const ip = req.socket.remoteAddress;
|
|
110
|
+
let remoteServerId = null;
|
|
111
|
+
|
|
112
|
+
const protocolHeader = req.headers['sec-websocket-protocol'];
|
|
113
|
+
if (protocolHeader) {
|
|
114
|
+
try {
|
|
115
|
+
const parsed = JSON.parse(decodeURIComponent(protocolHeader));
|
|
116
|
+
remoteServerId = parsed.id;
|
|
117
|
+
} catch (err) {
|
|
118
|
+
// Ignore protocol decoding errors
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const identifier = remoteServerId || ip;
|
|
123
|
+
console.log(`[@cocreate/server-mesh/inbound] Secure inbound peer connection established: ${identifier}`);
|
|
124
|
+
|
|
125
|
+
ws.isAlive = true;
|
|
126
|
+
ws.remoteAddress = ip;
|
|
127
|
+
ws.on('pong', () => { ws.isAlive = true; });
|
|
128
|
+
|
|
129
|
+
ws.on('message', (rawData) => {
|
|
130
|
+
this.handleInboundSocketMessage(rawData, ws);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
ws.on('close', () => {
|
|
134
|
+
console.log(`[@cocreate/server-mesh/inbound] Inbound peer connection ${identifier} dropped.`);
|
|
135
|
+
this.mesh.activeConnections.delete(identifier);
|
|
136
|
+
if (remoteServerId) this.mesh.activeConnections.delete(ip);
|
|
137
|
+
this.mesh.removeSocketFromAllOrgServers(ws);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
ws.on('error', (err) => {
|
|
141
|
+
console.error(`[@cocreate/server-mesh/inbound] Inbound socket error (${identifier}):`, err.message);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
this.mesh.activeConnections.set(identifier, ws);
|
|
145
|
+
if (remoteServerId) this.mesh.activeConnections.set(ip, ws);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ... existing code ...
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Relays inbound payloads down to local worker processes.
|
|
153
|
+
*
|
|
154
|
+
* @param {Object} payload
|
|
155
|
+
*/
|
|
156
|
+
broadcastInboundLocal(payload) {
|
|
157
|
+
if (this.server && typeof this.server.send === 'function') {
|
|
158
|
+
try {
|
|
159
|
+
this.server.send({
|
|
160
|
+
method: 'mesh.inbound',
|
|
161
|
+
broadcast: payload.broadcast ?? true,
|
|
162
|
+
workers: payload.workers,
|
|
163
|
+
master: false,
|
|
164
|
+
data: typeof payload.data === 'string' ? payload.data : JSON.stringify(payload.data)
|
|
165
|
+
});
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.error("[@cocreate/server-mesh/inbound] Failed to relay inbound message to workers:", err.message);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Starts interval timer to ping connected clients and evict dead sockets.
|
|
174
|
+
*/
|
|
175
|
+
startHeartbeat() {
|
|
176
|
+
if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
|
|
177
|
+
|
|
178
|
+
this.heartbeatInterval = setInterval(() => {
|
|
179
|
+
if (!this.meshWss) return;
|
|
180
|
+
|
|
181
|
+
this.meshWss.clients.forEach((ws) => {
|
|
182
|
+
if (ws.isAlive === false) {
|
|
183
|
+
console.warn(`[@cocreate/server-mesh/inbound] Unresponsive client socket detected. Terminating connection.`);
|
|
184
|
+
return ws.terminate();
|
|
185
|
+
}
|
|
186
|
+
ws.isAlive = false;
|
|
187
|
+
try {
|
|
188
|
+
ws.ping();
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.error("[@cocreate/server-mesh/inbound] Ping error:", err.message);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}, this.heartbeatMs);
|
|
194
|
+
|
|
195
|
+
if (this.heartbeatInterval.unref) {
|
|
196
|
+
this.heartbeatInterval.unref();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Tears down the server instance, ending heartbeats and closing listening sockets.
|
|
202
|
+
*
|
|
203
|
+
* @returns {Promise<void>}
|
|
204
|
+
*/
|
|
205
|
+
async close() {
|
|
206
|
+
if (this.heartbeatInterval) {
|
|
207
|
+
clearInterval(this.heartbeatInterval);
|
|
208
|
+
this.heartbeatInterval = null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (this.meshWss) {
|
|
212
|
+
try { this.meshWss.close(); } catch {}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return new Promise((resolve) => {
|
|
216
|
+
if (this.secureMeshServer) {
|
|
217
|
+
this.secureMeshServer.close(() => {
|
|
218
|
+
console.log(`[@cocreate/server-mesh/inbound] Secure port ${this.meshPort} successfully released.`);
|
|
219
|
+
resolve();
|
|
220
|
+
});
|
|
221
|
+
} else {
|
|
222
|
+
resolve();
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|