@ruvector/edge-net 0.4.0 → 0.4.1

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.
@@ -95,13 +95,14 @@ export async function getFirebaseConfigAsync() {
95
95
  export const DEFAULT_FIREBASE_CONFIG = null;
96
96
 
97
97
  /**
98
- * Signaling paths in Firestore
98
+ * Signaling collection names in Firestore
99
+ * Each is a top-level collection (Firestore requires collection/document pairs)
99
100
  */
100
101
  export const SIGNALING_PATHS = {
101
- peers: 'edge-net/peers',
102
- signals: 'edge-net/signals',
103
- rooms: 'edge-net/rooms',
104
- ledger: 'edge-net/ledger',
102
+ peers: 'edgenet_peers',
103
+ signals: 'edgenet_signals',
104
+ rooms: 'edgenet_rooms',
105
+ ledger: 'edgenet_ledger',
105
106
  };
106
107
 
107
108
  // ============================================
@@ -167,26 +168,21 @@ export class FirebaseSignaling extends EventEmitter {
167
168
  try {
168
169
  // Dynamic import Firebase (tree-shakeable)
169
170
  const { initializeApp, getApps } = await import('firebase/app');
170
- const { getFirestore, collection, doc, setDoc, onSnapshot, deleteDoc, query, where, orderBy, limit } = await import('firebase/firestore');
171
- const { getDatabase, ref, set, onValue, onDisconnect, serverTimestamp } = await import('firebase/database');
171
+ const { getFirestore, collection, doc, setDoc, onSnapshot, deleteDoc, query, where, orderBy, limit, serverTimestamp } = await import('firebase/firestore');
172
172
 
173
- // Store Firebase methods for later use
173
+ // Store Firebase methods for later use (Firestore only - no RTDB)
174
174
  this.firebase = {
175
- collection, doc, setDoc, onSnapshot, deleteDoc, query, where, orderBy, limit,
176
- ref, set, onValue, onDisconnect, serverTimestamp
175
+ collection, doc, setDoc, onSnapshot, deleteDoc, query, where, orderBy, limit, serverTimestamp
177
176
  };
178
177
 
179
178
  // Initialize or reuse existing app
180
179
  const apps = getApps();
181
180
  this.app = apps.length ? apps[0] : initializeApp(this.config);
182
181
 
183
- // Initialize Firestore (for signaling)
182
+ // Initialize Firestore (for signaling AND presence - no RTDB needed)
184
183
  this.db = getFirestore(this.app);
185
184
 
186
- // Initialize Realtime Database (for presence)
187
- this.rtdb = getDatabase(this.app);
188
-
189
- // Register presence
185
+ // Register presence in Firestore
190
186
  await this.registerPresence();
191
187
 
192
188
  // Listen for peers
@@ -196,7 +192,7 @@ export class FirebaseSignaling extends EventEmitter {
196
192
  this.subscribeToSignals();
197
193
 
198
194
  this.isConnected = true;
199
- console.log(' ✅ Firebase signaling connected');
195
+ console.log(' ✅ Firebase signaling connected (Firestore)');
200
196
 
201
197
  this.emit('connected');
202
198
  return true;
@@ -209,54 +205,76 @@ export class FirebaseSignaling extends EventEmitter {
209
205
  }
210
206
 
211
207
  /**
212
- * Register this peer's presence in Firebase
208
+ * Register this peer's presence in Firestore
213
209
  */
214
210
  async registerPresence() {
215
- const { ref, set, onDisconnect, serverTimestamp } = this.firebase;
211
+ const { doc, setDoc, serverTimestamp } = this.firebase;
216
212
 
217
- const presenceRef = ref(this.rtdb, `presence/${this.room}/${this.peerId}`);
213
+ const presenceRef = doc(this.db, SIGNALING_PATHS.peers, this.peerId);
218
214
 
219
- // Set online status
220
- await set(presenceRef, {
215
+ // Set online status in Firestore
216
+ await setDoc(presenceRef, {
221
217
  peerId: this.peerId,
222
218
  room: this.room,
223
219
  online: true,
224
220
  lastSeen: serverTimestamp(),
225
221
  capabilities: ['compute', 'storage', 'verify'],
226
- });
222
+ }, { merge: true });
227
223
 
228
- // Remove on disconnect
229
- onDisconnect(presenceRef).remove();
224
+ // Set up heartbeat to maintain presence (Firestore doesn't have onDisconnect)
225
+ this._heartbeatInterval = setInterval(async () => {
226
+ try {
227
+ await setDoc(presenceRef, { lastSeen: serverTimestamp() }, { merge: true });
228
+ } catch (e) {
229
+ // Ignore heartbeat errors
230
+ }
231
+ }, 30000);
230
232
 
231
233
  console.log(` 📡 Registered presence: ${this.peerId.slice(0, 8)}...`);
232
234
  }
233
235
 
234
236
  /**
235
- * Subscribe to peer presence updates
237
+ * Subscribe to peer presence updates (using Firestore)
236
238
  */
237
239
  subscribeToPeers() {
238
- const { ref, onValue } = this.firebase;
239
-
240
- const roomRef = ref(this.rtdb, `presence/${this.room}`);
241
-
242
- const unsubscribe = onValue(roomRef, (snapshot) => {
243
- const peers = snapshot.val() || {};
240
+ const { collection, query, where, onSnapshot } = this.firebase;
244
241
 
245
- // Track new peers
246
- for (const [peerId, data] of Object.entries(peers)) {
247
- if (peerId !== this.peerId && !this.peers.has(peerId)) {
248
- this.peers.set(peerId, data);
249
- this.emit('peer-discovered', { peerId, ...data });
250
- }
251
- }
242
+ // Query peers in same room that were active in last 2 minutes
243
+ const peersRef = collection(this.db, SIGNALING_PATHS.peers);
244
+ const q = query(peersRef, where('room', '==', this.room));
252
245
 
253
- // Track disconnected peers
254
- for (const peerId of this.peers.keys()) {
255
- if (!peers[peerId]) {
256
- this.peers.delete(peerId);
257
- this.emit('peer-left', { peerId });
246
+ const unsubscribe = onSnapshot(q, (snapshot) => {
247
+ const now = Date.now();
248
+ const staleThreshold = 2 * 60 * 1000; // 2 minutes
249
+
250
+ snapshot.docChanges().forEach((change) => {
251
+ const data = change.doc.data();
252
+ const peerId = change.doc.id;
253
+
254
+ if (peerId === this.peerId) return; // Skip self
255
+
256
+ if (change.type === 'added' || change.type === 'modified') {
257
+ // Check if peer is still active (lastSeen within threshold)
258
+ const lastSeen = data.lastSeen?.toMillis?.() || 0;
259
+ if (now - lastSeen < staleThreshold) {
260
+ if (!this.peers.has(peerId)) {
261
+ this.peers.set(peerId, data);
262
+ this.emit('peer-discovered', { peerId, ...data });
263
+ }
264
+ } else {
265
+ // Peer is stale
266
+ if (this.peers.has(peerId)) {
267
+ this.peers.delete(peerId);
268
+ this.emit('peer-left', { peerId });
269
+ }
270
+ }
271
+ } else if (change.type === 'removed') {
272
+ if (this.peers.has(peerId)) {
273
+ this.peers.delete(peerId);
274
+ this.emit('peer-left', { peerId });
275
+ }
258
276
  }
259
- }
277
+ });
260
278
  });
261
279
 
262
280
  this.unsubscribers.push(unsubscribe);
@@ -370,17 +388,27 @@ export class FirebaseSignaling extends EventEmitter {
370
388
  * Disconnect and cleanup
371
389
  */
372
390
  async disconnect() {
391
+ // Stop heartbeat
392
+ if (this._heartbeatInterval) {
393
+ clearInterval(this._heartbeatInterval);
394
+ this._heartbeatInterval = null;
395
+ }
396
+
373
397
  // Unsubscribe from all listeners
374
398
  for (const unsub of this.unsubscribers) {
375
399
  if (typeof unsub === 'function') unsub();
376
400
  }
377
401
  this.unsubscribers = [];
378
402
 
379
- // Remove presence
380
- if (this.rtdb && this.firebase) {
381
- const { ref, set } = this.firebase;
382
- const presenceRef = ref(this.rtdb, `presence/${this.room}/${this.peerId}`);
383
- await set(presenceRef, null);
403
+ // Remove presence from Firestore
404
+ if (this.db && this.firebase) {
405
+ try {
406
+ const { doc, deleteDoc } = this.firebase;
407
+ const presenceRef = doc(this.db, SIGNALING_PATHS.peers, this.peerId);
408
+ await deleteDoc(presenceRef);
409
+ } catch (e) {
410
+ // Ignore cleanup errors
411
+ }
384
412
  }
385
413
 
386
414
  this.isConnected = false;
package/p2p.js CHANGED
@@ -614,7 +614,7 @@ export class P2PNetwork extends EventEmitter {
614
614
  * Get current balance
615
615
  */
616
616
  getBalance() {
617
- return this.ledger?.getBalance() || 0;
617
+ return this.ledger?.balance?.() ?? this.ledger?.getBalance?.() ?? 0;
618
618
  }
619
619
 
620
620
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruvector/edge-net",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "description": "Distributed compute intelligence network with AI agents and workers - contribute browser compute, spawn distributed AI agents, earn credits. Features Time Crystal coordination, Neural DAG attention, P2P swarm intelligence, ONNX inference, WebRTC signaling, CRDT ledger, and multi-agent workflows.",
6
6
  "main": "ruvector_edge_net.js",
@@ -189,14 +189,7 @@
189
189
  "dependencies": {
190
190
  "@ruvector/ruvllm": "^0.2.3",
191
191
  "@xenova/transformers": "^2.17.2",
192
+ "firebase": "^10.14.1",
192
193
  "ws": "^8.18.3"
193
- },
194
- "peerDependencies": {
195
- "firebase": "^10.0.0"
196
- },
197
- "peerDependenciesMeta": {
198
- "firebase": {
199
- "optional": true
200
- }
201
194
  }
202
195
  }