nodio-cli 1.0.2 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodio-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Nodio distributed storage network",
5
5
  "main": "src/server/index.js",
6
6
  "type": "commonjs",
package/src/node/index.js CHANGED
@@ -31,7 +31,7 @@ async function isPortFree(port) {
31
31
  server.close(() => resolve(true));
32
32
  });
33
33
 
34
- server.listen(port, '127.0.0.1');
34
+ server.listen({ port, exclusive: true });
35
35
  });
36
36
  }
37
37
 
@@ -85,11 +85,18 @@ class NodioNodeRuntime {
85
85
  res.json({ ok: true, nodeId: this.nodeId });
86
86
  });
87
87
 
88
- await new Promise((resolve) => {
89
- app.listen(this.port, () => {
88
+ await new Promise((resolve, reject) => {
89
+ const server = app.listen({ port: this.port, exclusive: true }, () => {
90
90
  console.log(`Nodio node ${this.nodeId} listening on port ${this.port}`);
91
91
  resolve();
92
92
  });
93
+
94
+ server.once('error', (error) => {
95
+ if (error && error.code === 'EADDRINUSE') {
96
+ return reject(new Error(`port ${this.port} is already in use`));
97
+ }
98
+ return reject(error);
99
+ });
93
100
  });
94
101
  }
95
102
 
@@ -101,11 +108,13 @@ class NodioNodeRuntime {
101
108
  async registerNode() {
102
109
  const deviceKey = await getOrCreateDeviceKey();
103
110
  const nodeKey = await this.shardStore.getOrCreateNodeKey();
111
+ const knownNodeIds = await this.shardStore.discoverKnownNodeIds();
104
112
 
105
113
  const response = await axios.post(`${this.serverUrl}/api/nodes/register`, {
106
114
  nodeId: this.nodeId,
107
115
  deviceKey,
108
116
  nodeKey,
117
+ knownNodeIds,
109
118
  url: this.publicUrl,
110
119
  capacityBytes: this.capacityBytes,
111
120
  freeBytes: await this.freeBytes()
@@ -67,6 +67,43 @@ class LocalShardStore {
67
67
  return identity.nodeId || null;
68
68
  }
69
69
 
70
+ async discoverKnownNodeIds() {
71
+ const known = new Set();
72
+
73
+ const current = await this.readIdentity();
74
+ if (current.nodeId) {
75
+ known.add(current.nodeId);
76
+ }
77
+
78
+ const parentDir = path.dirname(this.baseDir);
79
+ let entries = [];
80
+ try {
81
+ entries = await fs.readdir(parentDir, { withFileTypes: true });
82
+ } catch {
83
+ return [...known];
84
+ }
85
+
86
+ for (const entry of entries) {
87
+ if (!entry.isDirectory()) {
88
+ continue;
89
+ }
90
+
91
+ const identityPath = path.join(parentDir, entry.name, 'node-identity.json');
92
+ try {
93
+ // eslint-disable-next-line no-await-in-loop
94
+ const raw = await fs.readFile(identityPath, 'utf-8');
95
+ const parsed = JSON.parse(raw);
96
+ if (parsed?.nodeId && typeof parsed.nodeId === 'string') {
97
+ known.add(parsed.nodeId);
98
+ }
99
+ } catch {
100
+ continue;
101
+ }
102
+ }
103
+
104
+ return [...known];
105
+ }
106
+
70
107
  async saveAssignedNodeId(nodeId) {
71
108
  if (!nodeId) {
72
109
  return;
@@ -31,7 +31,7 @@ function buildRoutes(config) {
31
31
 
32
32
  router.post('/nodes/register', async (req, res, next) => {
33
33
  try {
34
- const { nodeId, deviceKey, nodeKey, url, capacityBytes, freeBytes } = req.body;
34
+ const { nodeId, deviceKey, nodeKey, knownNodeIds, url, capacityBytes, freeBytes } = req.body;
35
35
 
36
36
  if (!url) {
37
37
  return res.status(400).json({ error: 'url is required' });
@@ -45,6 +45,13 @@ function buildRoutes(config) {
45
45
  if (deviceKey && typeof deviceKey !== 'string') {
46
46
  return res.status(400).json({ error: 'deviceKey must be a string when provided' });
47
47
  }
48
+ if (knownNodeIds !== undefined && !Array.isArray(knownNodeIds)) {
49
+ return res.status(400).json({ error: 'knownNodeIds must be an array when provided' });
50
+ }
51
+
52
+ const normalizedKnownNodeIds = Array.isArray(knownNodeIds)
53
+ ? [...new Set(knownNodeIds.filter((value) => typeof value === 'string' && value.length > 0))]
54
+ : [];
48
55
 
49
56
  let existingByNodeKey = null;
50
57
  if (nodeKey) {
@@ -55,24 +62,58 @@ function buildRoutes(config) {
55
62
  return res.status(409).json({ error: 'nodeKey is already associated with a different nodeId' });
56
63
  }
57
64
 
58
- const effectiveNodeId = nodeId || existingByNodeKey?.nodeId || `donor-${uuidv4().slice(0, 8)}`;
65
+ let effectiveNodeId = nodeId || existingByNodeKey?.nodeId || null;
66
+ let claimedKnownNode = null;
59
67
 
60
- const node = await NodeModel.findOneAndUpdate(
61
- { nodeId: effectiveNodeId },
62
- {
63
- $set: {
64
- nodeId: effectiveNodeId,
65
- ...(deviceKey ? { deviceKey } : {}),
66
- ...(nodeKey ? { nodeKey } : {}),
67
- url,
68
- capacityBytes: capacity,
69
- freeBytes: free,
70
- status: 'online',
71
- lastHeartbeatAt: new Date()
68
+ if (!effectiveNodeId && deviceKey && normalizedKnownNodeIds.length > 0) {
69
+ claimedKnownNode = await NodeModel.findOneAndUpdate(
70
+ {
71
+ deviceKey,
72
+ status: 'offline',
73
+ nodeId: { $in: normalizedKnownNodeIds }
74
+ },
75
+ {
76
+ $set: {
77
+ url,
78
+ capacityBytes: capacity,
79
+ freeBytes: free,
80
+ status: 'online',
81
+ lastHeartbeatAt: new Date(),
82
+ ...(nodeKey ? { nodeKey } : {})
83
+ }
84
+ },
85
+ {
86
+ new: true,
87
+ sort: { createdAt: 1 }
72
88
  }
73
- },
74
- { upsert: true, new: true, setDefaultsOnInsert: true }
75
- );
89
+ );
90
+
91
+ if (claimedKnownNode) {
92
+ effectiveNodeId = claimedKnownNode.nodeId;
93
+ }
94
+ }
95
+
96
+ if (!effectiveNodeId) {
97
+ effectiveNodeId = `donor-${uuidv4().slice(0, 8)}`;
98
+ }
99
+
100
+ const node = claimedKnownNode
101
+ || await NodeModel.findOneAndUpdate(
102
+ { nodeId: effectiveNodeId },
103
+ {
104
+ $set: {
105
+ nodeId: effectiveNodeId,
106
+ ...(deviceKey ? { deviceKey } : {}),
107
+ ...(nodeKey ? { nodeKey } : {}),
108
+ url,
109
+ capacityBytes: capacity,
110
+ freeBytes: free,
111
+ status: 'online',
112
+ lastHeartbeatAt: new Date()
113
+ }
114
+ },
115
+ { upsert: true, new: true, setDefaultsOnInsert: true }
116
+ );
76
117
 
77
118
  res.json({
78
119
  nodeId: node.nodeId,