@xtr-dev/rondevu-server 0.2.3 → 0.3.0

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/crypto.ts CHANGED
@@ -228,6 +228,60 @@ export function validateServiceFqn(fqn: string): { valid: boolean; error?: strin
228
228
  return { valid: true };
229
229
  }
230
230
 
231
+ /**
232
+ * Parse semantic version string into components
233
+ */
234
+ export function parseVersion(version: string): { major: number; minor: number; patch: number; prerelease?: string } | null {
235
+ const match = version.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(-[a-z0-9.-]+)?$/);
236
+ if (!match) return null;
237
+
238
+ return {
239
+ major: parseInt(match[1], 10),
240
+ minor: parseInt(match[2], 10),
241
+ patch: parseInt(match[3], 10),
242
+ prerelease: match[4]?.substring(1), // Remove leading dash
243
+ };
244
+ }
245
+
246
+ /**
247
+ * Check if two versions are compatible (same major version)
248
+ * Following semver rules: ^1.0.0 matches 1.x.x but not 2.x.x
249
+ */
250
+ export function isVersionCompatible(requested: string, available: string): boolean {
251
+ const req = parseVersion(requested);
252
+ const avail = parseVersion(available);
253
+
254
+ if (!req || !avail) return false;
255
+
256
+ // Major version must match
257
+ if (req.major !== avail.major) return false;
258
+
259
+ // If major is 0, minor must also match (0.x.y is unstable)
260
+ if (req.major === 0 && req.minor !== avail.minor) return false;
261
+
262
+ // Available version must be >= requested version
263
+ if (avail.minor < req.minor) return false;
264
+ if (avail.minor === req.minor && avail.patch < req.patch) return false;
265
+
266
+ // Prerelease versions are only compatible with exact matches
267
+ if (req.prerelease && req.prerelease !== avail.prerelease) return false;
268
+
269
+ return true;
270
+ }
271
+
272
+ /**
273
+ * Parse service FQN into service name and version
274
+ */
275
+ export function parseServiceFqn(fqn: string): { serviceName: string; version: string } | null {
276
+ const parts = fqn.split('@');
277
+ if (parts.length !== 2) return null;
278
+
279
+ return {
280
+ serviceName: parts[0],
281
+ version: parts[1],
282
+ };
283
+ }
284
+
231
285
  /**
232
286
  * Validates timestamp is within acceptable range (prevents replay attacks)
233
287
  */
@@ -317,3 +371,46 @@ export async function validateUsernameClaim(
317
371
 
318
372
  return { valid: true };
319
373
  }
374
+
375
+ /**
376
+ * Validates a service publish signature
377
+ * Message format: publish:{username}:{serviceFqn}:{timestamp}
378
+ */
379
+ export async function validateServicePublish(
380
+ username: string,
381
+ serviceFqn: string,
382
+ publicKey: string,
383
+ signature: string,
384
+ message: string
385
+ ): Promise<{ valid: boolean; error?: string }> {
386
+ // Validate username format
387
+ const usernameCheck = validateUsername(username);
388
+ if (!usernameCheck.valid) {
389
+ return usernameCheck;
390
+ }
391
+
392
+ // Parse message format: "publish:{username}:{serviceFqn}:{timestamp}"
393
+ const parts = message.split(':');
394
+ if (parts.length !== 4 || parts[0] !== 'publish' || parts[1] !== username || parts[2] !== serviceFqn) {
395
+ return { valid: false, error: 'Invalid message format (expected: publish:{username}:{serviceFqn}:{timestamp})' };
396
+ }
397
+
398
+ const timestamp = parseInt(parts[3], 10);
399
+ if (isNaN(timestamp)) {
400
+ return { valid: false, error: 'Invalid timestamp in message' };
401
+ }
402
+
403
+ // Validate timestamp
404
+ const timestampCheck = validateTimestamp(timestamp);
405
+ if (!timestampCheck.valid) {
406
+ return timestampCheck;
407
+ }
408
+
409
+ // Verify signature
410
+ const signatureValid = await verifyEd25519Signature(publicKey, signature, message);
411
+ if (!signatureValid) {
412
+ return { valid: false, error: 'Invalid signature' };
413
+ }
414
+
415
+ return { valid: true };
416
+ }
package/src/index.ts CHANGED
@@ -20,7 +20,6 @@ async function main() {
20
20
  offerMinTtl: `${config.offerMinTtl}ms`,
21
21
  cleanupInterval: `${config.cleanupInterval}ms`,
22
22
  maxOffersPerRequest: config.maxOffersPerRequest,
23
- maxTopicsPerOffer: config.maxTopicsPerOffer,
24
23
  corsOrigins: config.corsOrigins,
25
24
  version: config.version,
26
25
  });
