Skip to content

Commit abe006a

Browse files
fix(firebase): add firestore module
1 parent 176eba8 commit abe006a

17 files changed

+555
-113
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {
2+
TransferState,
3+
StateKey,
4+
makeStateKey
5+
} from '@angular/platform-browser'
6+
import { HttpParams } from '@angular/common/http'
7+
8+
export function removeHttpInterceptorCache<T>(ts: TransferState, key: string) {
9+
return function(val: T) {
10+
ts.remove(makeStateKey<string>(`G.${key}`))
11+
}
12+
}
13+
14+
export function cacheInStateTransfer<T>(
15+
ts: TransferState,
16+
key: StateKey<string>
17+
) {
18+
return function(val: T) {
19+
ts.set(key, val)
20+
}
21+
}
22+
23+
export function getParams(fromObject = {} as any) {
24+
return new HttpParams({
25+
fromObject: Object.keys(fromObject).reduce((acc, curr) => {
26+
return fromObject[curr]
27+
? {
28+
...acc,
29+
[curr]: fromObject[curr]
30+
}
31+
: { ...acc }
32+
}, {})
33+
})
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { InjectionToken } from '@angular/core'
2+
import { createHash } from 'crypto'
3+
import { HttpParams } from '@angular/common/http'
4+
5+
export interface LruCache {
6+
readonly get: <T>(key: string) => T
7+
readonly set: <T>(key: string, value: T) => T
8+
}
9+
10+
export const LRU_CACHE = new InjectionToken<LruCache>('fng.lru')
11+
export const FIREBASE_USER_AUTH_TOKEN = new InjectionToken<string>(
12+
'fng.fb.svr.usr.auth'
13+
)
14+
15+
function sha256(data: string) {
16+
return createHash('sha256')
17+
.update(data)
18+
.digest('base64')
19+
}
20+
21+
export function attemptToCacheInLru(key: string, lru?: LruCache) {
22+
return function(response?: any) {
23+
lru && response && lru.set(sha256(key), response)
24+
}
25+
}
26+
27+
export function attemptToGetLruCachedValue<T>(key: string, lru?: LruCache) {
28+
return lru && lru.get<T>(sha256(key))
29+
}
30+
31+
export function getFullUrl(base: string, params: HttpParams) {
32+
const stringifiedParams = params.toString()
33+
return stringifiedParams ? `${base}?${params.toString()}` : base
34+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { AngularFirestore, QueryFn } from 'angularfire2/firestore'
2+
import { Observable } from 'rxjs'
3+
4+
export interface IUniversalFirestoreService {
5+
readonly universalDoc: <T>(path: string) => Observable<T | undefined>
6+
readonly universalCollection: <T>(
7+
path: string,
8+
queryFn?: QueryFn
9+
) => Observable<ReadonlyArray<T>>
10+
}
11+
12+
export function extractFsHostFromLib(affs: AngularFirestore) {
13+
return (affs.firestore.app.options as any).projectId
14+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {
2+
NgModule,
3+
ModuleWithProviders,
4+
Optional,
5+
SkipSelf
6+
} from '@angular/core'
7+
import { UniversalFirestoreService } from './browser.firebase.fs.service'
8+
9+
// tslint:disable-next-line:no-class
10+
@NgModule()
11+
export class FirebaseFsBrowserModule {
12+
static forRoot(): ModuleWithProviders {
13+
return {
14+
ngModule: FirebaseFsBrowserModule,
15+
providers: [UniversalFirestoreService]
16+
}
17+
}
18+
19+
constructor(
20+
@Optional()
21+
@SkipSelf()
22+
parentModule: FirebaseFsBrowserModule
23+
) {
24+
// tslint:disable-next-line:no-if-statement
25+
if (parentModule)
26+
throw new Error(
27+
'FirebaseFsBrowserModule already loaded. Import in root module only.'
28+
)
29+
}
30+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {
2+
catchError,
3+
distinctUntilChanged,
4+
startWith,
5+
filter,
6+
take
7+
} from 'rxjs/operators'
8+
import { Injectable, ApplicationRef } from '@angular/core'
9+
import { TransferState } from '@angular/platform-browser'
10+
import { sha1 } from 'object-hash'
11+
import { of } from 'rxjs'
12+
import { AngularFirestore, QueryFn } from 'angularfire2/firestore'
13+
import {
14+
IUniversalFirestoreService,
15+
extractFsHostFromLib
16+
} from './browser.firebase.fs.common'
17+
import {
18+
makeFirestoreStateTransferKey,
19+
constructFsUrl
20+
} from './server.firebase.fs.common'
21+
22+
// tslint:disable:no-this
23+
// tslint:disable-next-line:no-class
24+
@Injectable()
25+
export class UniversalFirestoreService implements IUniversalFirestoreService {
26+
constructor(
27+
private ts: TransferState,
28+
public afs: AngularFirestore,
29+
appRef: ApplicationRef
30+
) {
31+
appRef.isStable
32+
.pipe(
33+
filter(Boolean),
34+
take(1)
35+
)
36+
.subscribe(() => this.turnOffCache())
37+
}
38+
39+
// tslint:disable-next-line:readonly-keyword
40+
private readFromCache = true
41+
42+
private turnOffCache() {
43+
// tslint:disable-next-line:no-object-mutation
44+
this.readFromCache = false
45+
}
46+
47+
universalDoc<T>(path: string) {
48+
const url = constructFsUrl(extractFsHostFromLib(this.afs), path)
49+
const cached = this.ts.get<T | undefined>(
50+
makeFirestoreStateTransferKey(url),
51+
undefined
52+
)
53+
54+
const base = this.afs.doc<T>(path).valueChanges()
55+
56+
return this.readFromCache && cached
57+
? base.pipe(
58+
startWith(cached as T),
59+
distinctUntilChanged((x, y) => sha1(x) === sha1(y)),
60+
catchError(err => of(undefined))
61+
)
62+
: base
63+
}
64+
65+
universalCollection<T>(path: string, queryFn?: QueryFn) {
66+
const url = constructFsUrl(extractFsHostFromLib(this.afs), path, true)
67+
68+
const cached = this.ts.get<ReadonlyArray<T>>(
69+
makeFirestoreStateTransferKey(url),
70+
[]
71+
)
72+
const base = this.afs.collection<T>(path, queryFn).valueChanges()
73+
74+
return this.readFromCache && cached.length > 0
75+
? base.pipe(
76+
startWith(cached),
77+
distinctUntilChanged((x, y) => sha1(x) === sha1(y)),
78+
catchError(err => of(cached))
79+
)
80+
: base
81+
}
82+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { makeStateKey } from '@angular/platform-browser'
2+
3+
export function makeFirestoreStateTransferKey(fullUrl: string) {
4+
return makeStateKey<string>(`FS.${fullUrl}`)
5+
}
6+
7+
export function constructFsUrl(host: string, path?: string, runQuery = false) {
8+
return `https://firestore.googleapis.com/v1beta1/projects/${host}/databases/(default)/documents${
9+
runQuery ? ':runQuery' : '/' + path
10+
}`
11+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {
2+
NgModule,
3+
ModuleWithProviders,
4+
Optional,
5+
SkipSelf
6+
} from '@angular/core'
7+
import { UniversalFirestoreService } from './browser.firebase.fs.service'
8+
import { ServerUniversalFirestoreService } from './server.firebase.fs.service'
9+
10+
// tslint:disable-next-line:no-class
11+
@NgModule()
12+
export class FirebaseFsServerModule {
13+
static forRoot(): ModuleWithProviders {
14+
return {
15+
ngModule: FirebaseFsServerModule,
16+
providers: [
17+
{
18+
provide: UniversalFirestoreService,
19+
useClass: ServerUniversalFirestoreService
20+
}
21+
]
22+
}
23+
}
24+
25+
constructor(
26+
@Optional()
27+
@SkipSelf()
28+
parentModule: FirebaseFsServerModule
29+
) {
30+
// tslint:disable-next-line:no-if-statement
31+
if (parentModule)
32+
throw new Error(
33+
'FirebaseFsServerModule already loaded. Import in root module only.'
34+
)
35+
}
36+
}

0 commit comments

Comments
 (0)