Skip to content

Commit 9d33e3c

Browse files
fix: harden firebase rtdb server module
1 parent 5b97ec7 commit 9d33e3c

File tree

2 files changed

+95
-25
lines changed

2 files changed

+95
-25
lines changed
Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,57 @@
1-
import { InjectionToken, NgModule } from '@angular/core'
1+
import {
2+
InjectionToken,
3+
NgModule,
4+
ModuleWithProviders,
5+
Optional,
6+
SkipSelf
7+
} from '@angular/core'
28
import { ServerUniversalRtDbService } from './server.firebase.rtdb.service'
39
import { HttpClient } from '@angular/common/http'
410
import { UniversalRtDbService } from './browser.firebase.rtdb.service'
511
import { AngularFireDatabase } from 'angularfire2/database'
612

13+
export interface LruCache {
14+
readonly get: <T>(key: string) => T
15+
readonly set: <T>(key: string, value: T) => T
16+
}
17+
718
export const FIREBASE_USER_AUTH_TOKEN = new InjectionToken<string>(
819
'fng.fb.svr.usr.auth'
920
)
1021

1122
export const FIREBASE_DATABASE_URL = new InjectionToken<string>('fng.fb.db.url')
23+
export const LRU_CACHE = new InjectionToken<LruCache>('fng.lru')
1224

1325
// tslint:disable-next-line:no-class
14-
@NgModule({
15-
providers: [
16-
{
17-
provide: UniversalRtDbService,
18-
useClass: ServerUniversalRtDbService,
19-
deps: [HttpClient, AngularFireDatabase, FIREBASE_USER_AUTH_TOKEN]
26+
@NgModule()
27+
export class FirebaseServerModule {
28+
static forRoot(): ModuleWithProviders {
29+
return {
30+
ngModule: FirebaseServerModule,
31+
providers: [
32+
{
33+
provide: UniversalRtDbService,
34+
useClass: ServerUniversalRtDbService,
35+
deps: [
36+
HttpClient,
37+
AngularFireDatabase,
38+
FIREBASE_USER_AUTH_TOKEN,
39+
LRU_CACHE
40+
]
41+
}
42+
]
2043
}
21-
]
22-
})
23-
export class FirebaseServerModule {}
44+
}
45+
46+
constructor(
47+
@Optional()
48+
@SkipSelf()
49+
parentModule: FirebaseServerModule
50+
) {
51+
// tslint:disable-next-line:no-if-statement
52+
if (parentModule)
53+
throw new Error(
54+
'FirebaseServerModule already loaded. Import in root module only.'
55+
)
56+
}
57+
}
Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,47 @@
1-
import { catchError, take } from 'rxjs/operators'
1+
import { catchError, take, tap } from 'rxjs/operators'
22
import { AngularFireDatabase } from 'angularfire2/database'
33
import { Inject, Injectable, Optional } from '@angular/core'
44
import { HttpClient, HttpParams } from '@angular/common/http'
5-
import { FIREBASE_USER_AUTH_TOKEN } from './server.firebase.module'
5+
import {
6+
FIREBASE_USER_AUTH_TOKEN,
7+
LRU_CACHE,
8+
LruCache
9+
} from './server.firebase.module'
610
import { Observable, of } from 'rxjs'
711
import { IUniversalRtdbService } from './rtdb.interface'
12+
import { createHash } from 'crypto'
13+
14+
function sha256(data: string) {
15+
return createHash('sha256')
16+
.update(data)
17+
.digest('base64')
18+
}
19+
20+
function constructFbUrl(db: AngularFireDatabase, path: string) {
21+
const query = db.database.ref(path)
22+
return `${query.toString()}.json`
23+
}
24+
25+
function getFullUrl(base: string, params: HttpParams) {
26+
const stringifiedParams = params.toString()
27+
return stringifiedParams ? `${base}?${params.toString()}` : base
28+
}
29+
30+
function getParams(fromObject = {}) {
31+
return new HttpParams({
32+
fromObject
33+
})
34+
}
35+
36+
function mapUndefined(err: any) {
37+
return of(undefined)
38+
}
39+
40+
function attempToCacheInLru(key: string, lru?: LruCache) {
41+
return function(response?: any) {
42+
lru && lru.set(sha256(key), response)
43+
}
44+
}
845

946
// tslint:disable:no-this
1047
// tslint:disable-next-line:no-class
@@ -15,26 +52,25 @@ export class ServerUniversalRtDbService implements IUniversalRtdbService {
1552
private afdb: AngularFireDatabase,
1653
@Optional()
1754
@Inject(FIREBASE_USER_AUTH_TOKEN)
18-
private authToken?: string
55+
private authToken?: string,
56+
@Optional()
57+
@Inject(LRU_CACHE)
58+
private lru?: LruCache
1959
) {}
2060

2161
universalObject<T>(path: string): Observable<T | undefined> {
22-
const query = this.afdb.database.ref(path)
23-
const url = `${query.toString()}.json`
62+
const url = constructFbUrl(this.afdb, path)
63+
const params = getParams({ auth: this.authToken })
64+
const cacheKey = getFullUrl(url, params)
65+
2466
const baseObs = this.authToken
25-
? this.http.get<T>(url, {
26-
params: new HttpParams({
27-
fromObject: {
28-
auth: this.authToken
29-
}
30-
})
31-
})
67+
? this.http.get<T>(url, { params })
3268
: this.http.get<T>(url)
69+
3370
return baseObs.pipe(
3471
take(1),
35-
catchError(err => {
36-
return of(undefined)
37-
})
72+
tap(attempToCacheInLru(cacheKey, this.lru)),
73+
catchError(mapUndefined)
3874
)
3975
}
4076
}

0 commit comments

Comments
 (0)