Skip to content

Commit

Permalink
Merge pull request #1 from FirebaseExtended/exp
Browse files Browse the repository at this point in the history
Compatibility with `firebase@exp`
  • Loading branch information
jamesdaniels authored Aug 25, 2021
2 parents 34d42e4 + d2568bb commit d81a565
Show file tree
Hide file tree
Showing 45 changed files with 2,503 additions and 1,610 deletions.
13 changes: 8 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
- "**"
paths-ignore:
- "**/*.md"
pull_request:
branches:
- "**"
release:
types:
- published
Expand All @@ -26,9 +29,9 @@ jobs:
id: node_modules_cache
with:
path: ./node_modules
key: ${{ runner.os }}-14-8-7-node_modules-${{ hashFiles('yarn.lock') }}
key: ${{ runner.os }}-14-next-7-node_modules-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-14-8-7-node_modules-
${{ runner.os }}-14-next-7-node_modules-
${{ runner.os }}-14-node_modules-
- name: Yarn offline cache
if: steps.node_modules_cache.outputs.cache-hit != 'true'
Expand Down Expand Up @@ -59,7 +62,7 @@ jobs:
strategy:
matrix:
node: ["12", "14", "16"]
firebase: ["8"]
firebase: ["next"]
rxjs: ["6", "7"]
fail-fast: false
name: Test firebase@${{ matrix.firebase }} rxjs@${{ matrix.rxjs }} on Node.js ${{ matrix.node }}
Expand Down Expand Up @@ -122,8 +125,8 @@ jobs:
publish:
runs-on: ubuntu-latest
name: Publish (NPM)
needs: ['test']
if: ${{ github.ref == 'refs/heads/main' || github.event_name == 'release' }}
needs: ['build']
if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/exp' || github.event_name == 'release' }}
steps:
- name: Setup node
uses: actions/setup-node@v2-beta
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
node_modules
dist
/*.log
/rxfire-*.tgz
/rxfire-*.tgz
61 changes: 39 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,38 @@ Firebase and RxJS for all frameworks.

Status: Beta


---

> **WARNING**: This branch is the work in progress for version 6 of RxFire. [You can find version 5 here](https://github.com/FirebaseExtended/rxfire/tree/v5), if you're looking for documentation or to contribute to stable.
---

## Install

```bash
# npm
npm i rxfire firebase rxjs --save
npm i rxfire@next firebase@next rxjs --save
# yarn
yarn add rxfire firebase rxjs
yarn add rxfire@next firebase@next rxjs
```

Make sure to install Firebase and RxJS individually as they are peer dependencies of RxFire.

## Example use:

```ts
import firebase from 'firebase/app';
import 'firebase/firestore';
import { initializeApp } from 'firebase/app';
import { getFirestore, collection, where, query } from 'firebase/firestore';
import { collectionData } from 'rxfire/firestore';
import { tap } from 'rxjs/operators';

const app = firebase.initializeApp({ /* config */ });
const citiesRef = app.firestore().collection('cities');
citiesRef.where('state', '==', 'CO');
const app = initializeApp({ /* config */ });
const firestore = getFirestore(app);
const citiesRef = query(
collection(firestore, 'cities'),
where('state', '==', 'CO')
);

