-
Notifications
You must be signed in to change notification settings - Fork 185
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2cff86d
commit 7a57c8d
Showing
11 changed files
with
730 additions
and
185 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,12 @@ | ||
import { DataProvider } from "./providers/DataProvider"; | ||
import { AuthProvider } from "./providers/AuthProvider"; | ||
import { RAFirebaseOptions } from "./providers/RAFirebaseOptions"; | ||
import ReadsLogger, { getFirebaseReadsLogger } from "./misc/reads-logger"; | ||
|
||
export { | ||
DataProvider as FirebaseDataProvider, | ||
AuthProvider as FirebaseAuthProvider, | ||
RAFirebaseOptions as RAFirebaseOptions | ||
RAFirebaseOptions as RAFirebaseOptions, | ||
ReadsLogger as FirebaseReadsLogger, | ||
getFirebaseReadsLogger | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import { RAFirebaseOptions } from "../../providers/RAFirebaseOptions"; | ||
import { RAFirebaseReadsLoggerError } from "./types"; | ||
import ReadsLoggerLocalStorageUtils from "./ReadsLoggerLocalStorageUtils"; | ||
import { log, logError } from "../logger"; | ||
import { BehaviorSubject, Observable } from "rxjs"; | ||
|
||
type ResetFn = () => void; | ||
type IncrementFn = (newReads: number) => void; | ||
|
||
interface CounterStreams<TStreamOrSubject extends Observable<number> = Observable<number>> { | ||
pageReads: TStreamOrSubject; | ||
lastPageReads: TStreamOrSubject; | ||
customReads: TStreamOrSubject; | ||
lastCustomReads: TStreamOrSubject; | ||
sessionReads: TStreamOrSubject; | ||
lastSessionReads: TStreamOrSubject; | ||
} | ||
|
||
export default class ReadsLogger { | ||
/* | ||
* CURRENT PAGE FIREBASE READS COUNTER INTERFACE | ||
* | ||
* pageReads should be used to track firebase reads for displayed page. | ||
* It means that resetPageReads() should be called on every navigation | ||
* in react-admin application | ||
* | ||
* lastPageReads will contain reads for previously displayed page | ||
*/ | ||
public get pageReads(): number { | ||
return this._pageReads; | ||
} | ||
public get lastPageReads(): number { | ||
return this._lastPageReads; | ||
} | ||
public resetPageReads: ResetFn = () => { | ||
this._lastPageReads = this.pageReads; | ||
this._pageReads = 0; | ||
this.subjects.pageReads.next(this.pageReads); | ||
this.subjects.lastPageReads.next(this.lastPageReads); | ||
}; | ||
public incrementPageReads: IncrementFn = newReads => { | ||
this._pageReads += newReads; | ||
this.subjects.pageReads.next(this.pageReads); | ||
}; | ||
private _lastPageReads: number = 0; | ||
private _pageReads: number = 0; | ||
/* | ||
* CUSTOM FIREBASE READS COUNTER INTERFACE | ||
* | ||
* customReads should be used to track user controlled | ||
* period of firebase reads | ||
* It means that resetCustomReads() should be called after | ||
* click on Reset Custom Counter button | ||
* | ||
* lastCustomReads will contain reads for previous custom counter | ||
*/ | ||
public get customReads(): number { | ||
return this.storage.getReads('customReads'); | ||
} | ||
public get lastCustomReads(): number { | ||
return this.storage.getReads('lastCustomReads'); | ||
} | ||
public resetCustomReads: ResetFn = () => { | ||
this.storage.resetReads('customReads'); | ||
this.subjects.customReads.next(this.customReads); | ||
this.subjects.lastCustomReads.next(this.lastCustomReads); | ||
}; | ||
public incrementCustomReads: IncrementFn = newReads => { | ||
this.storage.incrementReads(newReads, 'customReads'); | ||
this.subjects.customReads.next(this.customReads); | ||
}; | ||
/* | ||
* USER SESSION FIREBASE READS COUNTER INTERFACE | ||
* | ||
* sessionReads should be used to track all firebase reads | ||
* in user session. | ||
* It means that resetSessionReads() should be called on every logout from | ||
* react-admin application | ||
* | ||
* lastSessionReads will contain reads for previous user session | ||
*/ | ||
public get sessionReads(): number { | ||
return this.storage.getReads('sessionReads'); | ||
} | ||
public get lastSessionReads(): number { | ||
return this.storage.getReads('lastSessionReads'); | ||
} | ||
public resetSessionReads: ResetFn = () => { | ||
this.storage.resetReads('sessionReads'); | ||
this.subjects.sessionReads.next(this.sessionReads); | ||
this.subjects.lastSessionReads.next(this.lastSessionReads); | ||
}; | ||
public incrementSessionReads: IncrementFn = newReads => { | ||
this.storage.incrementReads(newReads, 'sessionReads'); | ||
this.subjects.sessionReads.next(this.sessionReads); | ||
}; | ||
/* | ||
* INCREMENT ALL COUNTERS | ||
*/ | ||
public incrementAll: IncrementFn = newReads => { | ||
this.incrementPageReads(newReads); | ||
this.incrementCustomReads(newReads); | ||
this.incrementSessionReads(newReads); | ||
}; | ||
/* | ||
* STREAMS | ||
*/ | ||
private readonly subjects: CounterStreams<BehaviorSubject<number>>; | ||
public get streams(): CounterStreams { | ||
return { | ||
pageReads: this.subjects.pageReads.asObservable(), | ||
lastPageReads: this.subjects.lastPageReads.asObservable(), | ||
customReads: this.subjects.customReads.asObservable(), | ||
lastCustomReads: this.subjects.lastCustomReads.asObservable(), | ||
sessionReads: this.subjects.sessionReads.asObservable(), | ||
lastSessionReads: this.subjects.lastSessionReads.asObservable() | ||
} | ||
} | ||
|
||
/* | ||
* Private constructor - reads logger will be singleton | ||
*/ | ||
private constructor( | ||
private storage: ReadsLoggerLocalStorageUtils | ||
) { | ||
this.subjects = this.initCounterSubjects(); | ||
} | ||
|
||
private initCounterSubjects = () => ({ | ||
pageReads: new BehaviorSubject(this.pageReads), | ||
lastPageReads: new BehaviorSubject(this.lastPageReads), | ||
customReads: new BehaviorSubject(this.customReads), | ||
lastCustomReads: new BehaviorSubject(this.lastCustomReads), | ||
sessionReads: new BehaviorSubject(this.sessionReads), | ||
lastSessionReads: new BehaviorSubject(this.lastSessionReads) | ||
}); | ||
|
||
/* | ||
* Statics | ||
* | ||
* private __instance - ReadsLogger singleton instance | ||
* | ||
* ReadsLogger.initLogger(options) - initializing ReadsLogger singleton | ||
* instance | ||
* | ||
* ReadsLogger.getLogger() - get ReadsLogger singleton instance | ||
*/ | ||
private static __instance: ReadsLogger = null; | ||
|
||
public static initLogger(options: RAFirebaseOptions) { | ||
const loggingEnabled = options.lazyLoading && | ||
options.lazyLoading.firebaseReadsLogging && | ||
options.lazyLoading.firebaseReadsLogging.enabled; | ||
|
||
if (!loggingEnabled) { | ||
throw new RAFirebaseReadsLoggerError('Logging not enabled'); | ||
} | ||
|
||
if (!ReadsLogger.__instance) { | ||
const storageUtils = new ReadsLoggerLocalStorageUtils(options); | ||
storageUtils.init() | ||
.then(() => { | ||
ReadsLogger.__instance = new ReadsLogger(storageUtils); | ||
log('Firebase Reads Logger Initialized!'); | ||
}) | ||
.catch(error => { | ||
logError(error.message); | ||
}) | ||
} | ||
} | ||
|
||
public static getLogger(): ReadsLogger { | ||
if (!ReadsLogger.__instance) { | ||
throw new RAFirebaseReadsLoggerError('Logger Not Initialized!'); | ||
} | ||
return ReadsLogger.__instance; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import { RAFirebaseReadsLoggerError, RAFirebaseReadsLoggerStorageKeys } from "./types"; | ||
import { RAFirebaseOptions, RAFirebaseReadsLoggingOptions } from "../../providers/RAFirebaseOptions"; | ||
import firebase from "firebase/app"; | ||
import "firebase/auth"; | ||
|
||
type ReadsKey = 'customReads' | 'sessionReads'; | ||
type GetReadsFn = (readsKey: keyof RAFirebaseReadsLoggerStorageKeys) => number; | ||
type ResetReadsFn = (readsKey: ReadsKey) => void; | ||
type IncrementReadsFn = (newReads: number, readsKey: ReadsKey) => void; | ||
|
||
export default class ReadsLoggerLocalStorageUtils { | ||
/* | ||
* PRIVATE FIELDS | ||
*/ | ||
private storageKeys: RAFirebaseReadsLoggerStorageKeys = null; | ||
private get hasKeys(): boolean { | ||
return !!this.storageKeys; | ||
} | ||
private readonly loggerOptions: RAFirebaseReadsLoggingOptions; | ||
private readonly DEFAULT_PREFIX: string = 'ra-firebase-reads'; | ||
private isInitialized: boolean = false; | ||
/* | ||
* PUBLIC LOCAL STORAGE UTILS INTERFACE | ||
*/ | ||
public authStateUnsubscribe: firebase.Unsubscribe; | ||
|
||
public get keys(): RAFirebaseReadsLoggerStorageKeys { | ||
return this.storageKeys; | ||
} | ||
|
||
public getReads: GetReadsFn = readsKey => { | ||
if (!this.hasKeys) { | ||
return 0; | ||
} | ||
const key = this.keys[readsKey]; | ||
const readsFromStorage = localStorage.getItem(key); | ||
if (readsFromStorage === null) { | ||
this.setReadsInStorage(readsKey, 0); | ||
return 0; | ||
} | ||
|
||
return parseInt(readsFromStorage, 10); | ||
}; | ||
|
||
public resetReads: ResetReadsFn = readsKey => { | ||
this.checkErrors(); | ||
const lastReadsKey = readsKey === 'customReads' ? | ||
'lastCustomReads' : 'lastSessionReads'; | ||
const newLastReads = this.getReads(readsKey); | ||
this.setReadsInStorage(lastReadsKey, newLastReads); | ||
this.setReadsInStorage(readsKey, 0); | ||
}; | ||
|
||
public incrementReads: IncrementReadsFn = (newReads, readsKey) => { | ||
this.checkErrors(); | ||
const oldReads: number = this.getReads(readsKey); | ||
const incrementedReads = oldReads + newReads; | ||
this.setReadsInStorage(readsKey, incrementedReads); | ||
}; | ||
|
||
/* | ||
* Init | ||
* | ||
* Should be called before any action in | ||
*/ | ||
public async init() { | ||
if (this.isInitialized) { | ||
throw new RAFirebaseReadsLoggerError('Storage utils already initialized.'); | ||
} | ||
this.isInitialized = await this.startUserSubscription(); | ||
return this.isInitialized; | ||
} | ||
/* | ||
* Class will be only imported in ReadsLogger file | ||
* and constructor will be only called in ReadsLogger.init() | ||
*/ | ||
constructor( | ||
private readonly options: RAFirebaseOptions | ||
) { | ||
this.loggerOptions = this.options.lazyLoading.firebaseReadsLogging; | ||
} | ||
/* | ||
* Initial subscription for user id to save counters for user | ||
*/ | ||
private async startUserSubscription(): Promise<boolean> { | ||
return new Promise((resolve, reject) => { | ||
const firebaseAuth = this.options.app.auth() || firebase.app().auth(); | ||
this.authStateUnsubscribe = firebaseAuth.onAuthStateChanged( | ||
userSnapshot => { | ||
if (userSnapshot.uid) { | ||
this.storageKeys = this.getLocalStorageKeys(userSnapshot.uid); | ||
resolve(true); | ||
} | ||
}, | ||
error => reject(error) | ||
); | ||
}); | ||
} | ||
/* | ||
* INTERNAL UTILS | ||
*/ | ||
private setReadsInStorage( | ||
key: keyof RAFirebaseReadsLoggerStorageKeys, | ||
reads: number | ||
): void { | ||
localStorage.setItem(this.keys[key], String(reads)); | ||
} | ||
|
||
private getLocalStorageKeys( | ||
userId: string | ||
): RAFirebaseReadsLoggerStorageKeys { | ||
const customPrefix = this.loggerOptions && | ||
this.loggerOptions.localStoragePrefix; | ||
const prefix = customPrefix || this.DEFAULT_PREFIX; | ||
|
||
return { | ||
customReads: `${prefix}-${userId}-custom-reads`, | ||
lastCustomReads: `${prefix}-${userId}-last-custom-reads`, | ||
sessionReads: `${prefix}-${userId}-session-reads`, | ||
lastSessionReads: `${prefix}-${userId}-last-session-reads` | ||
}; | ||
} | ||
|
||
private checkErrors() { | ||
this.checkInitError(); | ||
this.checkKeysError(); | ||
} | ||
|
||
private checkKeysError() { | ||
if (!this.hasKeys) { | ||
throw new RAFirebaseReadsLoggerError('Cannot read local storage keys.'); | ||
} | ||
} | ||
|
||
private checkInitError() { | ||
if (!this.isInitialized) { | ||
throw new RAFirebaseReadsLoggerError('Storage utils not initialized.'); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import ReadsLogger from "./ReadsLogger"; | ||
import { RAFirebaseOptions } from "../../providers/RAFirebaseOptions"; | ||
|
||
export const initFirebaseReadsLogger = (options: RAFirebaseOptions) => { | ||
ReadsLogger.initLogger(options); | ||
}; | ||
|
||
export const getFirebaseReadsLogger = () => ReadsLogger.getLogger(); | ||
|
||
export default ReadsLogger; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export interface RAFirebaseReadsLoggerStorageKeys { | ||
customReads: string; | ||
lastCustomReads: string; | ||
sessionReads: string; | ||
lastSessionReads: string; | ||
} | ||
|
||
export class RAFirebaseReadsLoggerError extends ReferenceError { | ||
constructor(message: string) { | ||
super(`RAFirebaseReadsLoggerError: ${message}`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.