diff --git a/README.md b/README.md index 183e66b..bd3a71f 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,9 @@ A set of reusable [React Hooks](https://reactjs.org/docs/hooks-intro.html) for [ [![npm version](https://img.shields.io/npm/v/react-firebase-hooks.svg?style=flat-square)](https://www.npmjs.com/package/react-firebase-hooks) [![npm downloads](https://img.shields.io/npm/dm/react-firebase-hooks.svg?style=flat-square)](https://www.npmjs.com/package/react-firebase-hooks) -This documentation is for v4 of React Firebase Hooks which makes the package compatible with Firebase v9 and drops support for previous versions of Firebase - more details [here](https://github.com/CSFrequency/react-firebase-hooks/releases/tag/v4.0.0). +This documentation is for v5 of React Firebase Hooks which requires Firebase v9 or higher. +- For v3 documentation (Firebase v9), see [here](https://github.com/CSFrequency/react-firebase-hooks/tree/v4.0.2). - For v3 documentation (Firebase v8), see [here](https://github.com/CSFrequency/react-firebase-hooks/tree/v3.0.4). - For v2 documentation, see [here](https://github.com/CSFrequency/react-firebase-hooks/tree/v2.2.0). @@ -28,18 +29,18 @@ This assumes that you’re using the [npm](https://npmjs.com) or [yarn](https:// ## Why? -There has been a **lot** of hype around React Hooks, but this hype merely reflects that there are obvious real world benefits of Hooks to React developers everywhere. - This library explores how React Hooks can work to make integration with Firebase even more straightforward than it already is. It takes inspiration for naming from RxFire and is based on an internal library that we had been using in a number of apps prior to the release of React Hooks. The implementation with hooks is 10x simpler than our previous implementation. -## Upgrading from v3 to v4 +## Upgrading from v4 to v5 -To upgrade your project from v3 to v4 check out the [Release Notes](https://github.com/CSFrequency/react-firebase-hooks/releases/tag/v4.0.0) which have full details of everything that needs to be changed. +To upgrade your project from v4 to v5 check out the [Release Notes](https://github.com/CSFrequency/react-firebase-hooks/releases/tag/v5.0.0) which have full details of everything that needs to be changed. ## Documentation -- [Auth Hooks](/auth) +- [Authentication Hooks](/auth) - [Cloud Firestore Hooks](/firestore) +- [Cloud Functions Hooks](/functions) +- [Cloud Messaging Hooks](/messaging) - [Cloud Storage Hooks](/storage) - [Realtime Database Hooks](/database) diff --git a/auth/README.md b/auth/README.md index 81b0d40..d44b318 100644 --- a/auth/README.md +++ b/auth/README.md @@ -13,11 +13,23 @@ List of Auth hooks: - [useAuthState](#useauthstate) - [useCreateUserWithEmailAndPassword](#usecreateuserwithemailandpassword) - [useSignInWithEmailAndPassword](#usesigninwithemailandpassword) +- [useSignInWithApple](#usesigninwithapple) +- [useSignInWithFacebook](#usesigninwithfacebook) +- [useSignInWithGithub](#usesigninwithgithub) +- [useSignInWithGoogle](#usesigninwithgoogle) +- [useSignInWithMicrosoft](#usesigninwithmicrosoft) +- [useSignInWithTwitter](#usesigninwithtwitter) +- [useSignInWithYahoo](#usesigninwithyahoo) +- [useUpdateEmail](#useupdateemail) +- [useUpdatePassword](#useupdatepassword) +- [useUpdateProfile](#useupdateprofile) +- [useSendPasswordResetEmail](#usesendpasswordresetemail) +- [useSendEmailVerification](#usesendemailverification) ### useAuthState ```js -const [user, loading, error] = useAuthState(auth); +const [user, loading, error] = useAuthState(auth, options); ``` Retrieve and monitor the authentication state from Firebase. @@ -25,6 +37,8 @@ Retrieve and monitor the authentication state from Firebase. The `useAuthState` hook takes the following parameters: - `auth`: `auth.Auth` instance for the app you would like to monitor +- `options`: (optional) `Object with the following parameters: + - `onUserChanged`: (optional) function to be called with `auth.User` each time the user changes. This allows you to do things like load custom claims. Returns: @@ -40,13 +54,13 @@ Returns: import { getAuth, signInWithEmailAndPassword, signOut } from 'firebase/auth'; import { useAuthState } from 'react-firebase-hooks/auth'; -const auth = getAuth(firebaseApp) +const auth = getAuth(firebaseApp); const login = () => { signInWithEmailAndPassword(auth, 'test@test.com', 'password'); }; const logout = () => { - signOut(auth) + signOut(auth); }; const CurrentUser = () => { @@ -165,7 +179,7 @@ const [ user, loading, error, -] = useSignInWithEmailAndPassword(auth, email, password); +] = useSignInWithEmailAndPassword(auth); ``` Login a user with email and password. Wraps the underlying `auth.signInWithEmailAndPassword` method and provides additional `loading` and `error` information. @@ -232,3 +246,501 @@ const SignIn = () => { ); }; ``` + +### useSignInWithApple + +```js +const [signInWithApple, user, loading, error] = useSignInWithApple(auth); +``` + +Login a user with Apple Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information. + +The `useSignInWithApple` hook takes the following parameters: + +- `auth`: `Auth` instance for the app you would like to monitor + +Returns: + +- `signInWithApple(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login +- `user`: The `auth.User` if the user was logged in or `undefined` if not +- `loading`: A `boolean` to indicate whether the the user login is processing +- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error + +#### Full example + +See [social login example](#social-login-example) + +### useSignInWithFacebook + +```js +const [signInWithFacebook, user, loading, error] = useSignInWithFacebook(auth); +``` + +Login a user with Facebook Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information. + +The `useSignInWithApple` hook takes the following parameters: + +- `auth`: `Auth` instance for the app you would like to monitor + +Returns: + +- `signInWithFacebook(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login +- `user`: The `auth.User` if the user was logged in or `undefined` if not +- `loading`: A `boolean` to indicate whether the the user login is processing +- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error + +#### Full example + +See [social login example](#social-login-example) + +### useSignInWithGithub + +```js +const [signInWithGithub, user, loading, error] = useSignInWithGithub(auth); +``` + +Login a user with Github Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information. + +The `useSignInWithGithub` hook takes the following parameters: + +- `auth`: `Auth` instance for the app you would like to monitor + +Returns: + +- `signInWithGithub(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login +- `user`: The `auth.User` if the user was logged in or `undefined` if not +- `loading`: A `boolean` to indicate whether the the user login is processing +- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error + +#### Full example + +See [social login example](#social-login-example) + +### useSignInWithGoogle + +```js +const [signInWithGoogle, user, loading, error] = useSignInWithGoogle(auth); +``` + +Login a user with Google Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.GoogleProvider` and provides additional `loading` and `error` information. + +The `useSignInWithGoogle` hook takes the following parameters: + +- `auth`: `Auth` instance for the app you would like to monitor + +Returns: + +- `signInWithGoogle(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login +- `user`: The `auth.User` if the user was logged in or `undefined` if not +- `loading`: A `boolean` to indicate whether the the user login is processing +- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error + +#### Full example + +See [social login example](#social-login-example) + +### useSignInWithMicrosoft + +```js +const [signInWithMicrosoft, user, loading, error] = useSignInWithMicrosoft( + auth +); +``` + +Login a user with Microsoftt Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information. + +The `useSignInWithMicrosoft` hook takes the following parameters: + +- `auth`: `Auth` instance for the app you would like to monitor + +Returns: + +- `signInWithMicrosoft(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login +- `user`: The `auth.User` if the user was logged in or `undefined` if not +- `loading`: A `boolean` to indicate whether the the user login is processing +- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error + +#### Full example + +See [social login example](#social-login-example) + +### useSignInWithTwittter + +```js +const [signInWithTwitter, user, loading, error] = useSignInWithTwitter(auth); +``` + +Login a user with Twitter Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information. + +The `useSignInWithTwitter` hook takes the following parameters: + +- `auth`: `Auth` instance for the app you would like to monitor + +Returns: + +- `signInWithTwitter(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login +- `user`: The `auth.User` if the user was logged in or `undefined` if not +- `loading`: A `boolean` to indicate whether the the user login is processing +- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error + +#### Full example + +See [social login example](#social-login-example) + +### useSignInWithYahoo + +```js +const [signInWithYahoo, user, loading, error] = useSignInWithYahoo(auth); +``` + +Login a user with Yahoo Authenticatiton. Wraps the underlying `auth.signInWithPopup` method with the `auth.OAuthProvider` and provides additional `loading` and `error` information. + +The `useSignInWithYahoo` hook takes the following parameters: + +- `auth`: `Auth` instance for the app you would like to monitor + +Returns: + +- `signInWithYahoo(scopes: string[], customOAuthParameters: auth.CustomParameters)`: a function you can call to start the login +- `user`: The `auth.User` if the user was logged in or `undefined` if not +- `loading`: A `boolean` to indicate whether the the user login is processing +- `error`: Any `Error` returned by Firebase when trying to login the user, or `undefined` if there is no error + +#### Full example + +See [social login example](#social-login-example) + +### Social Login Example + +```jsx +import { useSignInWithXXX } from 'react-firebase-hooks/auth'; + +const SignIn = () => { + const [signInWithXXX, user, loading, error] = useSignInWithXXX(auth); + + if (error) { + return ( +
+

Error: {error.message}

+
+ ); + } + if (loading) { + return

Loading...

; + } + if (user) { + return ( +
+

Signed In User: {user.email}

+
+ ); + } + return ( +
+ setEmail(e.target.value)} + /> + setPassword(e.target.value)} + /> + +
+ ); +}; +``` + +### useUpdateEmail + +```js +const [updateEmail, updating, error] = useUpdateEmail(auth); +``` + +Update the current user's email address. Wraps the underlying `auth.updateEmail` method and provides additional `updating` and `error` information. + +The `useUpdateEmail` hook takes the following parameters: + +- `auth`: `Auth` instance for the app you would like to monitor + +Returns: + +- `updateEmail(email: string)`: a function you can call to update the current user's email addres +- `updating`: A `boolean` to indicate whether the user update is processing +- `error`: Any `Error` returned by Firebase when trying to update the user, or `undefined` if there is no error + +#### Full Example + +```jsx +import { useUpdateEmail } from 'react-firebase-hooks/auth'; + +const UpdateEmail = () => { + const [email, setEmail] = useState(''); + const [updateEmail, updating, error] = useUpdateEmail(auth); + + if (error) { + return ( +
+

Error: {error.message}

+
+ ); + } + if (updating) { + return

Updating...

; + } + return ( +
+ setEmail(e.target.value)} + /> + +
+ ); +}; +``` + +### useUpdatePassword + +```js +const [updatePassword, updating, error] = useUpdatePassword(auth); +``` + +Update the current user's password. Wraps the underlying `auth.updatePassword` method and provides additional `updating` and `error` information. + +The `useUpdatePassword` hook takes the following parameters: + +- `auth`: `Auth` instance for the app you would like to monitor + +Returns: + +- `updatePassword(password: string)`: a function you can call to update the current user's password +- `updating`: A `boolean` to indicate whether the user update is processing +- `error`: Any `Error` returned by Firebase when trying to update the user, or `undefined` if there is no error + +#### Full Example + +```jsx +import { useUpdatePassword } from 'react-firebase-hooks/auth'; + +const UpdatePassword = () => { + const [password, setPassword] = useState(''); + const [updatePassword, updating, error] = useUpdatePassword(auth); + + if (error) { + return ( +
+

Error: {error.message}

+
+ ); + } + if (updating) { + return

Updating...

; + } + return ( +
+ setPassword(e.target.value)} + /> + +
+ ); +}; +``` + +### useUpdateProfile + +```js +const [updateProfile, updating, error] = useUpdateProfile(auth); +``` + +Update the current user's profile. Wraps the underlying `auth.updateProfile` method and provides additional `updating` and `error` information. + +The `useUpdateProfile` hook takes the following parameters: + +- `auth`: `Auth` instance for the app you would like to monitor + +Returns: + +- `updateProfile({ displayName: string, photoURL: string })`: a function you can call to update the current user's profile +- `updating`: A `boolean` to indicate whether the user update is processing +- `error`: Any `Error` returned by Firebase when trying to update the user, or `undefined` if there is no error + +#### Full Example + +```jsx +import { useUpdateProfile } from 'react-firebase-hooks/auth'; + +const UpdateProfile = () => { + const [displayName, setDisplayName] = useState(''); + const [photoURL, setPhotoURL] = useState(''); + const [updateProfile, updating, error] = useUpdateProfile(auth); + + if (error) { + return ( +
+

Error: {error.message}

+
+ ); + } + if (updating) { + return

Updating...

; + } + return ( +
+ setDisplayName(e.target.value)} + /> + setPhotoURL(e.target.value)} + /> + +
+ ); +}; +``` + +### useSendPasswordResetEmail + +```js +const [sendPasswordResetEmail, sending, error] = useSendPasswordResetEmail( + auth +); +``` + +Send a password reset email to the specified email address. Wraps the underlying `auth.sendPasswordResetEmail` method and provides additional `sending` and `error` information. + +The `useSendPasswordResetEmail` hook takes the following parameters: + +- `auth`: `Auth` instance for the app you would like to monitor + +Returns: + +- `sendPasswordResetEmail(email: string)`: a function you can call to send a password reset emaail +- `sending`: A `boolean` to indicate whether the email is being sent +- `error`: Any `Error` returned by Firebase when trying to send the email, or `undefined` if there is no error + +#### Full Example + +```jsx +import { useSendPasswordResetEmail } from 'react-firebase-hooks/auth'; + +const SendPasswordReset = () => { + const [email, setEmail] = useState(''); + const [sendPasswordResetEmail, sending, error] = useSendPasswordResetEmail( + auth + ); + + if (error) { + return ( +
+

Error: {error.message}

+
+ ); + } + if (sending) { + return

Sending...

; + } + return ( +
+ setEmail(e.target.value)} + /> + +
+ ); +}; +``` + +### useSendEmailVerification + +```js +const [sendEmailVerification, sending, error] = useSendEmailVerification(auth); +``` + +Send a verification email to the current user. Wraps the underlying `auth.sendEmailVerification` method and provides additional `sending` and `error` information. + +The `useSendEmailVerification` hook takes the following parameters: + +- `auth`: `Auth` instance for the app you would like to monitor + +Returns: + +- `sendEmailVerification()`: a function you can call to send a password reset emaail +- `sending`: A `boolean` to indicate whether the email is being sent +- `error`: Any `Error` returned by Firebase when trying to send the email, or `undefined` if there is no error + +#### Full Example + +```jsx +import { useSendEmailVerification } from 'react-firebase-hooks/auth'; + +const SendEmailVerification = () => { + const [email, setEmail] = useState(''); + const [sendEmailVerification, sending, error] = useSendEmailVerification( + auth + ); + + if (error) { + return ( +
+

Error: {error.message}

+
+ ); + } + if (sending) { + return

Sending...

; + } + return ( +
+ +
+ ); +}; +``` diff --git a/auth/index.js.flow b/auth/index.js.flow deleted file mode 100644 index 2ee9b8a..0000000 --- a/auth/index.js.flow +++ /dev/null @@ -1,7 +0,0 @@ -// @flow -import type { Auth, AuthError, User } from 'firebase/auth'; - -type LoadingHook = [T | void, boolean, AuthError | void]; - -export type AuthStateHook = LoadingHook; -declare export function useAuthState(auth: Auth): AuthStateHook; diff --git a/auth/index.ts b/auth/index.ts index 7d97376..a880dad 100644 --- a/auth/index.ts +++ b/auth/index.ts @@ -1,5 +1,30 @@ export { default as useAuthState, AuthStateHook } from './useAuthState'; -export { default as useSignInWithEmailAndPassword } from './useSignInWithEmailAndPassword'; export { default as useCreateUserWithEmailAndPassword } from './useCreateUserWithEmailAndPassword'; +export { + default as useSendEmailVerification, + SendEmailVerificationHook, +} from './useSendEmailVerification'; +export { + default as useSendPasswordResetEmail, + SendPasswordResetEmailHook, +} from './useSendPasswordResetEmail'; +export { default as useSignInWithEmailAndPassword } from './useSignInWithEmailAndPassword'; +export { + useSignInWithApple, + useSignInWithFacebook, + useSignInWithGithub, + useSignInWithGoogle, + useSignInWithMicrosoft, + useSignInWithTwitter, + useSignInWithYahoo, +} from './useSignInWithPopup'; +export { + useUpdateEmail, + useUpdatePassword, + useUpdateProfile, + UpdateEmailHook, + UpdatePasswordHook, + UpdateProfileHook, +} from './useUpdateUser'; -export { EmailAndPasswordActionHook } from './types'; +export { EmailAndPasswordActionHook, SignInWithPopupHook } from './types'; diff --git a/auth/types.ts b/auth/types.ts index 204ffca..7c8f8cd 100644 --- a/auth/types.ts +++ b/auth/types.ts @@ -1,16 +1,24 @@ -import { ActionCodeSettings, AuthError, UserCredential } from 'firebase/auth'; +import { + ActionCodeSettings, + AuthError, + CustomParameters, + UserCredential, +} from 'firebase/auth'; -export type AuthActionHook = [ - (email: string, password: string) => Promise, - T | undefined, +export type AuthActionHook = [ + M, + UserCredential | undefined, boolean, - E | undefined + AuthError | undefined ]; export type CreateUserOptions = { emailVerificationOptions?: ActionCodeSettings; sendEmailVerification?: boolean; }; export type EmailAndPasswordActionHook = AuthActionHook< - UserCredential, - AuthError + (email: string, password: string) => Promise +>; + +export type SignInWithPopupHook = AuthActionHook< + (scopes?: string[], customOAuthParameters?: CustomParameters) => Promise >; diff --git a/auth/useAuthState.ts b/auth/useAuthState.ts index e888375..3c80778 100644 --- a/auth/useAuthState.ts +++ b/auth/useAuthState.ts @@ -4,15 +4,37 @@ import { LoadingHook, useLoadingValue } from '../util'; export type AuthStateHook = LoadingHook; -export default (auth: Auth): AuthStateHook => { +type AuthStateOptions = { + onUserChanged?: (user: User | null) => Promise; +}; + +export default (auth: Auth, options?: AuthStateOptions): AuthStateHook => { const { error, loading, setError, setValue, value } = useLoadingValue< User | null, Error >(() => auth.currentUser); useEffect(() => { - return onAuthStateChanged(auth, setValue, setError); - }, [auth]); + const listener = onAuthStateChanged( + auth, + async (user) => { + if (options?.onUserChanged) { + // onUserChanged function to process custom claims on any other trigger function + try { + await options.onUserChanged(user); + } catch (e) { + setError(e as Error); + } + } + setValue(user); + }, + setError + ); + + return () => { + listener(); + }; + }, [auth, options]); const resArray: AuthStateHook = [value, loading, error]; return useMemo(() => resArray, resArray); diff --git a/auth/useSendEmailVerification.ts b/auth/useSendEmailVerification.ts new file mode 100644 index 0000000..5c91f34 --- /dev/null +++ b/auth/useSendEmailVerification.ts @@ -0,0 +1,39 @@ +import { + Auth, + AuthError, + sendEmailVerification as fbSendEmailVerification, +} from 'firebase/auth'; +import { useMemo, useState } from 'react'; + +export type SendEmailVerificationHook = [ + () => Promise, + boolean, + AuthError | Error | undefined +]; + +export default (auth: Auth): SendEmailVerificationHook => { + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + + const sendEmailVerification = async () => { + setLoading(true); + try { + if (auth.currentUser) { + await fbSendEmailVerification(auth.currentUser); + } else { + setError(new Error('No user is logged in') as AuthError); + } + } catch (err) { + setError(err as AuthError); + } finally { + setLoading(false); + } + }; + + const resArray: SendEmailVerificationHook = [ + sendEmailVerification, + loading, + error, + ]; + return useMemo(() => resArray, resArray); +}; diff --git a/auth/useSendPasswordResetEmail.ts b/auth/useSendPasswordResetEmail.ts new file mode 100644 index 0000000..13c20d3 --- /dev/null +++ b/auth/useSendPasswordResetEmail.ts @@ -0,0 +1,39 @@ +import { + Auth, + AuthError, + sendPasswordResetEmail as fbSendPasswordResetEmail, +} from 'firebase/auth'; +import { useMemo, useState } from 'react'; + +export type SendPasswordResetEmailHook = [ + (email: string) => Promise, + boolean, + AuthError | Error | undefined +]; + +export default (auth: Auth): SendPasswordResetEmailHook => { + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + + const sendPasswordResetEmail = async (email: string) => { + setLoading(true); + try { + if (auth.currentUser) { + await fbSendPasswordResetEmail(auth, email); + } else { + setError(new Error('No user is logged in') as AuthError); + } + } catch (err) { + setError(err as AuthError); + } finally { + setLoading(false); + } + }; + + const resArray: SendPasswordResetEmailHook = [ + sendPasswordResetEmail, + loading, + error, + ]; + return useMemo(() => resArray, resArray); +}; diff --git a/auth/useSignInWithPopup.ts b/auth/useSignInWithPopup.ts new file mode 100644 index 0000000..a7c6bf5 --- /dev/null +++ b/auth/useSignInWithPopup.ts @@ -0,0 +1,151 @@ +import { useState, useMemo } from 'react'; +import { + Auth, + AuthError, + AuthProvider, + CustomParameters, + FacebookAuthProvider, + GithubAuthProvider, + GoogleAuthProvider, + OAuthProvider, + TwitterAuthProvider, + signInWithPopup, + UserCredential, +} from 'firebase/auth'; +import { SignInWithPopupHook } from './types'; + +export const useSignInWithApple = (auth: Auth): SignInWithPopupHook => { + return useSignInWithOAuth(auth, 'apple.com'); +}; + +export const useSignInWithFacebook = (auth: Auth): SignInWithPopupHook => { + const createFacebookAuthProvider = ( + scopes?: string[], + customOAuthParameters?: CustomParameters + ) => { + const provider = new FacebookAuthProvider(); + if (scopes) { + scopes.forEach((scope) => provider.addScope(scope)); + } + if (customOAuthParameters) { + provider.setCustomParameters(customOAuthParameters); + } + return provider; + }; + return useSignInWithPopup(auth, createFacebookAuthProvider); +}; + +export const useSignInWithGithub = (auth: Auth): SignInWithPopupHook => { + const createGithubAuthProvider = ( + scopes?: string[], + customOAuthParameters?: CustomParameters + ) => { + const provider = new GithubAuthProvider(); + if (scopes) { + scopes.forEach((scope) => provider.addScope(scope)); + } + if (customOAuthParameters) { + provider.setCustomParameters(customOAuthParameters); + } + return provider; + }; + return useSignInWithPopup(auth, createGithubAuthProvider); +}; + +export const useSignInWithGoogle = (auth: Auth): SignInWithPopupHook => { + const createGoogleAuthProvider = ( + scopes?: string[], + customOAuthParameters?: CustomParameters + ) => { + const provider = new GoogleAuthProvider(); + if (scopes) { + scopes.forEach((scope) => provider.addScope(scope)); + } + if (customOAuthParameters) { + provider.setCustomParameters(customOAuthParameters); + } + return provider; + }; + return useSignInWithPopup(auth, createGoogleAuthProvider); +}; + +export const useSignInWithMicrosoft = (auth: Auth): SignInWithPopupHook => { + return useSignInWithOAuth(auth, 'microsoft.com'); +}; + +export const useSignInWithTwitter = (auth: Auth): SignInWithPopupHook => { + const createTwitterAuthProvider = ( + scopes?: string[], + customOAuthParameters?: CustomParameters + ) => { + const provider = new TwitterAuthProvider(); + if (scopes) { + scopes.forEach((scope) => provider.addScope(scope)); + } + if (customOAuthParameters) { + provider.setCustomParameters(customOAuthParameters); + } + return provider; + }; + return useSignInWithPopup(auth, createTwitterAuthProvider); +}; + +export const useSignInWithYahoo = (auth: Auth): SignInWithPopupHook => { + return useSignInWithOAuth(auth, 'yahoo.com'); +}; + +const useSignInWithOAuth = ( + auth: Auth, + providerId: string +): SignInWithPopupHook => { + const createOAuthProvider = ( + scopes?: string[], + customOAuthParameters?: CustomParameters + ) => { + const provider = new OAuthProvider(providerId); + if (scopes) { + scopes.forEach((scope) => provider.addScope(scope)); + } + if (customOAuthParameters) { + provider.setCustomParameters(customOAuthParameters); + } + return provider; + }; + return useSignInWithPopup(auth, createOAuthProvider); +}; + +const useSignInWithPopup = ( + auth: Auth, + createProvider: ( + scopes?: string[], + customOAuthParameters?: CustomParameters + ) => AuthProvider +): SignInWithPopupHook => { + const [error, setError] = useState(); + const [loggedInUser, setLoggedInUser] = useState(); + const [loading, setLoading] = useState(false); + + const signInWithGoogle = async ( + scopes?: string[], + customOAuthParameters?: CustomParameters + ) => { + setLoading(true); + try { + const provider = createProvider(scopes, customOAuthParameters); + const user = await signInWithPopup(auth, provider); + setLoggedInUser(user); + } catch (err) { + setError(err as AuthError); + } finally { + setLoading(false); + } + }; + + const resArray: SignInWithPopupHook = [ + signInWithGoogle, + loggedInUser, + loading, + error, + ]; + return useMemo(() => resArray, resArray); +}; diff --git a/auth/useUpdateUser.ts b/auth/useUpdateUser.ts new file mode 100644 index 0000000..6c26f32 --- /dev/null +++ b/auth/useUpdateUser.ts @@ -0,0 +1,92 @@ +import { + Auth, + AuthError, + updateEmail as fbUpdateEmail, + updatePassword as fbUpdatePassword, + updateProfile as fbUpdateProfile, +} from 'firebase/auth'; +import { useMemo, useState } from 'react'; + +type Profile = { + displayName?: string | null; + photoURL?: string | null; +}; + +export type UpdateUserHook = [M, boolean, AuthError | Error | undefined]; + +export type UpdateEmailHook = UpdateUserHook<(email: string) => Promise>; +export type UpdatePasswordHook = UpdateUserHook< + (password: string) => Promise +>; +export type UpdateProfileHook = UpdateUserHook< + (profile: Profile) => Promise +>; + +export const useUpdateEmail = (auth: Auth): UpdateEmailHook => { + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + + const updateEmail = async (email: string) => { + setLoading(true); + try { + if (auth.currentUser) { + await fbUpdateEmail(auth.currentUser, email); + } else { + setError(new Error('No user is logged in') as AuthError); + } + } catch (err) { + setError(err as AuthError); + } finally { + setLoading(false); + } + }; + + const resArray: UpdateEmailHook = [updateEmail, loading, error]; + return useMemo(() => resArray, resArray); +}; + +export const useUpdatePassword = (auth: Auth): UpdatePasswordHook => { + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + + const updatePassword = async (password: string) => { + setLoading(true); + try { + if (auth.currentUser) { + await fbUpdatePassword(auth.currentUser, password); + } else { + setError(new Error('No user is logged in') as AuthError); + } + } catch (err) { + setError(err as AuthError); + } finally { + setLoading(false); + } + }; + + const resArray: UpdatePasswordHook = [updatePassword, loading, error]; + return useMemo(() => resArray, resArray); +}; + +export const useUpdateProfile = (auth: Auth): UpdateProfileHook => { + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + + const updateProfile = async (profile: Profile) => { + setLoading(true); + try { + if (auth.currentUser) { + await fbUpdateProfile(auth.currentUser, profile); + } else { + setError(new Error('No user is logged in') as AuthError); + } + } catch (err) { + setError(err as AuthError); + } finally { + setLoading(false); + } + }; + + const resArray: UpdateProfileHook = [updateProfile, loading, error]; + return useMemo(() => resArray, resArray); +}; diff --git a/database/index.js.flow b/database/index.js.flow deleted file mode 100644 index 948c997..0000000 --- a/database/index.js.flow +++ /dev/null @@ -1,26 +0,0 @@ -// @flow -import type { DataSnapshot, Query } from 'firebase/database'; - -type LoadingHook = [T | void, boolean, Error | void]; - -export type ListHook = LoadingHook; -export type ListKeysHook = LoadingHook; -export type ListValsHook = LoadingHook; -export type ObjectHook = LoadingHook; -export type ObjectValHook = LoadingHook; - -declare export function useList(query?: Query | null): ListHook; -declare export function useListKeys(query?: Query | null): ListKeysHook; -declare export function useListVals( - query?: Query | null, - options?: { - keyField?: string, - } -): ListValsHook; -declare export function useObject(query?: Query | null): ObjectHook; -declare export function useObjectVal( - query?: Query | null, - options?: { - keyField?: string, - } -): ObjectValHook; diff --git a/firestore/README.md b/firestore/README.md index 4dfd357..ff7042d 100644 --- a/firestore/README.md +++ b/firestore/README.md @@ -19,16 +19,16 @@ import { useCollection } from 'react-firebase-hooks/firestore'; List of Cloud Firestore hooks: - [React Firebase Hooks - Cloud Firestore](#react-firebase-hooks---cloud-firestore) - - [useCollection](#usecollection) - - [Full example](#full-example) - - [useCollectionOnce](#usecollectiononce) - - [useCollectionData](#usecollectiondata) - - [useCollectionDataOnce](#usecollectiondataonce) - - [useDocument](#usedocument) - - [Full example](#full-example-1) - - [useDocumentOnce](#usedocumentonce) - - [useDocumentData](#usedocumentdata) - - [useDocumentDataOnce](#usedocumentdataonce) + - [useCollection](#usecollection) + - [Full example](#full-example) + - [useCollectionOnce](#usecollectiononce) + - [useCollectionData](#usecollectiondata) + - [useCollectionDataOnce](#usecollectiondataonce) + - [useDocument](#usedocument) + - [Full example](#full-example-1) + - [useDocumentOnce](#usedocumentonce) + - [useDocumentData](#usedocumentdata) + - [useDocumentDataOnce](#usedocumentdataonce) - [Transforming data](#transforming-data) Additional functionality: @@ -115,7 +115,8 @@ Returns: ### useCollectionData ```js -const [values, loading, error] = useCollectionData (query, options); +const [values, loading, error, snapshot] = + useCollectionData < T > (query, options); ``` As `useCollection`, but this hook extracts a typed list of the `firestore.QuerySnapshot.docs` values, rather than the @@ -125,22 +126,21 @@ The `useCollectionData` hook takes the following parameters: - `query`: (optional) `firestore.Query` for the data you would like to load - `options`: (optional) `Object` with the following parameters: - - `idField`: (optional) name of the field that should be populated with the `firestore.QuerySnapshot.id` property. - - `refField`: (optional) name of the field that should be populated with the `firestore.QuerySnapshot.ref` property. - `snapshotListenOptions`: (optional) `firestore.SnapshotListenOptions` to customise how the collection is loaded - `snapshotOptions`: (optional) `firestore.SnapshotOptions` to customise how data is retrieved from snapshots - - `transform`: (optional) a function that receives the raw `firestore.DocumentData` for each item in the collection to allow manual transformation of the data where required by the application. See [`Transforming data`](#transforming-data) below. Returns: - `values`: an array of `T`, or `undefined` if no query is supplied - `loading`: a `boolean` to indicate if the data is still being loaded - `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error +- `snapshot`: a `firestore.QuerySnapshot`, or `undefined` if no query is supplied. This allows access to the underlying snapshot if needed for any reason, e.g. to view the snapshot metadata ### useCollectionDataOnce ```js -const [values, loading, error] = useCollectionDataOnce(query, options); +const [values, loading, error, snapshot] = + useCollectionDataOnce < T > (query, options); ``` As `useCollectionData`, but this hook will only read the current value of the `firestore.Query`. @@ -151,16 +151,14 @@ The `useCollectionDataOnce` hook takes the following parameters: - `options`: (optional) `Object` with the following parameters: - `getOptions`: (optional) `Object` to customise how the collection is loaded - `source`: (optional): `'default' | 'server' | 'cache'` Describes whether we should get from server or cache. - - `idField`: (optional) name of the field that should be populated with the `firestore.QuerySnapshot.id` property. - - `refField`: (optional) name of the field that should be populated with the `firestore.QuerySnapshot.ref` property. - `snapshotOptions`: (optional) `firestore.SnapshotOptions` to customise how data is retrieved from snapshots - - `transform`: (optional) a function that receives the raw `firestore.DocumentData` for each item in the collection to allow manual transformation of the data where required by the application. See [`Transforming data`](#transforming-data) below. Returns: - `values`: an array of `T`, or `undefined` if no query is supplied - `loading`: a `boolean` to indicate if the data is still being loaded - `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error +- `snapshot`: a `firestore.QuerySnapshot`, or `undefined` if no query is supplied. This allows access to the underlying snapshot if needed for any reason, e.g. to view the snapshot metadata ### useDocument @@ -190,7 +188,7 @@ import { useDocument } from 'react-firebase-hooks/firestore'; const FirestoreDocument = () => { const [value, loading, error] = useDocument( - doc(getFirestore(firebaseApp, 'hooks', 'nBShXiRGFAhuiPfBaGpt')), + doc(getFirestore(firebaseApp), 'hooks', 'nBShXiRGFAhuiPfBaGpt'), { snapshotListenOptions: { includeMetadataChanges: true }, } @@ -210,7 +208,7 @@ const FirestoreDocument = () => { ### useDocumentOnce ```js -const [snapshot, loading, error] = useDocumentOnce(reference, options); +const [snapshot, loading, error, reload] = useDocumentOnce(reference, options); ``` Retrieve the current value of the `firestore.DocumentReference`. @@ -227,11 +225,13 @@ Returns: - `snapshot`: a `firestore.DocumentSnapshot`, or `undefined` if no reference is supplied - `loading`: a `boolean` to indicate if the data is still being loaded - `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error +- `reload()`: a function that can be called to trigger a reload of the data ### useDocumentData ```js -const [value, loading, error] = useDocumentData(reference, options); +const [value, loading, error, snapshot] = + useDocumentData < T > (reference, options); ``` As `useDocument`, but this hook extracts the typed contents of `firestore.DocumentSnapshot.data()`, rather than the @@ -241,22 +241,21 @@ The `useDocumentData` hook takes the following parameters: - `reference`: (optional) `firestore.DocumentReference` for the data you would like to load - `options`: (optional) `Object` with the following parameters: - - `idField`: (optional) name of the field that should be populated with the `firestore.DocumentSnapshot.id` property. - - `refField`: (optional) name of the field that should be populated with the `firestore.QuerySnapshot.ref` property. - `snapshotListenOptions`: (optional) `firestore.SnapshotListenOptions` to customise how the collection is loaded - `snapshotOptions`: (optional) `firestore.SnapshotOptions` to customise how data is retrieved from snapshots - - `transform`: (optional) a function that receives the raw `firestore.DocumentData` to allow manual transformation of the data where required by the application. See [`Transforming data`](#transforming-data) below. Returns: - `value`: `T`, or `undefined` if no query is supplied - `loading`: a `boolean` to indicate if the data is still being loaded - `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error +- `snapshot`: a `firestore.DocumentSnapshot`, or `undefined` if no query is supplied. This allows access to the underlying snapshot if needed for any reason, e.g. to view the snapshot metadata ### useDocumentDataOnce ```js -const [value, loading, error] = useDocumentDataOnce (reference, options); +const [value, loading, error, snapshot, reload] = + useDocumentDataOnce < T > (reference, options); ``` As `useDocument`, but this hook will only read the current value of the `firestore.DocumentReference`. @@ -266,28 +265,19 @@ The `useDocumentDataOnce` hook takes the following parameters: - `reference`: (optional) `firestore.DocumentReference` for the data you would like to load - `options`: (optional) `Object` with the following parameters: - `getOptions`: (optional) `Object` to customise how the collection is loaded - - `source`: (optional): `'default' | 'server' | 'cache'` Describes whether we should get from server or cache. - - `idField`: (optional) name of the field that should be populated with the `firestore.DocumentSnapshot.id` property. - - `refField`: (optional) name of the field that should be populated with the `firestore.QuerySnapshot.ref` property. + - `source`: (optional): `'default' | 'server' | 'cache'` Describes whether we should get from server or cach - `snapshotOptions`: (optional) `firestore.SnapshotOptions` to customise how data is retrieved from snapshots - - `transform`: (optional) a function that receives the raw `firestore.DocumentData` to allow manual transformation of the data where required by the application. See [`Transforming data`](#transforming-data) below. Returns: - `value`: `T`, or `undefined` if no query is supplied - `loading`: a `boolean` to indicate if the data is still being loaded - `error`: Any `firestore.FirestoreError` returned by Firebase when trying to load the data, or `undefined` if there is no error +- `snapshot`: a `firestore.DocumentSnapshot`, or `undefined` if no query is supplied. This allows access to the underlying snapshot if needed for any reason, e.g. to view the snapshot metadata +- `reload()`: a function that can be called to trigger a reload of the data ## Transforming data -Firestore allows a restricted number of data types in its store, which may not be flexible enough for your application. Both `useCollectionData` and `useDocumentData` support an optional `transform` function which allows the transformation of the underlying Firestore data into whatever format the application requires, e.g. a `Date` type. - -```js -transform?: (val: any) => T; -``` - -The `transform` function is passed a single row of a data, so will be called once when used with `useDocumentData` and multiple times when used with `useCollectionData`. - -The `transform` function will not receive the `id` or `ref` values referenced in the properties named in the `idField` or `refField` options, nor it is expected to produce them. Either or both, if specified, will be merged afterwards. +Firestore allows a restricted number of data types in its store, which may not be flexible enough for your application. As of Firebase 9, there is a built in FirestoreDataConverter which allows you to transform data as it leaves the Firestore database. This is described here: https://firebase.google.com/docs/reference/js/firestore_.firestoredataconverter -If the `transform` function is defined within your React component, it is recomended that you memoize the function to prevent unnecessry renders. +> This has replaced the `transform`, `idField` and `refField` options that were available in `react-firebase-hooks` v4 and earlier. diff --git a/firestore/helpers/index.ts b/firestore/helpers/index.ts index 9818d04..d803d9f 100644 --- a/firestore/helpers/index.ts +++ b/firestore/helpers/index.ts @@ -1,40 +1,12 @@ import { CollectionReference, - DocumentData, DocumentReference, - DocumentSnapshot, Query, queryEqual, refEqual, - SnapshotOptions, } from 'firebase/firestore'; import { RefHook, useComparatorRef } from '../../util'; -export const snapshotToData = ( - snapshot: DocumentSnapshot, - snapshotOptions?: SnapshotOptions, - idField?: string, - refField?: string, - transform?: (val: any) => T -) => { - if (!snapshot.exists()) { - return undefined; - } - - let data = snapshot.data(snapshotOptions) as DocumentData; - if (transform) { - data = transform(data); - } - if (idField) { - data[idField] = snapshot.id; - } - if (refField) { - data[refField] = snapshot.ref; - } - - return data; -}; - const isRefEqual = < T extends DocumentReference | CollectionReference >( diff --git a/firestore/index.js.flow b/firestore/index.js.flow deleted file mode 100644 index c7ca114..0000000 --- a/firestore/index.js.flow +++ /dev/null @@ -1,75 +0,0 @@ -// @flow -import type { - DocumentReference, - DocumentSnapshot, - Query, - QuerySnapshot, - SnapshotListenOptions, -} from 'firebase/firestore'; - -type LoadingHook = [T | void, boolean, Error | void]; - -export type GetOptions = { - source?: 'default' | 'server' | 'cache'; -}; -export type CollectionHook = LoadingHook; -export type CollectionDataHook = LoadingHook; -export type DocumentHook = LoadingHook; -export type DocumentDataHook = LoadingHook; - -declare export function useCollection( - query?: Query | null, - options?: { - snapshotListenOptions?: SnapshotListenOptions, - } -): CollectionHook; -declare export function useCollectionOnce( - query?: Query | null, - options?: { - getOptions?: GetOptions, - } -): CollectionHook; -declare export function useCollectionData( - query?: Query | null, - options?: { - idField?: string, - refField?: string, - snapshotListenOptions?: SnapshotListenOptions, - } -): CollectionDataHook; -declare export function useCollectionDataOnce( - query?: Query | null, - options?: { - getOptions?: GetOptions, - idField?: string, - refField?: string, - } -): CollectionDataHook; -declare export function useDocument( - ref?: DocumentReference | null, - options?: { - snapshotListenOptions?: SnapshotListenOptions, - } -): DocumentHook; -declare export function useDocumentOnce( - ref?: DocumentReference | null, - options?: { - getOptions?: GetOptions, - } -): DocumentHook; -declare export function useDocumentData( - ref?: DocumentReference | null, - options?: { - idField?: string, - refField?: string, - snapshotListenOptions?: SnapshotListenOptions, - } -): DocumentDataHook; -declare export function useDocumentDataOnce( - ref?: DocumentReference | null, - options?: { - getOptions?: GetOptions, - idField?: string, - refField?: string, - } -): DocumentDataHook; diff --git a/firestore/types.ts b/firestore/types.ts index da1e2d8..3fbb911 100644 --- a/firestore/types.ts +++ b/firestore/types.ts @@ -1,6 +1,5 @@ import { DocumentData, - DocumentReference, DocumentSnapshot, FirestoreError, QuerySnapshot, @@ -10,10 +9,7 @@ import { import { LoadingHook } from '../util'; export type IDOptions = { - idField?: string; - refField?: string; snapshotOptions?: SnapshotOptions; - transform?: (val: any) => T; }; export type Options = { snapshotListenOptions?: SnapshotListenOptions; @@ -26,28 +22,37 @@ export type GetOptions = { source?: 'default' | 'server' | 'cache'; }; export type OnceDataOptions = OnceOptions & IDOptions; -export type Data< - T = DocumentData, - IDField extends string = '', - RefField extends string = '' -> = T & Record & Record>; export type CollectionHook = LoadingHook< QuerySnapshot, FirestoreError >; -export type CollectionDataHook< - T = DocumentData, - IDField extends string = '', - RefField extends string = '' -> = LoadingHook[], FirestoreError>; +export type CollectionOnceHook = [ + ...CollectionHook, + () => Promise +]; +export type CollectionDataHook = [ + ...LoadingHook, + QuerySnapshot | undefined +]; +export type CollectionDataOnceHook = [ + ...CollectionDataHook, + () => Promise +]; export type DocumentHook = LoadingHook< DocumentSnapshot, FirestoreError >; -export type DocumentDataHook< - T = DocumentData, - IDField extends string = '', - RefField extends string = '' -> = LoadingHook, FirestoreError>; +export type DocumentOnceHook = [ + ...DocumentHook, + () => Promise +]; +export type DocumentDataHook = [ + ...LoadingHook, + DocumentSnapshot | undefined +]; +export type DocumentDataOnceHook = [ + ...DocumentDataHook, + () => Promise +]; diff --git a/firestore/useCollection.ts b/firestore/useCollection.ts index 98810be..d483a35 100644 --- a/firestore/useCollection.ts +++ b/firestore/useCollection.ts @@ -7,14 +7,16 @@ import { onSnapshot, Query, QuerySnapshot, + SnapshotOptions, } from 'firebase/firestore'; import { useEffect, useMemo } from 'react'; import { useLoadingValue } from '../util'; -import { snapshotToData, useIsFirestoreQueryEqual } from './helpers'; +import { useIsFirestoreQueryEqual } from './helpers'; import { CollectionDataHook, + CollectionDataOnceHook, CollectionHook, - Data, + CollectionOnceHook, DataOptions, GetOptions, OnceDataOptions, @@ -26,129 +28,133 @@ export const useCollection = ( query?: Query | null, options?: Options ): CollectionHook => { - return useCollectionInternal(true, query, options); -}; + const { error, loading, reset, setError, setValue, value } = useLoadingValue< + QuerySnapshot, + FirestoreError + >(); + const ref = useIsFirestoreQueryEqual>(query, reset); -export const useCollectionOnce = ( - query?: Query | null, - options?: OnceOptions -): CollectionHook => { - return useCollectionInternal(false, query, options); -}; + useEffect(() => { + if (!ref.current) { + setValue(undefined); + return; + } + const unsubscribe = options?.snapshotListenOptions + ? onSnapshot( + ref.current, + options.snapshotListenOptions, + setValue, + setError + ) + : onSnapshot(ref.current, setValue, setError); -export const useCollectionData = < - T = DocumentData, - IDField extends string = '', - RefField extends string = '' ->( - query?: Query | null, - options?: DataOptions -): CollectionDataHook => { - return useCollectionDataInternal(true, query, options); -}; + return () => { + unsubscribe(); + }; + }, [ref.current, options]); -export const useCollectionDataOnce = < - T = DocumentData, - IDField extends string = '', - RefField extends string = '' ->( - query?: Query | null, - options?: OnceDataOptions -): CollectionDataHook => { - return useCollectionDataInternal(false, query, options); + const resArray: CollectionHook = [ + value as QuerySnapshot, + loading, + error, + ]; + return useMemo(() => resArray, resArray); }; -const useCollectionInternal = ( - listen: boolean, +export const useCollectionOnce = ( query?: Query | null, - options?: Options & OnceOptions -) => { + options?: OnceOptions +): CollectionOnceHook => { const { error, loading, reset, setError, setValue, value } = useLoadingValue< QuerySnapshot, FirestoreError >(); + let effectActive = true; const ref = useIsFirestoreQueryEqual>(query, reset); - useEffect(() => { - if (!ref.current) { + const loadData = async ( + query?: Query | null, + options?: Options & OnceOptions + ) => { + if (!query) { setValue(undefined); return; } - if (listen) { - const listener = - options && options.snapshotListenOptions - ? onSnapshot( - ref.current, - options.snapshotListenOptions, - setValue, - setError - ) - : onSnapshot(ref.current, setValue, setError); + const get = getDocsFnFromGetOptions(options?.getOptions); - return () => { - listener(); - }; - } else { - const get = getDocsFnFromGetOptions( - options ? options.getOptions : undefined - ); - get(ref.current).then(setValue).catch(setError); + try { + const result = await get(query); + if (effectActive) { + setValue(result); + } + } catch (error) { + if (effectActive) { + setError(error as FirestoreError); + } } - }, [ref.current]); + }; - const resArray: CollectionHook = [ + useEffect(() => { + loadData(ref.current, options); + + return () => { + effectActive = false; + }; + }, [ref.current, options]); + + const resArray: CollectionOnceHook = [ value as QuerySnapshot, loading, error, + () => loadData(ref.current, options), ]; return useMemo(() => resArray, resArray); }; -const useCollectionDataInternal = < - T = DocumentData, - IDField extends string = '', - RefField extends string = '' ->( - listen: boolean, +export const useCollectionData = ( + query?: Query | null, + options?: DataOptions +): CollectionDataHook => { + const snapshotOptions = options?.snapshotOptions; + const [snapshots, loading, error] = useCollection(query, options); + const values = getValuesFromSnapshots(snapshots, snapshotOptions); + const resArray: CollectionDataHook = [values, loading, error, snapshots]; + return useMemo(() => resArray, resArray); +}; + +export const useCollectionDataOnce = ( query?: Query | null, - options?: DataOptions & OnceDataOptions -): CollectionDataHook => { - const idField = options ? options.idField : undefined; - const refField = options ? options.refField : undefined; - const snapshotOptions = options ? options.snapshotOptions : undefined; - const transform = options ? options.transform : undefined; - const [snapshots, loading, error] = useCollectionInternal( - listen, + options?: OnceDataOptions +): CollectionDataOnceHook => { + const snapshotOptions = options?.snapshotOptions; + const [snapshots, loading, error, loadData] = useCollectionOnce( query, options ); - const values = useMemo( - () => - (snapshots - ? snapshots.docs.map((doc) => - snapshotToData( - doc, - snapshotOptions, - idField, - refField, - transform - ) - ) - : undefined) as Data[], - [snapshots, snapshotOptions, idField, refField, transform] - ); - - const resArray: CollectionDataHook = [ + const values = getValuesFromSnapshots(snapshots, snapshotOptions); + const resArray: CollectionDataOnceHook = [ values, loading, error, + snapshots, + loadData, ]; return useMemo(() => resArray, resArray); }; -function getDocsFnFromGetOptions( +const getValuesFromSnapshots = ( + snapshots?: QuerySnapshot, + options?: SnapshotOptions +) => { + return useMemo(() => snapshots?.docs.map((doc) => doc.data(options)) as T[], [ + snapshots, + options, + ]); +}; + +const getDocsFnFromGetOptions = ( { source }: GetOptions = { source: 'default' } -) { +) => { switch (source) { default: case 'default': @@ -158,4 +164,4 @@ function getDocsFnFromGetOptions( case 'server': return getDocsFromServer; } -} +}; diff --git a/firestore/useDocument.ts b/firestore/useDocument.ts index a9a3551..e4e34db 100644 --- a/firestore/useDocument.ts +++ b/firestore/useDocument.ts @@ -10,143 +10,153 @@ import { } from 'firebase/firestore'; import { useEffect, useMemo } from 'react'; import { useLoadingValue } from '../util'; -import { snapshotToData, useIsFirestoreRefEqual } from './helpers'; +import { useIsFirestoreRefEqual } from './helpers'; import { - Data, DataOptions, DocumentDataHook, + DocumentDataOnceHook, DocumentHook, + DocumentOnceHook, GetOptions, OnceDataOptions, OnceOptions, Options, } from './types'; + export const useDocument = ( docRef?: DocumentReference | null, options?: Options ): DocumentHook => { - return useDocumentInternal(true, docRef, options); -}; + const { error, loading, reset, setError, setValue, value } = useLoadingValue< + DocumentSnapshot, + FirestoreError + >(); + const ref = useIsFirestoreRefEqual>(docRef, reset); -export const useDocumentOnce = ( - docRef?: DocumentReference | null, - options?: OnceOptions -): DocumentHook => { - return useDocumentInternal(false, docRef, options); -}; + useEffect(() => { + if (!ref.current) { + setValue(undefined); + return; + } + const unsubscribe = options?.snapshotListenOptions + ? onSnapshot( + ref.current, + options.snapshotListenOptions, + setValue, + setError + ) + : onSnapshot(ref.current, setValue, setError); -export const useDocumentData = < - T = DocumentData, - IDField extends string = '', - RefField extends string = '' ->( - docRef?: DocumentReference | null, - options?: DataOptions -): DocumentDataHook => { - return useDocumentDataInternal(true, docRef, options); -}; + return () => { + unsubscribe(); + }; + }, [ref.current, options]); -export const useDocumentDataOnce = < - T = DocumentData, - IDField extends string = '', - RefField extends string = '' ->( - docRef?: DocumentReference | null, - options?: OnceDataOptions -): DocumentDataHook => { - return useDocumentDataInternal(false, docRef, options); + const resArray: DocumentHook = [ + value as DocumentSnapshot, + loading, + error, + ]; + return useMemo(() => resArray, resArray); }; -const useDocumentInternal = ( - listen: boolean, +export const useDocumentOnce = ( docRef?: DocumentReference | null, - options?: Options & OnceOptions -): DocumentHook => { + options?: OnceOptions +): DocumentOnceHook => { const { error, loading, reset, setError, setValue, value } = useLoadingValue< DocumentSnapshot, FirestoreError >(); + let effectActive = true; const ref = useIsFirestoreRefEqual>(docRef, reset); + const loadData = async ( + reference?: DocumentReference | null, + options?: OnceOptions + ) => { + if (!reference) { + setValue(undefined); + return; + } + const get = getDocFnFromGetOptions(options?.getOptions); + + try { + const result = await get(reference); + if (effectActive) { + setValue(result); + } + } catch (error) { + if (effectActive) { + setError(error as FirestoreError); + } + } + }; + useEffect(() => { if (!ref.current) { setValue(undefined); return; } - if (listen) { - const listener = - options && options.snapshotListenOptions - ? onSnapshot( - ref.current, - options.snapshotListenOptions, - setValue, - setError - ) - : onSnapshot(ref.current, setValue, setError); - - return () => { - listener(); - }; - } else { - const get = getDocFnFromGetOptions( - options ? options.getOptions : undefined - ); - - get(ref.current).then(setValue).catch(setError); - } - }, [ref.current]); - const resArray: DocumentHook = [ + loadData(ref.current, options); + + return () => { + effectActive = false; + }; + }, [ref.current, options]); + + const resArray: DocumentOnceHook = [ value as DocumentSnapshot, loading, error, + () => loadData(ref.current, options), ]; return useMemo(() => resArray, resArray); }; -const useDocumentDataInternal = < - T = DocumentData, - IDField extends string = '', - RefField extends string = '' ->( - listen: boolean, +export const useDocumentData = ( docRef?: DocumentReference | null, options?: DataOptions -): DocumentDataHook => { - const idField = options ? options.idField : undefined; - const refField = options ? options.refField : undefined; - const snapshotOptions = options ? options.snapshotOptions : undefined; - const transform = options ? options.transform : undefined; - const [snapshot, loading, error] = useDocumentInternal( - listen, +): DocumentDataHook => { + const snapshotOptions = options?.snapshotOptions; + const [snapshot, loading, error] = useDocument(docRef, options); + const value = useMemo(() => snapshot?.data(snapshotOptions) as T, [ + snapshot, + snapshotOptions, + ]); + + const resArray: DocumentDataHook = [value, loading, error, snapshot]; + return useMemo(() => resArray, resArray); +}; + +export const useDocumentDataOnce = ( + docRef?: DocumentReference | null, + options?: OnceDataOptions +): DocumentDataOnceHook => { + const snapshotOptions = options?.snapshotOptions; + const [snapshot, loading, error, loadData] = useDocumentOnce( docRef, options ); - const value = useMemo( - () => - (snapshot - ? snapshotToData( - snapshot, - snapshotOptions, - idField, - refField, - transform - ) - : undefined) as Data, - [snapshot, snapshotOptions, idField, refField, transform] - ); + const value = useMemo(() => snapshot?.data(snapshotOptions) as T, [ + snapshot, + snapshotOptions, + ]); - const resArray: DocumentDataHook = [ + const resArray: DocumentDataOnceHook = [ value, loading, error, + snapshot, + loadData, ]; return useMemo(() => resArray, resArray); }; -function getDocFnFromGetOptions( +const getDocFnFromGetOptions = ( { source }: GetOptions = { source: 'default' } -) { +) => { switch (source) { default: case 'default': @@ -156,4 +166,4 @@ function getDocFnFromGetOptions( case 'server': return getDocFromServer; } -} +}; diff --git a/functions/README.md b/functions/README.md new file mode 100644 index 0000000..5008942 --- /dev/null +++ b/functions/README.md @@ -0,0 +1,65 @@ +# React Firebase Hooks - Cloud Functions + +React Firebase Hooks provides a convenience hook for HttpsCallable functions, providing an `error` and `loading` property +to give a complete lifecycle for executing a HttpsCallable function on Firebase Cloud Functions. + +All hooks can be imported from `react-firebase-hooks/functions`, e.g. + +```js +import { useHttpsCallable } from 'react-firebase-hooks/functions'; +``` + +List of Cloud Functions hooks: + +- [React Firebase Hooks - Cloud Functions](#react-firebase-hooks---cloud-functions) + - [useHttpsCallable](#usehttpscallable) + - [Full example](#full-example) + +### useHttpsCallable + +```js +const [executeCallable, loading, error] = useHttpsCallable(functions, name); +``` + +Generate a callable function and monitor its execution. + +The `useHttpsCallable` hook takes the following parameters: + +- `functions`: `functions.Functions` instance for your Firebase app +- `name`: A `string` representing the name of the function to call + +Returns: + +- `executeCallable(data)`: a function you can call to execute the HttpsCallable +- `loading`: a `boolean` to indicate if the function is still being executed +- `error`: Any `Error` returned by Firebase when trying to execute the function, or `undefined` if there is no error + +#### Full example + +```js +import { getFunctions } from 'firebase/functions'; +import { useHttpsCallable } from 'react-firebase-hooks/functions'; + +const HttpsCallable = () => { + const [executeCallable, executing, error] = useHttpsCallable( + getFunctions(firebaseApp), + 'myHttpsCallable' + ); + return ( +
+

