symmetry-cli 1.0.7 → 1.0.11
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/.github/workflows/node.js.yml +0 -1
- package/dist/symmetry.js +2 -2
- package/package.json +3 -10
- package/readme.md +16 -0
- package/src/symmetry.ts +1 -2
- package/__test__/cli.test.ts +0 -62
- package/dist/client.js +0 -230
- package/dist/config-manager.js +0 -58
- package/dist/config.js +0 -44
- package/dist/constants.js +0 -31
- package/dist/database.js +0 -9
- package/dist/logger.js +0 -45
- package/dist/peer-repository.js +0 -114
- package/dist/provider.js +0 -307
- package/dist/server.js +0 -143
- package/dist/session-manager.js +0 -68
- package/dist/session-repository.js +0 -127
- package/dist/types.js +0 -2
- package/dist/utils.js +0 -53
- package/dist/websocket-server.js +0 -52
- package/src/config.ts +0 -51
- package/src/constants.ts +0 -31
- package/src/logger.ts +0 -47
- package/src/provider.ts +0 -411
- package/src/types.ts +0 -243
- package/src/utils.ts +0 -52
package/dist/peer-repository.js
DELETED
@@ -1,114 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
-
};
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.PeerRepository = void 0;
|
7
|
-
const chalk_1 = __importDefault(require("chalk"));
|
8
|
-
const database_1 = require("./database");
|
9
|
-
const logger_1 = require("./logger");
|
10
|
-
class PeerRepository {
|
11
|
-
constructor() {
|
12
|
-
this.db = database_1.database;
|
13
|
-
}
|
14
|
-
upsert(message) {
|
15
|
-
return new Promise((resolve, reject) => {
|
16
|
-
this.db.run(`
|
17
|
-
INSERT OR REPLACE INTO peers (
|
18
|
-
key, discovery_key, gpu_memory, model_name, public, server_key, last_seen, online
|
19
|
-
) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, TRUE)
|
20
|
-
`, [
|
21
|
-
message.key,
|
22
|
-
message.discoveryKey,
|
23
|
-
message.config.gpuMemory,
|
24
|
-
message.config.modelName,
|
25
|
-
message.config.public,
|
26
|
-
message.config.serverKey,
|
27
|
-
], function (err) {
|
28
|
-
if (err) {
|
29
|
-
reject(err);
|
30
|
-
}
|
31
|
-
else {
|
32
|
-
resolve(this.lastID);
|
33
|
-
}
|
34
|
-
});
|
35
|
-
});
|
36
|
-
}
|
37
|
-
getByDiscoveryKey(discoveryKey) {
|
38
|
-
return new Promise((resolve, reject) => {
|
39
|
-
this.db.get("SELECT * FROM peers WHERE discovery_key = ?", [discoveryKey], (err, row) => {
|
40
|
-
if (err) {
|
41
|
-
reject(err);
|
42
|
-
}
|
43
|
-
else {
|
44
|
-
resolve(row);
|
45
|
-
}
|
46
|
-
});
|
47
|
-
});
|
48
|
-
}
|
49
|
-
getPeer(randomPeerRequest) {
|
50
|
-
return new Promise((resolve, reject) => {
|
51
|
-
const { modelName } = randomPeerRequest;
|
52
|
-
this.db.get(`SELECT * FROM peers WHERE model_name = ? ORDER BY RANDOM() LIMIT 1`, [modelName], (err, row) => {
|
53
|
-
if (err) {
|
54
|
-
reject(err);
|
55
|
-
}
|
56
|
-
else {
|
57
|
-
resolve(row);
|
58
|
-
}
|
59
|
-
});
|
60
|
-
});
|
61
|
-
}
|
62
|
-
updateLastSeen(peerKey) {
|
63
|
-
return new Promise((resolve, reject) => {
|
64
|
-
this.db.run("UPDATE peers SET last_seen = ?, online = FALSE WHERE key = ?", [new Date().toISOString(), peerKey], function (err) {
|
65
|
-
if (err) {
|
66
|
-
console.error(chalk_1.default.red("❌ Error updating peer last seen in database:"), err);
|
67
|
-
reject(err);
|
68
|
-
}
|
69
|
-
else {
|
70
|
-
if (this.changes > 0) {
|
71
|
-
logger_1.logger.info(chalk_1.default.yellow("🕒 Peer disconnected"));
|
72
|
-
}
|
73
|
-
else {
|
74
|
-
logger_1.logger.info(chalk_1.default.yellow("⚠️ Peer not found in database"));
|
75
|
-
}
|
76
|
-
resolve(this.changes);
|
77
|
-
}
|
78
|
-
});
|
79
|
-
});
|
80
|
-
}
|
81
|
-
async getActivePeerCount() {
|
82
|
-
return new Promise((resolve, reject) => {
|
83
|
-
this.db.get("SELECT COUNT(*) as count FROM peers WHERE online = TRUE", (err, row) => {
|
84
|
-
if (err) {
|
85
|
-
reject(err);
|
86
|
-
}
|
87
|
-
else {
|
88
|
-
resolve(row.count);
|
89
|
-
}
|
90
|
-
});
|
91
|
-
});
|
92
|
-
}
|
93
|
-
async getActiveModelCount() {
|
94
|
-
return new Promise((resolve, reject) => {
|
95
|
-
this.db.get("SELECT COUNT(DISTINCT model_name) as count FROM peers WHERE online = TRUE", (err, row) => {
|
96
|
-
if (err) {
|
97
|
-
reject(err);
|
98
|
-
}
|
99
|
-
else {
|
100
|
-
resolve(row.count);
|
101
|
-
}
|
102
|
-
});
|
103
|
-
});
|
104
|
-
}
|
105
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
106
|
-
updateStats(peerKey, data) {
|
107
|
-
// TODO: Update stats in database
|
108
|
-
logger_1.logger.info(peerKey, data);
|
109
|
-
}
|
110
|
-
}
|
111
|
-
exports.PeerRepository = PeerRepository;
|
112
|
-
module.exports = {
|
113
|
-
PeerRepository,
|
114
|
-
};
|
package/dist/provider.js
DELETED
@@ -1,307 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
-
};
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.SymmetryProvider = void 0;
|
7
|
-
const node_stream_1 = require("node:stream");
|
8
|
-
const promises_1 = require("stream/promises");
|
9
|
-
const chalk_1 = __importDefault(require("chalk"));
|
10
|
-
const hyperswarm_1 = __importDefault(require("hyperswarm"));
|
11
|
-
const hypercore_crypto_1 = __importDefault(require("hypercore-crypto"));
|
12
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
13
|
-
const config_1 = require("./config");
|
14
|
-
const utils_1 = require("./utils");
|
15
|
-
const logger_1 = require("./logger");
|
16
|
-
const constants_1 = require("./constants");
|
17
|
-
class SymmetryProvider {
|
18
|
-
constructor(configPath) {
|
19
|
-
this._challenge = null;
|
20
|
-
this._conversationIndex = 0;
|
21
|
-
this._discoveryKey = null;
|
22
|
-
this._isPublic = false;
|
23
|
-
this._providerConnections = 0;
|
24
|
-
this._providerSwarm = null;
|
25
|
-
this._serverSwarm = null;
|
26
|
-
this._serverPeer = null;
|
27
|
-
logger_1.logger.info(`🔗 Initializing client using config file: ${configPath}`);
|
28
|
-
this._config = new config_1.ConfigManager(configPath);
|
29
|
-
this._isPublic = this._config.get("public");
|
30
|
-
}
|
31
|
-
async init() {
|
32
|
-
this._providerSwarm = new hyperswarm_1.default({
|
33
|
-
maxConnections: this._config.get("maxConnections"),
|
34
|
-
});
|
35
|
-
const keyPair = hypercore_crypto_1.default.keyPair(Buffer.alloc(32).fill(this._config.get("name")));
|
36
|
-
this._discoveryKey = hypercore_crypto_1.default.discoveryKey(keyPair.publicKey);
|
37
|
-
const discovery = this._providerSwarm.join(this._discoveryKey, {
|
38
|
-
server: true,
|
39
|
-
client: true,
|
40
|
-
});
|
41
|
-
await discovery.flushed();
|
42
|
-
this._providerSwarm.on("error", (err) => {
|
43
|
-
logger_1.logger.error(chalk_1.default.red("🚨 Swarm Error:"), err);
|
44
|
-
});
|
45
|
-
this._providerSwarm.on("connection", (peer) => {
|
46
|
-
logger_1.logger.info(`⚡️ New connection from peer: ${peer.rawStream.remoteHost}`);
|
47
|
-
this.listeners(peer);
|
48
|
-
});
|
49
|
-
logger_1.logger.info(`📁 Symmetry client initialized.`);
|
50
|
-
logger_1.logger.info(`🔑 Discovery key: ${this._discoveryKey.toString("hex")}`);
|
51
|
-
if (this._isPublic) {
|
52
|
-
logger_1.logger.info(chalk_1.default.white(`🔑 Server key: ${this._config.get("serverKey")}`));
|
53
|
-
logger_1.logger.info(chalk_1.default.white("🔗 Joining server, please wait."));
|
54
|
-
this.joinServer();
|
55
|
-
}
|
56
|
-
process.on("SIGINT", async () => {
|
57
|
-
var _a;
|
58
|
-
await ((_a = this._providerSwarm) === null || _a === void 0 ? void 0 : _a.destroy());
|
59
|
-
process.exit(0);
|
60
|
-
});
|
61
|
-
process.on("uncaughtException", (err) => {
|
62
|
-
if (err.message === "connection reset by peer") {
|
63
|
-
this._providerConnections = Math.max(0, this._providerConnections - 1);
|
64
|
-
}
|
65
|
-
});
|
66
|
-
}
|
67
|
-
async destroySwarms() {
|
68
|
-
var _a, _b;
|
69
|
-
await ((_a = this._providerSwarm) === null || _a === void 0 ? void 0 : _a.destroy());
|
70
|
-
await ((_b = this._serverSwarm) === null || _b === void 0 ? void 0 : _b.destroy());
|
71
|
-
}
|
72
|
-
async testProviderCall() {
|
73
|
-
const testCall = async () => {
|
74
|
-
logger_1.logger.info(chalk_1.default.white(`👋 Saying hello to your provider...`));
|
75
|
-
const testMessages = [
|
76
|
-
{ role: "user", content: "Hello, this is a test message." },
|
77
|
-
];
|
78
|
-
const req = this.buildStreamRequest(testMessages);
|
79
|
-
if (!req) {
|
80
|
-
logger_1.logger.error(chalk_1.default.red("❌ Failed to build test request"));
|
81
|
-
throw new Error("Failed to build test request");
|
82
|
-
}
|
83
|
-
const { requestOptions, requestBody } = req;
|
84
|
-
const { protocol, hostname, port, path, method, headers } = requestOptions;
|
85
|
-
const url = `${protocol}://${hostname}:${port}${path}`;
|
86
|
-
logger_1.logger.info(chalk_1.default.white(`🚀 Sending test request to ${url}`));
|
87
|
-
try {
|
88
|
-
const response = await fetch(url, {
|
89
|
-
method,
|
90
|
-
headers,
|
91
|
-
body: JSON.stringify(requestBody),
|
92
|
-
});
|
93
|
-
if (!response.ok) {
|
94
|
-
logger_1.logger.error(chalk_1.default.red(`❌ Server responded with status code: ${response.status}`));
|
95
|
-
this.destroySwarms();
|
96
|
-
throw new Error(`Server responded with status code: ${response.status}`);
|
97
|
-
}
|
98
|
-
if (!response.body) {
|
99
|
-
logger_1.logger.error(chalk_1.default.red("❌ Failed to get a ReadableStream from the response"));
|
100
|
-
this.destroySwarms();
|
101
|
-
throw new Error("Failed to get a ReadableStream from the response");
|
102
|
-
}
|
103
|
-
logger_1.logger.info(chalk_1.default.white(`📡 Got response, checking stream...`));
|
104
|
-
const reader = response.body.getReader();
|
105
|
-
const { done } = await reader.read();
|
106
|
-
if (done) {
|
107
|
-
logger_1.logger.error(chalk_1.default.red("❌ Stream ended without data"));
|
108
|
-
this.destroySwarms();
|
109
|
-
throw new Error("Stream ended without data");
|
110
|
-
}
|
111
|
-
logger_1.logger.info(chalk_1.default.green(`✅ Test inference call successful!`));
|
112
|
-
}
|
113
|
-
catch (error) {
|
114
|
-
this.destroySwarms();
|
115
|
-
logger_1.logger.error(chalk_1.default.red(`❌ Error during test inference call: ${error}`));
|
116
|
-
throw error;
|
117
|
-
}
|
118
|
-
logger_1.logger.info(chalk_1.default.white(`🔗 Test call successful!`));
|
119
|
-
};
|
120
|
-
setTimeout(() => testCall(), constants_1.PROVIDER_HELLO_TIMEOUT);
|
121
|
-
}
|
122
|
-
async joinServer() {
|
123
|
-
this._serverSwarm = new hyperswarm_1.default();
|
124
|
-
const serverKey = Buffer.from(this._config.get("serverKey"));
|
125
|
-
this._serverSwarm.join(hypercore_crypto_1.default.discoveryKey(serverKey), {
|
126
|
-
client: true,
|
127
|
-
server: false,
|
128
|
-
});
|
129
|
-
this._serverSwarm.flush();
|
130
|
-
this._serverSwarm.on("connection", (peer) => {
|
131
|
-
var _a;
|
132
|
-
this._serverPeer = peer;
|
133
|
-
logger_1.logger.info(chalk_1.default.green("🔗 Connected to server."));
|
134
|
-
this.testProviderCall();
|
135
|
-
this._challenge = hypercore_crypto_1.default.randomBytes(32);
|
136
|
-
this._serverPeer.write((0, utils_1.createMessage)(constants_1.serverMessageKeys.challenge, {
|
137
|
-
challenge: this._challenge,
|
138
|
-
}));
|
139
|
-
this._serverPeer.write((0, utils_1.createMessage)(constants_1.serverMessageKeys.join, {
|
140
|
-
...this._config.getAll(),
|
141
|
-
discoveryKey: (_a = this._discoveryKey) === null || _a === void 0 ? void 0 : _a.toString("hex"),
|
142
|
-
}));
|
143
|
-
this._serverPeer.on("data", async (buffer) => {
|
144
|
-
var _a;
|
145
|
-
if (!buffer)
|
146
|
-
return;
|
147
|
-
const data = (0, utils_1.safeParseJson)(buffer.toString());
|
148
|
-
if (data && data.key) {
|
149
|
-
switch (data.key) {
|
150
|
-
case constants_1.serverMessageKeys.challenge:
|
151
|
-
this.handleServerVerification(data.data);
|
152
|
-
break;
|
153
|
-
case constants_1.serverMessageKeys.ping:
|
154
|
-
(_a = this._serverPeer) === null || _a === void 0 ? void 0 : _a.write((0, utils_1.createMessage)(constants_1.serverMessageKeys.pong));
|
155
|
-
break;
|
156
|
-
}
|
157
|
-
}
|
158
|
-
});
|
159
|
-
});
|
160
|
-
}
|
161
|
-
getServerPublicKey(serverKeyHex) {
|
162
|
-
const publicKey = Buffer.from(serverKeyHex, "hex");
|
163
|
-
if (publicKey.length !== 32) {
|
164
|
-
throw new Error(`Expected a 32-byte public key, but got ${publicKey.length} bytes`);
|
165
|
-
}
|
166
|
-
return publicKey;
|
167
|
-
}
|
168
|
-
handleServerVerification(data) {
|
169
|
-
if (!this._challenge) {
|
170
|
-
console.log("No challenge set. Cannot verify.");
|
171
|
-
return;
|
172
|
-
}
|
173
|
-
const serverKeyHex = this._config.get("serverKey");
|
174
|
-
try {
|
175
|
-
const publicKey = this.getServerPublicKey(serverKeyHex);
|
176
|
-
const signatureBuffer = Buffer.from(data.signature.data, "base64");
|
177
|
-
const verified = hypercore_crypto_1.default.verify(this._challenge, signatureBuffer, publicKey);
|
178
|
-
if (verified) {
|
179
|
-
logger_1.logger.info(chalk_1.default.greenBright(`✅ Verification successful.`));
|
180
|
-
}
|
181
|
-
else {
|
182
|
-
logger_1.logger.error(`❌ Verification failed!`);
|
183
|
-
}
|
184
|
-
}
|
185
|
-
catch (error) {
|
186
|
-
console.error("Error during verification:", error);
|
187
|
-
}
|
188
|
-
}
|
189
|
-
listeners(peer) {
|
190
|
-
peer.on("data", async (buffer) => {
|
191
|
-
if (!buffer)
|
192
|
-
return;
|
193
|
-
const data = (0, utils_1.safeParseJson)(buffer.toString());
|
194
|
-
if (data && data.key) {
|
195
|
-
switch (data.key) {
|
196
|
-
case constants_1.serverMessageKeys.newConversation:
|
197
|
-
this._conversationIndex = this._conversationIndex + 1;
|
198
|
-
break;
|
199
|
-
case constants_1.serverMessageKeys.inference:
|
200
|
-
logger_1.logger.info(`📦 Inference message received from ${peer.rawStream.remoteHost}`);
|
201
|
-
await this.handleInferenceRequest(data, peer);
|
202
|
-
break;
|
203
|
-
}
|
204
|
-
}
|
205
|
-
});
|
206
|
-
}
|
207
|
-
getMessagesWithSystem(messages) {
|
208
|
-
const systemMessage = this._config.get("systemMessage");
|
209
|
-
if (messages.length === 2 && systemMessage) {
|
210
|
-
messages.unshift({
|
211
|
-
role: "system",
|
212
|
-
content: systemMessage,
|
213
|
-
});
|
214
|
-
}
|
215
|
-
return messages;
|
216
|
-
}
|
217
|
-
async handleInferenceRequest(data, peer) {
|
218
|
-
const emitterKey = data.data.key;
|
219
|
-
const messages = this.getMessagesWithSystem(data === null || data === void 0 ? void 0 : data.data.messages);
|
220
|
-
const req = this.buildStreamRequest(messages);
|
221
|
-
if (!req)
|
222
|
-
return;
|
223
|
-
const { requestOptions, requestBody } = req;
|
224
|
-
const { protocol, hostname, port, path, method, headers } = requestOptions;
|
225
|
-
const url = `${protocol}://${hostname}:${port}${path}`;
|
226
|
-
try {
|
227
|
-
const response = await fetch(url, {
|
228
|
-
method,
|
229
|
-
headers,
|
230
|
-
body: JSON.stringify(requestBody),
|
231
|
-
});
|
232
|
-
if (!response.ok) {
|
233
|
-
throw new Error(`Server responded with status code: ${response.status}`);
|
234
|
-
}
|
235
|
-
if (!response.body) {
|
236
|
-
throw new Error("Failed to get a ReadableStream from the response");
|
237
|
-
}
|
238
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
239
|
-
const responseStream = node_stream_1.Readable.fromWeb(response.body);
|
240
|
-
const peerStream = new node_stream_1.PassThrough();
|
241
|
-
responseStream.pipe(peerStream);
|
242
|
-
let completion = "";
|
243
|
-
const provider = this._config.get("apiProvider");
|
244
|
-
peer.write(JSON.stringify({
|
245
|
-
symmetryEmitterKey: emitterKey,
|
246
|
-
}));
|
247
|
-
const peerPipeline = (0, promises_1.pipeline)(peerStream, async function (source) {
|
248
|
-
for await (const chunk of source) {
|
249
|
-
if (peer.writable) {
|
250
|
-
completion += (0, utils_1.getChatDataFromProvider)(provider, (0, utils_1.safeParseStreamResponse)(chunk.toString()));
|
251
|
-
const write = peer.write(chunk);
|
252
|
-
if (!write) {
|
253
|
-
await new Promise((resolve) => peer.once("drain", resolve));
|
254
|
-
}
|
255
|
-
}
|
256
|
-
else {
|
257
|
-
break;
|
258
|
-
}
|
259
|
-
}
|
260
|
-
});
|
261
|
-
await Promise.resolve(peerPipeline);
|
262
|
-
peer.write((0, utils_1.createMessage)(constants_1.serverMessageKeys.inferenceEnded, data === null || data === void 0 ? void 0 : data.data.key));
|
263
|
-
if (this._config.get("dataCollectionEnabled") &&
|
264
|
-
data.data.key === constants_1.serverMessageKeys.inference) {
|
265
|
-
this.saveCompletion(completion, peer, data.data.messages);
|
266
|
-
}
|
267
|
-
}
|
268
|
-
catch (error) {
|
269
|
-
let errorMessage = "An error occurred during inference";
|
270
|
-
if (error instanceof Error)
|
271
|
-
errorMessage = error.message;
|
272
|
-
logger_1.logger.error(`🚨 ${errorMessage}`);
|
273
|
-
}
|
274
|
-
}
|
275
|
-
async saveCompletion(completion, peer, messages) {
|
276
|
-
node_fs_1.default.writeFile(`${this._config.get("path")}/${peer.publicKey.toString("hex")}-${this._conversationIndex}.json`, JSON.stringify([
|
277
|
-
...messages,
|
278
|
-
{
|
279
|
-
role: "assistant",
|
280
|
-
content: completion,
|
281
|
-
},
|
282
|
-
]), () => {
|
283
|
-
logger_1.logger.info(`📝 Completion saved to file`);
|
284
|
-
});
|
285
|
-
}
|
286
|
-
buildStreamRequest(messages) {
|
287
|
-
const requestOptions = {
|
288
|
-
hostname: this._config.get("apiHostname"),
|
289
|
-
port: Number(this._config.get("apiPort")),
|
290
|
-
path: this._config.get("apiPath"),
|
291
|
-
protocol: this._config.get("apiProtocol"),
|
292
|
-
method: "POST",
|
293
|
-
headers: {
|
294
|
-
"Content-Type": "application/json",
|
295
|
-
Authorization: `Bearer ${this._config.get("apiKey")}`,
|
296
|
-
},
|
297
|
-
};
|
298
|
-
const requestBody = {
|
299
|
-
model: this._config.get("modelName"),
|
300
|
-
messages: messages || undefined,
|
301
|
-
stream: true,
|
302
|
-
};
|
303
|
-
return { requestOptions, requestBody };
|
304
|
-
}
|
305
|
-
}
|
306
|
-
exports.SymmetryProvider = SymmetryProvider;
|
307
|
-
exports.default = SymmetryProvider;
|
package/dist/server.js
DELETED
@@ -1,143 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
-
};
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.SymmetryServer = void 0;
|
7
|
-
const chalk_1 = __importDefault(require("chalk"));
|
8
|
-
const corestore_1 = __importDefault(require("corestore"));
|
9
|
-
const hyperdrive_1 = __importDefault(require("hyperdrive"));
|
10
|
-
const hyperswarm_1 = __importDefault(require("hyperswarm"));
|
11
|
-
const path_1 = __importDefault(require("path"));
|
12
|
-
const config_manager_1 = require("./config-manager");
|
13
|
-
const utils_1 = require("./utils");
|
14
|
-
const logger_1 = require("./logger");
|
15
|
-
const peer_repository_1 = require("./peer-repository");
|
16
|
-
const constants_1 = require("./constants");
|
17
|
-
const session_manager_1 = require("./session-manager");
|
18
|
-
const session_repository_1 = require("./session-repository");
|
19
|
-
const websocket_server_1 = require("./websocket-server");
|
20
|
-
class SymmetryServer {
|
21
|
-
constructor(configPath) {
|
22
|
-
logger_1.logger.info(`🔗 Initializing server using config file: ${configPath}`);
|
23
|
-
this._config = (0, config_manager_1.createConfigManager)(configPath, false);
|
24
|
-
if (!this._config.isServerConfig()) {
|
25
|
-
throw new Error("Invalid configuration for server");
|
26
|
-
}
|
27
|
-
this._peerRepository = new peer_repository_1.PeerRepository();
|
28
|
-
this._sessionRepository = new session_repository_1.SessionRepository();
|
29
|
-
this._sessionManager = new session_manager_1.SessionManager(this._sessionRepository, 5);
|
30
|
-
}
|
31
|
-
async init() {
|
32
|
-
var _a, _b;
|
33
|
-
const corePath = path_1.default.join(this._config.get("path"), "symmetry-core");
|
34
|
-
const store = new corestore_1.default(corePath);
|
35
|
-
const core = new hyperdrive_1.default(store);
|
36
|
-
const swarm = new hyperswarm_1.default();
|
37
|
-
await core.ready();
|
38
|
-
const discovery = swarm.join(core.discoveryKey, { server: true });
|
39
|
-
await discovery.flushed();
|
40
|
-
swarm.on("connection", (peer) => {
|
41
|
-
logger_1.logger.info(`🔗 New Connection: ${peer.rawStream.remoteHost}`);
|
42
|
-
store.replicate(peer);
|
43
|
-
this.listeners(peer);
|
44
|
-
});
|
45
|
-
this._wsServer = new websocket_server_1.WsServer(this._config.get("webSocketPort"), this._peerRepository, swarm);
|
46
|
-
logger_1.logger.info(`🔑 Discovery key: ${(_a = core.discoveryKey) === null || _a === void 0 ? void 0 : _a.toString("hex")}`);
|
47
|
-
logger_1.logger.info(`🔑 Drive key: ${(_b = core.key) === null || _b === void 0 ? void 0 : _b.toString("hex")}`);
|
48
|
-
logger_1.logger.info(chalk_1.default.green(`\u2713 Websocket server started: ws://localhost:${this._config.get("webSocketPort")}`));
|
49
|
-
logger_1.logger.info(chalk_1.default.green(`\u2713 Symmetry server started, waiting for connections...`));
|
50
|
-
}
|
51
|
-
listeners(peer) {
|
52
|
-
peer.on("error", (err) => err);
|
53
|
-
peer.on("close", () => {
|
54
|
-
const peerKey = peer.publicKey.toString("hex");
|
55
|
-
this._peerRepository.updateLastSeen(peerKey);
|
56
|
-
logger_1.logger.info(`🔗 Connection Closed: Peer ${peerKey.slice(0, 6)}...${peerKey.slice(-6)}`);
|
57
|
-
});
|
58
|
-
peer.on("data", (message) => {
|
59
|
-
const data = (0, utils_1.safeParseJson)(message.toString());
|
60
|
-
if (!data)
|
61
|
-
return;
|
62
|
-
if (data.key) {
|
63
|
-
switch (data === null || data === void 0 ? void 0 : data.key) {
|
64
|
-
case constants_1.serverMessageKeys.join:
|
65
|
-
this.join(peer, data.data);
|
66
|
-
break;
|
67
|
-
case constants_1.serverMessageKeys.requestProvider:
|
68
|
-
this.handlePeerSession(peer, data.data);
|
69
|
-
break;
|
70
|
-
case constants_1.serverMessageKeys.verifySession:
|
71
|
-
this.handlePeerSessionValidation(peer, data.data);
|
72
|
-
break;
|
73
|
-
}
|
74
|
-
}
|
75
|
-
});
|
76
|
-
}
|
77
|
-
async join(peer, message) {
|
78
|
-
const peerKey = peer.publicKey.toString("hex");
|
79
|
-
try {
|
80
|
-
await this._peerRepository.upsert({
|
81
|
-
...message,
|
82
|
-
key: peerKey,
|
83
|
-
});
|
84
|
-
logger_1.logger.info(`👋 Peer provider joined ${peer.rawStream.remoteHost}`);
|
85
|
-
peer.write((0, utils_1.createMessage)(constants_1.serverMessageKeys.joinAck, {
|
86
|
-
status: "success",
|
87
|
-
key: peerKey,
|
88
|
-
}));
|
89
|
-
}
|
90
|
-
catch (error) {
|
91
|
-
let errorMessage = "";
|
92
|
-
if (error instanceof Error)
|
93
|
-
errorMessage = error.message;
|
94
|
-
logger_1.logger.error(`🚨 ${errorMessage}`);
|
95
|
-
}
|
96
|
-
}
|
97
|
-
async handlePeerSession(peer, randomPeerRequest) {
|
98
|
-
try {
|
99
|
-
const providerPeer = await this._peerRepository.getPeer(randomPeerRequest);
|
100
|
-
const sessionToken = await this._sessionManager.createSession(providerPeer.discovery_key);
|
101
|
-
peer.write((0, utils_1.createMessage)(constants_1.serverMessageKeys.providerDetails, {
|
102
|
-
providerId: providerPeer.key,
|
103
|
-
sessionToken,
|
104
|
-
}));
|
105
|
-
}
|
106
|
-
catch (error) {
|
107
|
-
let errorMessage = "";
|
108
|
-
if (error instanceof Error)
|
109
|
-
errorMessage = error.message;
|
110
|
-
logger_1.logger.error(`🚨 ${errorMessage}`);
|
111
|
-
}
|
112
|
-
}
|
113
|
-
async handlePeerSessionValidation(peer, message) {
|
114
|
-
if (!message.sessionToken)
|
115
|
-
return;
|
116
|
-
try {
|
117
|
-
const providerDiscoveryKey = await this._sessionManager.verifySession(message.sessionToken);
|
118
|
-
if (!providerDiscoveryKey)
|
119
|
-
return;
|
120
|
-
const providerPeer = await this._peerRepository.getByDiscoveryKey(providerDiscoveryKey);
|
121
|
-
if (!providerPeer)
|
122
|
-
return;
|
123
|
-
peer.write((0, utils_1.createMessage)(constants_1.serverMessageKeys.sessionValid, {
|
124
|
-
discoveryKey: providerPeer.discovery_key,
|
125
|
-
}));
|
126
|
-
await this._sessionManager.extendSession(message.sessionToken);
|
127
|
-
}
|
128
|
-
catch (error) {
|
129
|
-
logger_1.logger.error(`Session verification error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
130
|
-
peer.write((0, utils_1.createMessage)(constants_1.serverMessageKeys.sessionValid, {
|
131
|
-
valid: false,
|
132
|
-
error: "Error verifying session",
|
133
|
-
}));
|
134
|
-
}
|
135
|
-
}
|
136
|
-
async cleanupSessions() {
|
137
|
-
await this._sessionManager.cleanupExpiredSessions();
|
138
|
-
}
|
139
|
-
}
|
140
|
-
exports.SymmetryServer = SymmetryServer;
|
141
|
-
module.exports = {
|
142
|
-
SymmetryServer,
|
143
|
-
};
|
package/dist/session-manager.js
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
-
};
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.SessionManager = void 0;
|
7
|
-
const logger_1 = require("./logger");
|
8
|
-
const crypto_1 = __importDefault(require("crypto"));
|
9
|
-
class SessionManager {
|
10
|
-
constructor(sessionRepository, sessionDurationMinutes = 60) {
|
11
|
-
this.sessionRepository = sessionRepository;
|
12
|
-
this.sessionDuration = sessionDurationMinutes * 60 * 1000;
|
13
|
-
}
|
14
|
-
async createSession(providerId) {
|
15
|
-
const sessionId = crypto_1.default.randomUUID();
|
16
|
-
const now = new Date();
|
17
|
-
const session = {
|
18
|
-
id: sessionId,
|
19
|
-
providerId,
|
20
|
-
createdAt: now,
|
21
|
-
expiresAt: new Date(now.getTime() + this.sessionDuration),
|
22
|
-
};
|
23
|
-
await this.sessionRepository.create(session);
|
24
|
-
logger_1.logger.info(`🖇️ Session created for provider: ${providerId}`);
|
25
|
-
return sessionId;
|
26
|
-
}
|
27
|
-
async verifySession(sessionId) {
|
28
|
-
const session = await this.sessionRepository.get(sessionId);
|
29
|
-
if (!session) {
|
30
|
-
logger_1.logger.warning(`❌ Session not found: ${sessionId}`);
|
31
|
-
return null;
|
32
|
-
}
|
33
|
-
if (new Date() > session.expiresAt) {
|
34
|
-
logger_1.logger.warning(`🕛 Session expired: ${sessionId}`);
|
35
|
-
await this.sessionRepository.delete(sessionId);
|
36
|
-
return null;
|
37
|
-
}
|
38
|
-
return session.providerId;
|
39
|
-
}
|
40
|
-
async extendSession(sessionId) {
|
41
|
-
const session = await this.sessionRepository.get(sessionId);
|
42
|
-
if (!session) {
|
43
|
-
logger_1.logger.warning(`🚨 Cannot extend non-existent session: ${sessionId}`);
|
44
|
-
return false;
|
45
|
-
}
|
46
|
-
session.expiresAt = new Date(Date.now() + this.sessionDuration);
|
47
|
-
await this.sessionRepository.update(session);
|
48
|
-
logger_1.logger.info(`🎟️ Session extended: ${sessionId}`);
|
49
|
-
return true;
|
50
|
-
}
|
51
|
-
async deleteSession(sessionId) {
|
52
|
-
const result = await this.sessionRepository.delete(sessionId);
|
53
|
-
if (result) {
|
54
|
-
// cross bin emoji
|
55
|
-
logger_1.logger.info(`🗑 Session deleted: ${sessionId}`);
|
56
|
-
}
|
57
|
-
else {
|
58
|
-
logger_1.logger.warning(`🚨 Failed to delete session: ${sessionId}`);
|
59
|
-
}
|
60
|
-
return result;
|
61
|
-
}
|
62
|
-
async cleanupExpiredSessions() {
|
63
|
-
const deletedCount = await this.sessionRepository.deleteExpired();
|
64
|
-
logger_1.logger.info(`🕛 Cleaned up ${deletedCount} expired sessions`);
|
65
|
-
return deletedCount;
|
66
|
-
}
|
67
|
-
}
|
68
|
-
exports.SessionManager = SessionManager;
|