collectionData(citiesRef, 'id')
.pipe(
Expand All @@ -49,22 +59,27 @@ RxJS provides multiple operators and creation methods for combining observable s
The example below streams a list of "cities" from Firestore and then retrieves their image from a Cloud Storage bucket. Both tasks are asynchronous but RxJS makes it easy to combine these tasks together.

```ts
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/storage';
import { initializeApp } from 'firebase/app';
import { getStorage, ref } from 'firebase/storage';
import { getFirestore, collection, where, query } from 'firebase/firestore';
import { collectionData } from 'rxfire/firestore';
import { getDownloadURL } from 'rxfire/storage';
import { combineLatest } from 'rxjs';
import { switchMap } from 'rxjs/operators';

const app = firebase.initializeApp({ /* config */ });
const citiesRef = app.firestore().collection('cities');
citiesRef.where('state', '==', 'CO');
const app = initializeApp({ /* config */ });
const firestore = getFirestore(app);
const storage = getStorage(app);
const citiesRef = query(
collection(firestore, 'cities'),
where('state', '==', 'CO')
);

collectionData(citiesRef, 'id')
.pipe(
switchMap(cities => {
return combineLatest(...cities.map(c => {
const ref = storage.ref(`/cities/${c.id}.png`);
const ref = ref(storage, `/cities/${c.id}.png`);
return getDownloadURL(ref).pipe(map(imageURL => ({ imageURL, ...c })));
}));
})
Expand All @@ -79,12 +94,13 @@ collectionData(citiesRef, 'id')
RxFire is a complementary library to Firebase. It is not meant to wrap the entire Firebase SDK. RxFire's purpose is to simplify async streams from Firebase. You need to import the Firebase SDK and initialize an app before using RxFire.

```ts
import firebase from 'firebase/app';
import 'firebase/storage'; // import only the features needed
import { initializeApp } from 'firebase/app';
import { getStorage, ref } from 'firebase/storage';
import { getDownloadURL } from 'rxfire/storage';

const app = firebase.initializeApp({ /* config */ });
const ref = app.storage().ref('data.json');
const app = initializeApp({ /* config */ });
const storage = getStorage(app);
const ref = ref(storage, 'data.json');

// Now you can use RxFire!
const url$ = getDownloadURL(ref);
Expand All @@ -104,12 +120,13 @@ import { } from 'rxfire/functions';
RxFire is a set of functions. Most functions create observables and from there you can use regular RxJS operators. Some functions are custom operators. But at the end of the day, it's all just functions. This is important for **tree shaking**. Any unused functions are stripped from your final build if you use a module bundler like Webpack or Rollup.

```ts
import firebase from 'firebase/app';
import 'firebase/storage';
import { initializeApp } from 'firebase/app';
import { getStorage, ref } from 'firebase/storage';
import { getDownloadURL, put /* not used! */ } 'rxfire/storage';

const app = firebase.initializeApp({ /* config */ });
const ref = app.storage().ref('data.json');
const app = initializeApp({ /* config */ });
const storage = getStorage(app);
const ref = ref(storage, 'data.json');

const url$ = getDownloadURL(ref);
```
Expand Down
37 changes: 23 additions & 14 deletions auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,27 @@

// auth is used as a namespace to access types
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import firebase from 'firebase';
import {Observable, from, of} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import { Auth } from 'firebase/auth';
import { onAuthStateChanged, onIdTokenChanged, getIdToken } from 'firebase/auth';
import { Observable, from, of } from 'rxjs';
import { switchMap } from 'rxjs/operators';

type Auth = firebase.auth.Auth;
type User = firebase.User;
type User = import('firebase/auth').User;

/**
* Create an observable of authentication state. The observer is only
* triggered on sign-in or sign-out.
* @param auth firebase.auth.Auth
*/
export function authState(auth: Auth): Observable<User | null> {
return new Observable((subscriber) => {
const unsubscribe = auth.onAuthStateChanged(subscriber);
return {unsubscribe};
export function authState(auth: Auth): Observable<User|null> {
return new Observable(subscriber => {
const unsubscribe = onAuthStateChanged(
auth,
subscriber.next.bind(subscriber),
subscriber.error.bind(subscriber),
subscriber.complete.bind(subscriber),
);
return { unsubscribe };
});
}

Expand All @@ -41,10 +46,14 @@ export function authState(auth: Auth): Observable<User | null> {
* sign-out, and token refresh events
* @param auth firebase.auth.Auth
*/
export function user(auth: Auth): Observable<User | null> {
return new Observable((subscriber) => {
const unsubscribe = auth.onIdTokenChanged(subscriber);
return {unsubscribe};
export function user(auth: Auth): Observable<User|null> {
return new Observable(subscriber => {
const unsubscribe = onIdTokenChanged(auth,
subscriber.next.bind(subscriber),
subscriber.error.bind(subscriber),
subscriber.complete.bind(subscriber),
);
return { unsubscribe };
});
}

Expand All @@ -55,6 +64,6 @@ export function user(auth: Auth): Observable<User | null> {
*/
export function idToken(auth: Auth): Observable<string | null> {
return user(auth).pipe(
switchMap((user) => (user ? from(user.getIdToken()) : of(null))),
switchMap(user => (user ? from(getIdToken(user)) : of(null)))
);
}
6 changes: 3 additions & 3 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ else
fi;

npm --no-git-tag-version --allow-same-version -f version $OVERRIDE_VERSION
yarn build

echo "npm publish . --tag $NPM_TAG" > ./dist/publish.sh
chmod +x ./dist/publish.sh
yarn build &&
echo "npm publish . --tag $NPM_TAG" > ./dist/publish.sh &&
chmod +x ./dist/publish.sh
12 changes: 12 additions & 0 deletions database.rules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"rules": {
".read": true,
".write": true,
"$a": {
".indexOn": ["name"],
"$b": {
".indexOn": ["name"]
}
}
}
}
40 changes: 20 additions & 20 deletions database/fromRef.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* @license
* Copyright 2018 Google LLC
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,37 +15,37 @@
* limitations under the License.
*/

import firebase from 'firebase';
import {Observable} from 'rxjs';
import {delay} from 'rxjs/operators';
import {ListenEvent, QueryChange} from './interfaces';
import { Observable } from 'rxjs';
import { delay } from 'rxjs/operators';
import { ListenEvent, QueryChange, ListenerMethods } from './interfaces';
import { off } from 'firebase/database';

/**
* Create an observable from a Database Reference or Database Query.
* @param ref Database Reference
* @param event Listen event type ('value', 'added', 'changed', 'removed', 'moved')
*/
export function fromRef(
ref: firebase.database.Query,
event: ListenEvent,
ref: import('firebase/database').Query,
event: ListenEvent
): Observable<QueryChange> {
return new Observable<QueryChange>((subscriber) => {
const fn = ref.on(
event,
(snapshot, prevKey) => {
subscriber.next({snapshot, prevKey, event});
},
subscriber.error.bind(subscriber),
return new Observable<QueryChange>(subscriber => {
const fn = ListenerMethods[event](
ref,
(snapshot, prevKey) => {
subscriber.next({ snapshot, prevKey, event });
},
subscriber.error.bind(subscriber)
);
return {
unsubscribe() {
ref.off(event, fn);
},
off(ref, event, fn);
}
};
}).pipe(
// Ensures subscribe on observable is async. This handles
// a quirk in the SDK where on/once callbacks can happen
// synchronously.
delay(0),
// Ensures subscribe on observable is async. This handles
// a quirk in the SDK where on/once callbacks can happen
// synchronously.
delay(0)
);
}
2 changes: 1 addition & 1 deletion database/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* @license
* Copyright 2018 Google LLC
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
17 changes: 13 additions & 4 deletions database/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* @license
* Copyright 2018 Google LLC
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,10 +15,11 @@
* limitations under the License.
*/

import firebase from 'firebase';
import { onChildAdded, onChildChanged, onChildMoved, onChildRemoved, onValue } from 'firebase/database';

export type Query = import('firebase/database').Query;

export enum ListenEvent {
/* eslint-disable no-unused-vars */
added = 'child_added',
removed = 'child_removed',
changed = 'child_changed',
Expand All @@ -27,7 +28,15 @@ export enum ListenEvent {
}

export interface QueryChange {
snapshot: firebase.database.DataSnapshot;
snapshot: import('firebase/database').DataSnapshot;
prevKey: string | null | undefined;
event: ListenEvent;
}

export const ListenerMethods = Object.freeze({
[ListenEvent.added]: onChildAdded,
[ListenEvent.removed]: onChildRemoved,
[ListenEvent.changed]: onChildChanged,
[ListenEvent.moved]: onChildMoved,
[ListenEvent.value]: onValue,
});
Loading

0 comments on commit d81a565

Please sign in to comment.