Skip to content

Instantly share code, notes, and snippets.

@bradennapier
Created June 14, 2018 00:53
Show Gist options
  • Save bradennapier/8877efb4f9e2991cedc66f57f2f3aa3c to your computer and use it in GitHub Desktop.
Save bradennapier/8877efb4f9e2991cedc66f57f2f3aa3c to your computer and use it in GitHub Desktop.
import { Process } from 'redux-saga-process';
import { call, put, select, fork } from 'redux-saga/effects';
import { createTaskManager } from 'saga-task-manager';
import appConfig from 'utils/config';
import processLoadsWithScope from './config/processLoadsWithScope';
import processReducer from './config/processReducer';
import processSelectors from './config/processSelectors';
import processActionRoutes from './config/processActionRoutes';
import handleAuthStateChangedSaga from './sagas/handleAuthStateChanged';
const buildConfig = config => ({
pid: 'firebase',
reduces: 'user',
log: true,
...config,
});
/* Private Process Selectors */
const firebaseConfigSelector = state => state.config.firebase;
const firebaseTokenSelector = state => state.config.keys.firebase;
/* Wait to load until we receive AUTH_SESSION */
const processLoadsOnAction = 'AUTH_SESSION';
/**
* configureFirebaseGeneralProcess
* @param {Object} _config [description]
* @return {Process} [description]
*/
export default function configureFirebaseGeneralProcess(_config) {
const config = buildConfig(_config);
let scope;
let firebase;
const processConfig = {
pid: config.pid,
reduces: config.reduces,
};
class FirebaseGeneralProcess extends Process {
constructor(processID, prevState, proc) {
super(processID, prevState, proc);
this.state = {
// store the core firebase database refs
refs: {},
// our previous state (when hot reloaded)
...prevState,
// is firebase ready to be used?
ready: false,
};
}
// https://firebase.google.com/docs/reference/node/firebase.auth.Auth
* provideToken(retry = true) {
const fireToken = yield select(firebaseTokenSelector);
if (!fireToken) {
throw new Error(
'[RTDB - General]: Tried to Authenticate with Firebase but a firebase token was not found',
);
}
try {
yield call([this.state.authorizer, this.state.authorizer.signInWithCustomToken], fireToken);
} catch (e) {
yield fork([this, this.handleError], 'token', e, retry);
}
}
* handleFirebaseReady() {
// get the paths that we want to listen to globally
return;
const schema = scope.getListenerSchema(this.state.refs.dealer, {
states: {
projects: {
isDashPro: true,
isDashConnect: true,
isOnline: true,
isStatus: true,
},
},
meta: {
projects: true,
},
});
const handlers = {
onEvent: [this, this.handleFirebasePathEvent],
onError: [this, this.handleError],
};
for (const listenerPath of Object.keys(schema)) {
const listenerSchema = schema[listenerPath];
yield call(
[this.task, this.task.create],
'listeners',
listenerPath,
scope.firebasePathObserver,
listenerPath,
listenerSchema,
handlers,
);
}
}
* handleFirebasePathEvent(
[event, value, eventSchema],
firebasePath,
/* , listenerSchema , listener */
) {
const splitPath = firebasePath.split('/');
const type = `FB${splitPath.join('_').toUpperCase()}`;
const name = splitPath.pop();
yield put({
type,
name,
event,
value,
eventSchema,
});
}
handleCancel() {}
handleError(evt, e /* , retry */) {
const { code, message } = e;
switch (code) {
case 'auth/invalid-custom-token': {
// Thrown if the custom token format is incorrect.
// This could also mean that the auth token has expired and
// must be renewed.
console.error('[RTDB]: Failed to Login to the Realtime Database: ', message);
break;
}
case 'auth/custom-token-mismatch': {
// Thrown if the custom token is for a different Firebase App.
break;
}
default: {
console.warn('[RTDB]: An Unknown Realtime Database Error has Occurred!');
console.error(code, message);
break;
}
}
}
* handleFirebaseStartup() {
scope = this.scope;
firebase = scope.firebase;
// our process will not start until firebase is ready to be initiated and
// the user has authenticated .
if (firebase.apps.length === 0) {
const firebaseConfig = yield select(firebaseConfigSelector);
if (firebaseConfig) {
firebase.initializeApp(firebaseConfig);
} else {
console.error(
'Failed to Initiate App, Realtime Database Configuration Error [RTDB GeneralProcess]',
);
}
}
if (!this.state.authorizer) {
this.state.authorizer = firebase.auth();
}
yield call([this, this.provideToken]);
if (!this.state.refs.db) {
this.state.refs.db = firebase.database().ref(appConfig('firebase.path'));
}
yield call(
[this.task, this.task.create],
'fireauth',
'onAuthState',
scope.firebaseAuthObserver,
this.state.authorizer,
'onAuthStateChanged',
{
onEvent: [this, handleAuthStateChangedSaga],
onCancel: [this, this.handleCancel],
onError: [this, this.handleError],
},
);
}
/* When we want to logout of the Firebase we call this.
*/
* handleFirebaseLogout() {
if (this.state.authorizer) {
this.state.authorizer.signOut();
yield put({
type: 'FIREBASE_LOGOUT',
});
}
this.state.ready = false;
}
/* When our auth observer indicates we lost authentication this will
be called
*/
handleFirebaseLoggedOut() {
console.warn('[RTDB] - USER LOGGED OUT!');
}
* processStarts(processID) {
scope = this.scope;
firebase = scope.firebase;
this.task = createTaskManager(processID, {
name: 'DASH_FIREBASE',
log: config.log,
icon: '☀️',
});
yield call([this, this.handleAuthSession]);
}
* handleAuthSession() {
yield call([this, this.handleFirebaseStartup]);
}
/**
* handleUserLogout
* Called by handleAuthStateChangedSaga when the
* user has logged out and when a signout is
* detected so that we can logout the user.
*/
* handleUserLogout() {
yield call([this, this.handleFirebaseLogout]);
yield call([this.task, this.task.cancelAll]);
}
}
return {
process: FirebaseGeneralProcess,
config: processConfig,
selectors: processSelectors,
actionRoutes: processActionRoutes,
reducer: processReducer,
loadOnAction: processLoadsOnAction,
loadProcess: processLoadsWithScope,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment