voluptasmollitia
Version:
Monorepo for the Firebase JavaScript SDK
376 lines (349 loc) • 11.6 kB
text/typescript
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FirebaseApp, _FirebaseService } from '@firebase/app-compat';
import * as exp from '@firebase/auth-exp/internal';
import * as compat from '@firebase/auth-types';
import { Provider } from '@firebase/component';
import { ErrorFn, Observer, Unsubscribe } from '@firebase/util';
import {
_validatePersistenceArgument,
Persistence,
_getPersistencesFromRedirect,
_savePersistenceForRedirect
} from './persistence';
import { _isPopupRedirectSupported } from './platform';
import { CompatPopupRedirectResolver } from './popup_redirect';
import { User } from './user';
import {
convertConfirmationResult,
convertCredential
} from './user_credential';
import { ReverseWrapper, Wrapper } from './wrap';
const _assert: typeof exp._assert = exp._assert;
export class Auth
implements compat.FirebaseAuth, Wrapper<exp.Auth>, _FirebaseService {
readonly _delegate: exp.AuthImpl;
constructor(readonly app: FirebaseApp, provider: Provider<'auth-exp'>) {
if (provider.isInitialized()) {
this._delegate = provider.getImmediate() as exp.AuthImpl;
this.linkUnderlyingAuth();
return;
}
const { apiKey } = app.options;
// TODO: platform needs to be determined using heuristics
_assert(apiKey, exp.AuthErrorCode.INVALID_API_KEY, {
appName: app.name
});
let persistences: exp.Persistence[] = [exp.inMemoryPersistence];
// Only deal with persistences in web environments
if (typeof window !== 'undefined') {
// Note this is slightly different behavior: in this case, the stored
// persistence is checked *first* rather than last. This is because we want
// to prefer stored persistence type in the hierarchy.
persistences = _getPersistencesFromRedirect(apiKey, app.name);
for (const persistence of [
exp.indexedDBLocalPersistence,
exp.browserLocalPersistence
]) {
if (!persistences.includes(persistence)) {
persistences.push(persistence);
}
}
}
// TODO: platform needs to be determined using heuristics
_assert(apiKey, exp.AuthErrorCode.INVALID_API_KEY, {
appName: app.name
});
// Only use a popup/redirect resolver in browser environments
const resolver =
typeof window !== 'undefined' ? CompatPopupRedirectResolver : undefined;
this._delegate = provider.initialize({
options: {
persistence: persistences,
popupRedirectResolver: resolver
}
}) as exp.AuthImpl;
this._delegate._updateErrorMap(exp.debugErrorMap);
this.linkUnderlyingAuth();
}
get emulatorConfig(): compat.EmulatorConfig | null {
return this._delegate.emulatorConfig;
}
get currentUser(): compat.User | null {
if (!this._delegate.currentUser) {
return null;
}
return User.getOrCreate(this._delegate.currentUser);
}
get languageCode(): string | null {
return this._delegate.languageCode;
}
get settings(): compat.AuthSettings {
return this._delegate.settings;
}
get tenantId(): string | null {
return this._delegate.tenantId;
}
useDeviceLanguage(): void {
this._delegate.useDeviceLanguage();
}
signOut(): Promise<void> {
return this._delegate.signOut();
}
useEmulator(url: string, options?: { disableWarnings: boolean }): void {
exp.useAuthEmulator(this._delegate, url, options);
}
applyActionCode(code: string): Promise<void> {
return exp.applyActionCode(this._delegate, code);
}
checkActionCode(code: string): Promise<compat.ActionCodeInfo> {
return exp.checkActionCode(this._delegate, code);
}
confirmPasswordReset(code: string, newPassword: string): Promise<void> {
return exp.confirmPasswordReset(this._delegate, code, newPassword);
}
async createUserWithEmailAndPassword(
email: string,
password: string
): Promise<compat.UserCredential> {
return convertCredential(
this._delegate,
exp.createUserWithEmailAndPassword(this._delegate, email, password)
);
}
fetchProvidersForEmail(email: string): Promise<string[]> {
return this.fetchSignInMethodsForEmail(email);
}
fetchSignInMethodsForEmail(email: string): Promise<string[]> {
return exp.fetchSignInMethodsForEmail(this._delegate, email);
}
isSignInWithEmailLink(emailLink: string): boolean {
return exp.isSignInWithEmailLink(this._delegate, emailLink);
}
async getRedirectResult(): Promise<compat.UserCredential> {
_assert(
_isPopupRedirectSupported(),
this._delegate,
exp.AuthErrorCode.OPERATION_NOT_SUPPORTED
);
const credential = await exp.getRedirectResult(
this._delegate,
CompatPopupRedirectResolver
);
if (!credential) {
return {
credential: null,
user: null
};
}
return convertCredential(this._delegate, Promise.resolve(credential));
}
// This function should only be called by frameworks (e.g. FirebaseUI-web) to log their usage.
// It is not intended for direct use by developer apps. NO jsdoc here to intentionally leave it
// out of autogenerated documentation pages to reduce accidental misuse.
addFrameworkForLogging(framework: string): void {
exp.addFrameworkForLogging(this._delegate, framework);
}
onAuthStateChanged(
nextOrObserver: Observer<unknown> | ((a: compat.User | null) => unknown),
errorFn?: (error: compat.Error) => unknown,
completed?: Unsubscribe
): Unsubscribe {
const { next, error, complete } = wrapObservers(
nextOrObserver,
errorFn,
completed
);
return this._delegate.onAuthStateChanged(next!, error, complete);
}
onIdTokenChanged(
nextOrObserver: Observer<unknown> | ((a: compat.User | null) => unknown),
errorFn?: (error: compat.Error) => unknown,
completed?: Unsubscribe
): Unsubscribe {
const { next, error, complete } = wrapObservers(
nextOrObserver,
errorFn,
completed
);
return this._delegate.onIdTokenChanged(next!, error, complete);
}
sendSignInLinkToEmail(
email: string,
actionCodeSettings: compat.ActionCodeSettings
): Promise<void> {
return exp.sendSignInLinkToEmail(this._delegate, email, actionCodeSettings);
}
sendPasswordResetEmail(
email: string,
actionCodeSettings?: compat.ActionCodeSettings | null
): Promise<void> {
return exp.sendPasswordResetEmail(
this._delegate,
email,
actionCodeSettings || undefined
);
}
async setPersistence(persistence: string): Promise<void> {
_validatePersistenceArgument(this._delegate, persistence);
let converted;
switch (persistence) {
case Persistence.SESSION:
converted = exp.browserSessionPersistence;
break;
case Persistence.LOCAL:
// Not using isIndexedDBAvailable() since it only checks if indexedDB is defined.
const isIndexedDBFullySupported = await exp
._getInstance<exp.PersistenceInternal>(exp.indexedDBLocalPersistence)
._isAvailable();
converted = isIndexedDBFullySupported
? exp.indexedDBLocalPersistence
: exp.browserLocalPersistence;
break;
case Persistence.NONE:
converted = exp.inMemoryPersistence;
break;
default:
return exp._fail(exp.AuthErrorCode.ARGUMENT_ERROR, {
appName: this._delegate.name
});
}
return this._delegate.setPersistence(converted);
}
signInAndRetrieveDataWithCredential(
credential: compat.AuthCredential
): Promise<compat.UserCredential> {
return this.signInWithCredential(credential);
}
signInAnonymously(): Promise<compat.UserCredential> {
return convertCredential(
this._delegate,
exp.signInAnonymously(this._delegate)
);
}
signInWithCredential(
credential: compat.AuthCredential
): Promise<compat.UserCredential> {
return convertCredential(
this._delegate,
exp.signInWithCredential(this._delegate, credential as exp.AuthCredential)
);
}
signInWithCustomToken(token: string): Promise<compat.UserCredential> {
return convertCredential(
this._delegate,
exp.signInWithCustomToken(this._delegate, token)
);
}
signInWithEmailAndPassword(
email: string,
password: string
): Promise<compat.UserCredential> {
return convertCredential(
this._delegate,
exp.signInWithEmailAndPassword(this._delegate, email, password)
);
}
signInWithEmailLink(
email: string,
emailLink?: string
): Promise<compat.UserCredential> {
return convertCredential(
this._delegate,
exp.signInWithEmailLink(this._delegate, email, emailLink)
);
}
signInWithPhoneNumber(
phoneNumber: string,
applicationVerifier: compat.ApplicationVerifier
): Promise<compat.ConfirmationResult> {
return convertConfirmationResult(
this._delegate,
exp.signInWithPhoneNumber(
this._delegate,
phoneNumber,
applicationVerifier
)
);
}
async signInWithPopup(
provider: compat.AuthProvider
): Promise<compat.UserCredential> {
_assert(
_isPopupRedirectSupported(),
this._delegate,
exp.AuthErrorCode.OPERATION_NOT_SUPPORTED
);
return convertCredential(
this._delegate,
exp.signInWithPopup(
this._delegate,
provider as exp.AuthProvider,
CompatPopupRedirectResolver
)
);
}
async signInWithRedirect(provider: compat.AuthProvider): Promise<void> {
_assert(
_isPopupRedirectSupported(),
this._delegate,
exp.AuthErrorCode.OPERATION_NOT_SUPPORTED
);
await _savePersistenceForRedirect(this._delegate);
return exp.signInWithRedirect(
this._delegate,
provider as exp.AuthProvider,
CompatPopupRedirectResolver
);
}
updateCurrentUser(user: compat.User | null): Promise<void> {
// remove ts-ignore once overloads are defined for exp functions to accept compat objects
// @ts-ignore
return this._delegate.updateCurrentUser(user);
}
verifyPasswordResetCode(code: string): Promise<string> {
return exp.verifyPasswordResetCode(this._delegate, code);
}
unwrap(): exp.Auth {
return this._delegate;
}
_delete(): Promise<void> {
return this._delegate._delete();
}
private linkUnderlyingAuth(): void {
((this._delegate as unknown) as ReverseWrapper<Auth>).wrapped = () => this;
}
}
function wrapObservers(
nextOrObserver: Observer<unknown> | ((a: compat.User | null) => unknown),
error?: (error: compat.Error) => unknown,
complete?: Unsubscribe
): Partial<Observer<exp.User | null>> {
let next = nextOrObserver;
if (typeof nextOrObserver !== 'function') {
({ next, error, complete } = nextOrObserver);
}
// We know 'next' is now a function
const oldNext = next as (a: compat.User | null) => unknown;
const newNext = (user: exp.User | null): unknown =>
oldNext(user && User.getOrCreate(user as exp.User));
return {
next: newNext,
error: error as ErrorFn,
complete
};
}