@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/README.md +132 -132
- package/agents.md +275 -275
- package/client.md +318 -318
- package/dist/client.d.ts +0 -1
- package/dist/client.js +0 -3
- package/dist/interfaces.d.ts +25 -0
- package/dist/interfaces.js +17 -0
- package/dist/library.d.ts +2 -4
- package/dist/library.js +18 -1
- package/encryption.md +533 -533
- package/firebase.md +602 -602
- package/libraries.md +1652 -1652
- package/package.json +49 -49
- package/server.md +196 -196
- package/test.md +291 -291
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
|
+
|