From 72dab0d4e4a5b204d3e150b96377bced5f0b9824 Mon Sep 17 00:00:00 2001 From: David East Date: Sun, 12 Mar 2017 12:44:35 -0700 Subject: [PATCH] chore(tests): Absolute urls --- src/database/database.ts | 36 +++++++---------- src/database/firebase_list_factory.spec.ts | 6 +++ src/database/firebase_list_factory.ts | 41 ++++++++++++-------- src/database/firebase_object_factory.spec.ts | 6 +++ src/database/firebase_object_factory.ts | 19 ++++++--- src/utils.ts | 37 +++++++++++++++++- 6 files changed, 98 insertions(+), 47 deletions(-) diff --git a/src/database/database.ts b/src/database/database.ts index 30b8a2d83..afcc99d87 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -3,36 +3,26 @@ import 'firebase/database'; import { Inject, Injectable } from '@angular/core'; import { FirebaseAppConfigToken, FirebaseAppConfig, FirebaseApp } from '../angularfire2'; import { FirebaseListFactory } from './index'; -import { FirebaseListFactoryOpts, FirebaseObjectFactoryOpts } from '../interfaces'; +import { FirebaseListFactoryOpts, FirebaseObjectFactoryOpts, PathReference } from '../interfaces'; import * as utils from '../utils'; -import { - FirebaseListObservable, - FirebaseObjectObservable, - FirebaseObjectFactory, -} from './index'; +import { FirebaseListObservable, FirebaseObjectObservable, FirebaseObjectFactory } from './index'; @Injectable() export class AngularFireDatabase { + constructor(private app: FirebaseApp) {} - list (urlOrRef:string | firebase.database.Reference, opts?:FirebaseListFactoryOpts):FirebaseListObservable { - return utils.checkForUrlOrFirebaseRef(urlOrRef, { - isUrl: () => FirebaseListFactory(this.app.database().ref(urlOrRef), opts), - isRef: () => FirebaseListFactory(urlOrRef) - }); + + list(pathOrRef: PathReference, opts?:FirebaseListFactoryOpts):FirebaseListObservable { + const ref = utils.getRef(this.app, pathOrRef); + return FirebaseListFactory(utils.getRef(this.app, ref), opts); } - object(urlOrRef: string | firebase.database.Reference, opts?:FirebaseObjectFactoryOpts):FirebaseObjectObservable { - return utils.checkForUrlOrFirebaseRef(urlOrRef, { - isUrl: () => FirebaseObjectFactory(this.app.database().ref(urlOrRef), opts), - isRef: () => FirebaseObjectFactory(urlOrRef) + + object(pathOrRef: PathReference, opts?:FirebaseObjectFactoryOpts):FirebaseObjectObservable { + return utils.checkForUrlOrFirebaseRef(pathOrRef, { + isUrl: () => FirebaseObjectFactory(this.app.database().ref(pathOrRef), opts), + isRef: () => FirebaseObjectFactory(pathOrRef) }); } -} -function getAbsUrl (root:FirebaseAppConfig, url:string) { - if (!(/^[a-z]+:\/\/.*/.test(url))) { - // Provided url is relative. - // Strip any leading slash - url = root.databaseURL + '/' + utils.stripLeadingSlash(url); - } - return url; } + diff --git a/src/database/firebase_list_factory.spec.ts b/src/database/firebase_list_factory.spec.ts index 912f043f0..9b3674dff 100644 --- a/src/database/firebase_list_factory.spec.ts +++ b/src/database/firebase_list_factory.spec.ts @@ -61,6 +61,12 @@ describe('FirebaseListFactory', () => { expect(list instanceof FirebaseListObservable).toBe(true); }); + it('should take an absolute url in the constructor', () => { + const absoluteUrl = COMMON_CONFIG.databaseURL + '/questions'; + const list = FirebaseListFactory(absoluteUrl); + expect(list instanceof FirebaseListObservable).toBe(true); + }); + }); describe('query', () => { diff --git a/src/database/firebase_list_factory.ts b/src/database/firebase_list_factory.ts index 8cad3f7e5..78aaad899 100644 --- a/src/database/firebase_list_factory.ts +++ b/src/database/firebase_list_factory.ts @@ -1,30 +1,37 @@ import * as firebase from 'firebase/app'; +import * as utils from '../utils'; import 'firebase/database'; import { AFUnwrappedDataSnapshot } from '../interfaces'; import { FirebaseListObservable } from './firebase_list_observable'; import { Observer } from 'rxjs/Observer'; import { observeOn } from 'rxjs/operator/observeOn'; import { observeQuery } from './query_observable'; -import { Query, FirebaseListFactoryOpts, PathReference } from '../interfaces'; -import * as utils from '../utils'; +import { Query, FirebaseListFactoryOpts, PathReference, QueryReference, DatabaseQuery, DatabaseReference } from '../interfaces'; import { switchMap } from 'rxjs/operator/switchMap'; import { map } from 'rxjs/operator/map'; export function FirebaseListFactory ( - pathOrReference: PathReference, + pathRef: PathReference, { preserveSnapshot, query = {} } :FirebaseListFactoryOpts = {}): FirebaseListObservable { - let ref: firebase.database.Reference | firebase.database.Query; + let ref: QueryReference; - utils.checkForUrlOrFirebaseRef(pathOrReference, { - isUrl: () => ref = firebase.database().ref(pathOrReference), - isRef: () => ref = pathOrReference, - isQuery: () => ref = pathOrReference, + utils.checkForUrlOrFirebaseRef(pathRef, { + isUrl: () => { + const path = pathRef as string; + if(utils.isAbsoluteUrl(path)) { + ref = firebase.database().refFromURL(path) + } else { + ref = firebase.database().ref(path); + } + }, + isRef: () => ref = pathRef, + isQuery: () => ref = pathRef, }); // if it's just a reference or string, create a regular list observable - if ((utils.isFirebaseRef(pathOrReference) || - utils.isString(pathOrReference)) && + if ((utils.isFirebaseRef(pathRef) || + utils.isString(pathRef)) && utils.isEmptyObject(query)) { return firebaseListObservable(ref, { preserveSnapshot }); } @@ -32,7 +39,7 @@ export function FirebaseListFactory ( const queryObs = observeQuery(query); return new FirebaseListObservable(ref, subscriber => { let sub = switchMap.call(map.call(queryObs, query => { - let queried: firebase.database.Query = ref; + let queried: DatabaseQuery = ref; // Only apply the populated keys // apply ordering and available querying options // eg: ref.orderByChild('height').startAt(3) @@ -112,13 +119,13 @@ export function FirebaseListFactory ( } /** - * Creates a FirebaseListObservable from a reference or query. Options can be provided as a second parameter. - * This function understands the nuances of the Firebase SDK event ordering and other quirks. This function - * takes into account that not all .on() callbacks are guaranteed to be asynchonous. It creates a initial array - * from a promise of ref.once('value'), and then starts listening to child events. When the initial array - * is loaded, the observable starts emitting values. + * Creates a FirebaseListObservable from a reference or query. Options can be provided as a second + * parameter. This function understands the nuances of the Firebase SDK event ordering and other + * quirks. This function takes into account that not all .on() callbacks are guaranteed to be + * asynchonous. It creates a initial array from a promise of ref.once('value'), and then starts + * listening to child events. When the initial array is loaded, the observable starts emitting values. */ -function firebaseListObservable(ref: firebase.database.Reference | firebase.database.Query, {preserveSnapshot}: FirebaseListFactoryOpts = {}): FirebaseListObservable { +function firebaseListObservable(ref: firebase.database.Reference | DatabaseQuery, {preserveSnapshot}: FirebaseListFactoryOpts = {}): FirebaseListObservable { const toValue = preserveSnapshot ? (snapshot => snapshot) : utils.unwrapMapFn; const toKey = preserveSnapshot ? (value => value.key) : (value => value.$key); diff --git a/src/database/firebase_object_factory.spec.ts b/src/database/firebase_object_factory.spec.ts index 8d78c1393..3d289a383 100644 --- a/src/database/firebase_object_factory.spec.ts +++ b/src/database/firebase_object_factory.spec.ts @@ -38,6 +38,12 @@ describe('FirebaseObjectFactory', () => { expect(object instanceof FirebaseObjectObservable).toBe(true); }); + it('should take an absolute url in the constructor', () => { + const absoluteUrl = COMMON_CONFIG.databaseURL + '/questions/one'; + const list = FirebaseObjectFactory(absoluteUrl); + expect(list instanceof FirebaseObjectObservable).toBe(true); + }); + }); describe('methods', () => { diff --git a/src/database/firebase_object_factory.ts b/src/database/firebase_object_factory.ts index 5b85fd01c..b300a7aeb 100644 --- a/src/database/firebase_object_factory.ts +++ b/src/database/firebase_object_factory.ts @@ -4,17 +4,24 @@ import { observeOn } from 'rxjs/operator/observeOn'; import * as firebase from 'firebase/app'; import 'firebase/database'; import * as utils from '../utils'; -import { FirebaseObjectFactoryOpts, PathReference } from '../interfaces'; +import { FirebaseObjectFactoryOpts, PathReference, DatabaseReference } from '../interfaces'; export function FirebaseObjectFactory ( - pathReference: PathReference, + pathRef: PathReference, { preserveSnapshot }: FirebaseObjectFactoryOpts = {}): FirebaseObjectObservable { - let ref: firebase.database.Reference; + let ref: DatabaseReference; - utils.checkForUrlOrFirebaseRef(pathReference, { - isUrl: () => ref = firebase.database().ref(pathReference), - isRef: () => ref = pathReference + utils.checkForUrlOrFirebaseRef(pathRef, { + isUrl: () => { + const path = pathRef as string; + if(utils.isAbsoluteUrl(path)) { + ref = firebase.database().refFromURL(path) + } else { + ref = firebase.database().ref(path); + } + }, + isRef: () => ref = pathRef }); const objectObservable = new FirebaseObjectObservable((obs: Observer) => { diff --git a/src/utils.ts b/src/utils.ts index fd0f6e22b..5f5849ddc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,7 +2,10 @@ import * as firebase from 'firebase/app'; import { Subscription } from 'rxjs/Subscription'; import { Scheduler } from 'rxjs/Scheduler'; import { queue } from 'rxjs/scheduler/queue'; -import { AFUnwrappedDataSnapshot } from './interfaces'; +import { AFUnwrappedDataSnapshot, PathReference, DatabaseReference } from './interfaces'; +import { FirebaseApp } from './app/index'; + +const REGEX_ABSOLUTE_URL = /^[a-z]+:\/\/.*/; export function isNil(obj: any): boolean { return obj === undefined || obj === null; @@ -92,6 +95,15 @@ export function stripTrailingSlash(value: string): string { } } +function getAbsUrl(root: string, url:string) { + if (!(/^[a-z]+:\/\/.*/.test(url))) { + // Provided url is relative. + // Strip any leading slash + url = root + '/' + stripLeadingSlash(url); + } + return url; +} + export function stripLeadingSlash(value: string): string { // Is the last char a / if (value.substring(0, 1) === '/') { @@ -101,6 +113,29 @@ export function stripLeadingSlash(value: string): string { } } +export function isAbsoluteUrl(url: string) { + return REGEX_ABSOLUTE_URL.test(url); +} + +/** + * Returns a database reference given a Firebase App and an + * absolute or relative path. + * @param app - Firebase App + * @param path - Database path, relative or absolute + */ +export function getRef(app: FirebaseApp, pathRef: PathReference): DatabaseReference { + // if a db ref was passed in, just return it + if(isFirebaseRef(pathRef)) { + return pathRef as DatabaseReference; + } + + const path = pathRef as string; + if(isAbsoluteUrl(pathRef)) { + return app.database().refFromURL(path); + } + return app.database().ref(path); +} + /** * TODO: remove this scheduler once Rx has a more robust story for working * with zones.