tutorbook
Version:
Web app connecting students with expert mentors and tutors.
109 lines (102 loc) • 4.05 kB
text/typescript
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<void> {
const [err, res] = await to<AxiosResponse<UserJSON>, AxiosError<ApiError>>(
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<UserJSON>).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<void> {
const provider: AuthProvider = new firebase.auth.GoogleAuthProvider();
const [err, cred] = await to<UserCredential, AuthError>(
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<UserInterface> = {
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.');
}
}