UNPKG

react-native-firebase-for-netmera

Version:
361 lines (308 loc) 8.81 kB
// @flow import INTERNALS from '../../../utils/internals'; import { SharedEventEmitter } from '../../../utils/events'; import { generatePushID, isFunction, isAndroid, isIOS, isString, nativeToJSError, } from '../../../utils'; import { getNativeModule } from '../../../utils/native'; import type Auth from '..'; import type { NativeErrorInterface } from '../../../common/commonTypes.flow'; type PhoneAuthSnapshot = { state: 'sent' | 'timeout' | 'verified' | 'error', verificationId: string, code: string | null, error: NativeErrorInterface | null, }; type PhoneAuthError = { code: string | null, verificationId: string, message: string | null, stack: string | null, }; export default class PhoneAuthListener { _auth: Auth; _timeout: number; _publicEvents: Object; _internalEvents: Object; _forceResending: boolean; _reject: Function | null; _resolve: Function | null; _credential: Object | null; _promise: Promise<*> | null; _phoneAuthRequestKey: string; /** * * @param auth * @param phoneNumber * @param timeout * @param forceResend */ constructor( auth: Auth, phoneNumber: string, timeout?: number, forceResend?: boolean ) { this._auth = auth; this._reject = null; this._resolve = null; this._promise = null; this._credential = null; this._timeout = timeout || 20; // 20 secs this._forceResending = forceResend || false; this._phoneAuthRequestKey = generatePushID(); // internal events this._internalEvents = { codeSent: `phone:auth:${this._phoneAuthRequestKey}:onCodeSent`, verificationFailed: `phone:auth:${this._phoneAuthRequestKey}:onVerificationFailed`, verificationComplete: `phone:auth:${this._phoneAuthRequestKey}:onVerificationComplete`, codeAutoRetrievalTimeout: `phone:auth:${this._phoneAuthRequestKey}:onCodeAutoRetrievalTimeout`, }; // user observer events this._publicEvents = { // error cb error: `phone:auth:${this._phoneAuthRequestKey}:error`, // observer event: `phone:auth:${this._phoneAuthRequestKey}:event`, // success cb success: `phone:auth:${this._phoneAuthRequestKey}:success`, }; // setup internal event listeners this._subscribeToEvents(); // start verification flow natively if (isAndroid) { getNativeModule(this._auth).verifyPhoneNumber( phoneNumber, this._phoneAuthRequestKey, this._timeout, this._forceResending ); } if (isIOS) { getNativeModule(this._auth).verifyPhoneNumber( phoneNumber, this._phoneAuthRequestKey ); } } /** * Subscribes to all EE events on this._internalEvents * @private */ _subscribeToEvents() { const events = Object.keys(this._internalEvents); for (let i = 0, len = events.length; i < len; i++) { const type = events[i]; SharedEventEmitter.once( this._internalEvents[type], // $FlowExpectedError: Flow doesn't support indexable signatures on classes: https://github.com/facebook/flow/issues/1323 this[`_${type}Handler`].bind(this) ); } } /** * Subscribe a users listener cb to the snapshot events. * @param observer * @private */ _addUserObserver(observer) { SharedEventEmitter.addListener(this._publicEvents.event, observer); } /** * Send a snapshot event to users event observer. * @param snapshot PhoneAuthSnapshot * @private */ _emitToObservers(snapshot: PhoneAuthSnapshot) { SharedEventEmitter.emit(this._publicEvents.event, snapshot); } /** * Send a error snapshot event to any subscribed errorCb's * @param snapshot * @private */ _emitToErrorCb(snapshot) { const { error } = snapshot; if (this._reject) this._reject(error); SharedEventEmitter.emit(this._publicEvents.error, error); } /** * Send a success snapshot event to any subscribed completeCb's * @param snapshot * @private */ _emitToSuccessCb(snapshot) { if (this._resolve) this._resolve(snapshot); SharedEventEmitter.emit(this._publicEvents.success, snapshot); } /** * Removes all listeners for this phone auth instance * @private */ _removeAllListeners() { setTimeout(() => { // move to next event loop - not sure if needed // internal listeners Object.values(this._internalEvents).forEach(event => { SharedEventEmitter.removeAllListeners(event); }); // user observer listeners Object.values(this._publicEvents).forEach(publicEvent => { SharedEventEmitter.removeAllListeners(publicEvent); }); }, 0); } /** * Create a new internal deferred promise, if not already created * @private */ _promiseDeferred() { if (!this._promise) { this._promise = new Promise((resolve, reject) => { this._resolve = result => { this._resolve = null; return resolve(result); }; this._reject = possibleError => { this._reject = null; return reject(possibleError); }; }); } } /* -------------------------- --- INTERNAL EVENT HANDLERS ---------------------------- */ /** * Internal code sent event handler * @private * @param credential */ _codeSentHandler(credential) { const snapshot: PhoneAuthSnapshot = { verificationId: credential.verificationId, code: null, error: null, state: 'sent', }; this._emitToObservers(snapshot); if (isIOS) { this._emitToSuccessCb(snapshot); } if (isAndroid) { // android can auto retrieve so we don't emit to successCb immediately, // if auto retrieve times out then that will emit to successCb } } /** * Internal code auto retrieve timeout event handler * @private * @param credential */ _codeAutoRetrievalTimeoutHandler(credential) { const snapshot: PhoneAuthSnapshot = { verificationId: credential.verificationId, code: null, error: null, state: 'timeout', }; this._emitToObservers(snapshot); this._emitToSuccessCb(snapshot); } /** * Internal verification complete event handler * @param credential * @private */ _verificationCompleteHandler(credential) { const snapshot: PhoneAuthSnapshot = { verificationId: credential.verificationId, code: credential.code || null, error: null, state: 'verified', }; this._emitToObservers(snapshot); this._emitToSuccessCb(snapshot); this._removeAllListeners(); } /** * Internal verification failed event handler * @param state * @private */ _verificationFailedHandler(state) { const snapshot: PhoneAuthSnapshot = { verificationId: state.verificationId, code: null, error: null, state: 'error', }; const { code, message, nativeErrorMessage } = state.error; snapshot.error = nativeToJSError(code, message, { nativeErrorMessage }); this._emitToObservers(snapshot); this._emitToErrorCb(snapshot); this._removeAllListeners(); } /* ------------- -- PUBLIC API --------------*/ on( event: string, observer: PhoneAuthSnapshot => void, errorCb?: PhoneAuthError => void, successCb?: PhoneAuthSnapshot => void ): this { if (!isString(event)) { throw new Error( INTERNALS.STRINGS.ERROR_MISSING_ARG_NAMED('event', 'string', 'on') ); } if (event !== 'state_changed') { throw new Error( INTERNALS.STRINGS.ERROR_ARG_INVALID_VALUE( 'event', 'state_changed', event ) ); } if (!isFunction(observer)) { throw new Error( INTERNALS.STRINGS.ERROR_MISSING_ARG_NAMED('observer', 'function', 'on') ); } this._addUserObserver(observer); if (isFunction(errorCb)) { SharedEventEmitter.once(this._publicEvents.error, errorCb); } if (isFunction(successCb)) { SharedEventEmitter.once(this._publicEvents.success, successCb); } return this; } /** * Promise .then proxy * @param fn */ then(fn: PhoneAuthSnapshot => void) { this._promiseDeferred(); // $FlowFixMe: Unsure how to annotate `bind` here if (this._promise) return this._promise.then.bind(this._promise)(fn); return undefined; // will never get here - just to keep flow happy } /** * Promise .catch proxy * @param fn */ catch(fn: Error => void) { this._promiseDeferred(); // $FlowFixMe: Unsure how to annotate `bind` here if (this._promise) return this._promise.catch.bind(this._promise)(fn); return undefined; // will never get here - just to keep flow happy } }