import { User, UserJSON, UserInterface, ApiError } from '@tutorbook/model'; import { mutate } from 'swr'; import axios, { AxiosError, AxiosResponse } from 'axios'; import to from 'await-to-js'; import firebase from '@tutorbook/firebase'; import 'firebase/auth'; /** * Type aliases so that we don't have to type out the whole type. We could try * importing these directly from the `@firebase/firestore-types` or the * `@google-cloud/firestore` packages, but that's not recommended. * @todo Perhaps figure out a way to **only** import the type defs we need. */ type Auth = firebase.auth.Auth; type AuthError = firebase.auth.AuthError; type AuthProvider = firebase.auth.AuthProvider; type UserCredential = firebase.auth.UserCredential; const auth: Auth = firebase.auth(); export async function signup(newUser: User, parents?: User[]): Promise { const [err, res] = await to, AxiosError>( axios.post('/api/users', { user: newUser.toJSON(), parents: (parents || []).map((parent: User) => parent.toJSON()), }) ); if (err && err.response) { // The request was made and the server responded with a status // code that falls out of the range of 2xx console.error(`[ERROR] ${err.response.data.msg}`, err.response.data); firebase.analytics().logEvent('exception', { description: `User API responded with error: ${err.response.data.msg}`, user: newUser.toJSON(), fatal: true, }); if (err.response.data.msg.indexOf('already exists') >= 0) { console.warn('[WARNING] User already existed.'); } else { throw new Error(err.response.data.msg); } } else if (err && err.request) { // The request was made but no response was received // `err.request` is an instance of XMLHttpRequest in the // browser and an instance of http.ClientRequest in node.js console.error('[ERROR] User API did not respond:', err.request); firebase.analytics().logEvent('exception', { description: 'User API did not respond.', user: newUser.toJSON(), fatal: true, }); throw new Error('User creation API did not respond.'); } else if (err) { // Something happened in setting up the request that triggered // an err console.error('[ERROR] Calling user API:', err); firebase.analytics().logEvent('exception', { description: `Error calling user API: ${err.message}`, user: newUser.toJSON(), fatal: true, }); throw new Error(`Error calling user API: ${err.message}`); } else { const signedInUser: User = User.fromJSON( (res as AxiosResponse).data ); await auth.signInWithCustomToken(signedInUser.token as string); await mutate('/api/account', signedInUser.toJSON()); firebase.analytics().logEvent('login', { method: 'custom_token' }); } } export async function signupWithGoogle( newUser?: User, parents?: User[] ): Promise { const provider: AuthProvider = new firebase.auth.GoogleAuthProvider(); const [err, cred] = await to( auth.signInWithPopup(provider) ); if (err) { firebase.analytics().logEvent('exception', { description: `Error while signing up with Google. ${err.message}`, user: (newUser || new User()).toJSON(), fatal: false, }); throw new Error(err.message); } else if (cred && cred.user) { const firebaseUser: Partial = { id: cred.user.uid, name: cred.user.displayName as string, photo: cred.user.photoURL as string, email: cred.user.email as string, phone: cred.user.phoneNumber as string, }; const signedInUser = new User({ ...newUser, ...firebaseUser }); await mutate('/api/account', signedInUser.toJSON()); return signup(signedInUser, parents); } else { firebase.analytics().logEvent('exception', { description: 'No user in sign-in with Google response.', fatal: false, }); throw new Error('No user in sign-in with Google response.'); } }