@cerebral/firebase
Install
NPM
npm install @cerebral/firebase
Description
The Firebase provider is a Cerebral friendly wrapper around the Firebase client. By default the Firebase client is heavily event based, even just getting some value, handling authentication etc. This is useful in some types of apps, but Cerebral has a very straight forward way of thinking about side effects. You will find that a lot of the API exposed by the Firebase client is simplified!
Instantiate
import {Controller} from 'cerebral'
import FirebaseProvider from '@cerebral/firebase'
const controller = Controller({
providers: [
FirebaseProvider({
config: {
apiKey: '{apiKey}',
authDomain: '{authDomain}',
databaseURL: '{databaseURL}',
storageBucket: '{storageBucket}'
},
specPrefix: 'CJ',
queuePath: 'myqueue'
sendTaskExecutionDetails: false
})
]
})
Important notes
-
The Cerebral firebase provider uses dot notation to keep consistency with Cerebral itself
-
All factories supports template tags, allowing you to dynamically create paths and points to values
cancelOnDisconnect
Cancel setting a value when Firebase detects disconnect.
action
function someAction({ firebase, state }) {
firebase.cancelOnDisconnect()
}
operator
import { state } from 'cerebral/tags'
import { cancelOnDisconnect } from '@cerebral/firebase/operators'
export default [cancelOnDisconnect()]
createUserWithEmailAndPassword
Register a new user with email and password.
action
function someAction({ firebase, state }) {
const email = state.get('register.email')
const password = state.get('register.password')
return firebase
.createUserWithEmailAndPassword(email, password)
.then((response) => {
})
}
operator
import { state } from 'cerebral/tags'
import { createUserWithEmailAndPassword } from '@cerebral/firebase/operators'
export default [
createUserWithEmailAndPassword(state`newUser.email`, state`newUser.password`)
]
operator with paths
import { state } from 'cerebral/tags'
import { createUserWithEmailAndPassword } from '@cerebral/firebase/operators'
export default [
createUserWithEmailAndPassword(state`newUser.email`, state`newUser.password`),
{
success: [
],
error: [
]
}
]
deleteFile
Use deleteFile
to remove an uploaded file. Specify the containing folder and filename.
action
function someAction({ firebase, props }) {
return firebase.deleteFile('folderName', props.fileName).then(() => {
})
}
operator
import { props, state, string } from 'cerebral/tags'
import { deleteFile } from '@cerebral/firebase/operators'
export default [
deleteFile(
string`posts.all.${props`postId`}`,
state`posts.all.${props`postId`}.imageName`
),
deleteFile(
string`posts.all.${props`postId`}`,
state`posts.all.${props`postId`}.imageName`
),
{
success: [],
error: []
}
]
operator with paths
import { props, state, string } from 'cerebral/tags'
import { deleteFile } from '@cerebral/firebase/operators'
export default [
deleteFile(
string`posts.all.${props`postId`}`,
state`posts.all.${props`postId`}.imageName`
),
{
success: [
],
error: [
]
}
]
error
The Firebase errors are passed to signal and global catch handlers, or .catch
handler in your actions.
FirebaseProviderError (base)
import {FirebaseProviderError} from '@cerebral/firebase'
{
name: 'HttpProviderError',
message: 'Some firebase error message'
stack: '...'
}
FirebaseProviderAuthenticationError
import {FirebaseProviderAuthenticationError} from '@cerebral/firebase'
{
name: 'HttpProviderError',
message: 'Some firebase error message'
code: 10
stack: '...'
}
getDownloadURL
Will get the download url of a given file in firebase storage.
action
function someAction({ firebase, props }) {
return firebase
.getDownloadURL('images', `${props.path}.jpg`)
.then((response) => {
})
}
operator
import { getDownloadURL } from '@cerebral/firebase/operators'
export default [
getDownloadURL('images', string`${props`path`}.jpg`),
{
success: [
],
error: [
]
}
]
getUser
Will resolve to {user: {}}
if user exists. If user was redirected from Facebook/Google etc. as part of first sign in, this method will handle the confirmed registration of the user.
action
function someAction({ firebase }) {
return firebase.getUser().then((response) => {
})
}
operator
import { getUser } from '@cerebral/firebase/operators'
export default [
getUser()
]
operator with paths
import { getUser } from '@cerebral/firebase/operators'
export default [
getUser(),
{
success: [
],
error: [
]
}
]
linkWith{PROVIDER}
Link an anonymous account with Facebook, Google or Github.
action
function someAction({ firebase, state }) {
return firebase
.linkWithFacebook({
redirect: false,
scopes: []
})
.then((response) => {
})
}
operator
import { state } from 'cerebral/tags'
import { linkWithFacebook } from '@cerebral/firebase/operators'
export default [
linkWithFacebook({
redirect: state`useragent.media.small`
})
]
operator with paths
import { state } from 'cerebral/tags'
import { linkWithFacebook } from '@cerebral/firebase/operators'
export default [
linkWithFacebook({
redirect: state`useragent.media.small`
}),
{
success: [
],
error: [
]
}
]
Similar you can sign in with Google or GitHub.
Just use linkWithGoogle
or linkWithGithub
instead of linkWithFacebook
.
off
action
function someAction({ firebase }) {
firebase.off('posts', 'onChildChanged')
firebase.off('posts')
firebase.off('posts.*', 'onChildChanged')
firebase.off('posts.*')
}
operator
import { string } from 'cerebral/tags'
import { off } from '@cerebral/firebase/operators'
export default [
off('posts', 'onChildChanged'),
off(string`posts.${state`selectedPostKey`}`)
]
onChildAdded
action
function someAction({ firebase }) {
firebase.onChildAdded('posts', 'posts.postAdded', {
payload: {},
endAt: 5,
equalTo: 5,
limitToFirst: 5,
limitToLast: 5,
orderByChild: 'count',
orderByKey: true,
orderByValue: true,
startAt: 5
})
}
This will immediately grab and trigger the signal posts.postAdded
for every post grabbed. Note this is just registering a listener, not returning a value from the action. The signal is triggered with the payload: { key: 'someKey', value: {} }
.
To stop listening for updates to the posts:
function someAction({ firebase }) {
firebase.off('posts', 'onChildAdded')
}
operator
import { state, string, signal } from 'cerebral/tags'
import { onChildAdded } from '@cerebral/firebase/operators'
export default [
onChildAdded(string`foo.bar`, signal`some.signal`, {
orderByChild: 'count',
limitToFirst: state`config.limitToFirst`
})
]
onChildChanged
action
function someAction({ firebase }) {
firebase.onChildChanged('posts', 'posts.postChanged', {
})
}
This will trigger the signal posts.postChanged
whenever a post is changed in the selection. The signal is triggered with the payload: { key: 'someKey', value: {} }
.
To stop listening:
function someAction({ firebase }) {
firebase.off('posts', 'onChildChanged')
}
operator
import { onChildChanged } from '@cerebral/firebase/operators'
import { string, signal } from 'cerebral/tags'
export default [
onChildChanged(string`foo.bar`, signal`some.signal`, {
})
]
onChildRemoved
action
function someAction({ firebase }) {
firebase.onChildRemoved('posts', 'posts.postRemoved', {
})
}
This will trigger the signal posts.postRemoved
whenever a post is removed from the selection. The signal is triggered with the payload: { key: 'someKey' }
.
To stop listening:
function someAction({ firebase }) {
firebase.off('posts', 'onChildRemoved')
}
operator
import { onChildRemoved } from '@cerebral/firebase/operators'
import { string, signal } from 'cerebral/tags'
export default [
onChildRemoved(string`foo.bar`, signal`some.signal`, {
})
]
onValue
action
function someAction({ firebase }) {
firebase.onValue('someKey.foo', 'someModule.fooUpdated', {
payload: {}
})
}
This will NOT immediately grab the value and trigger the signal passed, the first event is discarded for more predictable behaviour. To grab existing value, just use value
.
To stop listening for updates to the value:
function someAction({ firebase }) {
firebase.off('someKey.foo', 'onValue')
}
operator
import { onValue } from '@cerebral/firebase/operators'
import { string, signal } from 'cerebral/tags'
export default [onValue(string`foo.bar`, signal`some.signal`)]
push
Generates a new child location using a unique key and returns its reference from the action. An example being {key: "-KWKImT_t3SLmkJ4s3-w"}
.
action
function someAction({ firebase }) {
return firebase
.push('users', {
name: 'Bob'
})
.then((response) => {
})
}
operator
import { state } from 'cerebral/tags'
import { push } from '@cerebral/firebase/operators'
export default [
push('users', state`newUser`)
]
operator with paths
import { state } from 'cerebral/tags'
import { push } from '@cerebral/firebase/operators'
export default [
push('users', state`newUser`),
{
success: [
],
error: [
]
}
]
output
{
key: 'theAddedKey'
}
put
Upload a new file at the given location. Please note that the file is not stored inside the realtime database but into Google Cloud Storage (please consult filrebase documentation). This means that you need to take care of storage security as well.
Note that put
expects a folder as first argument and will use the name of the provided file. If you want to control the filename, add this in the options. In this case, make sure to respect file type and extension...
action
function someAction({ firebase, props }) {
return firebase.put('folderName', props.file, {
progress({progress, bytesTransferred, totalBytes, state}) {
},
filename: 'customName.png'
{ type: 'avatar'
}
})
.then((response) => {
})
}
operator
import {props, signal, string, state} from 'cerebral/tags'
import {put} from '@cerebral/firebase/operators'
export default [
put(string`posts.all.${props`postId`}`, props`file`, {
progress: signal`gallery.progress`
progress: state`gallery.progress`
}),
]
operator with paths
import {props, signal, string, state} from 'cerebral/tags'
import {put} from '@cerebral/firebase/operators'
export default [
put(string`posts.all.${props`postId`}`, props`file`, {
progress: signal`gallery.progress`
progress: state`gallery.progress`
}), {
success: [
],
error: [
]
}
]
remove
Remove the data at this database location.
action
function someAction({ firebase }) {
return firebase.remove('foo.bar').then(() => {
})
}
operator
import { props, string } from 'cerebral/tags'
import { remove } from '@cerebral/firebase/operators'
export default [
remove(string`users.${props`userKey`}`)
]
operator with paths
import { props, string } from 'cerebral/tags'
import { remove } from '@cerebral/firebase/operators'
export default [
remove(string`users.${props`userKey`}`),
{
success: [
],
error: [
]
}
]
sendPasswordResetEmail
action
function someAction({ firebase, state }) {
return firebase.sendPasswordResetEmail(state.get('user.email')).then(() => {
})
}
operator
import { state } from 'cerebral/tags'
import { sendPasswordResetEmail } from '@cerebral/firebase/operators'
export default [
sendPasswordResetEmail(state`user.email`)
]
operator with paths
import { state } from 'cerebral/tags'
import { sendPasswordResetEmail } from '@cerebral/firebase/operators'
export default [
sendPasswordResetEmail(state`user.email`),
{
success: [
],
error: [
]
}
]
set
Write data to this database location. This will overwrite any data at this location and all child locations. Passing null for the new value is equivalent to calling remove(); all data at this location or any child location will be deleted.
action
function someAction({ firebase }) {
return firebase.set('foo.bar', 'baz').then(() => {
})
}
operator
import { props } from 'cerebral/tags'
import { set } from '@cerebral/firebase/operators'
export default [
set('foo.bar', props`foo`)
]
operator with paths
import { props } from 'cerebral/tags'
import { set } from '@cerebral/firebase/operators'
export default [
set('foo.bar', props`foo`),
{
success: [
],
error: [
]
}
]
setOnDisconnect
Sets a value when Firebase detects user has disconnected.
action
function someAction({ firebase, state }) {
firebase.setOnDisconnect(
`activeUsers.${state.get('app.user.uid')}`,
'someValue'
)
}
operator
import { state } from 'cerebral/tags'
import { setOnDisconnect } from '@cerebral/firebase/operators'
export default [
setOnDisconnect(string`activeUsers.${state`app.user.uid`}`, null)
]
signInAnonymously
This login will method will resolve to existing anonymous or create a new one for you.
action
function someAction({ firebase }) {
return firebase.signInAnonymously().then((user) => {
})
}
operator
import { signInAnonymously } from '@cerebral/firebase/operators'
export default [
signInAnonymously()
]
operator with paths
import { signInAnonymously } from '@cerebral/firebase/operators'
export default [
signInAnonymously(),
{
success: [
],
error: [
]
}
]
signInWithCustomToken
Sign in a custom token.
action
function someAction({ firebase, state }) {
return firebase.signInWithCustomToken(state.get('token'))
}
factory
import { props, state } from 'cerebral/tags'
import { signInWithCustomToken } from '@cerebral/firebase/operators'
export default [
signInWithCustomToken(props`token`),
signInWithCustomToken(state`token`),
{
success: [],
error: []
}
]
output
{
user: {
}
}
signInWithEmailAndPassword
Sign in a user with email and password.
action
function someAction({ firebase, state }) {
const email = state.get('register.email')
const password = state.get('register.password')
return firebase
.signInWithEmailAndPassword(email, password)
.then((response) => {
})
}
operator
import { props } from 'cerebral/tags'
import { signInWithEmailAndPassword } from '@cerebral/firebase/operators'
export default [
signInWithEmailAndPassword(props`email`, props`password`)
]
operator with paths
import { props } from 'cerebral/tags'
import { signInWithEmailAndPassword } from '@cerebral/firebase/operators'
export default [
signInWithEmailAndPassword(props`email`, props`password`),
{
success: [
],
error: [
]
}
]
signInWith{PROVIDER}
Sign in a user with Facebook, Google or Github.
action
function someAction({ firebase, state }) {
return firebase
.signInWithFacebook({
redirect: false,
scopes: []
})
.then((response) => {
})
}
operator
import { state } from 'cerebral/tags'
import { signInWithFacebook } from '@cerebral/firebase/operators'
export default [
signInWithFacebook({
redirect: state`useragent.media.small`
})
]
operator with paths
import { state } from 'cerebral/tags'
import { signInWithFacebook } from '@cerebral/firebase/operators'
export default [
signInWithFacebook({
redirect: state`useragent.media.small`
}),
{
success: [
],
error: [
]
}
]
Similar you can sign in with Google or GitHub.
Just use signInWithGoogle
or signInWithGithub
instead of signInWithFacebook
.
signOut
Sign out user. getUser will now not resolve a user anymore.
action
function someAction({ firebase }) {
return firebase.signOut().then(() => {
})
}
operator
import { signOut } from '@cerebral/firebase/operators'
export default [
signOut()
]
operator with paths
import { signOut } from '@cerebral/firebase/operators'
export default [
signOut(),
{
success: [
],
error: [
]
}
]
task
If you are using the firebase-queue and need to create tasks, you can do that with:
action
function someAction({ firebase, state }) {
return firebase
.task('create_post', {
uid: state.get('app.user.uid'),
text: state.get('posts.newPostText')
})
.then(() => {
})
}
This will add a task at queue/tasks
. There is no output from a resolved task, it just resolves when the action has been processed.
operator
import { state, props } from 'cerebral/tags'
import { task } from '@cerebral/firebase/operators'
export default [
task('some_task', {
uid: state`user.uid`,
data: props`data`
}),
task('some_task', {
uid: state`user.uid`,
data: props`data`
}),
{
success: [],
error: []
}
]
operator with paths
import { state, props } from 'cerebral/tags'
import { task } from '@cerebral/firebase/operators'
export default [
task('some_task', {
uid: state`user.uid`,
data: props`data`
}),
{
success: [
],
error: [
]
}
]
transaction
Atomically modifies the data at the provided location.
Unlike a normal set(), which just overwrites the data regardless of its previous value, transaction() is used to modify the existing value to a new value, ensuring there are no conflicts with other clients writing to the same location at the same time.
To accomplish this, you pass transaction() an update function which is used to transform the current value into a new value. If another client writes to the location before your new value is successfully written, your update function will be called again with the new current value, and the write will be retried. This will happen repeatedly until your write succeeds without conflict or you abort the transaction by not returning a value from your update function.
action
function someAction({ firebase }) {
function transactionFunction(currentData) {
if (currentData === null) {
return { foo: 'bar' }
}
return
}
return firebase
.transaction('some.transaction.path', transactionFunction)
.then((result) => {
if (result.committed) {
return { result: result.value }
} else {
throw new Error('Transaction failed')
}
})
.then((response) => {
})
}
This will add a task at queue/tasks
. There is no output from a resolved task, it just resolves when the action has been processed.
operator
import {transaction} from '@cerebral/firebase/operators'
function transactionFunction() {...}
export default [
transaction('foo.bar', transactionFunction),
]
operator with paths
import {transaction} from '@cerebral/firebase/operators'
function transactionFunction() {...}
export default [
transaction('foo.bar', transactionFunction), {
success: [
],
error: [
]
}
]
Note: Modifying data with set() will cancel any pending transactions at that location, so extreme care should be taken if mixing set() and transaction() to update the same data.
Note: When using transactions with Security and Firebase Rules in place, be aware that a client needs .read access in addition to .write access in order to perform a transaction. This is because the client-side nature of transactions requires the client to read the data in order to transactionally update it.
update
As opposed to the set() method, update() can be use to selectively update only the referenced properties at the current location (instead of replacing all the child properties at the current location).
action
function someAction({ firebase }) {
return firebase
.update('some.path', {
foo: 'bar',
'items.item1.isAwesome': true
})
.then(() => {
})
}
operator
import { props } from 'cerebral/tags'
import { update } from '@cerebral/firebase/operators'
export default [
update('some.path', {
'foo.bar': props`bar`,
'foo.baz': props`baz`
}),
update('some.path', {
'foo.bar': props`bar`,
'foo.baz': props`baz`
}),
{
success: [],
error: []
}
]
operator with paths
import { props } from 'cerebral/tags'
import { update } from '@cerebral/firebase/operators'
export default [
update('some.path', {
'foo.bar': props`bar`,
'foo.baz': props`baz`
}),
{
success: [
],
error: [
]
}
]
value
action
function someAction({ firebase }) {
return firebase.value('someKey.foo').then((response) => {
})
}
operator
import { value } from '@cerebral/firebase/operators'
import { state } from 'cerebral/tags'
export default [
value('foo.bar'),
value('foo.bar'),
{
success: [],
error: []
}
]
operator with paths
import { value } from '@cerebral/firebase/operators'
export default [
value('foo.bar'),
{
success: [
],
error: [
]
}
]