cerebral-provider-wilddog

1.0.1 • Public • Published

cerebral-provider-firebase

Firebase provider for Cerebral

Install

This project is still in alpha. To test alpha version check instructions in monorepo.

Setup

import {Controller} from 'cerebral'
import FirebaseProvider from 'cerebral-provider-firebase'
 
const controller = Controller({
  providers: [
    FirebaseProvider({
      config: {
        apiKey: '{apiKey}',
        authDomain: '{authDomain}',
        databaseURL: '{databaseURL}',
        storageBucket: '{storageBucket}'
      },
      // When using tasks and firebase queue you can prefix
      // the specs triggered. This is useful in development
      // when multiple developers are working on the
      // same instance
      specPrefix: 'CJ'
    })
  ]
})

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

Set data

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, path}) {
  return firebase.set('foo.bar', 'baz')
    .then(path.success)
    .catch(path.error)
}

factory

import {props} from 'cerebral/tags'
import {set} from 'cerebral-provider-firebase'
 
export default [
  set('foo.bar', props`foo`), {
    success: [],
    error: []
  }
]

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, path}) {
  return firebase.update('some.path', {
    'foo': 'bar',
    'items.item1.isAwesome': true
  })
    .then(path.success)
    .catch(path.error)
}

factory

import {props} from 'cerebral/tags'
import {update} from 'cerebral-provider-firebase'
 
export default [
  update('some.path', {
    'foo.bar': props`bar`,
    'foo.baz': props`baz`
  }), {
    success: [],
    error: []
  }
]

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, path}) {
  return firebase.push('users', {
    name: 'Bob'
  })
    .then(path.success)
    .catch(path.error)
}

factory

import {state} from 'cerebral/tags'
import {push} from 'cerebral-provider-firebase'
 
export default [
  push('users', state`newUser`), {
    success: [],
    error: []
  }
]

remove

Remove the data at this database location.

action

function someAction({ firebase, path}) {
  return firebase.remove('foo.bar')
    .then(path.success)
    .catch(path.error)
}

factory

import {props, string} from 'cerebral/tags'
import {remove} from 'cerebral-provider-firebase'
 
export default [
  remove(string`users.${props`userKey`}`), {
    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, path}) {
 
  function transactionFunction(currentData){
    if (currentData === null) {
      return { foo: 'bar' }
    }
 
    return // Abort the transaction.
  }
 
  return firebase.transaction('some.transaction.path', transactionFunction)
    .then((result) => {
      if(result.committed){
        return path.success({result: result.value})
      } else {
        return path.abort()
      }
    })
    .catch(path.error)
}

factory

import {transaction} from 'cerebral-provider-firebase'
 
function transactionFunction(currentData){
  if (currentData === null) {
    return { foo: 'bar' }
  }
 
  return // Abort the transaction.
}
 
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.

Retrieve data

Value

action

function someAction({ firebase, path }) {
  return firebase.value('someKey.foo')
    .then(path.success)
    .catch(path.error);
}

The result will be available as { key: 'foo', value: 'bar' }. Or { error: 'error message'}.

factory

import {value} from 'cerebral-provider-firebase'
 
export default [
  value('foo.bar'), {
    success: [],
    error: []
  }
]

Retrieve data with updates

When you also want to know when your queried data updates you have the following methods:

onValue

action

function someAction({ firebase }) {
  firebase.onValue('someKey.foo', 'someModule.fooUpdated',  {
    payload: {}, // Merged with the payload passed on new data
  });
}

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', 'someModule.fooUpdated');
}

factory

import {onValue} from 'cerebral-provider-firebase'
 
export default [
  onValue('foo.bar', 'some.signal')
]

onChildAdded

action

function someAction({ firebase }) {
  firebase.onChildAdded('posts', 'posts.postAdded', {
    payload: {}, // Merged with the payload passed on new data
    limitToFirst: 5, // Read Firebase docs
    limitToLast: 5, // Read Firebase docs
    startAt: 5, // Read Firebase docs
    endAt: 5, // Read Firebase docs
    equalTo: 5, // Read Firebase docs
    orderByChild: 'count', // Read Firebase docs
    orderByKey: true, // Read Firebase docs
    orderByValue: true // Read Firebase docs
  });
}

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', 'posts.postAdded');
}

factory

import {state} from 'cerebral/tags'
import {onChildAdded} from 'cerebral-provider-firebase'
 
export default [
  onChildAdded('foo.bar', 'some.signal', {
    orderByChild: 'count',
    limitToFirst: state`config.limitToFirst`
  })
]

onChildRemoved

action

function someAction({ firebase }) {
  firebase.onChildRemoved('posts', 'posts.postRemoved', {
    // Same options as above
  });
}

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', 'posts.postRemoved');
}

factory

import {onChildRemoved} from 'cerebral-provider-firebase'
 
export default [
  onChildRemoved('foo.bar', 'some.signal', {
    // Same options as above
  })
]

onChildChanged

action

function someAction({ firebase }) {
  firebase.onChildChanged('posts', 'posts.postChanged', {
    // Same options as above
  });
}

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', 'posts.postChanged');
}

factory

import {onChildChanged} from 'cerebral-provider-firebase'
 
export default [
  onChildChanged('foo.bar', 'some.signal', {
    // Same options as above
  })
]

Tasks

If you are using the firebase-queue and need to create tasks, you can do that with:

action

function someAction({ firebase, path, state }) {
  return firebase.task('create_post', {
    uid: state.get('app.user.uid'),
    text: state.get('posts.newPostText')
  })
    .then(path.success)
    .catch(path.error);
}

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.

factory

import {state, props} from 'cerebral/tags'
import {task} from 'cerebral-provider-firebase'
 
