src/util/database.js
// import firebase from 'react-native-firebase';
import firebase from 'firebase'; // Using regular firebase here because there are some problems when trying to move to react-native-firebase
import 'firebase/storage'; // Necessary for jest tests
import { Platform, NativeModules } from 'react-native';
import { LoginButton, AccessToken, LoginManager } from 'react-native-fbsdk';
import {default as config} from '../config/config';
import TimeUtil from './timeutility';
const { RNTwitterSignIn } = NativeModules;
const BikeImages = 'BikeImages/';
const ProfileImages = 'ProfileImages/';
/**
* Class for the firebase database connection and operations
*/
class FirebaseDatabase {
/**
* Creates an instance of the FirebaseDatabase class.
* Initializes the app as a firebase app and sets up the storage references.
* @constructor
*/
constructor() {
this.currentUser = null;
if (!firebase.apps.length) {
firebase.initializeApp(config.databaseConfig);
}
this.setupDatabaseRef();
this.setupStorageRef();
}
/**
* Set up the database reference to the PProject table
*/
setupDatabaseRef() {
this.refDB = firebase.database().ref('PProject/');
}
/**
* Set up the storage reference
*/
setupStorageRef() {
this.refStorage = firebase.storage().ref();
}
/**
* Returns the storage without a reference.
*
* @return {Object} The storage without reference
*/
getStorageWithoutRef() {
return firebase.storage();
}
signUp(email,password,onSuccess, onError) {
return firebase.auth().createUserWithEmailAndPassword(email,password)
}
checkVerify() {
let user = firebase.auth().currentUser;
let errorMessage='';
if (user.emailVerified == false) {
console.log("user email verified"+user.emailVerified);
errorMessage = 'email not verified';
return errorMessage;
} else {
return true;
// successful login
}
}
sendEmail() {
//console.log("user in send email is : "+ user);
firebase.auth().currentUser.sendEmailVerification().then(function() {
// Email Verification sent!
// [START_EXCLUDE]
// [END_EXCLUDE]
}).catch(function(error) {
// Handle Errors here.
let errorCode = error.code;
let errorMessage = error.message;
console.log('error for email: '+ errorMessage);
});
}
sendresetEmail(onError) {
let email = getCurrentUserEmail();
firebase.auth().sendPasswordResetEmail(email).then(function() {
// Email sent.
}).catch(function(error) {
// An error happened.
let errorCode = error.code;
let errorMessage = error.message;
onError(errorMessage);
});
}
/**
* Accesses Firebase data to sign in with email and password.
* This function is called asynchronously. Use 'async' and 'await'.
*
* @param {string} email - A user's email
* @param {string} password - A user's password
* @param {Function} onError - A function callback to execute on error
*/
async signIn(email, password, onError) {
await firebase.auth().signInWithEmailAndPassword(email, password).catch(onError);
}
signinwithFB(onError) {
//console.log('begin signinwithFB');
LoginManager.logInWithReadPermissions(['public_profile', 'email']).then(
// console.log('walk into else'),
AccessToken.getCurrentAccessToken().then(function(data) {
let accessToken = firebase.auth.FacebookAuthProvider.credential(data.accessToken);
console.log('accessToken'+accessToken)
this.handleFirebaseLogin(accessToken);
}.bind(this))
);
}
signInwithTwitter(onError){
RNTwitterSignIn.init('pdfOq2bGgmAD59pe3241W1hMg','xPRtJaBCqmZoFKPV7N8YcllUqOi4d0QWR521rebCQFcMUFGYE3');
RNTwitterSignIn.logIn().then((loginData)=>{
let accessToken = firebase.auth
.TwitterAuthProvider
.credential(
loginData.authToken,
loginData.authTokenSecret
);
this.handleFirebaseLogin(accessToken);
}).catch((error) => {
console.log(error)
onError('Unable to sign-in with Twitter');
});
user = firebase.auth().currentUser;
this.setAccount(user.uid);
// console.log('did login')
}
handleFirebaseLogin(accessToken) {
// console.log(accessToken)
firebase.auth().signInAndRetrieveDataWithCredential(accessToken).then((data)=> {
let user = firebase.auth().currentUser;
}).catch((error)=> {
let errorCode = error.code;
let errorMessage = error.message;
let email = error.email;
let credential = error.credential;
if (errorCode === 'auth/account-exists-with-different-credential') {
// Email already associated with another account.
}
})
console.log('did into handle firebase login')
}
setAccount(user){
//let user = firebase.auth().currentUser;
console.log("user in set account is: "+user.uid);
this.refDB.child('Users/').child(user.uid).set({
id:user.uid,
circle_lat:"",
circle_long:"",
circle_r:"",
deviceToken:"",
full_name:'',
phoneNum:"",
email:user.email,
thumbnail:['http://chittagongit.com//images/default-user-icon/default-user-icon-8.jpg']
});
}
/**
* Sign out of the database.
*
* @param {Function} onSuccess - A callback function on a successful signout
* @param {Function} onError - A callback function on a failure to signout
*/
signOut(onSuccess, onError) {
this.currentUser = null;
firebase.auth().signOut().then(onSuccess, onError);
}
/**
* Write to the database in table 'Bike' using the id as the child. Adds a date time and owner to the data
*
* @param {Object} bikeData - Bike data to add to the database
* @param {Function} onSuccess - A function callback to execute on the success of writing to the database
* @param {Function} onError - A function callback to execute on an error when writing to the database
*/
writeBikeData(bikeData, onSuccess, onError) {
this.getCurrentUser((userID) => {
bikeData.owner = userID;
this.refDB.child('Bike/').child(bikeData.id).set(bikeData, onSuccess).catch(onError);
});
}
/**
* Write to the database in the table 'Users' using the user id as the child.
*
* @param {Object} profileData - The data to upload
* @param {Function} onSuccess - A function callback to execute on the success of writing to the database
* @param {Function} onError - A function callback to execute on an error when writing to the database
*/
writeProfileData(profileData, onSuccess, onError) {
this.refDB.child('Users/').child(profileData.id).set(profileData, onSuccess).catch(onError);
}
/**
* Overwrites data in the database by reading the data, merging it with the new values and writing back to the same ID.
*
* @param {Object} newBikeData - New data to write
* @param {Function} onSuccess - A function callback to run when writing is successful
* @param {Function} onError - A function callback to run when writing fails
*/
editBikeData(newBikeData, onSuccess, onError) {
const bikeID = newBikeData.id;
this.refDB.child('Bike/').once('value', (snapshot) => {
let bikeData = snapshot.val();
let originalBikeData = bikeData[bikeID];
let updatedObj = this.merge(originalBikeData, newBikeData);
this.refDB.child('Bike/').child(bikeID).set(updatedObj, onSuccess).catch(onError);
}).catch((error) => {
onError(error);
console.log(error);
});
}
/**
* Overwrites data in the database by reading the data, merging it with the new values and writing back to the same ID.
*
* @param {Object} newProfileData - New data to write
* @param {Function} onSuccess - A function callback to run when writing is successful
* @param {Function} onError - A function callback to run when writing fails
*/
editProfileData(newProfileData, onSuccess, onError) {
const userID = newProfileData.id;
this.refDB.child('Users/' + userID).once('value', (snapshot) => {
let originalUserData = snapshot.val();
let updatedObj = this.merge(originalUserData, newProfileData);
this.refDB.child('Users/').child(userID).set(updatedObj, onSuccess).catch(onError);
}).catch((error) => {
onError(error);
console.log(error);
});
}
/**
* Merges the data of two objects. Keeps everything in originalObj, adds any key in newObj and overwrites duplicates in originalObj.
*
* @param {Object} originalObj - Original data to replace
* @param {Object} newObj - New data to add
*
* @return {Object} Merged data
*/
merge(originalObj, newObj) {
// Merge them together by just overwriting existing keys and adding new ones
// key: the name of the object key
// index: the ordinal position of the key within the object
Object.keys(newObj).forEach((key,index) => {
originalObj[key] = newObj[key];
});
return originalObj; // Need to return original because it has datetime and owner
}
/**
* Read data from the user table once, only looking for a specific user id.
*
* @param {string} id - The current user's id
* @param {Function} callback - A function callback that is with the value(s) read
*/
readProfileDataOnce(id, callback) {
this.refDB.child('Users/' + id).once('value', callback);
}
/**
* Read data from the bike table only once.
*
* @param {Function} callback - A function callback that is with the value(s) read
*/
readBikeDataOnce(callback) {
this.refDB.child('Bike/').once('value', callback);
}
/**
* Read data from the bike table every time there is a change in the database.
*
* @param {Function} callback - A function callback that is with the value(s) read
* @return {Object} A listener from the 'on' function
*/
readBikeDataOn(callback) {
return this.listenOn('Bike/', 'value', callback);
// this.refDB.child('Bike/').on('value', callback);
}
readBikeDataOff(listener) {
this.listenOff('Bike/', 'value', listener);
}
/**
* General function for listening to the database for events.
* Be as specific as possible when specifying the child (don't just listen on root).
* See https://firebase.google.com/docs/reference/js/firebase.database.Reference#on
* for event names.
*
* @param {string} child - A child to listen on
* @param {string} event - An event to listen for
* @param {Function} callback - A function callback to trigger when data is recieved
* @return {Object} A listener from the 'on' function
*/
listenOn(child, event, callback) {
return this.refDB.child(child).on(event, callback);
}
listenOff(child, event, listener) {
this.refDB.child(child).off(event, listener);
}
/**
* Removes a bike id from the database.
*
* @param {string} key - A bike id to remove
* @param {Function} callback - A function callback to trigger when the bike is removed
*/
removeBikeItem(key, callback) {
this.removeItem('Bike/', key, callback);
}
/**
* Removes an item from the database given by 'removeChild' in the 'table'.
*
* @param {string} table - A table to remove from
* @param {string} removeChild - A child id to remove
* @param {Function} callback - A function callback to trigger when item is removed
*/
removeItem(table, removeChild, callback) {
this.refDB.child(table).child(removeChild).remove().then(() => {
callback(true);
}).catch(() => {
callback(false);
});
}
/**
* Trigger the on reads by adding and removing an item from the database in the Bike Table.
*
* @param {Object} data - The temporary object to store
* @param {Function} onError - A callback function if there is an error writing
*/
triggerTemporaryItem(data, onError) {
this.refDB.child('Bike/').child(data.tempID).set(data, (result) => {
this.refDB.child('Bike/').child(data.tempID).remove();
}).catch(onError);
}
/**
* Returns a new unique key generated by the database.
*
* @return {string} A newly generated unique key
*/
getNewBikeID() {
return this.refDB.child('Bike').push().key;
}
/**
* Returns a new unique key generated by the database. Shouldn't need to use this because user IDs are generated on Sign-up.
*/
getNewProfileID() {
return this.refDB.child('Users').push().key;
}
/**
* Makes the database go offline.
*/
goOffline() {
firebase.database().goOffline();
}
/**
* Makes the database go online.
*/
goOnline() {
firebase.database().goOnline();
}
/**
* Removes an item from the database by a supplied key.
*
* @param {string} key - An id in the database
*/
// removeBikeItem(key) {
// this.refDB.child('Bike').child(key).remove();
// }
/**
* Returns the currently logged in user's id.
*
* @param {Function} onComplete - A callback function when the state has changed
* @return {Function} Function to unsubscribe from the authentication listener
*/
getCurrentUser(onComplete) {
return firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log("user id: " + user.uid);
onComplete(user.uid);
} else {
console.log("user not defined");
onComplete(null);
}
});
}
/**
* Listens for authentication changes and only calls the onSuccess function if a user is defined.
*
* @param {Function} onSuccess - A callback function when the state has changed and a user is defined
*/
listenForAuthChange(onSuccess) {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log("user id: " + user.uid);
onSuccess(user.uid);
}
});
}
/**
* Remove a images by their url from storage.
*
* @param {string} thumbnails - A list of images to delete
* @param {Function} callback - A function to call on completion or on failure
*/
removeImages(thumbnails, callback) {
let result = true;
const storageWithoutRef = this.getStorageWithoutRef(); // We need to use the refFromURL so we can only use the storage without a reference
// Firebase does not support deleting directories so we must loop through the thumbnails and delete each file
for (let i=0; i < thumbnails.length; i++) {
storageWithoutRef.refFromURL(thumbnails[i]).delete().then(() => {
result = result && true;
}).catch((error) => {
result = result && false;
});
}
callback(result);
}
/**
* Return the possible image folders in the firebase storage.
* See top definitions for names.
* Possible TODO : Fetch folder names from the storage so to not hardcode them in.
*
* @return {Object} The string names of the folders, includes '/' in each name
*/
getImageFolders() {
return { BikeImages, ProfileImages };
}
/**
* Asynchronously write an image to firebase storage.
*
* @param {string} id - The id of the bike to write to
* @param {Object} file - The file object to write
* @param {string} filename - The name of the file
* @param {string} baseFolder - The base folder of the images. One of "BikeImages/", "ProfileImages/" etc. (Must include '/')
* @param {Function} onSuccess - The callback to call on a successful upload
* @param {Function} onError - The callback to call on a failed upload
*/
async writeImage(id, file, filename, baseFolder, onSuccess, onError) {
// Create a blob because the firebase 'put' function requires a blob
// I found out later that when we get an image from the user, we can actually get it as data
// and since the firebase 'put' function also accepts the data format, we can use that instead
// which would save the XMLHttpRequest to create a blob.
// TODO : Optimization - Use data format for images instead of creating a blob which would eliminate
// the following Promise
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = () => {
resolve(xhr.response);
};
xhr.onerror = (e) => {
console.log(e);
reject(new TypeError('Network request failed'));
};
xhr.responseType = 'blob';
xhr.open('GET', file.uri, true);
xhr.send(null);
});
const task = this.refStorage.child(baseFolder + id + '/' + filename).put(blob);
task.on('state_changed', (snapshot) => {
// Observe state change events such as progress, pause, and resume
// Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
let progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
switch (snapshot.state) {
case firebase.storage.TaskState.PAUSED: // or 'paused'
// console.log('Upload is paused');
break;
case firebase.storage.TaskState.RUNNING: // or 'running'
// console.log('Upload is running');
break;
}
}, onError, () => {
task.snapshot.ref.getDownloadURL().then((downloadURL) => {
// console.log('File available at', downloadURL);
blob.close(); // Make sure to close the blob
onSuccess(downloadURL);
return null;
});
});
}
/*
* Use this function when moving to react-native-firebase. The above function works with regular firebase.
*/
// async writeImage(id, file, filename, baseFolder, onSuccess, onError) {
// const task = this.refStorage.child(baseFolder + id + '/' + filename).putFile(file.uri);
// task.on('state_changed', (snapshot) => {
// // Observe state change events such as progress, pause, and resume
// // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
// let progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
// console.log('Upload is ' + progress + '% done');
// switch (snapshot.state) {
// case firebase.storage.TaskState.PAUSED: // or 'paused'
// // console.log('Upload is paused');
// break;
// case firebase.storage.TaskState.RUNNING: // or 'running'
// // console.log('Upload is running');
// break;
// }
// }, onError, () => {
// task.snapshot.ref.getDownloadURL().then((downloadURL) => {
// // console.log('File available at', downloadURL);
// blob.close(); // Make sure to close the blob
// onSuccess(downloadURL);
// return null;
// });
// });
// }
}
const Database = new FirebaseDatabase();
export default Database;