@xtr-dev/rondevu-server 0.2.4 → 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/README.md +77 -68
- package/dist/index.js +218 -104
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/src/app.ts +266 -138
- package/src/config.ts +1 -3
- package/src/crypto.ts +54 -0
- package/src/index.ts +0 -1
- package/src/storage/d1.ts +54 -7
- package/src/storage/hash-id.ts +4 -9
- package/src/storage/sqlite.ts +66 -15
- package/src/storage/types.ts +43 -10
- package/src/worker.ts +1 -3
- package/src/bloom.ts +0 -66
package/dist/index.js
CHANGED
|
@@ -757,12 +757,35 @@ function createApp(storage, config) {
|
|
|
757
757
|
return c.json({ error: "Internal server error" }, 500);
|
|
758
758
|
}
|
|
759
759
|
});
|
|
760
|
-
app.
|
|
760
|
+
app.get("/users/:username", async (c) => {
|
|
761
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");
|
|
762
785
|
const body = await c.req.json();
|
|
763
|
-
const {
|
|
764
|
-
if (!
|
|
765
|
-
return c.json({ error: "Missing required parameters:
|
|
786
|
+
const { publicKey, signature, message } = body;
|
|
787
|
+
if (!publicKey || !signature || !message) {
|
|
788
|
+
return c.json({ error: "Missing required parameters: publicKey, signature, message" }, 400);
|
|
766
789
|
}
|
|
767
790
|
const validation = await validateUsernameClaim(username, publicKey, signature, message);
|
|
768
791
|
if (!validation.valid) {
|
|
@@ -779,7 +802,7 @@ function createApp(storage, config) {
|
|
|
779
802
|
username: claimed.username,
|
|
780
803
|
claimedAt: claimed.claimedAt,
|
|
781
804
|
expiresAt: claimed.expiresAt
|
|
782
|
-
},
|
|
805
|
+
}, 201);
|
|
783
806
|
} catch (err2) {
|
|
784
807
|
if (err2.message?.includes("already claimed")) {
|
|
785
808
|
return c.json({ error: "Username already claimed by different public key" }, 409);
|
|
@@ -791,47 +814,70 @@ function createApp(storage, config) {
|
|
|
791
814
|
return c.json({ error: "Internal server error" }, 500);
|
|
792
815
|
}
|
|
793
816
|
});
|
|
794
|
-
app.get("/
|
|
817
|
+
app.get("/users/:username/services", async (c) => {
|
|
795
818
|
try {
|
|
796
819
|
const username = c.req.param("username");
|
|
797
|
-
const
|
|
798
|
-
if (!claimed) {
|
|
799
|
-
return c.json({
|
|
800
|
-
username,
|
|
801
|
-
available: true
|
|
802
|
-
}, 200);
|
|
803
|
-
}
|
|
820
|
+
const services = await storage.listServicesForUsername(username);
|
|
804
821
|
return c.json({
|
|
805
|
-
username
|
|
806
|
-
|
|
807
|
-
claimedAt: claimed.claimedAt,
|
|
808
|
-
expiresAt: claimed.expiresAt,
|
|
809
|
-
publicKey: claimed.publicKey
|
|
822
|
+
username,
|
|
823
|
+
services
|
|
810
824
|
}, 200);
|
|
811
825
|
} catch (err2) {
|
|
812
|
-
console.error("Error
|
|
826
|
+
console.error("Error listing services:", err2);
|
|
813
827
|
return c.json({ error: "Internal server error" }, 500);
|
|
814
828
|
}
|
|
815
829
|
});
|
|
816
|
-
app.get("/
|
|
830
|
+
app.get("/users/:username/services/:fqn", async (c) => {
|
|
817
831
|
try {
|
|
818
832
|
const username = c.req.param("username");
|
|
819
|
-
const
|
|
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
|
+
}
|
|
820
854
|
return c.json({
|
|
821
|
-
|
|
822
|
-
|
|
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
|
|
823
865
|
}, 200);
|
|
824
866
|
} catch (err2) {
|
|
825
|
-
console.error("Error
|
|
867
|
+
console.error("Error getting service:", err2);
|
|
826
868
|
return c.json({ error: "Internal server error" }, 500);
|
|
827
869
|
}
|
|
828
870
|
});
|
|
829
|
-
app.post("/services", authMiddleware, async (c) => {
|
|
871
|
+
app.post("/users/:username/services", authMiddleware, async (c) => {
|
|
872
|
+
let serviceFqn;
|
|
873
|
+
let offers = [];
|
|
830
874
|
try {
|
|
875
|
+
const username = c.req.param("username");
|
|
831
876
|
const body = await c.req.json();
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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);
|
|
835
881
|
}
|
|
836
882
|
const fqnValidation = validateServiceFqn(serviceFqn);
|
|
837
883
|
if (!fqnValidation.valid) {
|
|
@@ -848,6 +894,13 @@ function createApp(storage, config) {
|
|
|
848
894
|
if (!signatureValidation.valid) {
|
|
849
895
|
return c.json({ error: "Invalid signature for username" }, 403);
|
|
850
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
|
+
}
|
|
851
904
|
if (typeof sdp !== "string" || sdp.length === 0) {
|
|
852
905
|
return c.json({ error: "Invalid SDP" }, 400);
|
|
853
906
|
}
|
|
@@ -860,7 +913,7 @@ function createApp(storage, config) {
|
|
|
860
913
|
config.offerMaxTtl
|
|
861
914
|
);
|
|
862
915
|
const expiresAt = Date.now() + offerTtl;
|
|
863
|
-
|
|
916
|
+
offers = await storage.createOffers([{
|
|
864
917
|
peerId,
|
|
865
918
|
sdp,
|
|
866
919
|
expiresAt
|
|
@@ -878,52 +931,45 @@ function createApp(storage, config) {
|
|
|
878
931
|
metadata: metadata ? JSON.stringify(metadata) : void 0
|
|
879
932
|
});
|
|
880
933
|
return c.json({
|
|
881
|
-
serviceId: result.service.id,
|
|
882
934
|
uuid: result.indexUuid,
|
|
935
|
+
serviceFqn,
|
|
936
|
+
username,
|
|
937
|
+
serviceId: result.service.id,
|
|
883
938
|
offerId: offer.id,
|
|
939
|
+
sdp: offer.sdp,
|
|
940
|
+
isPublic: result.service.isPublic,
|
|
941
|
+
metadata,
|
|
942
|
+
createdAt: result.service.createdAt,
|
|
884
943
|
expiresAt: result.service.expiresAt
|
|
885
944
|
}, 201);
|
|
886
945
|
} catch (err2) {
|
|
887
946
|
console.error("Error creating service:", err2);
|
|
888
|
-
|
|
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);
|
|
889
958
|
}
|
|
890
959
|
});
|
|
891
|
-
app.
|
|
960
|
+
app.delete("/users/:username/services/:fqn", authMiddleware, async (c) => {
|
|
892
961
|
try {
|
|
893
|
-
const
|
|
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
|
+
}
|
|
894
968
|
const service = await storage.getServiceByUuid(uuid);
|
|
895
969
|
if (!service) {
|
|
896
970
|
return c.json({ error: "Service not found" }, 404);
|
|
897
971
|
}
|
|
898
|
-
const
|
|
899
|
-
if (!offer) {
|
|
900
|
-
return c.json({ error: "Associated offer not found" }, 404);
|
|
901
|
-
}
|
|
902
|
-
return c.json({
|
|
903
|
-
serviceId: service.id,
|
|
904
|
-
username: service.username,
|
|
905
|
-
serviceFqn: service.serviceFqn,
|
|
906
|
-
offerId: service.offerId,
|
|
907
|
-
sdp: offer.sdp,
|
|
908
|
-
isPublic: service.isPublic,
|
|
909
|
-
metadata: service.metadata ? JSON.parse(service.metadata) : void 0,
|
|
910
|
-
createdAt: service.createdAt,
|
|
911
|
-
expiresAt: service.expiresAt
|
|
912
|
-
}, 200);
|
|
913
|
-
} catch (err2) {
|
|
914
|
-
console.error("Error getting service:", err2);
|
|
915
|
-
return c.json({ error: "Internal server error" }, 500);
|
|
916
|
-
}
|
|
917
|
-
});
|
|
918
|
-
app.delete("/services/:serviceId", authMiddleware, async (c) => {
|
|
919
|
-
try {
|
|
920
|
-
const serviceId = c.req.param("serviceId");
|
|
921
|
-
const body = await c.req.json();
|
|
922
|
-
const { username } = body;
|
|
923
|
-
if (!username) {
|
|
924
|
-
return c.json({ error: "Missing required parameter: username" }, 400);
|
|
925
|
-
}
|
|
926
|
-
const deleted = await storage.deleteService(serviceId, username);
|
|
972
|
+
const deleted = await storage.deleteService(service.id, username);
|
|
927
973
|
if (!deleted) {
|
|
928
974
|
return c.json({ error: "Service not found or not owned by this username" }, 404);
|
|
929
975
|
}
|
|
@@ -933,24 +979,39 @@ function createApp(storage, config) {
|
|
|
933
979
|
return c.json({ error: "Internal server error" }, 500);
|
|
934
980
|
}
|
|
935
981
|
});
|
|
936
|
-
app.
|
|
982
|
+
app.get("/services/:uuid", async (c) => {
|
|
937
983
|
try {
|
|
938
|
-
const
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
if (!serviceFqn) {
|
|
942
|
-
return c.json({ error: "Missing required parameter: serviceFqn" }, 400);
|
|
943
|
-
}
|
|
944
|
-
const uuid = await storage.queryService(username, serviceFqn);
|
|
945
|
-
if (!uuid) {
|
|
984
|
+
const uuid = c.req.param("uuid");
|
|
985
|
+
const service = await storage.getServiceByUuid(uuid);
|
|
986
|
+
if (!service) {
|
|
946
987
|
return c.json({ error: "Service not found" }, 404);
|
|
947
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
|
+
}
|
|
948
1001
|
return c.json({
|
|
949
1002
|
uuid,
|
|
950
|
-
|
|
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
|
|
951
1012
|
}, 200);
|
|
952
1013
|
} catch (err2) {
|
|
953
|
-
console.error("Error
|
|
1014
|
+
console.error("Error getting service:", err2);
|
|
954
1015
|
return c.json({ error: "Internal server error" }, 500);
|
|
955
1016
|
}
|
|
956
1017
|
});
|
|
@@ -1020,6 +1081,28 @@ function createApp(storage, config) {
|
|
|
1020
1081
|
return c.json({ error: "Internal server error" }, 500);
|
|
1021
1082
|
}
|
|
1022
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
|
+
});
|
|
1023
1106
|
app.delete("/offers/:offerId", authMiddleware, async (c) => {
|
|
1024
1107
|
try {
|
|
1025
1108
|
const offerId = c.req.param("offerId");
|
|
@@ -1059,20 +1142,28 @@ function createApp(storage, config) {
|
|
|
1059
1142
|
return c.json({ error: "Internal server error" }, 500);
|
|
1060
1143
|
}
|
|
1061
1144
|
});
|
|
1062
|
-
app.get("/offers/
|
|
1145
|
+
app.get("/offers/:offerId/answer", authMiddleware, async (c) => {
|
|
1063
1146
|
try {
|
|
1147
|
+
const offerId = c.req.param("offerId");
|
|
1064
1148
|
const peerId = getAuthenticatedPeerId(c);
|
|
1065
|
-
const
|
|
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
|
+
}
|
|
1066
1159
|
return c.json({
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
answeredAt: offer.answeredAt
|
|
1072
|
-
}))
|
|
1160
|
+
offerId: offer.id,
|
|
1161
|
+
answererId: offer.answererPeerId,
|
|
1162
|
+
sdp: offer.answerSdp,
|
|
1163
|
+
answeredAt: offer.answeredAt
|
|
1073
1164
|
}, 200);
|
|
1074
1165
|
} catch (err2) {
|
|
1075
|
-
console.error("Error getting
|
|
1166
|
+
console.error("Error getting answer:", err2);
|
|
1076
1167
|
return c.json({ error: "Internal server error" }, 500);
|
|
1077
1168
|
}
|
|
1078
1169
|
});
|
|
@@ -1143,8 +1234,7 @@ function loadConfig() {
|
|
|
1143
1234
|
offerMaxTtl: parseInt(process.env.OFFER_MAX_TTL || "86400000", 10),
|
|
1144
1235
|
offerMinTtl: parseInt(process.env.OFFER_MIN_TTL || "60000", 10),
|
|
1145
1236
|
cleanupInterval: parseInt(process.env.CLEANUP_INTERVAL || "60000", 10),
|
|
1146
|
-
maxOffersPerRequest: parseInt(process.env.MAX_OFFERS_PER_REQUEST || "100", 10)
|
|
1147
|
-
maxTopicsPerOffer: parseInt(process.env.MAX_TOPICS_PER_OFFER || "50", 10)
|
|
1237
|
+
maxOffersPerRequest: parseInt(process.env.MAX_OFFERS_PER_REQUEST || "100", 10)
|
|
1148
1238
|
};
|
|
1149
1239
|
}
|
|
1150
1240
|
|
|
@@ -1153,11 +1243,9 @@ var import_better_sqlite3 = __toESM(require("better-sqlite3"));
|
|
|
1153
1243
|
var import_node_crypto = require("node:crypto");
|
|
1154
1244
|
|
|
1155
1245
|
// src/storage/hash-id.ts
|
|
1156
|
-
async function generateOfferHash(sdp
|
|
1246
|
+
async function generateOfferHash(sdp) {
|
|
1157
1247
|
const sanitizedOffer = {
|
|
1158
|
-
sdp
|
|
1159
|
-
topics: [...topics].sort()
|
|
1160
|
-
// Sort topics for consistency
|
|
1248
|
+
sdp
|
|
1161
1249
|
};
|
|
1162
1250
|
const jsonString = JSON.stringify(sanitizedOffer);
|
|
1163
1251
|
const encoder = new TextEncoder();
|
|
@@ -1184,10 +1272,11 @@ var SQLiteStorage = class {
|
|
|
1184
1272
|
*/
|
|
1185
1273
|
initializeDatabase() {
|
|
1186
1274
|
this.db.exec(`
|
|
1187
|
-
--
|
|
1275
|
+
-- WebRTC signaling offers
|
|
1188
1276
|
CREATE TABLE IF NOT EXISTS offers (
|
|
1189
1277
|
id TEXT PRIMARY KEY,
|
|
1190
1278
|
peer_id TEXT NOT NULL,
|
|
1279
|
+
service_id TEXT,
|
|
1191
1280
|
sdp TEXT NOT NULL,
|
|
1192
1281
|
created_at INTEGER NOT NULL,
|
|
1193
1282
|
expires_at INTEGER NOT NULL,
|
|
@@ -1195,10 +1284,12 @@ var SQLiteStorage = class {
|
|
|
1195
1284
|
secret TEXT,
|
|
1196
1285
|
answerer_peer_id TEXT,
|
|
1197
1286
|
answer_sdp TEXT,
|
|
1198
|
-
answered_at INTEGER
|
|
1287
|
+
answered_at INTEGER,
|
|
1288
|
+
FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE
|
|
1199
1289
|
);
|
|
1200
1290
|
|
|
1201
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);
|
|
1202
1293
|
CREATE INDEX IF NOT EXISTS idx_offers_expires ON offers(expires_at);
|
|
1203
1294
|
CREATE INDEX IF NOT EXISTS idx_offers_last_seen ON offers(last_seen);
|
|
1204
1295
|
CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(answerer_peer_id);
|
|
@@ -1232,25 +1323,22 @@ var SQLiteStorage = class {
|
|
|
1232
1323
|
CREATE INDEX IF NOT EXISTS idx_usernames_expires ON usernames(expires_at);
|
|
1233
1324
|
CREATE INDEX IF NOT EXISTS idx_usernames_public_key ON usernames(public_key);
|
|
1234
1325
|
|
|
1235
|
-
-- Services table
|
|
1326
|
+
-- Services table (one service can have multiple offers)
|
|
1236
1327
|
CREATE TABLE IF NOT EXISTS services (
|
|
1237
1328
|
id TEXT PRIMARY KEY,
|
|
1238
1329
|
username TEXT NOT NULL,
|
|
1239
1330
|
service_fqn TEXT NOT NULL,
|
|
1240
|
-
offer_id TEXT NOT NULL,
|
|
1241
1331
|
created_at INTEGER NOT NULL,
|
|
1242
1332
|
expires_at INTEGER NOT NULL,
|
|
1243
1333
|
is_public INTEGER NOT NULL DEFAULT 0,
|
|
1244
1334
|
metadata TEXT,
|
|
1245
1335
|
FOREIGN KEY (username) REFERENCES usernames(username) ON DELETE CASCADE,
|
|
1246
|
-
FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE,
|
|
1247
1336
|
UNIQUE(username, service_fqn)
|
|
1248
1337
|
);
|
|
1249
1338
|
|
|
1250
1339
|
CREATE INDEX IF NOT EXISTS idx_services_username ON services(username);
|
|
1251
1340
|
CREATE INDEX IF NOT EXISTS idx_services_fqn ON services(service_fqn);
|
|
1252
1341
|
CREATE INDEX IF NOT EXISTS idx_services_expires ON services(expires_at);
|
|
1253
|
-
CREATE INDEX IF NOT EXISTS idx_services_offer ON services(offer_id);
|
|
1254
1342
|
|
|
1255
1343
|
-- Service index table (privacy layer)
|
|
1256
1344
|
CREATE TABLE IF NOT EXISTS service_index (
|
|
@@ -1274,19 +1362,21 @@ var SQLiteStorage = class {
|
|
|
1274
1362
|
const offersWithIds = await Promise.all(
|
|
1275
1363
|
offers.map(async (offer) => ({
|
|
1276
1364
|
...offer,
|
|
1277
|
-
|
|
1365
|
+
serviceId: offer.serviceId || null,
|
|
1366
|
+
id: offer.id || await generateOfferHash(offer.sdp)
|
|
1278
1367
|
}))
|
|
1279
1368
|
);
|
|
1280
1369
|
const transaction = this.db.transaction((offersWithIds2) => {
|
|
1281
1370
|
const offerStmt = this.db.prepare(`
|
|
1282
|
-
INSERT INTO offers (id, peer_id, sdp, created_at, expires_at, last_seen, secret)
|
|
1283
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1371
|
+
INSERT INTO offers (id, peer_id, service_id, sdp, created_at, expires_at, last_seen, secret)
|
|
1372
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1284
1373
|
`);
|
|
1285
1374
|
for (const offer of offersWithIds2) {
|
|
1286
1375
|
const now = Date.now();
|
|
1287
1376
|
offerStmt.run(
|
|
1288
1377
|
offer.id,
|
|
1289
1378
|
offer.peerId,
|
|
1379
|
+
offer.serviceId || null,
|
|
1290
1380
|
offer.sdp,
|
|
1291
1381
|
now,
|
|
1292
1382
|
offer.expiresAt,
|
|
@@ -1296,6 +1386,7 @@ var SQLiteStorage = class {
|
|
|
1296
1386
|
created.push({
|
|
1297
1387
|
id: offer.id,
|
|
1298
1388
|
peerId: offer.peerId,
|
|
1389
|
+
serviceId: offer.serviceId,
|
|
1299
1390
|
sdp: offer.sdp,
|
|
1300
1391
|
createdAt: now,
|
|
1301
1392
|
expiresAt: offer.expiresAt,
|
|
@@ -1498,16 +1589,20 @@ var SQLiteStorage = class {
|
|
|
1498
1589
|
const serviceId = (0, import_node_crypto.randomUUID)();
|
|
1499
1590
|
const indexUuid = (0, import_node_crypto.randomUUID)();
|
|
1500
1591
|
const now = Date.now();
|
|
1592
|
+
const offerRequests = request.offers.map((offer) => ({
|
|
1593
|
+
...offer,
|
|
1594
|
+
serviceId
|
|
1595
|
+
}));
|
|
1596
|
+
const offers = await this.createOffers(offerRequests);
|
|
1501
1597
|
const transaction = this.db.transaction(() => {
|
|
1502
1598
|
const serviceStmt = this.db.prepare(`
|
|
1503
|
-
INSERT INTO services (id, username, service_fqn,
|
|
1504
|
-
VALUES (?, ?, ?, ?, ?, ?,
|
|
1599
|
+
INSERT INTO services (id, username, service_fqn, created_at, expires_at, is_public, metadata)
|
|
1600
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1505
1601
|
`);
|
|
1506
1602
|
serviceStmt.run(
|
|
1507
1603
|
serviceId,
|
|
1508
1604
|
request.username,
|
|
1509
1605
|
request.serviceFqn,
|
|
1510
|
-
request.offerId,
|
|
1511
1606
|
now,
|
|
1512
1607
|
request.expiresAt,
|
|
1513
1608
|
request.isPublic ? 1 : 0,
|
|
@@ -1533,15 +1628,23 @@ var SQLiteStorage = class {
|
|
|
1533
1628
|
id: serviceId,
|
|
1534
1629
|
username: request.username,
|
|
1535
1630
|
serviceFqn: request.serviceFqn,
|
|
1536
|
-
offerId: request.offerId,
|
|
1537
1631
|
createdAt: now,
|
|
1538
1632
|
expiresAt: request.expiresAt,
|
|
1539
1633
|
isPublic: request.isPublic || false,
|
|
1540
1634
|
metadata: request.metadata
|
|
1541
1635
|
},
|
|
1542
|
-
indexUuid
|
|
1636
|
+
indexUuid,
|
|
1637
|
+
offers
|
|
1543
1638
|
};
|
|
1544
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
|
+
}
|
|
1545
1648
|
async getServiceById(serviceId) {
|
|
1546
1649
|
const stmt = this.db.prepare(`
|
|
1547
1650
|
SELECT * FROM services
|
|
@@ -1614,6 +1717,7 @@ var SQLiteStorage = class {
|
|
|
1614
1717
|
return {
|
|
1615
1718
|
id: row.id,
|
|
1616
1719
|
peerId: row.peer_id,
|
|
1720
|
+
serviceId: row.service_id || void 0,
|
|
1617
1721
|
sdp: row.sdp,
|
|
1618
1722
|
createdAt: row.created_at,
|
|
1619
1723
|
expiresAt: row.expires_at,
|
|
@@ -1632,13 +1736,24 @@ var SQLiteStorage = class {
|
|
|
1632
1736
|
id: row.id,
|
|
1633
1737
|
username: row.username,
|
|
1634
1738
|
serviceFqn: row.service_fqn,
|
|
1635
|
-
offerId: row.offer_id,
|
|
1636
1739
|
createdAt: row.created_at,
|
|
1637
1740
|
expiresAt: row.expires_at,
|
|
1638
1741
|
isPublic: row.is_public === 1,
|
|
1639
1742
|
metadata: row.metadata || void 0
|
|
1640
1743
|
};
|
|
1641
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
|
+
}
|
|
1642
1757
|
};
|
|
1643
1758
|
|
|
1644
1759
|
// src/index.ts
|
|
@@ -1654,7 +1769,6 @@ async function main() {
|
|
|
1654
1769
|
offerMinTtl: `${config.offerMinTtl}ms`,
|
|
1655
1770
|
cleanupInterval: `${config.cleanupInterval}ms`,
|
|
1656
1771
|
maxOffersPerRequest: config.maxOffersPerRequest,
|
|
1657
|
-
maxTopicsPerOffer: config.maxTopicsPerOffer,
|
|
1658
1772
|
corsOrigins: config.corsOrigins,
|
|
1659
1773
|
version: config.version
|
|
1660
1774
|
});
|