@xtr-dev/rondevu-server 0.5.6 → 0.5.7
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 +145 -7
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/src/config.ts +29 -1
- package/src/rpc.ts +64 -6
- package/src/storage/d1.ts +26 -0
- package/src/storage/memory.ts +20 -0
- package/src/storage/mysql.ts +28 -0
- package/src/storage/postgres.ts +28 -0
- package/src/storage/sqlite.ts +22 -0
- package/src/storage/types.ts +28 -0
- package/wrangler.toml +1 -1
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -27,6 +27,13 @@ export interface Config {
|
|
|
27
27
|
timestampMaxAge: number; // Max age for timestamps (replay protection)
|
|
28
28
|
timestampMaxFuture: number; // Max future tolerance for timestamps (clock skew)
|
|
29
29
|
masterEncryptionKey: string; // 64-char hex string for encrypting secrets (32 bytes)
|
|
30
|
+
// Resource limits (for abuse prevention)
|
|
31
|
+
maxOffersPerUser: number; // Max concurrent offers per user
|
|
32
|
+
maxTotalOffers: number; // Max total offers in storage
|
|
33
|
+
maxTotalCredentials: number; // Max total credentials in storage
|
|
34
|
+
maxIceCandidatesPerOffer: number; // Max ICE candidates per offer
|
|
35
|
+
credentialsPerIpPerHour: number; // Rate limit: credentials per IP per hour
|
|
36
|
+
requestsPerIpPerSecond: number; // Rate limit: requests per IP per second
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
/**
|
|
@@ -56,7 +63,7 @@ export function loadConfig(): Config {
|
|
|
56
63
|
console.error('⚠️ ONLY use NODE_ENV=development for local development');
|
|
57
64
|
console.error('⚠️ Generate production key with: openssl rand -hex 32');
|
|
58
65
|
// Random-looking dev key (not ASCII-readable to prevent accidental production use)
|
|
59
|
-
masterEncryptionKey = '
|
|
66
|
+
masterEncryptionKey = 'a3f8b9c2d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0';
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
// Validate master encryption key format
|
|
@@ -101,6 +108,13 @@ export function loadConfig(): Config {
|
|
|
101
108
|
timestampMaxAge: parsePositiveInt(process.env.TIMESTAMP_MAX_AGE, '60000', 'TIMESTAMP_MAX_AGE', 1000), // Min 1 second
|
|
102
109
|
timestampMaxFuture: parsePositiveInt(process.env.TIMESTAMP_MAX_FUTURE, '60000', 'TIMESTAMP_MAX_FUTURE', 1000), // Min 1 second
|
|
103
110
|
masterEncryptionKey,
|
|
111
|
+
// Resource limits
|
|
112
|
+
maxOffersPerUser: parsePositiveInt(process.env.MAX_OFFERS_PER_USER, '20', 'MAX_OFFERS_PER_USER', 1),
|
|
113
|
+
maxTotalOffers: parsePositiveInt(process.env.MAX_TOTAL_OFFERS, '10000', 'MAX_TOTAL_OFFERS', 1),
|
|
114
|
+
maxTotalCredentials: parsePositiveInt(process.env.MAX_TOTAL_CREDENTIALS, '50000', 'MAX_TOTAL_CREDENTIALS', 1),
|
|
115
|
+
maxIceCandidatesPerOffer: parsePositiveInt(process.env.MAX_ICE_CANDIDATES_PER_OFFER, '50', 'MAX_ICE_CANDIDATES_PER_OFFER', 1),
|
|
116
|
+
credentialsPerIpPerHour: parsePositiveInt(process.env.CREDENTIALS_PER_IP_PER_HOUR, '10', 'CREDENTIALS_PER_IP_PER_HOUR', 1),
|
|
117
|
+
requestsPerIpPerSecond: parsePositiveInt(process.env.REQUESTS_PER_IP_PER_SECOND, '50', 'REQUESTS_PER_IP_PER_SECOND', 1),
|
|
104
118
|
};
|
|
105
119
|
|
|
106
120
|
return config;
|
|
@@ -123,6 +137,13 @@ export const CONFIG_DEFAULTS = {
|
|
|
123
137
|
maxTotalOperations: 1000,
|
|
124
138
|
timestampMaxAge: 60000,
|
|
125
139
|
timestampMaxFuture: 60000,
|
|
140
|
+
// Resource limits
|
|
141
|
+
maxOffersPerUser: 20,
|
|
142
|
+
maxTotalOffers: 10000,
|
|
143
|
+
maxTotalCredentials: 50000,
|
|
144
|
+
maxIceCandidatesPerOffer: 50,
|
|
145
|
+
credentialsPerIpPerHour: 10,
|
|
146
|
+
requestsPerIpPerSecond: 50,
|
|
126
147
|
} as const;
|
|
127
148
|
|
|
128
149
|
/**
|
|
@@ -160,6 +181,13 @@ export function buildWorkerConfig(env: {
|
|
|
160
181
|
timestampMaxAge: CONFIG_DEFAULTS.timestampMaxAge,
|
|
161
182
|
timestampMaxFuture: CONFIG_DEFAULTS.timestampMaxFuture,
|
|
162
183
|
masterEncryptionKey: env.MASTER_ENCRYPTION_KEY,
|
|
184
|
+
// Resource limits
|
|
185
|
+
maxOffersPerUser: CONFIG_DEFAULTS.maxOffersPerUser,
|
|
186
|
+
maxTotalOffers: CONFIG_DEFAULTS.maxTotalOffers,
|
|
187
|
+
maxTotalCredentials: CONFIG_DEFAULTS.maxTotalCredentials,
|
|
188
|
+
maxIceCandidatesPerOffer: CONFIG_DEFAULTS.maxIceCandidatesPerOffer,
|
|
189
|
+
credentialsPerIpPerHour: CONFIG_DEFAULTS.credentialsPerIpPerHour,
|
|
190
|
+
requestsPerIpPerSecond: CONFIG_DEFAULTS.requestsPerIpPerSecond,
|
|
163
191
|
};
|
|
164
192
|
}
|
|
165
193
|
|
package/src/rpc.ts
CHANGED
|
@@ -16,13 +16,13 @@ const MAX_PAGE_SIZE = 100;
|
|
|
16
16
|
|
|
17
17
|
// ===== Rate Limiting =====
|
|
18
18
|
|
|
19
|
-
// Rate limiting
|
|
19
|
+
// Rate limiting windows (these are fixed, limits come from config)
|
|
20
20
|
// NOTE: Uses fixed-window rate limiting with full window reset on expiry
|
|
21
|
-
// - Window starts on first request and expires after
|
|
21
|
+
// - Window starts on first request and expires after window duration
|
|
22
22
|
// - When window expires, counter resets to 0 and new window starts
|
|
23
23
|
// - This is simpler than sliding windows but may allow bursts at window boundaries
|
|
24
|
-
const
|
|
25
|
-
const
|
|
24
|
+
const CREDENTIAL_RATE_WINDOW = 60 * 60 * 1000; // 1 hour in milliseconds
|
|
25
|
+
const REQUEST_RATE_WINDOW = 1000; // 1 second in milliseconds
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Check JSON object depth to prevent stack overflow from deeply nested objects
|
|
@@ -100,6 +100,9 @@ export const ErrorCodes = {
|
|
|
100
100
|
SDP_TOO_LARGE: 'SDP_TOO_LARGE',
|
|
101
101
|
BATCH_TOO_LARGE: 'BATCH_TOO_LARGE',
|
|
102
102
|
RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
|
|
103
|
+
TOO_MANY_OFFERS_PER_USER: 'TOO_MANY_OFFERS_PER_USER',
|
|
104
|
+
STORAGE_FULL: 'STORAGE_FULL',
|
|
105
|
+
TOO_MANY_ICE_CANDIDATES: 'TOO_MANY_ICE_CANDIDATES',
|
|
103
106
|
|
|
104
107
|
// Generic errors
|
|
105
108
|
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
|
@@ -276,6 +279,15 @@ const handlers: Record<string, RpcHandler> = {
|
|
|
276
279
|
* SECURITY: Rate limited per IP to prevent abuse (database-backed for multi-instance support)
|
|
277
280
|
*/
|
|
278
281
|
async generateCredentials(params: GenerateCredentialsParams, name, timestamp, signature, storage, config, request: RpcRequest & { clientIp?: string }) {
|
|
282
|
+
// Check total credentials limit
|
|
283
|
+
const credentialCount = await storage.getCredentialCount();
|
|
284
|
+
if (credentialCount >= config.maxTotalCredentials) {
|
|
285
|
+
throw new RpcError(
|
|
286
|
+
ErrorCodes.STORAGE_FULL,
|
|
287
|
+
`Server credential limit reached (${config.maxTotalCredentials}). Try again later.`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
279
291
|
// Rate limiting check (IP-based, stored in database)
|
|
280
292
|
// SECURITY: Use stricter global rate limit for requests without identifiable IP
|
|
281
293
|
let rateLimitKey: string;
|
|
@@ -289,7 +301,7 @@ const handlers: Record<string, RpcHandler> = {
|
|
|
289
301
|
rateLimit = 2; // Only 2 credentials per hour globally for all unknown IPs combined
|
|
290
302
|
} else {
|
|
291
303
|
rateLimitKey = `cred_gen:${request.clientIp}`;
|
|
292
|
-
rateLimit =
|
|
304
|
+
rateLimit = config.credentialsPerIpPerHour;
|
|
293
305
|
}
|
|
294
306
|
|
|
295
307
|
const allowed = await storage.checkRateLimit(
|
|
@@ -301,7 +313,7 @@ const handlers: Record<string, RpcHandler> = {
|
|
|
301
313
|
if (!allowed) {
|
|
302
314
|
throw new RpcError(
|
|
303
315
|
ErrorCodes.RATE_LIMIT_EXCEEDED,
|
|
304
|
-
`Rate limit exceeded. Maximum ${rateLimit}
|
|
316
|
+
`Rate limit exceeded. Maximum ${rateLimit} credentials per hour${request.clientIp ? ' per IP' : ' (global limit for unidentified IPs)'}.`
|
|
305
317
|
);
|
|
306
318
|
}
|
|
307
319
|
|
|
@@ -453,6 +465,24 @@ const handlers: Record<string, RpcHandler> = {
|
|
|
453
465
|
);
|
|
454
466
|
}
|
|
455
467
|
|
|
468
|
+
// Check per-user offer limit
|
|
469
|
+
const userOfferCount = await storage.getOfferCountByUsername(name);
|
|
470
|
+
if (userOfferCount + offers.length > config.maxOffersPerUser) {
|
|
471
|
+
throw new RpcError(
|
|
472
|
+
ErrorCodes.TOO_MANY_OFFERS_PER_USER,
|
|
473
|
+
`User offer limit exceeded. You have ${userOfferCount} offers, limit is ${config.maxOffersPerUser}.`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Check total offers limit
|
|
478
|
+
const totalOfferCount = await storage.getOfferCount();
|
|
479
|
+
if (totalOfferCount + offers.length > config.maxTotalOffers) {
|
|
480
|
+
throw new RpcError(
|
|
481
|
+
ErrorCodes.STORAGE_FULL,
|
|
482
|
+
`Server offer limit reached (${config.maxTotalOffers}). Try again later.`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
456
486
|
// Validate each offer has valid SDP
|
|
457
487
|
offers.forEach((offer, index) => {
|
|
458
488
|
if (!offer || typeof offer !== 'object') {
|
|
@@ -723,6 +753,15 @@ const handlers: Record<string, RpcHandler> = {
|
|
|
723
753
|
throw new RpcError(ErrorCodes.OFFER_NOT_FOUND, 'Offer not found');
|
|
724
754
|
}
|
|
725
755
|
|
|
756
|
+
// Check ICE candidates limit per offer
|
|
757
|
+
const currentCandidateCount = await storage.getIceCandidateCount(offerId);
|
|
758
|
+
if (currentCandidateCount + candidates.length > config.maxIceCandidatesPerOffer) {
|
|
759
|
+
throw new RpcError(
|
|
760
|
+
ErrorCodes.TOO_MANY_ICE_CANDIDATES,
|
|
761
|
+
`ICE candidate limit exceeded for offer. Current: ${currentCandidateCount}, limit: ${config.maxIceCandidatesPerOffer}.`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
|
|
726
765
|
const role = offer.username === name ? 'offerer' : 'answerer';
|
|
727
766
|
const count = await storage.addIceCandidates(
|
|
728
767
|
offerId,
|
|
@@ -807,6 +846,25 @@ export async function handleRpc(
|
|
|
807
846
|
ctx.req.header('x-forwarded-for')?.split(',')[0].trim() ||
|
|
808
847
|
undefined; // Don't use fallback - let handlers decide how to handle missing IP
|
|
809
848
|
|
|
849
|
+
// General request rate limiting (per IP per second)
|
|
850
|
+
if (clientIp) {
|
|
851
|
+
const rateLimitKey = `req:${clientIp}`;
|
|
852
|
+
const allowed = await storage.checkRateLimit(
|
|
853
|
+
rateLimitKey,
|
|
854
|
+
config.requestsPerIpPerSecond,
|
|
855
|
+
REQUEST_RATE_WINDOW
|
|
856
|
+
);
|
|
857
|
+
|
|
858
|
+
if (!allowed) {
|
|
859
|
+
// Return error for all requests in the batch
|
|
860
|
+
return requests.map(() => ({
|
|
861
|
+
success: false,
|
|
862
|
+
error: `Rate limit exceeded. Maximum ${config.requestsPerIpPerSecond} requests per second per IP.`,
|
|
863
|
+
errorCode: ErrorCodes.RATE_LIMIT_EXCEEDED,
|
|
864
|
+
}));
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
810
868
|
// Read auth headers (same for all requests in batch)
|
|
811
869
|
const name = ctx.req.header('X-Name');
|
|
812
870
|
const timestampHeader = ctx.req.header('X-Timestamp');
|
package/src/storage/d1.ts
CHANGED
|
@@ -631,6 +631,32 @@ export class D1Storage implements Storage {
|
|
|
631
631
|
// Connections are managed by the Cloudflare Workers runtime
|
|
632
632
|
}
|
|
633
633
|
|
|
634
|
+
// ===== Count Methods (for resource limits) =====
|
|
635
|
+
|
|
636
|
+
async getOfferCount(): Promise<number> {
|
|
637
|
+
const result = await this.db.prepare('SELECT COUNT(*) as count FROM offers').first() as { count: number } | null;
|
|
638
|
+
return result?.count ?? 0;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
async getOfferCountByUsername(username: string): Promise<number> {
|
|
642
|
+
const result = await this.db.prepare('SELECT COUNT(*) as count FROM offers WHERE username = ?')
|
|
643
|
+
.bind(username)
|
|
644
|
+
.first() as { count: number } | null;
|
|
645
|
+
return result?.count ?? 0;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async getCredentialCount(): Promise<number> {
|
|
649
|
+
const result = await this.db.prepare('SELECT COUNT(*) as count FROM credentials').first() as { count: number } | null;
|
|
650
|
+
return result?.count ?? 0;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async getIceCandidateCount(offerId: string): Promise<number> {
|
|
654
|
+
const result = await this.db.prepare('SELECT COUNT(*) as count FROM ice_candidates WHERE offer_id = ?')
|
|
655
|
+
.bind(offerId)
|
|
656
|
+
.first() as { count: number } | null;
|
|
657
|
+
return result?.count ?? 0;
|
|
658
|
+
}
|
|
659
|
+
|
|
634
660
|
// ===== Helper Methods =====
|
|
635
661
|
|
|
636
662
|
/**
|
package/src/storage/memory.ts
CHANGED
|
@@ -522,6 +522,26 @@ export class MemoryStorage implements Storage {
|
|
|
522
522
|
this.offersByAnswerer.clear();
|
|
523
523
|
}
|
|
524
524
|
|
|
525
|
+
// ===== Count Methods (for resource limits) =====
|
|
526
|
+
|
|
527
|
+
async getOfferCount(): Promise<number> {
|
|
528
|
+
return this.offers.size;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async getOfferCountByUsername(username: string): Promise<number> {
|
|
532
|
+
const offerIds = this.offersByUsername.get(username);
|
|
533
|
+
return offerIds ? offerIds.size : 0;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async getCredentialCount(): Promise<number> {
|
|
537
|
+
return this.credentials.size;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async getIceCandidateCount(offerId: string): Promise<number> {
|
|
541
|
+
const candidates = this.iceCandidates.get(offerId);
|
|
542
|
+
return candidates ? candidates.length : 0;
|
|
543
|
+
}
|
|
544
|
+
|
|
525
545
|
// ===== Helper Methods =====
|
|
526
546
|
|
|
527
547
|
private removeOfferFromIndexes(offer: Offer): void {
|
package/src/storage/mysql.ts
CHANGED
|
@@ -558,6 +558,34 @@ export class MySQLStorage implements Storage {
|
|
|
558
558
|
await this.pool.end();
|
|
559
559
|
}
|
|
560
560
|
|
|
561
|
+
// ===== Count Methods (for resource limits) =====
|
|
562
|
+
|
|
563
|
+
async getOfferCount(): Promise<number> {
|
|
564
|
+
const [rows] = await this.pool.query<RowDataPacket[]>('SELECT COUNT(*) as count FROM offers');
|
|
565
|
+
return Number(rows[0].count);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async getOfferCountByUsername(username: string): Promise<number> {
|
|
569
|
+
const [rows] = await this.pool.query<RowDataPacket[]>(
|
|
570
|
+
'SELECT COUNT(*) as count FROM offers WHERE username = ?',
|
|
571
|
+
[username]
|
|
572
|
+
);
|
|
573
|
+
return Number(rows[0].count);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
async getCredentialCount(): Promise<number> {
|
|
577
|
+
const [rows] = await this.pool.query<RowDataPacket[]>('SELECT COUNT(*) as count FROM credentials');
|
|
578
|
+
return Number(rows[0].count);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async getIceCandidateCount(offerId: string): Promise<number> {
|
|
582
|
+
const [rows] = await this.pool.query<RowDataPacket[]>(
|
|
583
|
+
'SELECT COUNT(*) as count FROM ice_candidates WHERE offer_id = ?',
|
|
584
|
+
[offerId]
|
|
585
|
+
);
|
|
586
|
+
return Number(rows[0].count);
|
|
587
|
+
}
|
|
588
|
+
|
|
561
589
|
// ===== Helper Methods =====
|
|
562
590
|
|
|
563
591
|
private rowToOffer(row: RowDataPacket): Offer {
|
package/src/storage/postgres.ts
CHANGED
|
@@ -565,6 +565,34 @@ export class PostgreSQLStorage implements Storage {
|
|
|
565
565
|
await this.pool.end();
|
|
566
566
|
}
|
|
567
567
|
|
|
568
|
+
// ===== Count Methods (for resource limits) =====
|
|
569
|
+
|
|
570
|
+
async getOfferCount(): Promise<number> {
|
|
571
|
+
const result = await this.pool.query('SELECT COUNT(*) as count FROM offers');
|
|
572
|
+
return Number(result.rows[0].count);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
async getOfferCountByUsername(username: string): Promise<number> {
|
|
576
|
+
const result = await this.pool.query(
|
|
577
|
+
'SELECT COUNT(*) as count FROM offers WHERE username = $1',
|
|
578
|
+
[username]
|
|
579
|
+
);
|
|
580
|
+
return Number(result.rows[0].count);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async getCredentialCount(): Promise<number> {
|
|
584
|
+
const result = await this.pool.query('SELECT COUNT(*) as count FROM credentials');
|
|
585
|
+
return Number(result.rows[0].count);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async getIceCandidateCount(offerId: string): Promise<number> {
|
|
589
|
+
const result = await this.pool.query(
|
|
590
|
+
'SELECT COUNT(*) as count FROM ice_candidates WHERE offer_id = $1',
|
|
591
|
+
[offerId]
|
|
592
|
+
);
|
|
593
|
+
return Number(result.rows[0].count);
|
|
594
|
+
}
|
|
595
|
+
|
|
568
596
|
// ===== Helper Methods =====
|
|
569
597
|
|
|
570
598
|
private rowToOffer(row: any): Offer {
|
package/src/storage/sqlite.ts
CHANGED
|
@@ -642,6 +642,28 @@ export class SQLiteStorage implements Storage {
|
|
|
642
642
|
this.db.close();
|
|
643
643
|
}
|
|
644
644
|
|
|
645
|
+
// ===== Count Methods (for resource limits) =====
|
|
646
|
+
|
|
647
|
+
async getOfferCount(): Promise<number> {
|
|
648
|
+
const result = this.db.prepare('SELECT COUNT(*) as count FROM offers').get() as { count: number };
|
|
649
|
+
return result.count;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
async getOfferCountByUsername(username: string): Promise<number> {
|
|
653
|
+
const result = this.db.prepare('SELECT COUNT(*) as count FROM offers WHERE username = ?').get(username) as { count: number };
|
|
654
|
+
return result.count;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
async getCredentialCount(): Promise<number> {
|
|
658
|
+
const result = this.db.prepare('SELECT COUNT(*) as count FROM credentials').get() as { count: number };
|
|
659
|
+
return result.count;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
async getIceCandidateCount(offerId: string): Promise<number> {
|
|
663
|
+
const result = this.db.prepare('SELECT COUNT(*) as count FROM ice_candidates WHERE offer_id = ?').get(offerId) as { count: number };
|
|
664
|
+
return result.count;
|
|
665
|
+
}
|
|
666
|
+
|
|
645
667
|
// ===== Helper Methods =====
|
|
646
668
|
|
|
647
669
|
/**
|
package/src/storage/types.ts
CHANGED
|
@@ -282,4 +282,32 @@ export interface Storage {
|
|
|
282
282
|
* Closes the storage connection and releases resources
|
|
283
283
|
*/
|
|
284
284
|
close(): Promise<void>;
|
|
285
|
+
|
|
286
|
+
// ===== Count Methods (for resource limits) =====
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Gets total number of offers in storage
|
|
290
|
+
* @returns Total offer count
|
|
291
|
+
*/
|
|
292
|
+
getOfferCount(): Promise<number>;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Gets number of offers for a specific user
|
|
296
|
+
* @param username Username identifier
|
|
297
|
+
* @returns Offer count for user
|
|
298
|
+
*/
|
|
299
|
+
getOfferCountByUsername(username: string): Promise<number>;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Gets total number of credentials in storage
|
|
303
|
+
* @returns Total credential count
|
|
304
|
+
*/
|
|
305
|
+
getCredentialCount(): Promise<number>;
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Gets number of ICE candidates for a specific offer
|
|
309
|
+
* @param offerId Offer identifier
|
|
310
|
+
* @returns ICE candidate count for offer
|
|
311
|
+
*/
|
|
312
|
+
getIceCandidateCount(offerId: string): Promise<number>;
|
|
285
313
|
}
|
package/wrangler.toml
CHANGED
|
@@ -6,7 +6,7 @@ compatibility_flags = ["nodejs_compat"]
|
|
|
6
6
|
workers_dev = true
|
|
7
7
|
preview_urls = true
|
|
8
8
|
|
|
9
|
-
routes = [{ pattern = "
|
|
9
|
+
routes = [{ pattern = "test.ronde.vu/*", zone_name = "ronde.vu" }]
|
|
10
10
|
|
|
11
11
|
# Cleanup runs every 5 minutes
|
|
12
12
|
[triggers]
|