@xtr-dev/rondevu-server 0.3.0 → 0.5.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.
@@ -3,14 +3,14 @@
3
3
  */
4
4
  export interface Offer {
5
5
  id: string;
6
- peerId: string;
6
+ username: string;
7
7
  serviceId?: string; // Optional link to service (null for standalone offers)
8
+ serviceFqn?: string; // Denormalized service FQN for easier queries
8
9
  sdp: string;
9
10
  createdAt: number;
10
11
  expiresAt: number;
11
12
  lastSeen: number;
12
- secret?: string;
13
- answererPeerId?: string;
13
+ answererUsername?: string;
14
14
  answerSdp?: string;
15
15
  answeredAt?: number;
16
16
  }
@@ -22,7 +22,7 @@ export interface Offer {
22
22
  export interface IceCandidate {
23
23
  id: number;
24
24
  offerId: string;
25
- peerId: string;
25
+ username: string;
26
26
  role: 'offerer' | 'answerer';
27
27
  candidate: any; // Full candidate object as JSON - don't enforce structure
28
28
  createdAt: number;
@@ -33,11 +33,11 @@ export interface IceCandidate {
33
33
  */
34
34
  export interface CreateOfferRequest {
35
35
  id?: string;
36
- peerId: string;
36
+ username: string;
37
37
  serviceId?: string; // Optional link to service
38
+ serviceFqn?: string; // Optional service FQN
38
39
  sdp: string;
39
40
  expiresAt: number;
40
- secret?: string;
41
41
  }
42
42
 
43
43
  /**
@@ -64,58 +64,27 @@ export interface ClaimUsernameRequest {
64
64
 
65
65
  /**
66
66
  * Represents a published service (can have multiple offers)
67
+ * New format: service:version@username (e.g., chat:1.0.0@alice)
67
68
  */
68
69
  export interface Service {
69
70
  id: string; // UUID v4
70
- username: string;
71
- serviceFqn: string; // com.example.chat@1.0.0
71
+ serviceFqn: string; // Full FQN: chat:1.0.0@alice
72
+ serviceName: string; // Extracted: chat
73
+ version: string; // Extracted: 1.0.0
74
+ username: string; // Extracted: alice
72
75
  createdAt: number;
73
76
  expiresAt: number;
74
- isPublic: boolean;
75
- metadata?: string; // JSON service description
76
77
  }
77
78
 
78
79
  /**
79
80
  * Request to create a single service
80
81
  */
81
82
  export interface CreateServiceRequest {
82
- username: string;
83
- serviceFqn: string;
83
+ serviceFqn: string; // Full FQN with username: chat:1.0.0@alice
84
84
  expiresAt: number;
85
- isPublic?: boolean;
86
- metadata?: string;
87
85
  offers: CreateOfferRequest[]; // Multiple offers per service
88
86
  }
89
87
 
90
- /**
91
- * Request to create multiple services in batch
92
- */
93
- export interface BatchCreateServicesRequest {
94
- services: CreateServiceRequest[];
95
- }
96
-
97
- /**
98
- * Represents a service index entry (privacy layer)
99
- */
100
- export interface ServiceIndex {
101
- uuid: string; // Random UUID for privacy
102
- serviceId: string;
103
- username: string;
104
- serviceFqn: string;
105
- createdAt: number;
106
- expiresAt: number;
107
- }
108
-
109
- /**
110
- * Service info for discovery (privacy-aware)
111
- */
112
- export interface ServiceInfo {
113
- uuid: string;
114
- isPublic: boolean;
115
- serviceFqn?: string; // Only present if public
116
- metadata?: string; // Only present if public
117
- }
118
-
119
88
  /**
120
89
  * Storage interface for rondevu DNS-like system
121
90
  * Implementations can use different backends (SQLite, D1, etc.)
@@ -131,11 +100,11 @@ export interface Storage {
131
100
  createOffers(offers: CreateOfferRequest[]): Promise<Offer[]>;
132
101
 
133
102
  /**
134
- * Retrieves all offers from a specific peer
135
- * @param peerId Peer identifier
136
- * @returns Array of offers from the peer
103
+ * Retrieves all offers from a specific user
104
+ * @param username Username identifier
105
+ * @returns Array of offers from the user
137
106
  */
138
- getOffersByPeerId(peerId: string): Promise<Offer[]>;
107
+ getOffersByUsername(username: string): Promise<Offer[]>;
139
108
 
140
109
  /**
141
110
  * Retrieves a specific offer by ID
@@ -147,10 +116,10 @@ export interface Storage {
147
116
  /**
148
117
  * Deletes an offer (with ownership verification)
149
118
  * @param offerId Offer identifier
150
- * @param ownerPeerId Peer ID of the owner (for verification)
119
+ * @param ownerUsername Username of the owner (for verification)
151
120
  * @returns true if deleted, false if not found or not owned
152
121
  */
153
- deleteOffer(offerId: string, ownerPeerId: string): Promise<boolean>;
122
+ deleteOffer(offerId: string, ownerUsername: string): Promise<boolean>;
154
123
 
155
124
  /**
156
125
  * Deletes all expired offers
@@ -162,36 +131,35 @@ export interface Storage {
162
131
  /**
163
132
  * Answers an offer (locks it to the answerer)
164
133
  * @param offerId Offer identifier
165
- * @param answererPeerId Answerer's peer ID
134
+ * @param answererUsername Answerer's username
166
135
  * @param answerSdp WebRTC answer SDP
167
- * @param secret Optional secret for protected offers
168
136
  * @returns Success status and optional error message
169
137
  */
170
- answerOffer(offerId: string, answererPeerId: string, answerSdp: string, secret?: string): Promise<{
138
+ answerOffer(offerId: string, answererUsername: string, answerSdp: string): Promise<{
171
139
  success: boolean;
172
140
  error?: string;
173
141
  }>;
174
142
 
175
143
  /**
176
144
  * Retrieves all answered offers for a specific offerer
177
- * @param offererPeerId Offerer's peer ID
145
+ * @param offererUsername Offerer's username
178
146
  * @returns Array of answered offers
179
147
  */
180
- getAnsweredOffers(offererPeerId: string): Promise<Offer[]>;
148
+ getAnsweredOffers(offererUsername: string): Promise<Offer[]>;
181
149
 
182
150
  // ===== ICE Candidate Management =====
183
151
 
184
152
  /**
185
153
  * Adds ICE candidates for an offer
186
154
  * @param offerId Offer identifier
187
- * @param peerId Peer ID posting the candidates
188
- * @param role Role of the peer (offerer or answerer)
155
+ * @param username Username posting the candidates
156
+ * @param role Role of the user (offerer or answerer)
189
157
  * @param candidates Array of candidate objects (stored as plain JSON)
190
158
  * @returns Number of candidates added
191
159
  */
192
160
  addIceCandidates(
193
161
  offerId: string,
194
- peerId: string,
162
+ username: string,
195
163
  role: 'offerer' | 'answerer',
196
164
  candidates: any[]
197
165
  ): Promise<number>;
@@ -225,13 +193,6 @@ export interface Storage {
225
193
  */
226
194
  getUsername(username: string): Promise<Username | null>;
227
195
 
228
- /**
229
- * Updates the last_used timestamp for a username (extends expiry)
230
- * @param username Username to update
231
- * @returns true if updated, false if not found
232
- */
233
- touchUsername(username: string): Promise<boolean>;
234
-
235
196
  /**
236
197
  * Deletes all expired usernames
237
198
  * @param now Current timestamp
@@ -244,24 +205,13 @@ export interface Storage {
244
205
  /**
245
206
  * Creates a new service with offers
246
207
  * @param request Service creation request (includes offers)
247
- * @returns Created service with generated ID, index UUID, and created offers
208
+ * @returns Created service with generated ID and created offers
248
209
  */
249
210
  createService(request: CreateServiceRequest): Promise<{
250
211
  service: Service;
251
- indexUuid: string;
252
212
  offers: Offer[];
253
213
  }>;
254
214
 
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
215
 
266
216
  /**
267
217
  * Gets all offers for a service
@@ -278,34 +228,40 @@ export interface Storage {
278
228
  getServiceById(serviceId: string): Promise<Service | null>;
279
229
 
280
230
  /**
281
- * Gets a service by its index UUID
282
- * @param uuid Index UUID
231
+ * Gets a service by its fully qualified name (FQN)
232
+ * @param serviceFqn Full service FQN (e.g., "chat:1.0.0@alice")
283
233
  * @returns Service if found, null otherwise
284
234
  */
285
- getServiceByUuid(uuid: string): Promise<Service | null>;
235
+ getServiceByFqn(serviceFqn: string): Promise<Service | null>;
236
+
237
+
238
+
286
239
 
287
- /**
288
- * Lists all services for a username (with privacy filtering)
289
- * @param username Username to query
290
- * @returns Array of service info (UUIDs only for private services)
291
- */
292
- listServicesForUsername(username: string): Promise<ServiceInfo[]>;
293
240
 
294
241
  /**
295
- * Queries a service by username and FQN
296
- * @param username Username
297
- * @param serviceFqn Service FQN
298
- * @returns Service index UUID if found, null otherwise
242
+ * Discovers services by name and version with pagination
243
+ * Returns unique available offers (where answerer_peer_id IS NULL)
244
+ * @param serviceName Service name (e.g., 'chat')
245
+ * @param version Version string for semver matching (e.g., '1.0.0')
246
+ * @param limit Maximum number of unique services to return
247
+ * @param offset Number of services to skip
248
+ * @returns Array of services with available offers
299
249
  */
300
- queryService(username: string, serviceFqn: string): Promise<string | null>;
250
+ discoverServices(
251
+ serviceName: string,
252
+ version: string,
253
+ limit: number,
254
+ offset: number
255
+ ): Promise<Service[]>;
301
256
 
302
257
  /**
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
258
+ * Gets a random available service by name and version
259
+ * Returns a single random offer that is available (answerer_peer_id IS NULL)
260
+ * @param serviceName Service name (e.g., 'chat')
261
+ * @param version Version string for semver matching (e.g., '1.0.0')
262
+ * @returns Random service with available offer, or null if none found
307
263
  */
308
- findServicesByName(username: string, serviceName: string): Promise<Service[]>;
264
+ getRandomService(serviceName: string, version: string): Promise<Service | null>;
309
265
 
310
266
  /**
311
267
  * Deletes a service (with ownership verification)
package/src/worker.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { createApp } from './app.ts';
2
2
  import { D1Storage } from './storage/d1.ts';
3
- import { generateSecretKey } from './crypto.ts';
4
3
  import { Config } from './config.ts';
5
4
 
6
5
  /**
@@ -8,7 +7,6 @@ import { Config } from './config.ts';
8
7
  */
9
8
  export interface Env {
10
9
  DB: D1Database;
11
- AUTH_SECRET?: string;
12
10
  OFFER_DEFAULT_TTL?: string;
13
11
  OFFER_MAX_TTL?: string;
14
12
  OFFER_MIN_TTL?: string;
@@ -25,9 +23,6 @@ export default {
25
23
  // Initialize D1 storage
26
24
  const storage = new D1Storage(env.DB);
27
25
 
28
- // Generate or use provided auth secret
29
- const authSecret = env.AUTH_SECRET || generateSecretKey();
30
-
31
26
  // Build config from environment
32
27
  const config: Config = {
33
28
  port: 0, // Not used in Workers
@@ -37,7 +32,6 @@ export default {
37
32
  ? env.CORS_ORIGINS.split(',').map(o => o.trim())
38
33
  : ['*'],
39
34
  version: env.VERSION || 'unknown',
40
- authSecret,
41
35
  offerDefaultTtl: env.OFFER_DEFAULT_TTL ? parseInt(env.OFFER_DEFAULT_TTL, 10) : 60000,
42
36
  offerMaxTtl: env.OFFER_MAX_TTL ? parseInt(env.OFFER_MAX_TTL, 10) : 86400000,
43
37
  offerMinTtl: env.OFFER_MIN_TTL ? parseInt(env.OFFER_MIN_TTL, 10) : 60000,
package/wrangler.toml CHANGED
@@ -7,7 +7,7 @@ compatibility_flags = ["nodejs_compat"]
7
7
  [[d1_databases]]
8
8
  binding = "DB"
9
9
  database_name = "rondevu-offers"
10
- database_id = "b94e3f71-816d-455b-a89d-927fa49532d0"
10
+ database_id = "3d469855-d37f-477b-b139-fa58843a54ff"
11
11
 
12
12
  # Environment variables
13
13
  [vars]
@@ -17,7 +17,7 @@ OFFER_MIN_TTL = "60000" # Min offer TTL: 1 minute
17
17
  MAX_OFFERS_PER_REQUEST = "100" # Max offers per request
18
18
  MAX_TOPICS_PER_OFFER = "50" # Max topics per offer
19
19
  CORS_ORIGINS = "*" # Comma-separated list of allowed origins
20
- VERSION = "0.1.0" # Semantic version
20
+ VERSION = "0.4.0" # Semantic version
21
21
 
22
22
  # AUTH_SECRET should be set as a secret, not a var
23
23
  # Run: npx wrangler secret put AUTH_SECRET
@@ -39,7 +39,7 @@ command = ""
39
39
 
40
40
  [observability]
41
41
  [observability.logs]
42
- enabled = false
42
+ enabled = true
43
43
  head_sampling_rate = 1
44
44
  invocation_logs = true
45
45
  persist = true
@@ -1,51 +0,0 @@
1
- import { Context, Next } from 'hono';
2
- import { validateCredentials } from '../crypto.ts';
3
-
4
- /**
5
- * Authentication middleware for Rondevu
6
- * Validates Bearer token in format: {peerId}:{encryptedSecret}
7
- */
8
- export function createAuthMiddleware(authSecret: string) {
9
- return async (c: Context, next: Next) => {
10
- const authHeader = c.req.header('Authorization');
11
-
12
- if (!authHeader) {
13
- return c.json({ error: 'Missing Authorization header' }, 401);
14
- }
15
-
16
- // Expect format: Bearer {peerId}:{secret}
17
- const parts = authHeader.split(' ');
18
- if (parts.length !== 2 || parts[0] !== 'Bearer') {
19
- return c.json({ error: 'Invalid Authorization header format. Expected: Bearer {peerId}:{secret}' }, 401);
20
- }
21
-
22
- const credentials = parts[1].split(':');
23
- if (credentials.length !== 2) {
24
- return c.json({ error: 'Invalid credentials format. Expected: {peerId}:{secret}' }, 401);
25
- }
26
-
27
- const [peerId, encryptedSecret] = credentials;
28
-
29
- // Validate credentials (async operation)
30
- const isValid = await validateCredentials(peerId, encryptedSecret, authSecret);
31
- if (!isValid) {
32
- return c.json({ error: 'Invalid credentials' }, 401);
33
- }
34
-
35
- // Attach peer ID to context for use in handlers
36
- c.set('peerId', peerId);
37
-
38
- await next();
39
- };
40
- }
41
-
42
- /**
43
- * Helper to get authenticated peer ID from context
44
- */
45
- export function getAuthenticatedPeerId(c: Context): string {
46
- const peerId = c.get('peerId');
47
- if (!peerId) {
48
- throw new Error('No authenticated peer ID in context');
49
- }
50
- return peerId;
51
- }