+ {error && Error: {JSON.stringify(error)}} + {executing && Function executing...} + +

+
+ ); +}; +``` diff --git a/functions/index.ts b/functions/index.ts new file mode 100644 index 0000000..6d223cd --- /dev/null +++ b/functions/index.ts @@ -0,0 +1,4 @@ +export { + default as useHttpsCallable, + HttpsCallableHook, +} from './useHttpsCallable'; diff --git a/functions/package.json b/functions/package.json new file mode 100644 index 0000000..1b12bfa --- /dev/null +++ b/functions/package.json @@ -0,0 +1,6 @@ +{ + "name": "react-firebase-hooks/functions", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "typings": "dist/functions/index.d.ts" +} diff --git a/functions/useHttpsCallable.ts b/functions/useHttpsCallable.ts new file mode 100644 index 0000000..ebc0fea --- /dev/null +++ b/functions/useHttpsCallable.ts @@ -0,0 +1,44 @@ +import { + Functions, + httpsCallable, + HttpsCallableResult, +} from 'firebase/functions'; +import { useMemo, useState } from 'react'; + +export type HttpsCallableHook = [ + (data?: RequestData) => Promise | unknown>, + boolean, + Error | undefined +]; + +export default ( + functions: Functions, + name: string +): HttpsCallableHook => { + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + + const callCallable = async ( + data?: RequestData + ): Promise | undefined> => { + const callable = httpsCallable(functions, name); + setLoading(true); + try { + return await callable(data); + } catch (err) { + setError(err as Error); + } finally { + setLoading(false); + } + }; + + const resArray: HttpsCallableHook = [ + callCallable, + loading, + error, + ]; + return useMemo>( + () => resArray, + resArray + ); +}; diff --git a/messaging/README.md b/messaging/README.md new file mode 100644 index 0000000..978de88 --- /dev/null +++ b/messaging/README.md @@ -0,0 +1,55 @@ +# React Firebase Hooks - Cloud Messaging + +React Firebase Hooks provides a convenience hook for getting a cloud messaging token, providing an `error` and `loading` property +to give a complete lifecycle for accessing the cloud messaging token on Firebase Cloud Messaging. + +All hooks can be imported from `react-firebase-hooks/messaging`, e.g. + +```js +import { useToken } from 'react-firebase-hooks/messaging'; +``` + +List of Cloud Messaging hooks: + +- [React Firebase Hooks - Cloud Messaging](#react-firebase-hooks---cloud-messaging) + - [useToken](#usetoken) + - [Full example](#full-example) + +### useToken + +```js +const [token, loading, error] = useToken(messaging, vapidKey); +``` + +Get a token from Firebase Cloud Messaging + +The `useToken` hook takes the following parameters: + +- `messaging`: `messaging.Messaging` instance for your Firebase app +- `vapidKey`: a `string` representing the VAPID key credential needed for Cloud Messaging + +Returns: + +- `token`: a `string` token to use with cloud messaging +- `loading`: a `boolean` to indicate if the function is still being executed +- `error`: Any `Error` returned by Firebase when trying to execute the function, or `undefined` if there is no error + +#### Full example + +```js +import { getMessaging } from 'firebase/messaging'; +import { useToken } from 'react-firebase-hooks/messaging'; + +const MessagingToken = () => { + const [token, loading, error] = useToken(getMessaging(firebaseApp)); + return ( +
+

+ {error && Error: {JSON.stringify(error)}} + {loading && Loading token...} + {token && Token:{token}} +

+
+ ); +}; +``` diff --git a/messaging/index.ts b/messaging/index.ts new file mode 100644 index 0000000..63bb228 --- /dev/null +++ b/messaging/index.ts @@ -0,0 +1 @@ +export { default as useToken, TokenHook } from './useToken'; diff --git a/messaging/package.json b/messaging/package.json new file mode 100644 index 0000000..6dc83a6 --- /dev/null +++ b/messaging/package.json @@ -0,0 +1,6 @@ +{ + "name": "react-firebase-hooks/messaging", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "typings": "dist/messaging/index.d.ts" +} diff --git a/messaging/useToken.ts b/messaging/useToken.ts new file mode 100644 index 0000000..f639edf --- /dev/null +++ b/messaging/useToken.ts @@ -0,0 +1,19 @@ +import { Messaging, getToken } from 'firebase/messaging'; +import { useEffect, useMemo } from 'react'; +import { LoadingHook, useLoadingValue } from '../util'; + +export type TokenHook = LoadingHook; + +export default (messaging: Messaging, vapidKey?: string): TokenHook => { + const { error, loading, setError, setValue, value } = useLoadingValue< + string | null, + Error + >(); + + useEffect(() => { + getToken(messaging, { vapidKey }).then(setValue).catch(setError); + }, [messaging]); + + const resArray: TokenHook = [value, loading, error]; + return useMemo(() => resArray, resArray); +}; diff --git a/package-lock.json b/package-lock.json index 661d80d..6700cae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,35 @@ { "name": "react-firebase-hooks", - "version": "4.0.1", + "version": "5.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.16.7" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", + "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, "@firebase/analytics": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.7.1.tgz", @@ -662,6 +688,32 @@ "yargs": "^16.1.1" } }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -726,13 +778,158 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", "dev": true }, - "@types/acorn": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.5.tgz", - "integrity": "sha512-603sPiZ4GVRHPvn6vNgEAvJewKsy+zwRWYS2MeIMemgoAtcjlw2G3lALxrb9OPA17J28bkB71R33yXlQbUatCA==", + "@rollup/plugin-commonjs": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz", + "integrity": "sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg==", "dev": true, "requires": { - "@types/estree": "*" + "@rollup/pluginutils": "^3.1.0", + "commondir": "^1.0.1", + "estree-walker": "^2.0.1", + "glob": "^7.1.6", + "is-reference": "^1.2.1", + "magic-string": "^0.25.7", + "resolve": "^1.17.0" + }, + "dependencies": { + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "dependencies": { + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + } + } + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "magic-string": { + "version": "0.25.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", + "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "resolve": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", + "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", + "dev": true, + "requires": { + "is-core-module": "^2.8.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + } + } + }, + "@rollup/plugin-node-resolve": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.2.tgz", + "integrity": "sha512-xyqbuf1vyOPC60jEKhx3DBHunymnCJswzjNTKfX4Jz7zCPar1UqbRZCNY1u5QaXh97beaFTWdoUUWiV4qX8o/g==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + }, + "dependencies": { + "@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "requires": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + } + }, + "builtin-modules": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz", + "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==", + "dev": true + }, + "estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "resolve": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", + "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", + "dev": true, + "requires": { + "is-core-module": "^2.8.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + } + } + }, + "@rollup/pluginutils": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.2.tgz", + "integrity": "sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==", + "dev": true, + "requires": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "dependencies": { + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + } + } + }, + "@ts-type/package-dts": { + "version": "1.0.56", + "resolved": "https://registry.npmjs.org/@ts-type/package-dts/-/package-dts-1.0.56.tgz", + "integrity": "sha512-nBjbCaVV+R81jk7q/6sRuNYOhDAbBOmaCkPI2quK0Rnye2f4FUHX+4JL+3rDPiRPHxgX3pNfVKKHBlPAHiRddw==", + "dev": true, + "requires": { + "@types/semver": "^7.3.9", + "ts-type": "^2.1.2" } }, "@types/estree": { @@ -741,12 +938,37 @@ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, + "@types/fs-extra": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.2.tgz", + "integrity": "sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, "@types/long": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", "dev": true }, + "@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, "@types/node": { "version": "16.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.1.tgz", @@ -769,19 +991,39 @@ "csstype": "^3.0.2" } }, - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/semver": { + "version": "7.3.9", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", + "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", "dev": true }, - "acorn-dynamic-import": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", - "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", + "@yarn-tool/resolve-package": { + "version": "1.0.41", + "resolved": "https://registry.npmjs.org/@yarn-tool/resolve-package/-/resolve-package-1.0.41.tgz", + "integrity": "sha512-ybJqQztdSkTr9Z0BkPo5OlQBZoHlXHHXxssPJgFT8lFgyOxlhkjUg3WBFn7MSK6JMUoXU32wd2KKFBL/Fgsb8Q==", "dev": true, "requires": { - "acorn": "^5.0.0" + "@ts-type/package-dts": "^1.0.56", + "pkg-dir": "< 6 >= 5", + "tslib": "^2.3.1", + "upath2": "^3.1.12" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } } }, "ansi-regex": { @@ -799,6 +1041,12 @@ "color-convert": "^2.0.1" } }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -815,12 +1063,58 @@ "concat-map": "0.0.1" } }, - "builtin-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-2.0.0.tgz", - "integrity": "sha512-3U5kUA5VPsRUA3nofm/BXX7GVHKfxz0hOBAPxXrIvHzlDRkQVqEn6yi8QJegxl4LzOHLdvb7XF5dVawa/VVYBg==", + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + } + } + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -847,16 +1141,22 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "colors": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", - "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", + "colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", "dev": true }, "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, "concat-map": { @@ -883,13 +1183,19 @@ "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==", "dev": true }, - "date-time": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-time/-/date-time-2.1.0.tgz", - "integrity": "sha512-/9+C44X7lot0IeiyfgJmETtRMhBidBYM2QFFIkGa0U1k+hSyY87Nw7PY3eDqpvCBm7I3WCSfPeZskW/YYq6m4g==", + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "requires": { - "time-zone": "^1.0.0" + "path-type": "^4.0.0" } }, "emoji-regex": { @@ -904,12 +1210,34 @@ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, - "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, "faye-websocket": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", @@ -919,6 +1247,84 @@ "websocket-driver": ">=0.5.1" } }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + } + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, "firebase": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.1.0.tgz", @@ -954,13 +1360,13 @@ } }, "fs-extra": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", - "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^3.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, @@ -970,6 +1376,19 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -990,12 +1409,52 @@ "path-is-absolute": "^1.0.0" } }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + } + }, "graceful-fs": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==", "dev": true }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "http-parser-js": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", @@ -1008,6 +1467,12 @@ "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==", "dev": true }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -1030,25 +1495,61 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "is-core-module": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz", + "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", "dev": true }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "dev": true + }, "is-reference": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.3.tgz", - "integrity": "sha512-W1iHHv/oyBb2pPxkBxtaewxa1BC58Pn5J0hogyCdefwUIvb6R+TGbAcIa4qPNYLqLhb3EnOgUf2MQkkF76BcKw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", "dev": true, "requires": { - "@types/estree": "0.0.39" + "@types/estree": "*" } }, "isarray": { @@ -1064,9 +1565,9 @@ "dev": true }, "jsonfile": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", - "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { "graceful-fs": "^4.1.6" @@ -1093,11 +1594,14 @@ "immediate": "~3.0.5" } }, - "locate-character": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-2.0.5.tgz", - "integrity": "sha512-n2GmejDXtOPBAZdIiEFy5dJ5N38xBCXLNOtw2WpB9kGh6pnrEuKlwYI+Tkpofc4wDtVXHtoAOJaMRlYG/oYaxg==", - "dev": true + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } }, "lodash.camelcase": { "version": "4.3.0", @@ -1120,13 +1624,35 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "magic-string": { - "version": "0.22.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", - "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { - "vlq": "^0.2.2" + "semver": "^6.0.0" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" } }, "minimatch": { @@ -1159,18 +1685,36 @@ "wrappy": "1" } }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, - "parse-ms": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", - "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=", - "dev": true - }, "path": { "version": "0.12.7", "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", @@ -1181,33 +1725,85 @@ "util": "^0.10.3" } }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-is-network-drive": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/path-is-network-drive/-/path-is-network-drive-1.0.13.tgz", + "integrity": "sha512-Hg74mRN6mmXV+gTm3INjFK40ncAmC/Lo4qoQaSZ+GT3hZzlKdWQSqAjqyPeW0SvObP2W073WyYEBWY9d3wOm3A==", + "dev": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "path-strip-sep": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/path-strip-sep/-/path-strip-sep-1.0.10.tgz", + "integrity": "sha512-JpCy+8LAJQQTO1bQsb/84s1g+/Stm3h39aOpPRBQ/paMUGVPPZChLTOTKHoaCkc/6sKuF7yVsnq5Pe1S6xQGcA==", + "dev": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, - "pretty-ms": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-3.2.0.tgz", - "integrity": "sha512-ZypexbfVUGTFxb0v+m1bUyy92DHe5SyYlnyY0msyms5zd3RwyvNgyxZZsXXgoyzlxjx5MiqtXUdhUfvQbe0A2Q==", + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", "dev": true, "requires": { - "parse-ms": "^1.0.0" + "find-up": "^5.0.0" } }, + "prettier": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "dev": true + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -1247,6 +1843,21 @@ "long": "^4.0.0" } }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, "react": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz", @@ -1278,21 +1889,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "require-relative": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", - "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=", + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, - "resolve": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", - "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -1303,84 +1905,97 @@ } }, "rollup": { - "version": "0.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.57.1.tgz", - "integrity": "sha512-I18GBqP0qJoJC1K1osYjreqA8VAKovxuI3I81RSk0Dmr4TgloI0tAULjZaox8OsJ+n7XRrhH6i0G2By/pj1LCA==", + "version": "2.63.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.63.0.tgz", + "integrity": "sha512-nps0idjmD+NXl6OREfyYXMn/dar3WGcyKn+KBzPdaLecub3x/LrId0wUcthcr8oZUAcZAR8NKcfGGFlNgGL1kQ==", "dev": true, "requires": { - "@types/acorn": "^4.0.3", - "acorn": "^5.5.3", - "acorn-dynamic-import": "^3.0.0", - "date-time": "^2.1.0", - "is-reference": "^1.1.0", - "locate-character": "^2.0.5", - "pretty-ms": "^3.1.0", - "require-relative": "^0.8.7", - "rollup-pluginutils": "^2.0.1", - "signal-exit": "^3.0.2", - "sourcemap-codec": "^1.4.1" - } - }, - "rollup-plugin-commonjs": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.1.0.tgz", - "integrity": "sha512-NrfE0g30QljNCnlJr7I2Xguz+44mh0dCxvfxwLnCwtaCK2LwFUp1zzAs8MQuOfhH4mRskqsjfOwGUap/L+WtEw==", - "dev": true, - "requires": { - "estree-walker": "^0.5.1", - "magic-string": "^0.22.4", - "resolve": "^1.5.0", - "rollup-pluginutils": "^2.0.1" - }, - "dependencies": { - "estree-walker": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz", - "integrity": "sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==", - "dev": true - } + "fsevents": "~2.3.2" } }, "rollup-plugin-copy": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-0.2.3.tgz", - "integrity": "sha1-2sGrgdHyILrrmOXEwBCCUuHtu5g=", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz", + "integrity": "sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ==", "dev": true, "requires": { - "colors": "^1.1.2", - "fs-extra": "^3.0.0" + "@types/fs-extra": "^8.0.1", + "colorette": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "10.0.1", + "is-plain-object": "^3.0.0" } }, - "rollup-plugin-node-resolve": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.3.0.tgz", - "integrity": "sha512-9zHGr3oUJq6G+X0oRMYlzid9fXicBdiydhwGChdyeNRGPcN/majtegApRKHLR5drboUvEWU+QeUmGTyEZQs3WA==", + "rollup-plugin-terser": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", + "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", "dev": true, "requires": { - "builtin-modules": "^2.0.0", - "is-module": "^1.0.0", - "resolve": "^1.1.6" + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "terser": "^5.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "rollup-plugin-typescript2": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.12.0.tgz", - "integrity": "sha512-UvHzfJyBWyV+d4Av7FG79PTipIK1VrNQoDrNWszAYd7i/ZcCfBg3rPkPiK9Z+WEHXeWBbCnxJlSqR2zKWgEkiw==", + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.31.1.tgz", + "integrity": "sha512-sklqXuQwQX+stKi4kDfEkneVESPi3YM/2S899vfRdF9Yi40vcC50Oq4A4cSZJNXsAQE/UsBZl5fAOsBLziKmjw==", "dev": true, "requires": { - "fs-extra": "^5.0.0", - "resolve": "^1.5.0", - "rollup-pluginutils": "^2.0.1", - "tslib": "^1.9.0" + "@rollup/pluginutils": "^4.1.0", + "@yarn-tool/resolve-package": "^1.0.36", + "find-cache-dir": "^3.3.1", + "fs-extra": "8.1.0", + "resolve": "1.20.0", + "tslib": "2.2.0" }, "dependencies": { "fs-extra": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", - "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } @@ -1393,25 +2008,32 @@ "requires": { "graceful-fs": "^4.1.6" } + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true } } }, - "rollup-plugin-uglify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-uglify/-/rollup-plugin-uglify-3.0.0.tgz", - "integrity": "sha512-dehLu9eRRoV4l09aC+ySntRw1OAfoyKdbk8Nelblj03tHoynkSybqyEpgavemi1LBOH6S1vzI58/mpxkZIe1iQ==", + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "requires": { - "uglify-es": "^3.3.7" - } - }, - "rollup-pluginutils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz", - "integrity": "sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1" + "queue-microtask": "^1.2.2" } }, "safe-buffer": { @@ -1443,28 +2065,52 @@ } } }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, "set-immediate-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", "dev": true }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "sourcemap-codec": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz", - "integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "dev": true }, "string-width": { @@ -1496,12 +2142,32 @@ "ansi-regex": "^5.0.1" } }, - "time-zone": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", - "integrity": "sha1-mcW/VZWJZq9tBtg73zgA3IL67F0=", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "terser": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz", + "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.20" + } + }, "tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -1522,27 +2188,50 @@ } } }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-type": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ts-type/-/ts-type-2.1.2.tgz", + "integrity": "sha512-64/2XoaipFXa/bidwXChQCSJ3VaCUVKrEaRPXiP5gLIlkPoAHfnwOMC0EWr42DsTm/+qYYuNwzxc0VQroeDs+g==", + "dev": true, + "requires": { + "tslib": "^2.3.1", + "typedarray-dts": "^1.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "dev": true }, - "typescript": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.1.tgz", - "integrity": "sha512-Ao/f6d/4EPLq0YwzsQz8iXflezpTkQzqAyenTiw4kCUGr1uPiFLC3+fZ+gMZz6eeI/qdRUqvC+HxIJzUAzEFdg==", + "typedarray-dts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typedarray-dts/-/typedarray-dts-1.0.0.tgz", + "integrity": "sha512-Ka0DBegjuV9IPYFT1h0Qqk5U4pccebNIJCGl8C5uU7xtOs+jpJvKGAY4fHGK25hTmXZOEUl9Cnsg5cS6K/b5DA==", "dev": true }, - "uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "dev": true, - "requires": { - "commander": "~2.13.0", - "source-map": "~0.6.1" - } + "typescript": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "dev": true }, "universalify": { "version": "0.1.2", @@ -1550,6 +2239,25 @@ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, + "upath2": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/upath2/-/upath2-3.1.12.tgz", + "integrity": "sha512-yC3eZeCyCXFWjy7Nu4pgjLhXNYjuzuUmJiRgSSw6TJp8Emc+E4951HGPJf+bldFC5SL7oBLeNbtm1fGzXn2gxw==", + "dev": true, + "requires": { + "path-is-network-drive": "^1.0.13", + "path-strip-sep": "^1.0.10", + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, "util": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", @@ -1573,12 +2281,6 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "vlq": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", - "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", - "dev": true - }, "websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -1651,6 +2353,12 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/package.json b/package.json index 3319c3b..71eca46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-firebase-hooks", - "version": "4.0.1", + "version": "5.0.0", "description": "React Hooks for Firebase", "author": "CS Frequency Limited (https://csfrequency.com)", "license": "Apache-2.0", @@ -15,25 +15,32 @@ "auth/dist/*.js", "auth/dist/auth", "auth/dist/util", - "auth/dist/*.js.flow", "auth/package.json", "database/dist/*.js", "database/dist/database", "database/dist/util", - "database/dist/*.js.flow", "database/package.json", "dist/*.js", "dist/*.js.map", "firestore/dist/*.js", "firestore/dist/firestore", "firestore/dist/util", - "firestore/dist/*.js.flow", "firestore/package.json", + "functions/dist/*.js", + "functions/dist/functions", + "functions/dist/util", + "functions/package.json", + "messaging/dist/*.js", + "messaging/dist/messaging", + "messaging/dist/util", + "messaging/package.json", "storage/dist/*.js", "storage/dist/storage", "storage/dist/util", - "storage/dist/*.js.flow", - "storage/package.json" + "storage/package.json", + "util/dist/*.js", + "util/dist/util", + "util/package.json" ], "repository": { "type": "git", @@ -49,21 +56,21 @@ }, "main": "dist/index.cjs.js", "module": "dist/index.esm.js", - "dependencies": {}, "devDependencies": { + "@rollup/plugin-commonjs": "^21.0.1", + "@rollup/plugin-node-resolve": "^13.1.2", "@types/react": "^17.0.0", "firebase": "9.1.0", "path": "^0.12.7", "prettier": "2.2.1", "react": "^17.0.1", "rimraf": "^2.6.2", - "rollup": "0.57.1", - "rollup-plugin-commonjs": "9.1.0", - "rollup-plugin-copy": "^0.2.3", - "rollup-plugin-node-resolve": "3.3.0", - "rollup-plugin-typescript2": "0.12.0", - "rollup-plugin-uglify": "3.0.0", - "typescript": "2.8.1" + "rollup": "^2.63.0", + "rollup-plugin-copy": "^3.4.0", + "rollup-plugin-terser": "^7.0.2", + "rollup-plugin-typescript2": "^0.31.1", + "tslib": "^2.3.1", + "typescript": "^4.5.4" }, "peerDependencies": { "react": ">= 16.8.0", diff --git a/rollup.config.js b/rollup.config.js index 7f8fc3d..4cf289e 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,21 +1,27 @@ import { resolve } from 'path'; -import commonjs from 'rollup-plugin-commonjs'; +import commonjs from '@rollup/plugin-commonjs'; import copy from 'rollup-plugin-copy'; -import resolveModule from 'rollup-plugin-node-resolve'; +import resolveModule from '@rollup/plugin-node-resolve'; +import { terser } from 'rollup-plugin-terser'; import typescript from 'rollup-plugin-typescript2'; -import uglify from 'rollup-plugin-uglify'; import pkg from './package.json'; import authPkg from './auth/package.json'; import databasePkg from './database/package.json'; import firestorePkg from './firestore/package.json'; +import functionsPkg from './functions/package.json'; +import messagingPkg from './messaging/package.json'; import storagePkg from './storage/package.json'; +import utilPkg from './util/package.json'; const pkgsByName = { auth: authPkg, database: databasePkg, firestore: firestorePkg, + functions: functionsPkg, + messaging: messagingPkg, storage: storagePkg, + util: utilPkg, }; const plugins = [ @@ -26,16 +32,26 @@ const plugins = [ commonjs(), ]; -const peerDependencies = pkg.peerDependencies || {}; const external = [ ...Object.keys(pkg.peerDependencies), 'firebase/auth', 'firebase/database', 'firebase/firestore', + 'firebase/functions', + 'firebase/messaging', 'firebase/storage', + 'firebase/util', ]; -const components = ['auth', 'database', 'firestore', 'storage']; +const components = [ + 'auth', + 'database', + 'firestore', + 'functions', + 'messaging', + 'storage', + 'util', +]; export default components .map((component) => { @@ -64,7 +80,7 @@ export default components }, plugins: [ ...plugins, - uglify(), + terser(), // Copy flow files copy({ [`${component}/index.js.flow`]: `${component}/dist/index.cjs.js.flow`, diff --git a/storage/README.md b/storage/README.md index 950ecb2..3b35257 100644 --- a/storage/README.md +++ b/storage/README.md @@ -15,6 +15,7 @@ import { useDownloadURL } from 'react-firebase-hooks/storage'; List of Cloud Storage hooks: - [useDownloadURL](#usedownloadurl) +- [useUploadFile](#useuploadfile) ### useDownloadURL @@ -32,20 +33,18 @@ Returns: - `downloadUrl`: A `string` download URL, or `undefined` if no storage reference is supplied - `loading`: A `boolean` to indicate whether the the download URL is still being loaded -- `error`: Any `storage.StorageError` returned by Firebase when trying to load the user, or `undefined` if there is no error +- `error`: Any `storage.StorageError` returned by Firebase when trying to load the URL, or `undefined` if there is no error #### Full example ```js -import { ref, getStorage } from 'firebase/storage'; +import { getStorage, storageRef } from 'firebase/storage'; import { useDownloadURL } from 'react-firebase-hooks/storage'; const storage = getStorage(firebaseApp); const DownloadURL = () => { - const [value, loading, error] = useDownloadURL( - ref(storage, 'path/to/file') - ); + const [value, loading, error] = useDownloadURL(ref(storage, 'path/to/file')); return (
@@ -62,3 +61,61 @@ const DownloadURL = () => { ); }; ``` + +### useUploadFile + +```js +const [uploadFile, uploading, snapshot, error] = useUploadFile(); +``` + +Upload a file to Firebase storage. + +The `useUploadFile` hook returns the following: + +- `uploadFile(ref, file, metadata)`: A function that can be called to upload a file, with attached metadata, to the storage reference supplied +- `uploading`: A `boolean` to indicate whether the the file is currently being uploaded +- `snapshot`: A `storage.UploadTaskSnapshot` to provide more detailed information about the upload +- `error`: Any `storage.StorageError` returned by Firebase when trying to upload the file, or `undefined` if there is no error + +#### Full example + +```js +import { getStorage, storageRef } from 'firebase/storage'; +import { useUploadFile } from 'react-firebase-hooks/storage'; + +const storage = getStorage(firebaseApp); + +const UploadFile = () => { + const [uploadFile, uploading, snapshot, error] = useUploadFile(); + const ref = storageRef(storage, 'file.jpg'); + const [selectedFile, setSelectedFile] = useState(); + + const upload = async () => { + if (selectedFile) { + const result = await uploadFile(ref, selectedFile, { + contentType: 'image/jpeg' + }); + alert(`Result: ${JSON.stringify(result)}`); + } + } + + return ( +
+

+ {error && Error: {error.message}} + {uploading && Uploading file...} + {snapshot && Snapshot: {JSON.stringify(snapshot)}} + {selectedFile && Selected file: {selectedFile.name}} + { + const file = e.target.files ? e.target.files[0] : undefined; + setSelectedFile(file); + }} + /> + +

+
+ ) +} +``` diff --git a/storage/index.js.flow b/storage/index.js.flow deleted file mode 100644 index 2624cbe..0000000 --- a/storage/index.js.flow +++ /dev/null @@ -1,7 +0,0 @@ -// @flow -import type { Reference, StorageError } from 'firebase/storage'; - -type LoadingHook = [T | void, boolean, StorageError | void]; - -export type DownloadURLHook = LoadingHook; -declare export function useDownloadURL(ref?: Reference | null): DownloadURLHook; diff --git a/storage/index.ts b/storage/index.ts index 9db554e..d8e2a13 100644 --- a/storage/index.ts +++ b/storage/index.ts @@ -1 +1,2 @@ export { default as useDownloadURL, DownloadURLHook } from './useDownloadURL'; +export { default as useUploadFile, UploadFileHook } from './useUploadFile'; diff --git a/storage/useUploadFile.ts b/storage/useUploadFile.ts new file mode 100644 index 0000000..88427aa --- /dev/null +++ b/storage/useUploadFile.ts @@ -0,0 +1,59 @@ +import { + StorageError, + StorageReference, + uploadBytesResumable, + UploadMetadata, + UploadResult, + UploadTaskSnapshot, +} from 'firebase/storage'; +import { useMemo, useState } from 'react'; + +export type UploadFileHook = [ + ( + storageRef: StorageReference, + data: Blob | Uint8Array | ArrayBuffer, + metadata?: UploadMetadata | undefined + ) => Promise, + boolean, + UploadTaskSnapshot | undefined, + StorageError | undefined +]; + +export default (): UploadFileHook => { + const [error, setError] = useState(); + const [uploading, setUploading] = useState(false); + const [snapshot, setSnapshot] = useState(); + + const uploadFile = async ( + storageRef: StorageReference, + data: Blob | Uint8Array | ArrayBuffer, + metadata?: UploadMetadata | undefined + ): Promise => { + return new Promise((resolve, reject) => { + setUploading(true); + const uploadTask = uploadBytesResumable(storageRef, data, metadata); + uploadTask.on( + 'state_changed', + (snapshot) => { + setSnapshot(snapshot); + }, + (error) => { + setUploading(false); + setError(error); + resolve(undefined); + }, + () => { + setUploading(false); + setSnapshot(undefined); + resolve({ + metadata: uploadTask.snapshot.metadata, + ref: uploadTask.snapshot.ref, + }); + } + ); + }); + }; + + const resArray: UploadFileHook = [uploadFile, uploading, snapshot, error]; + return useMemo(() => resArray, resArray); +}; diff --git a/util/package.json b/util/package.json index 0911fa5..0590645 100644 --- a/util/package.json +++ b/util/package.json @@ -1,5 +1,6 @@ { "name": "react-firebase-hooks/util", "main": "dist/index.cjs.js", - "module": "dist/index.esm.js" + "module": "dist/index.esm.js", + "typings": "dist/util/index.d.ts" }