@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/dist/index.js CHANGED
@@ -654,6 +654,29 @@ async function validateUsernameClaim(username, publicKey, signature, message) {
654
654
  }
655
655
  return { valid: true };
656
656
  }
657
+ async function validateServicePublish(username, serviceFqn, publicKey, signature, message) {
658
+ const usernameCheck = validateUsername(username);
659
+ if (!usernameCheck.valid) {
660
+ return usernameCheck;
661
+ }
662
+ const parts = message.split(":");
663
+ if (parts.length !== 4 || parts[0] !== "publish" || parts[1] !== username || parts[2] !== serviceFqn) {
664
+ return { valid: false, error: "Invalid message format (expected: publish:{username}:{serviceFqn}:{timestamp})" };
665
+ }
666
+ const timestamp = parseInt(parts[3], 10);
667
+ if (isNaN(timestamp)) {
668
+ return { valid: false, error: "Invalid timestamp in message" };
669
+ }
670
+ const timestampCheck = validateTimestamp(timestamp);
671
+ if (!timestampCheck.valid) {
672
+ return timestampCheck;
673
+ }
674
+ const signatureValid = await verifyEd25519Signature(publicKey, signature, message);
675
+ if (!signatureValid) {
676
+ return { valid: false, error: "Invalid signature" };
677
+ }
678
+ return { valid: true };
679
+ }
657
680
 
658
681
  // src/middleware/auth.ts
659
682
  function createAuthMiddleware(authSecret) {
@@ -734,12 +757,35 @@ function createApp(storage, config) {
734
757
  return c.json({ error: "Internal server error" }, 500);
735
758
  }
736
759
  });