export default [
  task('some_task', {
    uid: state`user.uid`,
    data: props`data`
  }), {
    success: [],
    error: []
  }
]

Authentication

Get user

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, path }) {
  return firebase.getUser()
    .then(path.success)
    .catch(path.error);
}

factory

import {getUser} from 'cerebral-provider-firebase'
 
export default [
  getUser(), {
    success: [],
    error: []
  }
]

Anonymous login

This login will method will resolve to existing anonymous or create a new one for you. Resolves to {user: {}}.

action

function someAction({ firebase, path }) {
  return firebase.signInAnonymously()
    .then(path.success)
    .catch(path.error);
}

factory

import {signInAnonymously} from 'cerebral-provider-firebase'
 
export default [
  signInAnonymously(), {
    success: [],
    error: []
  }
]

Create user with email and password

Register a new user with email and password. Resolves to {user: {}}.

action

function someAction({ firebase, path, state }) {
  const email = state.get('register.email')
  const password = state.get('register.password')
 
  return firebase.createUserWithEmailAndPassword(email, password)
    .then(path.success)
    .catch(path.error);
}

factory

import {state} from 'cerebral/tags'
import {createUserWithEmailAndPassword} from 'cerebral-provider-firebase'
 
export default [
  createUserWithEmailAndPassword(state`newUser.email`, state`newUser.password`), {
    success: [],
    error: []
  }
]

Sign in user with email and password

Sign in a user with email and password. Resolves to {user: {}}.

action

function someAction({ firebase, path, state }) {
  const email = state.get('register.email')
  const password = state.get('register.password')
 
  return firebase.signInWithEmailAndPassword(email, password)
    .then(path.success)
    .catch(path.error);
}

factory

import {props} from 'cerebral/tags'
import {signInWithEmailAndPassword} from 'cerebral-provider-firebase'
 
export default [
  signInWithEmailAndPassword(props`email`, props`password`), {
    success: [],
    error: []
  }
]

Sign in with Facebook, Google or GitHub

Sign in a user with Facebook. Resolves to {user: {}}, or redirects.

action

function someAction({ firebase, path, state }) {
  return firebase.signInWithFacebook({
    redirect: false, // Use popup or redirect. Redirect typically for mobile
    scopes: [] // Facebook scopes to access
  })
    .then(path.success)
    .catch(path.error);
}

factory

import {state} from 'cerebral/tags'
import {signInWithFacebook} from 'cerebral-provider-firebase'
 
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.

Link with Facebook, Google or GitHub

Link an anonymous account with Facebook. Resolves to {user: {}}, or redirects.

action

function someAction({ firebase, path, state }) {
  return firebase.linkWithFacebook({
    redirect: false, // Use popup or redirect. Redirect typically for mobile
    scopes: [] // Facebook scopes to access
  })
    .then(path.success)
    .catch(path.error);
}

factory

import {state} from 'cerebral/tags'
import {linkWithFacebook} from 'cerebral-provider-firebase'
 
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.

Sign out

Sign out user. getUser will now not resolve a user anymore.

action

function someAction({ firebase, path }) {
  return firebase.signOut()
    .then(path.success)
    .catch(path.error);
}

factory

import {signOut} from 'cerebral-provider-firebase'
 
export default [
  signOut(), {
    success: [],
    error: []
  }
]

Send reset password email

action

function someAction({ firebase, path, state }) {
  return firebase.sendPasswordResetEmail(state.get('user.email'))
    .then(path.success)
    .catch(path.error);
}

factory

import {state} from 'cerebral/tags'
import {sendPasswordResetEmail} from 'cerebral-provider-firebase'
 
export default [
  sendPasswordResetEmail(state`user.email`), {
    success: [],
    error: []
  }
]

On disconnect

setOnDisconnect

Sets a value when Firebase detects user has disconnected.

action

function someAction({ firebase, state }) {
  firebase.setOnDisconnect(`activeUsers.${state.get('app.user.uid')}`, 'someValue')
}

factory

import {state} from 'cerebral/tags'
import {setOnDisconnect} from 'cerebral-provider-firebase'
 
export default [
  setOnDisconnect(string`activeUsers.${state`app.user.uid`}`, null)
]

cancelOnDisconnect

Cancel setting a value when Firebase detects disconnect.

action

function someAction({ firebase, state }) {
  firebase.cancelOnDisconnect()
}

factory

import {state} from 'cerebral/tags'
import {cancelOnDisconnect} from 'cerebral-provider-firebase'
 
export default [
  cancelOnDisconnect()
]

File Storage

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.

The options expect a progress field that can be either a signal (that will be called with progress, totalBytes and bytesTransferred) or a state path to directly set progress value.

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...

On success, props contains an url and the filename.

import {props, signal, state, string} from 'cerebral/tags'
import {put} from 'cerebral-provider-firebase'
 
// we expect props.file to contain a file provided by
// a user in an <input type='file' />
export default [
  put(string`posts.all.${props`postId`}`, props`file`, {
    progress: signal`gallery.progress`
  }), {
    success: [
      set(state`posts.all.${props`postId`}.imageUrl`, props`url`),
      set(state`posts.all.${props`postId`}.imageName`, props`filename`),
    ],
    error: []
  }
]

delete

Use delete to remove an uploaded file. Specify the containing folder and filename.

import {props, state, string} from 'cerebral/tags'
import {put} from 'cerebral-provider-firebase'
 
export default [
  firebase.delete(
    string`posts.all.${props`postId`}`,
    state`posts.all.${props`postId`}.imageName`
  ), {
    success: [],
    error: []
  }
]

Readme

Keywords

Package Sidebar

Install

npm i cerebral-provider-wilddog

Weekly Downloads

1

Version

1.0.1

License

MIT

Last publish

Collaborators

  • feit