package/src/storage/d1.ts CHANGED
@@ -34,7 +34,7 @@ export class D1Storage implements Storage {
34
34
  */
35
35
  async initializeDatabase(): Promise<void> {
36
36
  await this.db.exec(`
37
- -- Offers table (no topics)
37
+ -- WebRTC signaling offers
38
38
  CREATE TABLE IF NOT EXISTS offers (
39
39
  id TEXT PRIMARY KEY,
40
40
  peer_id TEXT NOT NULL,
@@ -125,7 +125,7 @@ export class D1Storage implements Storage {
125
125
 
126
126
  // D1 doesn't support true transactions yet, so we do this sequentially
127
127
  for (const offer of offers) {
128
- const id = offer.id || await generateOfferHash(offer.sdp, []);
128
+ const id = offer.id || await generateOfferHash(offer.sdp);
129
129
  const now = Date.now();
130
130
 
131
131
  await this.db.prepare(`
@@ -401,6 +401,7 @@ export class D1Storage implements Storage {
401
401
  async createService(request: CreateServiceRequest): Promise<{
402
402
  service: Service;
403
403
  indexUuid: string;
404
+ offers: Offer[];
404
405
  }> {
405
406
  const serviceId = crypto.randomUUID();
406
407
  const indexUuid = crypto.randomUUID();
@@ -408,13 +409,12 @@ export class D1Storage implements Storage {
408
409
 
409
410
  // Insert service
410
411
  await this.db.prepare(`
411
- INSERT INTO services (id, username, service_fqn, offer_id, created_at, expires_at, is_public, metadata)
412
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
412
+ INSERT INTO services (id, username, service_fqn, created_at, expires_at, is_public, metadata)
413
+ VALUES (?, ?, ?, ?, ?, ?, ?)
413
414
  `).bind(
414
415
  serviceId,
415
416
  request.username,
416
417
  request.serviceFqn,
417
- request.offerId,
418
418
  now,
419
419
  request.expiresAt,
420
420
  request.isPublic ? 1 : 0,
@@ -434,6 +434,13 @@ export class D1Storage implements Storage {
434
434
  request.expiresAt
435
435
  ).run();
436
436
 
437
+ // Create offers with serviceId
438
+ const offerRequests = request.offers.map(offer => ({
439
+ ...offer,
440
+ serviceId,
441
+ }));
442
+ const offers = await this.createOffers(offerRequests);
443
+
437
444
  // Touch username to extend expiry
438
445
  await this.touchUsername(request.username);
439
446
 
@@ -442,16 +449,43 @@ export class D1Storage implements Storage {
442
449
  id: serviceId,
443
450
  username: request.username,
444
451
  serviceFqn: request.serviceFqn,
445
- offerId: request.offerId,
446
452
  createdAt: now,
447
453
  expiresAt: request.expiresAt,
448
454
  isPublic: request.isPublic || false,
449
455
  metadata: request.metadata,
450
456
  },
451
457
  indexUuid,
458
+ offers,
452
459
  };
453
460
  }
454
461
 
462
+ async batchCreateServices(requests: CreateServiceRequest[]): Promise<Array<{
463
+ service: Service;
464
+ indexUuid: string;
465
+ offers: Offer[];
466
+ }>> {
467
+ const results = [];
468
+ for (const request of requests) {
469
+ const result = await this.createService(request);
470
+ results.push(result);
471
+ }
472
+ return results;
473
+ }
474
+
475
+ async getOffersForService(serviceId: string): Promise<Offer[]> {
476
+ const result = await this.db.prepare(`
477
+ SELECT * FROM offers
478
+ WHERE service_id = ? AND expires_at > ?
479
+ ORDER BY created_at ASC
480
+ `).bind(serviceId, Date.now()).all();
481
+
482
+ if (!result.results) {
483
+ return [];
484
+ }
485
+
486
+ return result.results.map(row => this.rowToOffer(row as any));
487
+ }
488
+
455
489
  async getServiceById(serviceId: string): Promise<Service | null> {
456
490
  const result = await this.db.prepare(`
457
491
  SELECT * FROM services
@@ -510,6 +544,20 @@ export class D1Storage implements Storage {
510
544
  return result ? (result as any).uuid : null;
511
545
  }
512
546
 
547
+ async findServicesByName(username: string, serviceName: string): Promise<Service[]> {
548
+ const result = await this.db.prepare(`
549
+ SELECT * FROM services
550
+ WHERE username = ? AND service_fqn LIKE ? AND expires_at > ?
551
+ ORDER BY created_at DESC
552
+ `).bind(username, `${serviceName}@%`, Date.now()).all();
553
+
554
+ if (!result.results) {
555
+ return [];
556
+ }
557
+
558
+ return result.results.map(row => this.rowToService(row as any));
559
+ }
560
+
513
561
  async deleteService(serviceId: string, username: string): Promise<boolean> {
514
562
  const result = await this.db.prepare(`
515
563
  DELETE FROM services
@@ -560,7 +608,6 @@ export class D1Storage implements Storage {
560
608
  id: row.id,
561
609
  username: row.username,
562
610
  serviceFqn: row.service_fqn,
563
- offerId: row.offer_id,
564
611
  createdAt: row.created_at,
565
612
  expiresAt: row.expires_at,
566
613
  isPublic: row.is_public === 1,
@@ -1,22 +1,17 @@
1
1
  /**
2
2
  * Generates a content-based offer ID using SHA-256 hash
3
- * Creates deterministic IDs based on offer content (sdp, topics)
3
+ * Creates deterministic IDs based on offer SDP content
4
4
  * PeerID is not included as it's inferred from authentication
5
5
  * Uses Web Crypto API for compatibility with both Node.js and Cloudflare Workers
6
6
  *
7
7
  * @param sdp - The WebRTC SDP offer
8
- * @param topics - Array of topic strings
9
- * @returns SHA-256 hash of the sanitized offer content
8
+ * @returns SHA-256 hash of the SDP content
10
9
  */
11
- export async function generateOfferHash(
12
- sdp: string,
13
- topics: string[]
14
- ): Promise<string> {
10
+ export async function generateOfferHash(sdp: string): Promise<string> {
15
11
  // Sanitize and normalize the offer content
16
12
  // Only include core offer content (not peerId - that's inferred from auth)
17
13
  const sanitizedOffer = {
18
- sdp,
19
- topics: [...topics].sort(), // Sort topics for consistency
14
+ sdp
20
15
  };
21
16
 
22
17
  // Create non-prettified JSON string
@@ -36,10 +36,11 @@ export class SQLiteStorage implements Storage {
36
36
  */
37
37
  private initializeDatabase(): void {
38
38
  this.db.exec(`
39
- -- Offers table (no topics)
39
+ -- WebRTC signaling offers
40
40
  CREATE TABLE IF NOT EXISTS offers (
41
41
  id TEXT PRIMARY KEY,
42
42
  peer_id TEXT NOT NULL,
43
+ service_id TEXT,
43
44
  sdp TEXT NOT NULL,
44
45
  created_at INTEGER NOT NULL,
45
46
  expires_at INTEGER NOT NULL,
@@ -47,10 +48,12 @@ export class SQLiteStorage implements Storage {
47
48
  secret TEXT,
48
49
  answerer_peer_id TEXT,
49
50
  answer_sdp TEXT,
50
- answered_at INTEGER
51
+ answered_at INTEGER,
52
+ FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE
51
53
  );
52
54
 
53
55
  CREATE INDEX IF NOT EXISTS idx_offers_peer ON offers(peer_id);
56
+ CREATE INDEX IF NOT EXISTS idx_offers_service ON offers(service_id);
54
57
  CREATE INDEX IF NOT EXISTS idx_offers_expires ON offers(expires_at);
55
58
  CREATE INDEX IF NOT EXISTS idx_offers_last_seen ON offers(last_seen);
56
59
  CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(answerer_peer_id);
@@ -84,25 +87,22 @@ export class SQLiteStorage implements Storage {
84
87
  CREATE INDEX IF NOT EXISTS idx_usernames_expires ON usernames(expires_at);
85
88
  CREATE INDEX IF NOT EXISTS idx_usernames_public_key ON usernames(public_key);
86
89
 
87
- -- Services table
90
+ -- Services table (one service can have multiple offers)
88
91
  CREATE TABLE IF NOT EXISTS services (
89
92
  id TEXT PRIMARY KEY,
90
93
  username TEXT NOT NULL,
91
94
  service_fqn TEXT NOT NULL,
92
- offer_id TEXT NOT NULL,
93
95
  created_at INTEGER NOT NULL,
94
96
  expires_at INTEGER NOT NULL,
95
97
  is_public INTEGER NOT NULL DEFAULT 0,
96
98
  metadata TEXT,
97
99
  FOREIGN KEY (username) REFERENCES usernames(username) ON DELETE CASCADE,
98
- FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE,
99
100
  UNIQUE(username, service_fqn)
100
101
  );
101
102
 
102
103
  CREATE INDEX IF NOT EXISTS idx_services_username ON services(username);
103
104
  CREATE INDEX IF NOT EXISTS idx_services_fqn ON services(service_fqn);
104
105
  CREATE INDEX IF NOT EXISTS idx_services_expires ON services(expires_at);
105
- CREATE INDEX IF NOT EXISTS idx_services_offer ON services(offer_id);
106
106
 
107
107
  -- Service index table (privacy layer)
108
108
  CREATE TABLE IF NOT EXISTS service_index (
@@ -132,15 +132,15 @@ export class SQLiteStorage implements Storage {
132
132
  const offersWithIds = await Promise.all(
133
133
  offers.map(async (offer) => ({
134
134
  ...offer,
135
- id: offer.id || await generateOfferHash(offer.sdp, []),
135
+ id: offer.id || await generateOfferHash(offer.sdp),
136
136
  }))
137
137
  );
138
138
 
139
139
  // Use transaction for atomic creation
140
140
  const transaction = this.db.transaction((offersWithIds: (CreateOfferRequest & { id: string })[]) => {
141
141
  const offerStmt = this.db.prepare(`
142
- INSERT INTO offers (id, peer_id, sdp, created_at, expires_at, last_seen, secret)
143
- VALUES (?, ?, ?, ?, ?, ?, ?)
142
+ INSERT INTO offers (id, peer_id, service_id, sdp, created_at, expires_at, last_seen, secret)
143
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
144
144
  `);
145
145
 
146
146
  for (const offer of offersWithIds) {
@@ -150,6 +150,7 @@ export class SQLiteStorage implements Storage {
150
150
  offerStmt.run(
151
151
  offer.id,
152
152
  offer.peerId,
153
+ offer.serviceId || null,
153
154
  offer.sdp,
154
155
  now,
155
156
  offer.expiresAt,
@@ -160,6 +161,7 @@ export class SQLiteStorage implements Storage {
160
161
  created.push({
161
162
  id: offer.id,
162
163
  peerId: offer.peerId,
164
+ serviceId: offer.serviceId || undefined,
163
165
  sdp: offer.sdp,
164
166
  createdAt: now,
165
167
  expiresAt: offer.expiresAt,
@@ -426,23 +428,31 @@ export class SQLiteStorage implements Storage {
426
428
  async createService(request: CreateServiceRequest): Promise<{
427
429
  service: Service;
428
430
  indexUuid: string;
431
+ offers: Offer[];
429
432
  }> {
430
433
  const serviceId = randomUUID();
431
434
  const indexUuid = randomUUID();
432
435
  const now = Date.now();
433
436
 
437
+ // Create offers with serviceId
438
+ const offerRequests: CreateOfferRequest[] = request.offers.map(offer => ({
439
+ ...offer,
440
+ serviceId,
441
+ }));
442
+
443
+ const offers = await this.createOffers(offerRequests);
444
+
434
445
  const transaction = this.db.transaction(() => {
435
- // Insert service
446
+ // Insert service (no offer_id column anymore)
436
447
  const serviceStmt = this.db.prepare(`
437
- INSERT INTO services (id, username, service_fqn, offer_id, created_at, expires_at, is_public, metadata)
438
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
448
+ INSERT INTO services (id, username, service_fqn, created_at, expires_at, is_public, metadata)
449
+ VALUES (?, ?, ?, ?, ?, ?, ?)
439
450
  `);
440
451
 
441
452
  serviceStmt.run(
442
453
  serviceId,
443
454
  request.username,
444
455
  request.serviceFqn,
445
- request.offerId,
446
456
  now,
447
457
  request.expiresAt,
448
458
  request.isPublic ? 1 : 0,
@@ -475,16 +485,31 @@ export class SQLiteStorage implements Storage {
475
485
  id: serviceId,
476
486
  username: request.username,
477
487
  serviceFqn: request.serviceFqn,
478
- offerId: request.offerId,
479
488
  createdAt: now,
480
489
  expiresAt: request.expiresAt,
481
490
  isPublic: request.isPublic || false,
482
491
  metadata: request.metadata,
483
492
  },
484
493
  indexUuid,
494
+ offers,
485
495
  };
486
496
  }
487
497
 
498
+ async batchCreateServices(requests: CreateServiceRequest[]): Promise<Array<{
499
+ service: Service;
500
+ indexUuid: string;
501
+ offers: Offer[];
502
+ }>> {
503
+ const results = [];
504
+
505
+ for (const request of requests) {
506
+ const result = await this.createService(request);
507
+ results.push(result);
508
+ }
509
+
510
+ return results;
511
+ }
512
+
488
513
  async getServiceById(serviceId: string): Promise<Service | null> {
489
514
  const stmt = this.db.prepare(`
490
515
  SELECT * FROM services
@@ -547,6 +572,18 @@ export class SQLiteStorage implements Storage {
547
572
  return row ? row.uuid : null;
548
573
  }
549
574
 
575
+ async findServicesByName(username: string, serviceName: string): Promise<Service[]> {
576
+ const stmt = this.db.prepare(`
577
+ SELECT * FROM services
578
+ WHERE username = ? AND service_fqn LIKE ? AND expires_at > ?
579
+ ORDER BY created_at DESC
580
+ `);
581
+
582
+ const rows = stmt.all(username, `${serviceName}@%`, Date.now()) as any[];
583
+
584
+ return rows.map(row => this.rowToService(row));
585
+ }
586
+
550
587
  async deleteService(serviceId: string, username: string): Promise<boolean> {
551
588
  const stmt = this.db.prepare(`
552
589
  DELETE FROM services
@@ -576,6 +613,7 @@ export class SQLiteStorage implements Storage {
576
613
  return {
577
614
  id: row.id,
578
615
  peerId: row.peer_id,
616
+ serviceId: row.service_id || undefined,
579
617
  sdp: row.sdp,
580
618
  createdAt: row.created_at,
581
619
  expiresAt: row.expires_at,
@@ -595,11 +633,24 @@ export class SQLiteStorage implements Storage {
595
633
  id: row.id,
596
634
  username: row.username,
597
635
  serviceFqn: row.service_fqn,
598
- offerId: row.offer_id,
599
636
  createdAt: row.created_at,
600
637
  expiresAt: row.expires_at,
601
638
  isPublic: row.is_public === 1,
602
639
  metadata: row.metadata || undefined,
603
640
  };
604
641
  }
642
+
643
+ /**
644
+ * Get all offers for a service
645
+ */
646
+ async getOffersForService(serviceId: string): Promise<Offer[]> {
647
+ const stmt = this.db.prepare(`
648
+ SELECT * FROM offers
649
+ WHERE service_id = ? AND expires_at > ?
650
+ ORDER BY created_at ASC
651
+ `);
652
+
653
+ const rows = stmt.all(serviceId, Date.now()) as any[];
654
+ return rows.map(row => this.rowToOffer(row));
655
+ }
605
656
  }
@@ -1,15 +1,15 @@
1
1
  /**
2
- * Represents a WebRTC signaling offer (no topics)
2
+ * Represents a WebRTC signaling offer
3
3
  */
4
4
  export interface Offer {
5
5
  id: string;
6
6
  peerId: string;
7
+ serviceId?: string; // Optional link to service (null for standalone offers)
7
8
  sdp: string;
8
9
  createdAt: number;
9
10
  expiresAt: number;
10
11
  lastSeen: number;
11
12
  secret?: string;
12
- info?: string;
13
13
  answererPeerId?: string;
14
14
  answerSdp?: string;
15
15
  answeredAt?: number;
@@ -34,10 +34,10 @@ export interface IceCandidate {
34
34
  export interface CreateOfferRequest {
35
35
  id?: string;
36
36
  peerId: string;
37
+ serviceId?: string; // Optional link to service
37
38
  sdp: string;
38
39
  expiresAt: number;
39
40
  secret?: string;
40
- info?: string;
41
41
  }
42
42
 
43
43
  /**
@@ -63,13 +63,12 @@ export interface ClaimUsernameRequest {
63
63
  }
64
64
 
65
65
  /**
66
- * Represents a published service
66
+ * Represents a published service (can have multiple offers)
67
67
  */
68
68
  export interface Service {
69
69
  id: string; // UUID v4
70
70
  username: string;
71
71
  serviceFqn: string; // com.example.chat@1.0.0
72
- offerId: string; // Links to offers table
73
72
  createdAt: number;
74
73
  expiresAt: number;
75
74
  isPublic: boolean;
@@ -77,15 +76,22 @@ export interface Service {
77
76
  }
78
77
 
79
78
  /**
80
- * Request to create a service
79
+ * Request to create a single service
81
80
  */
82
81
  export interface CreateServiceRequest {
83
82
  username: string;
84
83
  serviceFqn: string;
85
- offerId: string;
86
84
  expiresAt: number;
87
85
  isPublic?: boolean;
88
86
  metadata?: string;
87
+ offers: CreateOfferRequest[]; // Multiple offers per service
88
+ }
89
+
90
+ /**
91
+ * Request to create multiple services in batch
92
+ */
93
+ export interface BatchCreateServicesRequest {
94
+ services: CreateServiceRequest[];
89
95
  }
90
96
 
91
97
  /**
@@ -236,15 +242,34 @@ export interface Storage {
236
242
  // ===== Service Management =====
237
243
 
238
244
  /**
239
- * Creates a new service
240
- * @param request Service creation request
241
- * @returns Created service with generated ID and index UUID
245
+ * Creates a new service with offers
246
+ * @param request Service creation request (includes offers)
247
+ * @returns Created service with generated ID, index UUID, and created offers
242
248
  */
243
249
  createService(request: CreateServiceRequest): Promise<{
244
250
  service: Service;
245
251
  indexUuid: string;
252
+ offers: Offer[];
246
253
  }>;
247
254
 
255
+ /**
256
+ * Creates multiple services with offers in batch
257
+ * @param requests Array of service creation requests
258
+ * @returns Array of created services with IDs, UUIDs, and offers
259
+ */
260
+ batchCreateServices(requests: CreateServiceRequest[]): Promise<Array<{
261
+ service: Service;
262
+ indexUuid: string;
263
+ offers: Offer[];
264
+ }>>;
265
+
266
+ /**
267
+ * Gets all offers for a service
268
+ * @param serviceId Service ID
269
+ * @returns Array of offers for the service
270
+ */
271
+ getOffersForService(serviceId: string): Promise<Offer[]>;
272
+
248
273
  /**
249
274
  * Gets a service by its service ID
250
275
  * @param serviceId Service ID
@@ -274,6 +299,14 @@ export interface Storage {
274
299
  */
275
300
  queryService(username: string, serviceFqn: string): Promise<string | null>;
276
301
 
302
+ /**
303
+ * Finds all services by username and service name (without version)
304
+ * @param username Username
305
+ * @param serviceName Service name (e.g., 'com.example.chat')
306
+ * @returns Array of services with matching service name
307
+ */
308
+ findServicesByName(username: string, serviceName: string): Promise<Service[]>;
309
+
277
310
  /**
278
311
  * Deletes a service (with ownership verification)
279
312
  * @param serviceId Service ID
package/src/worker.ts CHANGED
@@ -13,7 +13,6 @@ export interface Env {
13
13
  OFFER_MAX_TTL?: string;
14
14
  OFFER_MIN_TTL?: string;
15
15
  MAX_OFFERS_PER_REQUEST?: string;
16
- MAX_TOPICS_PER_OFFER?: string;
17
16
  CORS_ORIGINS?: string;
18
17
  VERSION?: string;
19
18
  }
@@ -43,8 +42,7 @@ export default {
43
42
  offerMaxTtl: env.OFFER_MAX_TTL ? parseInt(env.OFFER_MAX_TTL, 10) : 86400000,
44
43
  offerMinTtl: env.OFFER_MIN_TTL ? parseInt(env.OFFER_MIN_TTL, 10) : 60000,
45
44
  cleanupInterval: 60000, // Not used in Workers (scheduled handler instead)
46
- maxOffersPerRequest: env.MAX_OFFERS_PER_REQUEST ? parseInt(env.MAX_OFFERS_PER_REQUEST, 10) : 100,
47
- maxTopicsPerOffer: env.MAX_TOPICS_PER_OFFER ? parseInt(env.MAX_TOPICS_PER_OFFER, 10) : 50,
45
+ maxOffersPerRequest: env.MAX_OFFERS_PER_REQUEST ? parseInt(env.MAX_OFFERS_PER_REQUEST, 10) : 100
48
46
  };
49
47
 
50
48
  // Create Hono app
package/src/bloom.ts DELETED
@@ -1,66 +0,0 @@
1
- /**
2
- * Bloom filter utility for testing if peer IDs might be in a set
3
- * Used to filter out known peers from discovery results
4
- */
5
-
6
- export class BloomFilter {
7
- private bits: Uint8Array;
8
- private size: number;
9
- private numHashes: number;
10
-
11
- /**
12
- * Creates a bloom filter from a base64 encoded bit array
13
- */
14
- constructor(base64Data: string, numHashes: number = 3) {
15
- // Decode base64 to Uint8Array (works in both Node.js and Workers)
16
- const binaryString = atob(base64Data);
17
- const bytes = new Uint8Array(binaryString.length);
18
- for (let i = 0; i < binaryString.length; i++) {
19
- bytes[i] = binaryString.charCodeAt(i);
20
- }
21
- this.bits = bytes;
22
- this.size = this.bits.length * 8;
23
- this.numHashes = numHashes;
24
- }
25
-
26
- /**
27
- * Test if a peer ID might be in the filter
28
- * Returns true if possibly in set, false if definitely not in set
29
- */
30
- test(peerId: string): boolean {
31
- for (let i = 0; i < this.numHashes; i++) {
32
- const hash = this.hash(peerId, i);
33
- const index = hash % this.size;
34
- const byteIndex = Math.floor(index / 8);
35
- const bitIndex = index % 8;
36
-
37
- if (!(this.bits[byteIndex] & (1 << bitIndex))) {
38
- return false;
39
- }
40
- }
41
- return true;
42
- }
43
-
44
- /**
45
- * Simple hash function (FNV-1a variant)
46
- */
47
- private hash(str: string, seed: number): number {
48
- let hash = 2166136261 ^ seed;
49
- for (let i = 0; i < str.length; i++) {
50
- hash ^= str.charCodeAt(i);
51
- hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
52
- }
53
- return hash >>> 0;
54
- }
55
- }
56
-
57
- /**
58
- * Helper to parse bloom filter from base64 string
59
- */
60
- export function parseBloomFilter(base64: string): BloomFilter | null {
61
- try {
62
- return new BloomFilter(base64);
63
- } catch {
64
- return null;
65
- }
66
- }