UNPKG

react-native-firebase-compiled

Version:

A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Admob, Analytics, Auth, Crash Reporting, Cloud Firestore, Database, Dynamic Links, Functions, Messaging (FCM), Remote Config, Sto

232 lines (187 loc) 5.55 kB
/** * * Firestore Transaction representation wrapper */ import { getAppEventName, SharedEventEmitter } from '../../utils/events'; import { getNativeModule } from '../../utils/native'; import Transaction from './Transaction'; let transactionId = 0; /** * Uses the push id generator to create a transaction id * @returns {number} * @private */ const generateTransactionId = () => transactionId++; /** * @class TransactionHandler */ export default class TransactionHandler { constructor(firestore) { this._pending = {}; this._firestore = firestore; SharedEventEmitter.addListener(getAppEventName(this._firestore, 'firestore_transaction_event'), this._handleTransactionEvent.bind(this)); } /** * ------------- * INTERNAL API * ------------- */ /** * Add a new transaction and start it natively. * @param updateFunction */ _add(updateFunction) { const id = generateTransactionId(); // $FlowExpectedError: Transaction has to be populated const meta = { id, updateFunction, stack: new Error().stack.split('\n').slice(2).join('\n') }; this._pending[id] = { meta, transaction: new Transaction(this._firestore, meta) }; // deferred promise return new Promise((resolve, reject) => { getNativeModule(this._firestore).transactionBegin(id); meta.resolve = r => { resolve(r); this._remove(id); }; meta.reject = e => { reject(e); this._remove(id); }; }); } /** * Destroys a local instance of a transaction meta * * @param id * @private */ _remove(id) { getNativeModule(this._firestore).transactionDispose(id); delete this._pending[id]; } /** * ------------- * EVENTS * ------------- */ /** * Handles incoming native transaction events and distributes to correct * internal handler by event.type * * @param event * @returns {*} * @private */ _handleTransactionEvent(event) { // eslint-disable-next-line default-case switch (event.type) { case 'update': this._handleUpdate(event); break; case 'error': this._handleError(event); break; case 'complete': this._handleComplete(event); break; } } /** * Handles incoming native transaction update events * * @param event * @private */ async _handleUpdate(event) { const { id } = event; // abort if no longer exists js side if (!this._pending[id]) return this._remove(id); const { meta, transaction } = this._pending[id]; const { updateFunction, reject } = meta; // clear any saved state from previous transaction runs transaction._prepare(); let finalError; let updateFailed; let pendingResult; // run the users custom update functionality try { const possiblePromise = updateFunction(transaction); // validate user has returned a promise in their update function // TODO must it actually return a promise? Can't find any usages of it without one... if (!possiblePromise || !possiblePromise.then) { finalError = new Error('Update function for `firestore.runTransaction(updateFunction)` must return a Promise.'); } else { pendingResult = await possiblePromise; } } catch (exception) { // exception can still be falsey if user `Promise.reject();` 's with no args // so we track the exception with a updateFailed boolean to ensure no fall-through updateFailed = true; finalError = exception; } // reject the final promise and remove from native // update is failed when either the users updateFunction // throws an error or rejects a promise if (updateFailed || finalError) { // $FlowExpectedError: Reject will always be present return reject(finalError); } // capture the resolved result as we'll need this // to resolve the runTransaction() promise when // native emits that the transaction is final transaction._pendingResult = pendingResult; // send the buffered update/set/delete commands for native to process return getNativeModule(this._firestore).transactionApplyBuffer(id, transaction._commandBuffer); } /** * Handles incoming native transaction error events * * @param event * @private */ _handleError(event) { const { id, error } = event; const { meta } = this._pending[id]; if (meta && error) { const { code, message } = error; // build a JS error and replace its stack // with the captured one at start of transaction // so it's actually relevant to the user const errorWithStack = new Error(message); // $FlowExpectedError: code is needed for Firebase errors errorWithStack.code = code; // $FlowExpectedError: stack should be a stack trace errorWithStack.stack = `Error: ${message}\n${meta.stack}`; // $FlowExpectedError: Reject will always be present meta.reject(errorWithStack); } } /** * Handles incoming native transaction complete events * * @param event * @private */ _handleComplete(event) { const { id } = event; const { meta, transaction } = this._pending[id]; if (meta) { const pendingResult = transaction._pendingResult; // $FlowExpectedError: Resolve will always be present meta.resolve(pendingResult); } } }