@xtr-dev/rondevu-server 0.5.15 → 0.5.16

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": "@xtr-dev/rondevu-server",
3
- "version": "0.5.15",
3
+ "version": "0.5.16",
4
4
  "description": "DNS-like WebRTC signaling server with credential-based authentication and service discovery",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
package/src/config.ts CHANGED
@@ -20,6 +20,7 @@ export interface Config {
20
20
  offerDefaultTtl: number;
21
21
  offerMaxTtl: number;
22
22
  offerMinTtl: number;
23
+ answeredOfferTtl: number; // TTL after offer is answered (for ICE exchange)
23
24
  cleanupInterval: number;
24
25
  maxOffersPerRequest: number;
25
26
  maxBatchSize: number;
@@ -66,6 +67,7 @@ export function loadConfig(): Config {
66
67
  offerDefaultTtl: parsePositiveInt(process.env.OFFER_DEFAULT_TTL, '60000', 'OFFER_DEFAULT_TTL', 1000),
67
68
  offerMaxTtl: parsePositiveInt(process.env.OFFER_MAX_TTL, '86400000', 'OFFER_MAX_TTL', 1000),
68
69
  offerMinTtl: parsePositiveInt(process.env.OFFER_MIN_TTL, '60000', 'OFFER_MIN_TTL', 1000),
70
+ answeredOfferTtl: parsePositiveInt(process.env.ANSWERED_OFFER_TTL, '30000', 'ANSWERED_OFFER_TTL', 1000),
69
71
  cleanupInterval: parsePositiveInt(process.env.CLEANUP_INTERVAL, '60000', 'CLEANUP_INTERVAL', 1000),
70
72
  maxOffersPerRequest: parsePositiveInt(process.env.MAX_OFFERS_PER_REQUEST, '100', 'MAX_OFFERS_PER_REQUEST', 1),
71
73
  maxBatchSize: parsePositiveInt(process.env.MAX_BATCH_SIZE, '100', 'MAX_BATCH_SIZE', 1),
@@ -93,6 +95,7 @@ export const CONFIG_DEFAULTS = {
93
95
  offerDefaultTtl: 60000,
94
96
  offerMaxTtl: 86400000,
95
97
  offerMinTtl: 60000,
98
+ answeredOfferTtl: 30000, // 30 seconds TTL after offer is answered
96
99
  cleanupInterval: 60000,
97
100
  maxOffersPerRequest: 100,
98
101
  maxBatchSize: 100,
@@ -133,6 +136,7 @@ export function buildWorkerConfig(env: {
133
136
  offerDefaultTtl: env.OFFER_DEFAULT_TTL ? parseInt(env.OFFER_DEFAULT_TTL, 10) : CONFIG_DEFAULTS.offerDefaultTtl,
134
137
  offerMaxTtl: env.OFFER_MAX_TTL ? parseInt(env.OFFER_MAX_TTL, 10) : CONFIG_DEFAULTS.offerMaxTtl,
135
138
  offerMinTtl: env.OFFER_MIN_TTL ? parseInt(env.OFFER_MIN_TTL, 10) : CONFIG_DEFAULTS.offerMinTtl,
139
+ answeredOfferTtl: CONFIG_DEFAULTS.answeredOfferTtl,
136
140
  cleanupInterval: CONFIG_DEFAULTS.cleanupInterval,
137
141
  maxOffersPerRequest: env.MAX_OFFERS_PER_REQUEST ? parseInt(env.MAX_OFFERS_PER_REQUEST, 10) : CONFIG_DEFAULTS.maxOffersPerRequest,
138
142
  maxBatchSize: env.MAX_BATCH_SIZE ? parseInt(env.MAX_BATCH_SIZE, 10) : CONFIG_DEFAULTS.maxBatchSize,
package/src/rpc.ts CHANGED
@@ -454,7 +454,9 @@ const handlers: Record<string, RpcHandler> = {
454
454
  }
455
455
  }
456
456
 
457
- await storage.answerOffer(offerId, publicKey, sdp, matchedTags);
457
+ // Reduce TTL after answer for faster cleanup (answered offers no longer appear in discovery)
458
+ const newExpiresAt = Date.now() + config.answeredOfferTtl;
459
+ await storage.answerOffer(offerId, publicKey, sdp, matchedTags, newExpiresAt);
458
460
 
459
461
  return { success: true, offerId };
460
462
  },
package/src/storage/d1.ts CHANGED
@@ -166,7 +166,8 @@ export class D1Storage implements Storage {
166
166
  offerId: string,
167
167
  answererPublicKey: string,
168
168
  answerSdp: string,
169
- matchedTags?: string[]
169
+ matchedTags?: string[],
170
+ newExpiresAt?: number
170
171
  ): Promise<{ success: boolean; error?: string }> {
171
172
  const offer = await this.getOfferById(offerId);
172
173
 
@@ -178,12 +179,19 @@ export class D1Storage implements Storage {
178
179
  return { success: false, error: 'Offer already answered' };
179
180
  }
180
181
 
182
+ const now = Date.now();
181
183
  const matchedTagsJson = matchedTags ? JSON.stringify(matchedTags) : null;
182
- const result = await this.db.prepare(`
183
- UPDATE offers
184
- SET answerer_public_key = ?, answer_sdp = ?, answered_at = ?, matched_tags = ?
185
- WHERE id = ? AND answerer_public_key IS NULL
186
- `).bind(answererPublicKey, answerSdp, Date.now(), matchedTagsJson, offerId).run();
184
+
185
+ // Optionally reduce TTL for faster cleanup after answer
186
+ const query = newExpiresAt
187
+ ? `UPDATE offers SET answerer_public_key = ?, answer_sdp = ?, answered_at = ?, matched_tags = ?, expires_at = ? WHERE id = ? AND answerer_public_key IS NULL`
188
+ : `UPDATE offers SET answerer_public_key = ?, answer_sdp = ?, answered_at = ?, matched_tags = ? WHERE id = ? AND answerer_public_key IS NULL`;
189
+
190
+ const params = newExpiresAt
191
+ ? [answererPublicKey, answerSdp, now, matchedTagsJson, newExpiresAt, offerId]
192
+ : [answererPublicKey, answerSdp, now, matchedTagsJson, offerId];
193
+
194
+ const result = await this.db.prepare(query).bind(...params).run();
187
195
 
188
196
  if ((result.meta.changes || 0) === 0) {
189
197
  return { success: false, error: 'Offer already answered (race condition)' };
@@ -135,7 +135,8 @@ export class MemoryStorage implements Storage {
135
135
  offerId: string,
136
136
  answererPublicKey: string,
137
137
  answerSdp: string,
138
- matchedTags?: string[]
138
+ matchedTags?: string[],
139
+ newExpiresAt?: number
139
140
  ): Promise<{ success: boolean; error?: string }> {
140
141
  const offer = await this.getOfferById(offerId);
141
142
 
@@ -154,6 +155,11 @@ export class MemoryStorage implements Storage {
154
155
  offer.answeredAt = now;
155
156
  offer.matchedTags = matchedTags;
156
157
 
158
+ // Optionally reduce TTL for faster cleanup after answer
159
+ if (newExpiresAt !== undefined) {
160
+ offer.expiresAt = newExpiresAt;
161
+ }
162
+
157
163
  // Update answerer index
158
164
  if (!this.offersByAnswerer.has(answererPublicKey)) {
159
165
  this.offersByAnswerer.set(answererPublicKey, new Set());
@@ -187,7 +187,8 @@ export class MySQLStorage implements Storage {
187
187
  offerId: string,
188
188
  answererPublicKey: string,
189
189
  answerSdp: string,
190
- matchedTags?: string[]
190
+ matchedTags?: string[],
191
+ newExpiresAt?: number
191
192
  ): Promise<{ success: boolean; error?: string }> {
192
193
  const offer = await this.getOfferById(offerId);
193
194
 
@@ -199,12 +200,19 @@ export class MySQLStorage implements Storage {
199
200
  return { success: false, error: 'Offer already answered' };
200
201
  }
201
202
 
203
+ const now = Date.now();
202
204
  const matchedTagsJson = matchedTags ? JSON.stringify(matchedTags) : null;
203
- const [result] = await this.pool.query<ResultSetHeader>(
204
- `UPDATE offers SET answerer_public_key = ?, answer_sdp = ?, answered_at = ?, matched_tags = ?
205
- WHERE id = ? AND answerer_public_key IS NULL`,
206
- [answererPublicKey, answerSdp, Date.now(), matchedTagsJson, offerId]
207
- );
205
+
206
+ // Optionally reduce TTL for faster cleanup after answer
207
+ const query = newExpiresAt
208
+ ? `UPDATE offers SET answerer_public_key = ?, answer_sdp = ?, answered_at = ?, matched_tags = ?, expires_at = ? WHERE id = ? AND answerer_public_key IS NULL`
209
+ : `UPDATE offers SET answerer_public_key = ?, answer_sdp = ?, answered_at = ?, matched_tags = ? WHERE id = ? AND answerer_public_key IS NULL`;
210
+
211
+ const params = newExpiresAt
212
+ ? [answererPublicKey, answerSdp, now, matchedTagsJson, newExpiresAt, offerId]
213
+ : [answererPublicKey, answerSdp, now, matchedTagsJson, offerId];
214
+
215
+ const [result] = await this.pool.query<ResultSetHeader>(query, params);
208
216
 
209
217
  if (result.affectedRows === 0) {
210
218
  return { success: false, error: 'Offer already answered (race condition)' };
@@ -189,7 +189,8 @@ export class PostgreSQLStorage implements Storage {
189
189
  offerId: string,
190
190
  answererPublicKey: string,
191
191
  answerSdp: string,
192
- matchedTags?: string[]
192
+ matchedTags?: string[],
193
+ newExpiresAt?: number
193
194
  ): Promise<{ success: boolean; error?: string }> {
194
195
  const offer = await this.getOfferById(offerId);
195
196
 
@@ -201,12 +202,19 @@ export class PostgreSQLStorage implements Storage {
201
202
  return { success: false, error: 'Offer already answered' };
202
203
  }
203
204
 
205
+ const now = Date.now();
204
206
  const matchedTagsJson = matchedTags ? JSON.stringify(matchedTags) : null;
205
- const result = await this.pool.query(
206
- `UPDATE offers SET answerer_public_key = $1, answer_sdp = $2, answered_at = $3, matched_tags = $4
207
- WHERE id = $5 AND answerer_public_key IS NULL`,
208
- [answererPublicKey, answerSdp, Date.now(), matchedTagsJson, offerId]
209
- );
207
+
208
+ // Optionally reduce TTL for faster cleanup after answer
209
+ const query = newExpiresAt
210
+ ? `UPDATE offers SET answerer_public_key = $1, answer_sdp = $2, answered_at = $3, matched_tags = $4, expires_at = $5 WHERE id = $6 AND answerer_public_key IS NULL`
211
+ : `UPDATE offers SET answerer_public_key = $1, answer_sdp = $2, answered_at = $3, matched_tags = $4 WHERE id = $5 AND answerer_public_key IS NULL`;
212
+
213
+ const params = newExpiresAt
214
+ ? [answererPublicKey, answerSdp, now, matchedTagsJson, newExpiresAt, offerId]
215
+ : [answererPublicKey, answerSdp, now, matchedTagsJson, offerId];
216
+
217
+ const result = await this.pool.query(query, params);
210
218
 
211
219
  if ((result.rowCount ?? 0) === 0) {
212
220
  return { success: false, error: 'Offer already answered (race condition)' };
@@ -193,7 +193,8 @@ export class SQLiteStorage implements Storage {
193
193
  offerId: string,
194
194
  answererPublicKey: string,
195
195
  answerSdp: string,
196
- matchedTags?: string[]
196
+ matchedTags?: string[],
197
+ newExpiresAt?: number
197
198
  ): Promise<{ success: boolean; error?: string }> {
198
199
  // Check if offer exists and is not expired
199
200
  const offer = await this.getOfferById(offerId);
@@ -213,15 +214,19 @@ export class SQLiteStorage implements Storage {
213
214
  };
214
215
  }
215
216
 
216
- // Update offer with answer
217
+ // Update offer with answer (optionally reduce TTL for faster cleanup)
218
+ const now = Date.now();
217
219
  const stmt = this.db.prepare(`
218
220
  UPDATE offers
219
- SET answerer_public_key = ?, answer_sdp = ?, answered_at = ?, matched_tags = ?
221
+ SET answerer_public_key = ?, answer_sdp = ?, answered_at = ?, matched_tags = ?${newExpiresAt ? ', expires_at = ?' : ''}
220
222
  WHERE id = ? AND answerer_public_key IS NULL
221
223
  `);
222
224
 
223
225
  const matchedTagsJson = matchedTags ? JSON.stringify(matchedTags) : null;
224
- const result = stmt.run(answererPublicKey, answerSdp, Date.now(), matchedTagsJson, offerId);
226
+ const params = newExpiresAt
227
+ ? [answererPublicKey, answerSdp, now, matchedTagsJson, newExpiresAt, offerId]
228
+ : [answererPublicKey, answerSdp, now, matchedTagsJson, offerId];
229
+ const result = stmt.run(...params);
225
230
 
226
231
  if (result.changes === 0) {
227
232
  return {
@@ -100,9 +100,10 @@ export interface Storage {
100
100
  * @param answererPublicKey Answerer's public key
101
101
  * @param answerSdp WebRTC answer SDP
102
102
  * @param matchedTags Optional tags the answerer searched for to find this offer
103
+ * @param newExpiresAt Optional new expiry time (to reduce TTL after answer for faster cleanup)
103
104
  * @returns Success status and optional error message
104
105
  */
105
- answerOffer(offerId: string, answererPublicKey: string, answerSdp: string, matchedTags?: string[]): Promise<{
106
+ answerOffer(offerId: string, answererPublicKey: string, answerSdp: string, matchedTags?: string[], newExpiresAt?: number): Promise<{
106
107
  success: boolean;
107
108
  error?: string;
108
109
  }>;
package/wrangler.toml CHANGED
@@ -6,6 +6,8 @@ compatibility_flags = ["nodejs_compat"]
6
6
  workers_dev = true
7
7
  preview_urls = true
8
8
 
9
+ # Note: api.ronde.vu is a standalone VPS deployment (manual deployment)
10
+ # This worker is deployed to test.ronde.vu for testing purposes
9
11
  routes = [{ pattern = "test.ronde.vu/*", zone_name = "ronde.vu" }]
10
12
 
11
13
  # Cleanup runs every 5 minutes