737
- app.post("/usernames/claim", async (c) => {
760
+ app.get("/users/:username", async (c) => {
738
761
  try {
762
+ const username = c.req.param("username");
763
+ const claimed = await storage.getUsername(username);
764
+ if (!claimed) {
765
+ return c.json({
766
+ username,
767
+ available: true
768
+ }, 200);
769
+ }
770
+ return c.json({
771
+ username: claimed.username,
772
+ available: false,
773
+ claimedAt: claimed.claimedAt,
774
+ expiresAt: claimed.expiresAt,
775
+ publicKey: claimed.publicKey
776
+ }, 200);
777
+ } catch (err2) {
778
+ console.error("Error checking username:", err2);
779
+ return c.json({ error: "Internal server error" }, 500);
780
+ }
781
+ });
782
+ app.post("/users/:username", async (c) => {
783
+ try {
784
+ const username = c.req.param("username");
739
785
  const body = await c.req.json();
740
- const { username, publicKey, signature, message } = body;
741
- if (!username || !publicKey || !signature || !message) {
742
- return c.json({ error: "Missing required parameters: username, publicKey, signature, message" }, 400);
786
+ const { publicKey, signature, message } = body;
787
+ if (!publicKey || !signature || !message) {
788
+ return c.json({ error: "Missing required parameters: publicKey, signature, message" }, 400);
743
789
  }
744
790
  const validation = await validateUsernameClaim(username, publicKey, signature, message);
745
791
  if (!validation.valid) {
@@ -756,7 +802,7 @@ function createApp(storage, config) {
756
802
  username: claimed.username,
757
803
  claimedAt: claimed.claimedAt,
758
804
  expiresAt: claimed.expiresAt
759
- }, 200);
805
+ }, 201);
760
806
  } catch (err2) {
761
807
  if (err2.message?.includes("already claimed")) {
762
808
  return c.json({ error: "Username already claimed by different public key" }, 409);
@@ -768,47 +814,70 @@ function createApp(storage, config) {
768
814
  return c.json({ error: "Internal server error" }, 500);
769
815
  }
770
816
  });
771
- app.get("/usernames/:username", async (c) => {
817
+ app.get("/users/:username/services", async (c) => {
772
818
  try {
773
819
  const username = c.req.param("username");
774
- const claimed = await storage.getUsername(username);
775
- if (!claimed) {
776
- return c.json({
777
- username,
778
- available: true
779
- }, 200);
780
- }
820
+ const services = await storage.listServicesForUsername(username);
781
821
  return c.json({
782
- username: claimed.username,
783
- available: false,
784
- claimedAt: claimed.claimedAt,
785
- expiresAt: claimed.expiresAt,
786
- publicKey: claimed.publicKey
822
+ username,
823
+ services
787
824
  }, 200);
788
825
  } catch (err2) {
789
- console.error("Error checking username:", err2);
826
+ console.error("Error listing services:", err2);
790
827
  return c.json({ error: "Internal server error" }, 500);
791
828
  }
792
829
  });
793
- app.get("/usernames/:username/services", async (c) => {
830
+ app.get("/users/:username/services/:fqn", async (c) => {
794
831
  try {
795
832
  const username = c.req.param("username");
796
- const services = await storage.listServicesForUsername(username);
833
+ const serviceFqn = decodeURIComponent(c.req.param("fqn"));
834
+ const uuid = await storage.queryService(username, serviceFqn);
835
+ if (!uuid) {
836
+ return c.json({ error: "Service not found" }, 404);
837
+ }
838
+ const service = await storage.getServiceByUuid(uuid);
839
+ if (!service) {
840
+ return c.json({ error: "Service not found" }, 404);
841
+ }
842
+ const initialOffer = await storage.getOfferById(service.offerId);
843
+ if (!initialOffer) {
844
+ return c.json({ error: "Associated offer not found" }, 404);
845
+ }
846
+ const peerOffers = await storage.getOffersByPeerId(initialOffer.peerId);
847
+ const availableOffer = peerOffers.find((offer) => !offer.answererPeerId);
848
+ if (!availableOffer) {
849
+ return c.json({
850
+ error: "No available offers",
851
+ message: "All offers from this service are currently in use. Please try again later."
852
+ }, 503);
853
+ }
797
854
  return c.json({
798
- username,
799
- services
855
+ uuid,
856
+ serviceId: service.id,
857
+ username: service.username,
858
+ serviceFqn: service.serviceFqn,
859
+ offerId: availableOffer.id,
860
+ sdp: availableOffer.sdp,
861
+ isPublic: service.isPublic,
862
+ metadata: service.metadata ? JSON.parse(service.metadata) : void 0,
863
+ createdAt: service.createdAt,
864
+ expiresAt: service.expiresAt
800
865
  }, 200);
801
866
  } catch (err2) {
802
- console.error("Error listing services:", err2);
867
+ console.error("Error getting service:", err2);
803
868
  return c.json({ error: "Internal server error" }, 500);
804
869
  }
805
870
  });
806
- app.post("/services", authMiddleware, async (c) => {
871
+ app.post("/users/:username/services", authMiddleware, async (c) => {
872
+ let serviceFqn;
873
+ let offers = [];
807
874
  try {
875
+ const username = c.req.param("username");
808
876
  const body = await c.req.json();
809
- const { username, serviceFqn, sdp, ttl, isPublic, metadata, signature, message } = body;
810
- if (!username || !serviceFqn || !sdp) {
811
- return c.json({ error: "Missing required parameters: username, serviceFqn, sdp" }, 400);
877
+ serviceFqn = body.serviceFqn;
878
+ const { sdp, ttl, isPublic, metadata, signature, message } = body;
879
+ if (!serviceFqn || !sdp) {
880
+ return c.json({ error: "Missing required parameters: serviceFqn, sdp" }, 400);
812
881
  }
813
882
  const fqnValidation = validateServiceFqn(serviceFqn);
814
883
  if (!fqnValidation.valid) {
@@ -821,10 +890,17 @@ function createApp(storage, config) {
821
890
  if (!usernameRecord) {
822
891
  return c.json({ error: "Username not claimed" }, 404);
823
892
  }
824
- const signatureValidation = await validateUsernameClaim(username, usernameRecord.publicKey, signature, message);
893
+ const signatureValidation = await validateServicePublish(username, serviceFqn, usernameRecord.publicKey, signature, message);
825
894
  if (!signatureValidation.valid) {
826
895
  return c.json({ error: "Invalid signature for username" }, 403);
827
896
  }
897
+ const existingUuid = await storage.queryService(username, serviceFqn);
898
+ if (existingUuid) {
899
+ const existingService = await storage.getServiceByUuid(existingUuid);
900
+ if (existingService) {
901
+ await storage.deleteService(existingService.id, username);
902
+ }
903
+ }
828
904
  if (typeof sdp !== "string" || sdp.length === 0) {
829
905
  return c.json({ error: "Invalid SDP" }, 400);
830
906
  }
@@ -837,7 +913,7 @@ function createApp(storage, config) {
837
913
  config.offerMaxTtl
838
914
  );
839
915
  const expiresAt = Date.now() + offerTtl;
840
- const offers = await storage.createOffers([{
916
+ offers = await storage.createOffers([{
841
917
  peerId,
842
918
  sdp,
843
919
  expiresAt
@@ -855,52 +931,45 @@ function createApp(storage, config) {
855
931
  metadata: metadata ? JSON.stringify(metadata) : void 0
856
932
  });
857
933
  return c.json({
858
- serviceId: result.service.id,
859
934
  uuid: result.indexUuid,
935
+ serviceFqn,
936
+ username,
937
+ serviceId: result.service.id,
860
938
  offerId: offer.id,
939
+ sdp: offer.sdp,
940
+ isPublic: result.service.isPublic,
941
+ metadata,
942
+ createdAt: result.service.createdAt,
861
943
  expiresAt: result.service.expiresAt
862
944
  }, 201);
863
945
  } catch (err2) {
864
946
  console.error("Error creating service:", err2);
865
- return c.json({ error: "Internal server error" }, 500);
947
+ console.error("Error details:", {
948
+ message: err2.message,
949
+ stack: err2.stack,
950
+ username: c.req.param("username"),
951
+ serviceFqn,
952
+ offerId: offers[0]?.id
953
+ });
954
+ return c.json({
955
+ error: "Internal server error",
956
+ details: err2.message
957
+ }, 500);
866
958
  }
867
959
  });
868
- app.get("/services/:uuid", async (c) => {
960
+ app.delete("/users/:username/services/:fqn", authMiddleware, async (c) => {
869
961
  try {
870
- const uuid = c.req.param("uuid");
962
+ const username = c.req.param("username");
963
+ const serviceFqn = decodeURIComponent(c.req.param("fqn"));
964
+ const uuid = await storage.queryService(username, serviceFqn);
965
+ if (!uuid) {
966
+ return c.json({ error: "Service not found" }, 404);
967
+ }
871
968
  const service = await storage.getServiceByUuid(uuid);
872
969
  if (!service) {
873
970
  return c.json({ error: "Service not found" }, 404);
874
971
  }
875
- const offer = await storage.getOfferById(service.offerId);
876
- if (!offer) {
877
- return c.json({ error: "Associated offer not found" }, 404);
878
- }
879
- return c.json({
880
- serviceId: service.id,
881
- username: service.username,
882
- serviceFqn: service.serviceFqn,
883
- offerId: service.offerId,
884
- sdp: offer.sdp,
885
- isPublic: service.isPublic,
886
- metadata: service.metadata ? JSON.parse(service.metadata) : void 0,
887
- createdAt: service.createdAt,
888
- expiresAt: service.expiresAt
889
- }, 200);
890
- } catch (err2) {
891
- console.error("Error getting service:", err2);
892
- return c.json({ error: "Internal server error" }, 500);
893
- }
894
- });
895
- app.delete("/services/:serviceId", authMiddleware, async (c) => {
896
- try {
897
- const serviceId = c.req.param("serviceId");
898
- const body = await c.req.json();
899
- const { username } = body;
900
- if (!username) {
901
- return c.json({ error: "Missing required parameter: username" }, 400);
902
- }
903
- const deleted = await storage.deleteService(serviceId, username);
972
+ const deleted = await storage.deleteService(service.id, username);
904
973
  if (!deleted) {
905
974
  return c.json({ error: "Service not found or not owned by this username" }, 404);
906
975
  }
@@ -910,24 +979,39 @@ function createApp(storage, config) {
910
979
  return c.json({ error: "Internal server error" }, 500);
911
980
  }
912
981
  });
913
- app.post("/index/:username/query", async (c) => {
982
+ app.get("/services/:uuid", async (c) => {
914
983
  try {
915
- const username = c.req.param("username");
916
- const body = await c.req.json();
917
- const { serviceFqn } = body;
918
- if (!serviceFqn) {
919
- return c.json({ error: "Missing required parameter: serviceFqn" }, 400);
920
- }
921
- const uuid = await storage.queryService(username, serviceFqn);
922
- if (!uuid) {
984
+ const uuid = c.req.param("uuid");
985
+ const service = await storage.getServiceByUuid(uuid);
986
+ if (!service) {
923
987
  return c.json({ error: "Service not found" }, 404);
924
988
  }
989
+ const initialOffer = await storage.getOfferById(service.offerId);
990
+ if (!initialOffer) {
991
+ return c.json({ error: "Associated offer not found" }, 404);
992
+ }
993
+ const peerOffers = await storage.getOffersByPeerId(initialOffer.peerId);
994
+ const availableOffer = peerOffers.find((offer) => !offer.answererPeerId);
995
+ if (!availableOffer) {
996
+ return c.json({
997
+ error: "No available offers",
998
+ message: "All offers from this service are currently in use. Please try again later."
999
+ }, 503);
1000
+ }
925
1001
  return c.json({
926
1002
  uuid,
927
- allowed: true
1003
+ serviceId: service.id,
1004
+ username: service.username,
1005
+ serviceFqn: service.serviceFqn,
1006
+ offerId: availableOffer.id,
1007
+ sdp: availableOffer.sdp,
1008
+ isPublic: service.isPublic,
1009
+ metadata: service.metadata ? JSON.parse(service.metadata) : void 0,
1010
+ createdAt: service.createdAt,
1011
+ expiresAt: service.expiresAt
928
1012
  }, 200);
929
1013
  } catch (err2) {
930
- console.error("Error querying service:", err2);
1014
+ console.error("Error getting service:", err2);
931
1015
  return c.json({ error: "Internal server error" }, 500);
932
1016
  }
933
1017
  });
@@ -997,6 +1081,28 @@ function createApp(storage, config) {
997
1081
  return c.json({ error: "Internal server error" }, 500);
998
1082
  }
999
1083
  });
1084
+ app.get("/offers/:offerId", authMiddleware, async (c) => {
1085
+ try {
1086
+ const offerId = c.req.param("offerId");
1087
+ const offer = await storage.getOfferById(offerId);
1088
+ if (!offer) {
1089
+ return c.json({ error: "Offer not found" }, 404);
1090
+ }
1091
+ return c.json({
1092
+ id: offer.id,
1093
+ peerId: offer.peerId,
1094
+ sdp: offer.sdp,
1095
+ createdAt: offer.createdAt,
1096
+ expiresAt: offer.expiresAt,
1097
+ answererPeerId: offer.answererPeerId,
1098
+ answered: !!offer.answererPeerId,
1099
+ answerSdp: offer.answerSdp
1100
+ }, 200);
1101
+ } catch (err2) {
1102
+ console.error("Error getting offer:", err2);
1103
+ return c.json({ error: "Internal server error" }, 500);
1104
+ }
1105
+ });
1000
1106
  app.delete("/offers/:offerId", authMiddleware, async (c) => {
1001
1107
  try {
1002
1108
  const offerId = c.req.param("offerId");
@@ -1036,20 +1142,28 @@ function createApp(storage, config) {
1036
1142
  return c.json({ error: "Internal server error" }, 500);
1037
1143
  }
1038
1144
  });
1039
- app.get("/offers/answers", authMiddleware, async (c) => {
1145
+ app.get("/offers/:offerId/answer", authMiddleware, async (c) => {
1040
1146
  try {
1147
+ const offerId = c.req.param("offerId");
1041
1148
  const peerId = getAuthenticatedPeerId(c);
1042
- const offers = await storage.getAnsweredOffers(peerId);
1149
+ const offer = await storage.getOfferById(offerId);
1150
+ if (!offer) {
1151
+ return c.json({ error: "Offer not found" }, 404);
1152
+ }
1153
+ if (offer.peerId !== peerId) {
1154
+ return c.json({ error: "Not authorized to view this answer" }, 403);
1155
+ }
1156
+ if (!offer.answererPeerId || !offer.answerSdp) {
1157
+ return c.json({ error: "Offer not yet answered" }, 404);
1158
+ }
1043
1159
  return c.json({
1044
- answers: offers.map((offer) => ({
1045
- offerId: offer.id,
1046
- answererPeerId: offer.answererPeerId,
1047
- answerSdp: offer.answerSdp,
1048
- answeredAt: offer.answeredAt
1049
- }))
1160
+ offerId: offer.id,
1161
+ answererId: offer.answererPeerId,
1162
+ sdp: offer.answerSdp,
1163
+ answeredAt: offer.answeredAt
1050
1164
  }, 200);
1051
1165
  } catch (err2) {
1052
- console.error("Error getting answers:", err2);
1166
+ console.error("Error getting answer:", err2);
1053
1167
  return c.json({ error: "Internal server error" }, 500);
1054
1168
  }
1055
1169
  });
@@ -1120,8 +1234,7 @@ function loadConfig() {
1120
1234
  offerMaxTtl: parseInt(process.env.OFFER_MAX_TTL || "86400000", 10),
1121
1235
  offerMinTtl: parseInt(process.env.OFFER_MIN_TTL || "60000", 10),
1122
1236
  cleanupInterval: parseInt(process.env.CLEANUP_INTERVAL || "60000", 10),
1123
- maxOffersPerRequest: parseInt(process.env.MAX_OFFERS_PER_REQUEST || "100", 10),
1124
- maxTopicsPerOffer: parseInt(process.env.MAX_TOPICS_PER_OFFER || "50", 10)
1237
+ maxOffersPerRequest: parseInt(process.env.MAX_OFFERS_PER_REQUEST || "100", 10)
1125
1238
  };
1126
1239
  }
1127
1240
 
@@ -1130,11 +1243,9 @@ var import_better_sqlite3 = __toESM(require("better-sqlite3"));
1130
1243
  var import_node_crypto = require("node:crypto");
1131
1244
 
1132
1245
  // src/storage/hash-id.ts
1133
- async function generateOfferHash(sdp, topics) {
1246
+ async function generateOfferHash(sdp) {
1134
1247
  const sanitizedOffer = {
1135
- sdp,
1136
- topics: [...topics].sort()
1137
- // Sort topics for consistency
1248
+ sdp
1138
1249
  };
1139
1250
  const jsonString = JSON.stringify(sanitizedOffer);
1140
1251
  const encoder = new TextEncoder();
@@ -1161,10 +1272,11 @@ var SQLiteStorage = class {
1161
1272
  */
1162
1273
  initializeDatabase() {
1163
1274
  this.db.exec(`
1164
- -- Offers table (no topics)
1275
+ -- WebRTC signaling offers
1165
1276
  CREATE TABLE IF NOT EXISTS offers (
1166
1277
  id TEXT PRIMARY KEY,
1167
1278
  peer_id TEXT NOT NULL,
1279
+ service_id TEXT,
1168
1280
  sdp TEXT NOT NULL,
1169
1281
  created_at INTEGER NOT NULL,
1170
1282
  expires_at INTEGER NOT NULL,
@@ -1172,10 +1284,12 @@ var SQLiteStorage = class {
1172
1284
  secret TEXT,
1173
1285
  answerer_peer_id TEXT,
1174
1286
  answer_sdp TEXT,
1175
- answered_at INTEGER
1287
+ answered_at INTEGER,
1288
+ FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE
1176
1289
  );
1177
1290
 
1178
1291
  CREATE INDEX IF NOT EXISTS idx_offers_peer ON offers(peer_id);
1292
+ CREATE INDEX IF NOT EXISTS idx_offers_service ON offers(service_id);
1179
1293
  CREATE INDEX IF NOT EXISTS idx_offers_expires ON offers(expires_at);
1180
1294
  CREATE INDEX IF NOT EXISTS idx_offers_last_seen ON offers(last_seen);
1181
1295
  CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(answerer_peer_id);
@@ -1209,25 +1323,22 @@ var SQLiteStorage = class {
1209
1323
  CREATE INDEX IF NOT EXISTS idx_usernames_expires ON usernames(expires_at);
1210
1324
  CREATE INDEX IF NOT EXISTS idx_usernames_public_key ON usernames(public_key);
1211
1325
 
1212
- -- Services table
1326
+ -- Services table (one service can have multiple offers)
1213
1327
  CREATE TABLE IF NOT EXISTS services (
1214
1328
  id TEXT PRIMARY KEY,
1215
1329
  username TEXT NOT NULL,
1216
1330
  service_fqn TEXT NOT NULL,
1217
- offer_id TEXT NOT NULL,
1218
1331
  created_at INTEGER NOT NULL,
1219
1332
  expires_at INTEGER NOT NULL,
1220
1333
  is_public INTEGER NOT NULL DEFAULT 0,
1221
1334
  metadata TEXT,
1222
1335
  FOREIGN KEY (username) REFERENCES usernames(username) ON DELETE CASCADE,
1223
- FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE,
1224
1336
  UNIQUE(username, service_fqn)
1225
1337
  );
1226
1338
 
1227
1339
  CREATE INDEX IF NOT EXISTS idx_services_username ON services(username);
1228
1340
  CREATE INDEX IF NOT EXISTS idx_services_fqn ON services(service_fqn);
1229
1341
  CREATE INDEX IF NOT EXISTS idx_services_expires ON services(expires_at);
1230
- CREATE INDEX IF NOT EXISTS idx_services_offer ON services(offer_id);
1231
1342
 
1232
1343
  -- Service index table (privacy layer)
1233
1344
  CREATE TABLE IF NOT EXISTS service_index (
@@ -1251,19 +1362,21 @@ var SQLiteStorage = class {
1251
1362
  const offersWithIds = await Promise.all(
1252
1363
  offers.map(async (offer) => ({
1253
1364
  ...offer,
1254
- id: offer.id || await generateOfferHash(offer.sdp, [])
1365
+ serviceId: offer.serviceId || null,
1366
+ id: offer.id || await generateOfferHash(offer.sdp)
1255
1367
  }))
1256
1368
  );
1257
1369
  const transaction = this.db.transaction((offersWithIds2) => {
1258
1370
  const offerStmt = this.db.prepare(`
1259
- INSERT INTO offers (id, peer_id, sdp, created_at, expires_at, last_seen, secret)
1260
- VALUES (?, ?, ?, ?, ?, ?, ?)
1371
+ INSERT INTO offers (id, peer_id, service_id, sdp, created_at, expires_at, last_seen, secret)
1372
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1261
1373
  `);
1262
1374
  for (const offer of offersWithIds2) {
1263
1375
  const now = Date.now();
1264
1376
  offerStmt.run(
1265
1377
  offer.id,
1266
1378
  offer.peerId,
1379
+ offer.serviceId || null,
1267
1380
  offer.sdp,
1268
1381
  now,
1269
1382
  offer.expiresAt,
@@ -1273,6 +1386,7 @@ var SQLiteStorage = class {
1273
1386
  created.push({
1274
1387
  id: offer.id,
1275
1388
  peerId: offer.peerId,
1389
+ serviceId: offer.serviceId,
1276
1390
  sdp: offer.sdp,
1277
1391
  createdAt: now,
1278
1392
  expiresAt: offer.expiresAt,
@@ -1475,16 +1589,20 @@ var SQLiteStorage = class {
1475
1589
  const serviceId = (0, import_node_crypto.randomUUID)();
1476
1590
  const indexUuid = (0, import_node_crypto.randomUUID)();
1477
1591
  const now = Date.now();
1592
+ const offerRequests = request.offers.map((offer) => ({
1593
+ ...offer,
1594
+ serviceId
1595
+ }));
1596
+ const offers = await this.createOffers(offerRequests);
1478
1597
  const transaction = this.db.transaction(() => {
1479
1598
  const serviceStmt = this.db.prepare(`
1480
- INSERT INTO services (id, username, service_fqn, offer_id, created_at, expires_at, is_public, metadata)
1481
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1599
+ INSERT INTO services (id, username, service_fqn, created_at, expires_at, is_public, metadata)
1600
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1482
1601
  `);
1483
1602
  serviceStmt.run(
1484
1603
  serviceId,
1485
1604
  request.username,
1486
1605
  request.serviceFqn,
1487
- request.offerId,
1488
1606
  now,
1489
1607
  request.expiresAt,
1490
1608
  request.isPublic ? 1 : 0,
@@ -1510,15 +1628,23 @@ var SQLiteStorage = class {
1510
1628
  id: serviceId,
1511
1629
  username: request.username,
1512
1630
  serviceFqn: request.serviceFqn,
1513
- offerId: request.offerId,
1514
1631
  createdAt: now,
1515
1632
  expiresAt: request.expiresAt,
1516
1633
  isPublic: request.isPublic || false,
1517
1634
  metadata: request.metadata
1518
1635
  },
1519
- indexUuid
1636
+ indexUuid,
1637
+ offers
1520
1638
  };
1521
1639
  }
1640
+ async batchCreateServices(requests) {
1641
+ const results = [];
1642
+ for (const request of requests) {
1643
+ const result = await this.createService(request);
1644
+ results.push(result);
1645
+ }
1646
+ return results;
1647
+ }
1522
1648
  async getServiceById(serviceId) {
1523
1649
  const stmt = this.db.prepare(`
1524
1650
  SELECT * FROM services
@@ -1591,6 +1717,7 @@ var SQLiteStorage = class {
1591
1717
  return {
1592
1718
  id: row.id,
1593
1719
  peerId: row.peer_id,
1720
+ serviceId: row.service_id || void 0,
1594
1721
  sdp: row.sdp,
1595
1722
  createdAt: row.created_at,
1596
1723
  expiresAt: row.expires_at,
@@ -1609,13 +1736,24 @@ var SQLiteStorage = class {
1609
1736
  id: row.id,
1610
1737
  username: row.username,
1611
1738
  serviceFqn: row.service_fqn,
1612
- offerId: row.offer_id,
1613
1739
  createdAt: row.created_at,
1614
1740
  expiresAt: row.expires_at,
1615
1741
  isPublic: row.is_public === 1,
1616
1742
  metadata: row.metadata || void 0
1617
1743
  };
1618
1744
  }
1745
+ /**
1746
+ * Get all offers for a service
1747
+ */
1748
+ async getOffersForService(serviceId) {
1749
+ const stmt = this.db.prepare(`
1750
+ SELECT * FROM offers
1751
+ WHERE service_id = ? AND expires_at > ?
1752
+ ORDER BY created_at ASC
1753
+ `);
1754
+ const rows = stmt.all(serviceId, Date.now());
1755
+ return rows.map((row) => this.rowToOffer(row));
1756
+ }
1619
1757
  };
1620
1758
 
1621
1759
  // src/index.ts
@@ -1631,7 +1769,6 @@ async function main() {
1631
1769
  offerMinTtl: `${config.offerMinTtl}ms`,
1632
1770
  cleanupInterval: `${config.cleanupInterval}ms`,
1633
1771
  maxOffersPerRequest: config.maxOffersPerRequest,
1634
- maxTopicsPerOffer: config.maxTopicsPerOffer,
1635
1772
  corsOrigins: config.corsOrigins,
1636
1773
  version: config.version
1637
1774
  });