@redseat/api 0.0.12 → 0.0.14

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/firebase.md CHANGED
@@ -1,602 +1,602 @@
1
- # Firebase Connection and Server Retrieval
2
-
3
- This guide explains how to connect to Firebase Realtime Database and retrieve a list of servers stored for a user.
4
-
5
- ## Prerequisites
6
-
7
- ### Install Firebase SDK
8
-
9
- First, install the Firebase JavaScript SDK:
10
-
11
- ```bash
12
- npm install firebase
13
- # or
14
- pnpm add firebase
15
- # or
16
- yarn add firebase
17
- ```
18
-
19
- ### Firebase Configuration
20
-
21
- You'll need your Firebase project configuration. The configuration typically includes:
22
-
23
- - `apiKey`: Your Firebase API key
24
- - `authDomain`: Your Firebase auth domain
25
- - `databaseURL`: Your Firebase Realtime Database URL
26
- - `projectId`: Your Firebase project ID
27
- - `storageBucket`: Your Firebase storage bucket
28
- - `messagingSenderId`: Your messaging sender ID
29
- - `appId`: Your Firebase app ID
30
-
31
- These values can be obtained from your Firebase project settings in the Firebase Console.
32
-
33
- ## Firebase Initialization
34
-
35
- ### Client-Side Initialization
36
-
37
- Create a Firebase initialization file (e.g., `firebase.ts`):
38
-
39
- ```typescript
40
- import { initializeApp } from 'firebase/app';
41
- import { getAuth } from 'firebase/auth';
42
- import { getDatabase } from 'firebase/database';
43
-
44
- // Firebase configuration
45
- const firebaseConfig = {
46
- apiKey: "YOUR_API_KEY",
47
- authDomain: "YOUR_AUTH_DOMAIN",
48
- databaseURL: "YOUR_DATABASE_URL",
49
- projectId: "YOUR_PROJECT_ID",
50
- storageBucket: "YOUR_STORAGE_BUCKET",
51
- messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
52
- appId: "YOUR_APP_ID"
53
- };
54
-
55
- // Initialize Firebase
56
- const app = initializeApp(firebaseConfig);
57
-
58
- // Initialize Firebase services
59
- export const auth = getAuth(app);
60
- export const firebaseDb = getDatabase(app);
61
- ```
62
-
63
- ### Server-Side Initialization (Admin SDK)
64
-
65
- For server-side operations, use the Firebase Admin SDK:
66
-
67
- ```typescript
68
- import admin from 'firebase-admin';
69
-
70
- const config = {
71
- apiKey: "YOUR_API_KEY",
72
- authDomain: "YOUR_AUTH_DOMAIN",
73
- databaseURL: "YOUR_DATABASE_URL",
74
- projectId: "YOUR_PROJECT_ID",
75
- storageBucket: "YOUR_STORAGE_BUCKET",
76
- messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
77
- appId: "YOUR_APP_ID",
78
- credential: admin.credential.cert({
79
- project_id: "YOUR_PROJECT_ID",
80
- private_key: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
81
- client_email: "YOUR_SERVICE_ACCOUNT_EMAIL"
82
- })
83
- };
84
-
85
- const firebaseServer = !admin.apps.length
86
- ? admin.initializeApp(config)
87
- : admin.app();
88
-
89
- export default firebaseServer;
90
- ```
91
-
92
- ## Authentication
93
-
94
- ### Getting User ID Token
95
-
96
- To retrieve servers, you need an authenticated user. Here's how to get the user's ID token:
97
-
98
- ```typescript
99
- import { auth } from './firebase';
100
- import { onIdTokenChanged } from 'firebase/auth';
101
-
102
- // Listen for auth state changes
103
- onIdTokenChanged(auth, async (user) => {
104
- if (user) {
105
- // Get the ID token
106
- const idToken = await user.getIdToken();
107
- console.log('User ID token:', idToken);
108
- console.log('User UID:', user.uid);
109
- }
110
- });
111
- ```
112
-
113
- ### Manual Token Retrieval
114
-
115
- If you already have a user object:
116
-
117
- ```typescript
118
- import { auth } from './firebase';
119
-
120
- async function getIdToken(forceRefresh = false): Promise<string | undefined> {
121
- const user = auth.currentUser;
122
- if (user) {
123
- return await user.getIdToken(forceRefresh);
124
- }
125
- return undefined;
126
- }
127
- ```
128
-
129
- ## Database Structure
130
-
131
- Servers are stored in Firebase Realtime Database at the following path:
132
-
133
- ```
134
- users/{userId}/servers
135
- ```
136
-
137
- The data structure is an object where:
138
- - **Keys**: Server IDs (generated by Firebase)
139
- - **Values**: Server objects with properties like `name`, `id`, `url`, `port`, etc.
140
-
141
- Example structure:
142
- ```json
143
- {
144
- "users": {
145
- "user123": {
146
- "servers": {
147
- "server-id-1": {
148
- "name": "My Server",
149
- "id": "server-id-1",
150
- "url": "example.com",
151
- "port": 443
152
- },
153
- "server-id-2": {
154
- "name": "Another Server",
155
- "id": "server-id-2",
156
- "url": "another.example.com"
157
- }
158
- }
159
- }
160
- }
161
- }
162
- ```
163
-
164
- ## Retrieving Servers
165
-
166
- ### Client-Side Retrieval
167
-
168
- Using the Firebase client SDK:
169
-
170
- ```typescript
171
- import { ref, get } from 'firebase/database';
172
- import { firebaseDb } from './firebase';
173
- import type { IServer } from './interfaces';
174
-
175
- interface IUserServer {
176
- name: string;
177
- id: string;
178
- url?: string;
179
- port?: number;
180
- }
181
-
182
- async function getServers(userId: string): Promise<IUserServer[]> {
183
- try {
184
- // Create a reference to the servers path
185
- const serversRef = ref(firebaseDb, `users/${userId}/servers`);
186
-
187
- // Get the data snapshot
188
- const snapshot = await get(serversRef);
189
- const serversData = snapshot.val();
190
-
191
- // Convert object to array
192
- // Firebase returns an object with server IDs as keys
193
- const serversArray: IUserServer[] = serversData
194
- ? Object.entries(serversData).map<IUserServer>(([id, server]) => ({
195
- ...(server as IUserServer),
196
- id: id // Ensure ID is set from the key
197
- }))
198
- : [];
199
-
200
- return serversArray;
201
- } catch (error) {
202
- console.error('Error retrieving servers:', error);
203
- throw error;
204
- }
205
- }
206
- ```
207
-
208
- ### Complete Example with Authentication
209
-
210
- Here's a complete example that combines authentication and server retrieval:
211
-
212
- ```typescript
213
- import { ref, get } from 'firebase/database';
214
- import { onIdTokenChanged } from 'firebase/auth';
215
- import { auth, firebaseDb } from './firebase';
216
- import type { IServer } from './interfaces';
217
-
218
- interface IUserServer {
219
- name: string;
220
- id: string;
221
- url?: string;
222
- port?: number;
223
- }
224
-
225
- class ServerManager {
226
- private servers: IUserServer[] = [];
227
-
228
- constructor() {
229
- // Listen for authentication state changes
230
- onIdTokenChanged(auth, async (user) => {
231
- if (user) {
232
- // User is authenticated, retrieve servers
233
- await this.loadServers(user.uid);
234
- } else {
235
- // User is not authenticated, clear servers
236
- this.servers = [];
237
- }
238
- });
239
- }
240
-
241
- async loadServers(userId: string): Promise<void> {
242
- try {
243
- const serversRef = ref(firebaseDb, `users/${userId}/servers`);
244
- const snapshot = await get(serversRef);
245
- const serversData = snapshot.val();
246
-
247
- this.servers = serversData
248
- ? Object.entries(serversData).map<IUserServer>(([id, server]) => ({
249
- ...(server as IUserServer),
250
- id: id
251
- }))
252
- : [];
253
- } catch (error) {
254
- console.error('Error loading servers:', error);
255
- throw error;
256
- }
257
- }
258
-
259
- getServers(): IUserServer[] {
260
- return this.servers;
261
- }
262
- }
263
- ```
264
-
265
- ### Server-Side Retrieval (Admin SDK)
266
-
267
- For server-side operations using the Admin SDK:
268
-
269
- ```typescript
270
- import firebaseServer from './firebase-server';
271
- import type { IServer } from './interfaces';
272
-
273
- interface IUserServer {
274
- name: string;
275
- id: string;
276
- url?: string;
277
- port?: number;
278
- }
279
-
280
- async function getServers(userId: string): Promise<IUserServer[]> {
281
- try {
282
- const serversRef = firebaseServer.database().ref(`users/${userId}/servers`);
283
- const snapshot = await serversRef.get();
284
- const serversData = snapshot.val();
285
-
286
- const serversArray: IUserServer[] = serversData
287
- ? Object.entries(serversData).map<IUserServer>(([id, server]) => ({
288
- ...(server as IUserServer),
289
- id: id
290
- }))
291
- : [];
292
-
293
- return serversArray;
294
- } catch (error) {
295
- console.error('Error retrieving servers:', error);
296
- throw error;
297
- }
298
- }
299
- ```
300
-
301
- ### Server-Side with Token Verification
302
-
303
- When handling API requests, verify the ID token first:
304
-
305
- ```typescript
306
- import firebaseServer from './firebase-server';
307
- import type { IServer } from './interfaces';
308
-
309
- async function getServersForUser(idToken: string): Promise<IUserServer[]> {
310
- try {
311
- // Verify the ID token
312
- const decodedToken = await firebaseServer.auth().verifyIdToken(idToken);
313
- const userId = decodedToken.uid;
314
-
315
- // Retrieve servers for the authenticated user
316
- const serversRef = firebaseServer.database().ref(`users/${userId}/servers`);
317
- const snapshot = await serversRef.get();
318
- const serversData = snapshot.val();
319
-
320
- const serversArray: IUserServer[] = serversData
321
- ? Object.entries(serversData).map<IUserServer>(([id, server]) => ({
322
- ...(server as IUserServer),
323
- id: id
324
- }))
325
- : [];
326
-
327
- return serversArray;
328
- } catch (error) {
329
- console.error('Error retrieving servers:', error);
330
- throw error;
331
- }
332
- }
333
- ```
334
-
335
- ## Real-Time Updates
336
-
337
- To listen for real-time changes to the servers list:
338
-
339
- ```typescript
340
- import { ref, onValue, Unsubscribe } from 'firebase/database';
341
- import { firebaseDb } from './firebase';
342
-
343
- function subscribeToServers(
344
- userId: string,
345
- callback: (servers: IUserServer[]) => void
346
- ): Unsubscribe {
347
- const serversRef = ref(firebaseDb, `users/${userId}/servers`);
348
-
349
- return onValue(serversRef, (snapshot) => {
350
- const serversData = snapshot.val();
351
- const serversArray: IUserServer[] = serversData
352
- ? Object.entries(serversData).map<IUserServer>(([id, server]) => ({
353
- ...(server as IUserServer),
354
- id: id
355
- }))
356
- : [];
357
-
358
- callback(serversArray);
359
- }, (error) => {
360
- console.error('Error listening to servers:', error);
361
- });
362
- }
363
-
364
- // Usage
365
- const unsubscribe = subscribeToServers(userId, (servers) => {
366
- console.log('Servers updated:', servers);
367
- });
368
-
369
- // Cleanup when done
370
- // unsubscribe();
371
- ```
372
-
373
- ## TypeScript Interfaces
374
-
375
- The `IServer` interface is defined in `packages/api/src/interfaces.ts`:
376
-
377
- ```typescript
378
- export interface IServer {
379
- id: string;
380
- name: string;
381
- url: string;
382
- port?: number;
383
- }
384
- ```
385
-
386
- For user-specific server data, you may also use:
387
-
388
- ```typescript
389
- interface IUserServer {
390
- name: string;
391
- id: string;
392
- url?: string;
393
- port?: number;
394
- }
395
- ```
396
-
397
- ## Error Handling
398
-
399
- Always handle potential errors when working with Firebase:
400
-
401
- ```typescript
402
- import { ref, get } from 'firebase/database';
403
- import { firebaseDb } from './firebase';
404
-
405
- async function getServersSafely(userId: string): Promise<IUserServer[]> {
406
- try {
407
- const serversRef = ref(firebaseDb, `users/${userId}/servers`);
408
- const snapshot = await get(serversRef);
409
-
410
- if (!snapshot.exists()) {
411
- console.log('No servers found for user');
412
- return [];
413
- }
414
-
415
- const serversData = snapshot.val();
416
- return serversData
417
- ? Object.entries(serversData).map<IUserServer>(([id, server]) => ({
418
- ...(server as IUserServer),
419
- id: id
420
- }))
421
- : [];
422
- } catch (error) {
423
- if (error.code === 'PERMISSION_DENIED') {
424
- console.error('Permission denied. Check Firebase security rules.');
425
- } else if (error.code === 'UNAVAILABLE') {
426
- console.error('Firebase service unavailable.');
427
- } else {
428
- console.error('Unexpected error:', error);
429
- }
430
- throw error;
431
- }
432
- }
433
- ```
434
-
435
- ## Security Rules
436
-
437
- Ensure your Firebase Realtime Database security rules allow authenticated users to read their own servers:
438
-
439
- ```json
440
- {
441
- "rules": {
442
- "users": {
443
- "$uid": {
444
- "servers": {
445
- ".read": "$uid === auth.uid",
446
- ".write": "$uid === auth.uid"
447
- }
448
- }
449
- }
450
- }
451
- }
452
- ```
453
-
454
- ## Complete Example
455
-
456
- Here's a complete, production-ready example:
457
-
458
- ```typescript
459
- import { initializeApp } from 'firebase/app';
460
- import { getAuth, onIdTokenChanged, User } from 'firebase/auth';
461
- import { getDatabase, ref, get, onValue, Unsubscribe } from 'firebase/database';
462
- import type { IServer } from './interfaces';
463
-
464
- // Firebase configuration
465
- const firebaseConfig = {
466
- apiKey: process.env.FIREBASE_API_KEY,
467
- authDomain: process.env.FIREBASE_AUTH_DOMAIN,
468
- databaseURL: process.env.FIREBASE_DATABASE_URL,
469
- projectId: process.env.FIREBASE_PROJECT_ID,
470
- storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
471
- messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
472
- appId: process.env.FIREBASE_APP_ID
473
- };
474
-
475
- const app = initializeApp(firebaseConfig);
476
- const auth = getAuth(app);
477
- const db = getDatabase(app);
478
-
479
- interface IUserServer extends IServer {
480
- name: string;
481
- }
482
-
483
- class FirebaseServerService {
484
- private unsubscribeAuth?: () => void;
485
- private unsubscribeServers?: Unsubscribe;
486
- private servers: IUserServer[] = [];
487
-
488
- constructor(
489
- private onServersChanged?: (servers: IUserServer[]) => void
490
- ) {
491
- this.initialize();
492
- }
493
-
494
- private initialize(): void {
495
- this.unsubscribeAuth = onIdTokenChanged(auth, async (user) => {
496
- if (user) {
497
- await this.loadServers(user.uid);
498
- this.subscribeToServers(user.uid);
499
- } else {
500
- this.servers = [];
501
- this.unsubscribeServers?.();
502
- this.onServersChanged?.([]);
503
- }
504
- });
505
- }
506
-
507
- private async loadServers(userId: string): Promise<void> {
508
- try {
509
- const serversRef = ref(db, `users/${userId}/servers`);
510
- const snapshot = await get(serversRef);
511
-
512
- if (snapshot.exists()) {
513
- const serversData = snapshot.val();
514
- this.servers = Object.entries(serversData).map<IUserServer>(
515
- ([id, server]) => ({
516
- ...(server as IUserServer),
517
- id: id
518
- })
519
- );
520
- this.onServersChanged?.(this.servers);
521
- }
522
- } catch (error) {
523
- console.error('Error loading servers:', error);
524
- throw error;
525
- }
526
- }
527
-
528
- private subscribeToServers(userId: string): void {
529
- const serversRef = ref(db, `users/${userId}/servers`);
530
-
531
- this.unsubscribeServers = onValue(serversRef, (snapshot) => {
532
- if (snapshot.exists()) {
533
- const serversData = snapshot.val();
534
- this.servers = Object.entries(serversData).map<IUserServer>(
535
- ([id, server]) => ({
536
- ...(server as IUserServer),
537
- id: id
538
- })
539
- );
540
- this.onServersChanged?.(this.servers);
541
- } else {
542
- this.servers = [];
543
- this.onServersChanged?.([]);
544
- }
545
- }, (error) => {
546
- console.error('Error listening to servers:', error);
547
- });
548
- }
549
-
550
- getServers(): IUserServer[] {
551
- return this.servers;
552
- }
553
-
554
- async getServersOnce(userId: string): Promise<IUserServer[]> {
555
- const serversRef = ref(db, `users/${userId}/servers`);
556
- const snapshot = await get(serversRef);
557
-
558
- if (!snapshot.exists()) {
559
- return [];
560
- }
561
-
562
- const serversData = snapshot.val();
563
- return Object.entries(serversData).map<IUserServer>(
564
- ([id, server]) => ({
565
- ...(server as IUserServer),
566
- id: id
567
- })
568
- );
569
- }
570
-
571
- dispose(): void {
572
- this.unsubscribeAuth?.();
573
- this.unsubscribeServers?.();
574
- }
575
- }
576
-
577
- // Usage
578
- const serverService = new FirebaseServerService((servers) => {
579
- console.log('Servers updated:', servers);
580
- });
581
-
582
- // Get current servers
583
- const currentServers = serverService.getServers();
584
-
585
- // Cleanup when done
586
- // serverService.dispose();
587
- ```
588
-
589
- ## Summary
590
-
591
- To retrieve servers from Firebase:
592
-
593
- 1. **Install Firebase SDK**: `npm install firebase`
594
- 2. **Initialize Firebase**: Configure and initialize the Firebase app
595
- 3. **Authenticate User**: Get the user's ID token
596
- 4. **Access Database**: Create a reference to `users/{userId}/servers`
597
- 5. **Retrieve Data**: Use `get()` for one-time reads or `onValue()` for real-time updates
598
- 6. **Transform Data**: Convert the Firebase object structure to an array using `Object.entries()`
599
- 7. **Handle Errors**: Implement proper error handling for network and permission issues
600
-
601
- The key pattern is converting Firebase's object structure (where keys are server IDs) to an array format suitable for application use.
602
-
1
+ # Firebase Connection and Server Retrieval
2
+
3
+ This guide explains how to connect to Firebase Realtime Database and retrieve a list of servers stored for a user.
4
+
5
+ ## Prerequisites
6
+
7
+ ### Install Firebase SDK
8
+
9
+ First, install the Firebase JavaScript SDK:
10
+
11
+ ```bash
12
+ npm install firebase
13
+ # or
14
+ pnpm add firebase
15
+ # or
16
+ yarn add firebase
17
+ ```
18
+
19
+ ### Firebase Configuration
20
+
21
+ You'll need your Firebase project configuration. The configuration typically includes:
22
+
23
+ - `apiKey`: Your Firebase API key
24
+ - `authDomain`: Your Firebase auth domain
25
+ - `databaseURL`: Your Firebase Realtime Database URL
26
+ - `projectId`: Your Firebase project ID
27
+ - `storageBucket`: Your Firebase storage bucket
28
+ - `messagingSenderId`: Your messaging sender ID
29
+ - `appId`: Your Firebase app ID
30
+
31
+ These values can be obtained from your Firebase project settings in the Firebase Console.
32
+
33
+ ## Firebase Initialization
34
+
35
+ ### Client-Side Initialization
36
+
37
+ Create a Firebase initialization file (e.g., `firebase.ts`):
38
+
39
+ ```typescript
40
+ import { initializeApp } from 'firebase/app';
41
+ import { getAuth } from 'firebase/auth';
42
+ import { getDatabase } from 'firebase/database';
43
+
44
+ // Firebase configuration
45
+ const firebaseConfig = {
46
+ apiKey: "YOUR_API_KEY",
47
+ authDomain: "YOUR_AUTH_DOMAIN",
48
+ databaseURL: "YOUR_DATABASE_URL",
49
+ projectId: "YOUR_PROJECT_ID",
50
+ storageBucket: "YOUR_STORAGE_BUCKET",
51
+ messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
52
+ appId: "YOUR_APP_ID"
53
+ };
54
+
55
+ // Initialize Firebase
56
+ const app = initializeApp(firebaseConfig);
57
+
58
+ // Initialize Firebase services
59
+ export const auth = getAuth(app);
60
+ export const firebaseDb = getDatabase(app);
61
+ ```
62
+
63
+ ### Server-Side Initialization (Admin SDK)
64
+
65
+ For server-side operations, use the Firebase Admin SDK:
66
+
67
+ ```typescript
68
+ import admin from 'firebase-admin';
69
+
70
+ const config = {
71
+ apiKey: "YOUR_API_KEY",
72
+ authDomain: "YOUR_AUTH_DOMAIN",
73
+ databaseURL: "YOUR_DATABASE_URL",
74
+ projectId: "YOUR_PROJECT_ID",
75
+ storageBucket: "YOUR_STORAGE_BUCKET",
76
+ messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
77
+ appId: "YOUR_APP_ID",
78
+ credential: admin.credential.cert({
79
+ project_id: "YOUR_PROJECT_ID",
80
+ private_key: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
81
+ client_email: "YOUR_SERVICE_ACCOUNT_EMAIL"
82
+ })
83
+ };
84
+
85
+ const firebaseServer = !admin.apps.length
86
+ ? admin.initializeApp(config)
87
+ : admin.app();
88
+
89
+ export default firebaseServer;
90
+ ```
91
+
92
+ ## Authentication
93
+
94
+ ### Getting User ID Token
95
+
96
+ To retrieve servers, you need an authenticated user. Here's how to get the user's ID token:
97
+
98
+ ```typescript
99
+ import { auth } from './firebase';
100
+ import { onIdTokenChanged } from 'firebase/auth';
101
+
102
+ // Listen for auth state changes
103
+ onIdTokenChanged(auth, async (user) => {
104
+ if (user) {
105
+ // Get the ID token
106
+ const idToken = await user.getIdToken();
107
+ console.log('User ID token:', idToken);
108
+ console.log('User UID:', user.uid);
109
+ }
110
+ });
111
+ ```
112
+
113
+ ### Manual Token Retrieval
114
+
115
+ If you already have a user object:
116
+
117
+ ```typescript
118
+ import { auth } from './firebase';
119
+
120
+ async function getIdToken(forceRefresh = false): Promise<string | undefined> {
121
+ const user = auth.currentUser;
122
+ if (user) {
123
+ return await user.getIdToken(forceRefresh);
124
+ }
125
+ return undefined;
126
+ }
127
+ ```
128
+
129
+ ## Database Structure
130
+
131
+ Servers are stored in Firebase Realtime Database at the following path:
132
+
133
+ ```
134
+ users/{userId}/servers
135
+ ```
136
+
137
+ The data structure is an object where:
138
+ - **Keys**: Server IDs (generated by Firebase)
139
+ - **Values**: Server objects with properties like `name`, `id`, `url`, `port`, etc.
140
+
141
+ Example structure:
142
+ ```json
143
+ {
144
+ "users": {
145
+ "user123": {
146
+ "servers": {
147
+ "server-id-1": {
148
+ "name": "My Server",
149
+ "id": "server-id-1",
150
+ "url": "example.com",
151
+ "port": 443
152
+ },
153
+ "server-id-2": {
154
+ "name": "Another Server",
155
+ "id": "server-id-2",
156
+ "url": "another.example.com"
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+ ## Retrieving Servers
165
+
166
+ ### Client-Side Retrieval
167
+
168
+ Using the Firebase client SDK:
169
+
170
+ ```typescript
171
+ import { ref, get } from 'firebase/database';
172
+ import { firebaseDb } from './firebase';
173
+ import type { IServer } from './interfaces';
174
+
175
+ interface IUserServer {
176
+ name: string;
177
+ id: string;
178
+ url?: string;
179
+ port?: number;
180
+ }
181
+
182
+ async function getServers(userId: string): Promise<IUserServer[]> {
183
+ try {
184
+ // Create a reference to the servers path
185
+ const serversRef = ref(firebaseDb, `users/${userId}/servers`);
186
+
187
+ // Get the data snapshot
188
+ const snapshot = await get(serversRef);
189
+ const serversData = snapshot.val();
190
+
191
+ // Convert object to array
192
+ // Firebase returns an object with server IDs as keys
193
+ const serversArray: IUserServer[] = serversData
194
+ ? Object.entries(serversData).map<IUserServer>(([id, server]) => ({
195
+ ...(server as IUserServer),
196
+ id: id // Ensure ID is set from the key
197
+ }))
198
+ : [];
199
+
200
+ return serversArray;
201
+ } catch (error) {
202
+ console.error('Error retrieving servers:', error);
203
+ throw error;
204
+ }
205
+ }
206
+ ```
207
+
208
+ ### Complete Example with Authentication
209
+
210
+ Here's a complete example that combines authentication and server retrieval:
211
+
212
+ ```typescript
213
+ import { ref, get } from 'firebase/database';
214
+ import { onIdTokenChanged } from 'firebase/auth';
215
+ import { auth, firebaseDb } from './firebase';
216
+ import type { IServer } from './interfaces';
217
+
218
+ interface IUserServer {
219
+ name: string;
220
+ id: string;
221
+ url?: string;
222
+ port?: number;
223
+ }
224
+
225
+ class ServerManager {
226
+ private servers: IUserServer[] = [];
227
+
228
+ constructor() {
229
+ // Listen for authentication state changes
230
+ onIdTokenChanged(auth, async (user) => {
231
+ if (user) {
232
+ // User is authenticated, retrieve servers
233
+ await this.loadServers(user.uid);
234
+ } else {
235
+ // User is not authenticated, clear servers
236
+ this.servers = [];
237
+ }
238
+ });
239
+ }
240
+
241
+ async loadServers(userId: string): Promise<void> {
242
+ try {
243
+ const serversRef = ref(firebaseDb, `users/${userId}/servers`);
244
+ const snapshot = await get(serversRef);
245
+ const serversData = snapshot.val();
246
+
247
+ this.servers = serversData
248
+ ? Object.entries(serversData).map<IUserServer>(([id, server]) => ({
249
+ ...(server as IUserServer),
250
+ id: id
251
+ }))
252
+ : [];
253
+ } catch (error) {
254
+ console.error('Error loading servers:', error);
255
+ throw error;
256
+ }
257
+ }
258
+
259
+ getServers(): IUserServer[] {
260
+ return this.servers;
261
+ }
262
+ }
263
+ ```
264
+
265
+ ### Server-Side Retrieval (Admin SDK)
266
+
267
+ For server-side operations using the Admin SDK:
268
+
269
+ ```typescript
270
+ import firebaseServer from './firebase-server';
271
+ import type { IServer } from './interfaces';
272
+
273
+ interface IUserServer {
274
+ name: string;
275
+ id: string;
276
+ url?: string;
277
+ port?: number;
278
+ }
279
+
280
+ async function getServers(userId: string): Promise<IUserServer[]> {
281
+ try {
282
+ const serversRef = firebaseServer.database().ref(`users/${userId}/servers`);
283
+ const snapshot = await serversRef.get();
284
+ const serversData = snapshot.val();
285
+
286
+ const serversArray: IUserServer[] = serversData
287
+ ? Object.entries(serversData).map<IUserServer>(([id, server]) => ({
288
+ ...(server as IUserServer),
289
+ id: id
290
+ }))
291
+ : [];
292
+
293
+ return serversArray;
294
+ } catch (error) {
295
+ console.error('Error retrieving servers:', error);
296
+ throw error;
297
+ }
298
+ }
299
+ ```
300
+
301
+ ### Server-Side with Token Verification
302
+
303
+ When handling API requests, verify the ID token first:
304
+
305
+ ```typescript
306
+ import firebaseServer from './firebase-server';
307
+ import type { IServer } from './interfaces';
308
+
309
+ async function getServersForUser(idToken: string): Promise<IUserServer[]> {
310
+ try {
311
+ // Verify the ID token
312
+ const decodedToken = await firebaseServer.auth().verifyIdToken(idToken);
313
+ const userId = decodedToken.uid;
314
+
315
+ // Retrieve servers for the authenticated user
316
+ const serversRef = firebaseServer.database().ref(`users/${userId}/servers`);
317
+ const snapshot = await serversRef.get();
318
+ const serversData = snapshot.val();
319
+
320
+ const serversArray: IUserServer[] = serversData
321
+ ? Object.entries(serversData).map<IUserServer>(([id, server]) => ({
322
+ ...(server as IUserServer),
323
+ id: id
324
+ }))
325
+ : [];
326
+
327
+ return serversArray;
328
+ } catch (error) {
329
+ console.error('Error retrieving servers:', error);
330
+ throw error;
331
+ }
332
+ }
333
+ ```
334
+
335
+ ## Real-Time Updates
336
+
337
+ To listen for real-time changes to the servers list:
338
+
339
+ ```typescript
340
+ import { ref, onValue, Unsubscribe } from 'firebase/database';
341
+ import { firebaseDb } from './firebase';
342
+
343
+ function subscribeToServers(
344
+ userId: string,
345
+ callback: (servers: IUserServer[]) => void
346
+ ): Unsubscribe {
347
+ const serversRef = ref(firebaseDb, `users/${userId}/servers`);
348
+
349
+ return onValue(serversRef, (snapshot) => {
350
+ const serversData = snapshot.val();
351
+ const serversArray: IUserServer[] = serversData
352
+ ? Object.entries(serversData).map<IUserServer>(([id, server]) => ({
353
+ ...(server as IUserServer),
354
+ id: id
355
+ }))
356
+ : [];
357
+
358
+ callback(serversArray);
359
+ }, (error) => {
360
+ console.error('Error listening to servers:', error);
361
+ });
362
+ }
363
+
364
+ // Usage
365
+ const unsubscribe = subscribeToServers(userId, (servers) => {
366
+ console.log('Servers updated:', servers);
367
+ });
368
+
369
+ // Cleanup when done
370
+ // unsubscribe();
371
+ ```
372
+
373
+ ## TypeScript Interfaces
374
+
375
+ The `IServer` interface is defined in `packages/api/src/interfaces.ts`:
376
+
377
+ ```typescript
378
+ export interface IServer {
379
+ id: string;
380
+ name: string;
381
+ url: string;
382
+ port?: number;
383
+ }
384
+ ```
385
+
386
+ For user-specific server data, you may also use:
387
+
388
+ ```typescript
389
+ interface IUserServer {
390
+ name: string;
391
+ id: string;
392
+ url?: string;
393
+ port?: number;
394
+ }
395
+ ```
396
+
397
+ ## Error Handling
398
+
399
+ Always handle potential errors when working with Firebase:
400
+
401
+ ```typescript
402
+ import { ref, get } from 'firebase/database';
403
+ import { firebaseDb } from './firebase';
404
+
405
+ async function getServersSafely(userId: string): Promise<IUserServer[]> {
406
+ try {
407
+ const serversRef = ref(firebaseDb, `users/${userId}/servers`);
408
+ const snapshot = await get(serversRef);
409
+
410
+ if (!snapshot.exists()) {
411
+ console.log('No servers found for user');
412
+ return [];
413
+ }
414
+
415
+ const serversData = snapshot.val();
416
+ return serversData
417
+ ? Object.entries(serversData).map<IUserServer>(([id, server]) => ({
418
+ ...(server as IUserServer),
419
+ id: id
420
+ }))
421
+ : [];
422
+ } catch (error) {
423
+ if (error.code === 'PERMISSION_DENIED') {
424
+ console.error('Permission denied. Check Firebase security rules.');
425
+ } else if (error.code === 'UNAVAILABLE') {
426
+ console.error('Firebase service unavailable.');
427
+ } else {
428
+ console.error('Unexpected error:', error);
429
+ }
430
+ throw error;
431
+ }
432
+ }
433
+ ```
434
+
435
+ ## Security Rules
436
+
437
+ Ensure your Firebase Realtime Database security rules allow authenticated users to read their own servers:
438
+
439
+ ```json
440
+ {
441
+ "rules": {
442
+ "users": {
443
+ "$uid": {
444
+ "servers": {
445
+ ".read": "$uid === auth.uid",
446
+ ".write": "$uid === auth.uid"
447
+ }
448
+ }
449
+ }
450
+ }
451
+ }
452
+ ```
453
+
454
+ ## Complete Example
455
+
456
+ Here's a complete, production-ready example:
457
+
458
+ ```typescript
459
+ import { initializeApp } from 'firebase/app';
460
+ import { getAuth, onIdTokenChanged, User } from 'firebase/auth';
461
+ import { getDatabase, ref, get, onValue, Unsubscribe } from 'firebase/database';
462
+ import type { IServer } from './interfaces';
463
+
464
+ // Firebase configuration
465
+ const firebaseConfig = {
466
+ apiKey: process.env.FIREBASE_API_KEY,
467
+ authDomain: process.env.FIREBASE_AUTH_DOMAIN,
468
+ databaseURL: process.env.FIREBASE_DATABASE_URL,
469
+ projectId: process.env.FIREBASE_PROJECT_ID,
470
+ storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
471
+ messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
472
+ appId: process.env.FIREBASE_APP_ID
473
+ };
474
+
475
+ const app = initializeApp(firebaseConfig);
476
+ const auth = getAuth(app);
477
+ const db = getDatabase(app);
478
+
479
+ interface IUserServer extends IServer {
480
+ name: string;
481
+ }
482
+
483
+ class FirebaseServerService {
484
+ private unsubscribeAuth?: () => void;
485
+ private unsubscribeServers?: Unsubscribe;
486
+ private servers: IUserServer[] = [];
487
+
488
+ constructor(
489
+ private onServersChanged?: (servers: IUserServer[]) => void
490
+ ) {
491
+ this.initialize();
492
+ }
493
+
494
+ private initialize(): void {
495
+ this.unsubscribeAuth = onIdTokenChanged(auth, async (user) => {
496
+ if (user) {
497
+ await this.loadServers(user.uid);
498
+ this.subscribeToServers(user.uid);
499
+ } else {
500
+ this.servers = [];
501
+ this.unsubscribeServers?.();
502
+ this.onServersChanged?.([]);
503
+ }
504
+ });
505
+ }
506
+
507
+ private async loadServers(userId: string): Promise<void> {
508
+ try {
509
+ const serversRef = ref(db, `users/${userId}/servers`);
510
+ const snapshot = await get(serversRef);
511
+
512
+ if (snapshot.exists()) {
513
+ const serversData = snapshot.val();
514
+ this.servers = Object.entries(serversData).map<IUserServer>(
515
+ ([id, server]) => ({
516
+ ...(server as IUserServer),
517
+ id: id
518
+ })
519
+ );
520
+ this.onServersChanged?.(this.servers);
521
+ }
522
+ } catch (error) {
523
+ console.error('Error loading servers:', error);
524
+ throw error;
525
+ }
526
+ }
527
+
528
+ private subscribeToServers(userId: string): void {
529
+ const serversRef = ref(db, `users/${userId}/servers`);
530
+
531
+ this.unsubscribeServers = onValue(serversRef, (snapshot) => {
532
+ if (snapshot.exists()) {
533
+ const serversData = snapshot.val();
534
+ this.servers = Object.entries(serversData).map<IUserServer>(
535
+ ([id, server]) => ({
536
+ ...(server as IUserServer),
537
+ id: id
538
+ })
539
+ );
540
+ this.onServersChanged?.(this.servers);
541
+ } else {
542
+ this.servers = [];
543
+ this.onServersChanged?.([]);
544
+ }
545
+ }, (error) => {
546
+ console.error('Error listening to servers:', error);
547
+ });
548
+ }
549
+
550
+ getServers(): IUserServer[] {
551
+ return this.servers;
552
+ }
553
+
554
+ async getServersOnce(userId: string): Promise<IUserServer[]> {
555
+ const serversRef = ref(db, `users/${userId}/servers`);
556
+ const snapshot = await get(serversRef);
557
+
558
+ if (!snapshot.exists()) {
559
+ return [];
560
+ }
561
+
562
+ const serversData = snapshot.val();
563
+ return Object.entries(serversData).map<IUserServer>(
564
+ ([id, server]) => ({
565
+ ...(server as IUserServer),
566
+ id: id
567
+ })
568
+ );
569
+ }
570
+
571
+ dispose(): void {
572
+ this.unsubscribeAuth?.();
573
+ this.unsubscribeServers?.();
574
+ }
575
+ }
576
+
577
+ // Usage
578
+ const serverService = new FirebaseServerService((servers) => {
579
+ console.log('Servers updated:', servers);
580
+ });
581
+
582
+ // Get current servers
583
+ const currentServers = serverService.getServers();
584
+
585
+ // Cleanup when done
586
+ // serverService.dispose();
587
+ ```
588
+
589
+ ## Summary
590
+
591
+ To retrieve servers from Firebase:
592
+
593
+ 1. **Install Firebase SDK**: `npm install firebase`
594
+ 2. **Initialize Firebase**: Configure and initialize the Firebase app
595
+ 3. **Authenticate User**: Get the user's ID token
596
+ 4. **Access Database**: Create a reference to `users/{userId}/servers`
597
+ 5. **Retrieve Data**: Use `get()` for one-time reads or `onValue()` for real-time updates
598
+ 6. **Transform Data**: Convert the Firebase object structure to an array using `Object.entries()`
599
+ 7. **Handle Errors**: Implement proper error handling for network and permission issues
600
+
601
+ The key pattern is converting Firebase's object structure (where keys are server IDs) to an array format suitable for application use.
602
+