Skip to content
113 changes: 113 additions & 0 deletions projects/firebaseui-angular-library/src/lib/dynamic-loader.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';

export interface Resource {
name: string;
type: "css" | "js";
src: string;
}

export interface LoadedResource {
[name: string]: {
loaded: boolean,
type: "css" | "js",
src: string
}
}

@Injectable()
export class DynamicLoaderService {

private static ResourcesStore: Resource[] = [];
private static LoadedResources: LoadedResource = {};

private _document: Document | undefined = undefined;

constructor(@Inject(DOCUMENT) _document?: any) {
this._document = _document;

DynamicLoaderService.ResourcesStore.forEach((res) => {
DynamicLoaderService.LoadedResources[res.name] = {
loaded: false,
type: res.type,
src: res.src
};
});
}

/**
* Loads a series of previously registered Script(s)
* @param resNames The list of resources to load
*/
load(...resNames: string[]) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a return type

let promises: any[] = [];
promises = resNames.map((name) => this.loadResource(name));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be written as a shorthand form. No need for initializing the array first. Additionally, the array should be correctly typed - not with any.

return Promise.all(promises);
}

/**
* Loads a script given it's name.
* @param name Name of the script to laod.
*/
loadResource(name: string) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add return type

return new Promise((resolve, reject) => {
const resourceRef = DynamicLoaderService.LoadedResources[name];

if (!resourceRef) {
return reject({ resource: name, loaded: false, statusText: 'Resource not registered' });
}

if (resourceRef.loaded) {
return resolve({ resource: name, loaded: true, statusText: 'Already Loaded' });
}

const tag = resourceRef.type === "js" ? this._document.createElement("script") : this._document.createElement("link");

tag.onload = (e) => {
resourceRef.loaded = true;
return resolve({ resource: name, loaded: true, statusText: "Loaded", status: e });
};

tag.onerror = (error) => {
return reject({ resource: name, loaded: false, statusText: 'Error', error: error });
}

if (tag instanceof HTMLScriptElement) {
tag.type = "text/javascript";
tag.src = resourceRef.src;
}
if (tag instanceof HTMLLinkElement) {
tag.type = "text/css";
tag.href = resourceRef.src;
tag.rel = "stylesheet";
}

this._document.head.appendChild(tag);
});
}

/**
* Registers a series of Resource(s), without loading them
* @param resources A list of Resource(s)
*/
register(...resources: Resource[]) {
resources.forEach(res => {
DynamicLoaderService.ResourcesStore.push(res);
DynamicLoaderService.LoadedResources[res.name] = {
loaded: false,
type: res.type,
src: res.src
};
});
}

/**
* Registers and then loads a list of Resource(s)
* @param resource The list of resources
*/
registerAndLoad(...resource: Resource[]) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add return type

const resName = resource.map(s => s.name);
this.register(...resource);
return this.load(...resName);
}
}
Original file line number Diff line number Diff line change
@@ -1,47 +1,42 @@
import {Component, EventEmitter, Inject, NgZone, OnDestroy, OnInit, Output} from '@angular/core';
import {Component, Inject, OnDestroy, OnInit, EventEmitter, Output} from '@angular/core';
import {AngularFireAuth} from '@angular/fire/auth';
import {Subscription} from 'rxjs';
import {
FirebaseUISignInFailure,
FirebaseUISignInSuccessWithAuthResult,
NativeFirebaseUIAuthConfig,
} from './firebaseui-angular-library.helper';
import * as firebaseui from 'firebaseui';
import {ExtendedFirebaseUIAuthConfig, FirebaseUISignInSuccessWithAuthResult, FirebaseUISignInFailure} from './firebaseui-angular-library.helper';
import {User} from 'firebase/app';
import {FirebaseuiAngularLibraryService} from './firebaseui-angular-library.service';
import {FirebaseuiAngularLibraryService, DEFAULT_FIREBASE_UI_AUTH_CONTAINER} from './firebaseui-angular-library.service';
import 'firebase/auth';
import UserCredential = firebase.auth.UserCredential;

@Component({
selector: 'firebase-ui',
template: '<div id="firebaseui-auth-container"></div>'
})
export class FirebaseuiAngularLibraryComponent implements OnInit, OnDestroy {
private static readonly COMPUTED_CALLBACKS = 'COMPUTED_CALLBACKS';

@Output('signInSuccessWithAuthResult') signInSuccessWithAuthResultCallback: EventEmitter<FirebaseUISignInSuccessWithAuthResult> = new EventEmitter(); // tslint:disable-line
@Output('signInFailure') signInFailureCallback: EventEmitter<FirebaseUISignInFailure> = new EventEmitter(); // tslint:disable-line

private subscription: Subscription;

constructor(private angularFireAuth: AngularFireAuth,
@Inject('firebaseUIAuthConfig') private _firebaseUiConfig: NativeFirebaseUIAuthConfig,
@Inject('firebaseUIAuthConfigFeature') private _firebaseUiConfig_Feature: NativeFirebaseUIAuthConfig,
private ngZone: NgZone,
@Inject('firebaseUIAuthConfig') private _firebaseUiConfig: ExtendedFirebaseUIAuthConfig,
@Inject('firebaseUIAuthConfigFeature') private _firebaseUiConfig_Feature: ExtendedFirebaseUIAuthConfig,
private firebaseUIService: FirebaseuiAngularLibraryService) {

FirebaseuiAngularLibraryService.signInSuccessWithAuthResultCallback = this.signInSuccessWithAuthResultCallback;
FirebaseuiAngularLibraryService.signInFailureCallback = this.signInFailureCallback;
}

get firebaseUiConfig(): NativeFirebaseUIAuthConfig {
getFirebaseUiConfig(): ExtendedFirebaseUIAuthConfig {
return {
...this._firebaseUiConfig,
...this._firebaseUiConfig_Feature
} as NativeFirebaseUIAuthConfig;
} as ExtendedFirebaseUIAuthConfig;
}

ngOnInit(): void {
this.subscription = this.angularFireAuth.authState.subscribe((value: User) => {
if ((value && value.isAnonymous) || !value) {
if (this.firebaseUiConfig.signInOptions.length !== 0) {
if (this.getFirebaseUiConfig().signInOptions.length !== 0) {
this.firebaseUIPopup();
} else {
throw new Error('There must be at least one AuthProvider.');
Expand All @@ -56,58 +51,8 @@ export class FirebaseuiAngularLibraryComponent implements OnInit, OnDestroy {
}
}

private getUIAuthConfig(): NativeFirebaseUIAuthConfig {
if (!(this.firebaseUiConfig as NativeFirebaseUIAuthConfig).callbacks) {
this._firebaseUiConfig[FirebaseuiAngularLibraryComponent.COMPUTED_CALLBACKS] = true;
(this._firebaseUiConfig as NativeFirebaseUIAuthConfig).callbacks = this.getCallbacks();
}
return (this.firebaseUiConfig as NativeFirebaseUIAuthConfig);
}

private firebaseUIPopup() {
const firebaseUiInstance = this.firebaseUIService.firebaseUiInstance;
const uiAuthConfig = this.getUIAuthConfig();

// Check if callbacks got computed to reset them again after providing the to firebaseui.
// Necessary for allowing updating the firebaseui config during runtime.
let resetCallbacks = false;
if (uiAuthConfig[FirebaseuiAngularLibraryComponent.COMPUTED_CALLBACKS]) {
resetCallbacks = true;
delete uiAuthConfig[FirebaseuiAngularLibraryComponent.COMPUTED_CALLBACKS];
}

// show the firebaseui
firebaseUiInstance.start('#firebaseui-auth-container', uiAuthConfig);

if (resetCallbacks) {
(this._firebaseUiConfig as NativeFirebaseUIAuthConfig).callbacks = null;
}
}

private getCallbacks(): any {
const signInSuccessWithAuthResult = (authResult: UserCredential, redirectUrl) => {
this.ngZone.run(() => {
this.signInSuccessWithAuthResultCallback.emit({
authResult,
redirectUrl
});
});
return this.firebaseUiConfig.signInSuccessUrl;
};

const signInFailureCallback = (error: firebaseui.auth.AuthUIError) => {
this.ngZone.run(() => {
this.signInFailureCallback.emit({
code: error.code,
credential: error.credential
});
});
return Promise.reject();
};

return {
signInSuccessWithAuthResult: signInSuccessWithAuthResult,
signInFailure: signInFailureCallback,
};
private async firebaseUIPopup() {
await this.firebaseUIService.start(DEFAULT_FIREBASE_UI_AUTH_CONTAINER);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export const firebase = firebaseOriginal;
export const firebaseui = firebaseuiOriginal;

export type NativeFirebaseUIAuthConfig = firebaseuiOriginal.auth.Config;

export type ExtendedFirebaseUIAuthConfig = NativeFirebaseUIAuthConfig & {
language?: string
};

export class FirebaseUISignInSuccessWithAuthResult {
authResult: UserCredential;
Expand All @@ -22,3 +24,54 @@ export class FirebaseUISignInFailure {
code: string;
credential: firebaseOriginal.auth.AuthCredential;
}

export interface FirebaseUILanguage {
code: string;
name?: string;
isRtL?: boolean;
}

export const FirebaseUILanguages: FirebaseUILanguage[] = [
{ code: "ar", name: "Arabic", isRtL: true },
{ code: "bg", name: "Bulgarian" },
{ code: "ca", name: "Catalan" },
{ code: "zh_cn", name: "Chinese (Simplified)" },
{ code: "zh_tw", name: "Chinese (Traditional)" },
{ code: "hr", name: "Croatian" },
{ code: "cs", name: "Czech" },
{ code: "da", name: "Danish" },
{ code: "nl", name: "Dutch" },
{ code: "en", name: "English" },
{ code: "en_gb", name: "English (UK)" },
{ code: "fa", name: "Farsi", isRtL: true },
{ code: "fil", name: "Filipino" },
{ code: "fi", name: "Finnish" },
{ code: "fr", name: "French" },
{ code: "de", name: "German" },
{ code: "el", name: "Greek" },
{ code: "iw", name: "Hebrew", isRtL: true },
{ code: "hi", name: "Hindi" },
{ code: "hu", name: "Hungarian" },
{ code: "id", name: "Indonesian" },
{ code: "it", name: "Italian" },
{ code: "ja", name: "Japanese" },
{ code: "ko", name: "Korean" },
{ code: "lv", name: "Latvian" },
{ code: "lt", name: "Lithuanian" },
{ code: "no", name: "Norwegian (Bokmal)" },
{ code: "pl", name: "Polish" },
{ code: "pt_br", name: "Portuguese (Brazil)" },
{ code: "pt_pt", name: "Portuguese (Portugal)" },
{ code: "ro", name: "Romanian" },
{ code: "ru", name: "Russian" },
{ code: "sr", name: "Serbian" },
{ code: "sk", name: "Slovak" },
{ code: "sl", name: "Slovenian" },
{ code: "es", name: "Spanish" },
{ code: "es_419", name: "Spanish (Latin America)" },
{ code: "sv", name: "Swedish" },
{ code: "th", name: "Thai" },
{ code: "tr", name: "Turkish" },
{ code: "uk", name: "Ukrainian" },
{ code: "vi", name: "Vietnamese" }
];
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import {ModuleWithProviders, NgModule} from '@angular/core';
import {FirebaseuiAngularLibraryComponent} from './firebaseui-angular-library.component';
import {CommonModule} from '@angular/common';
import {NativeFirebaseUIAuthConfig} from './firebaseui-angular-library.helper';
import {ExtendedFirebaseUIAuthConfig} from './firebaseui-angular-library.helper';
import {FirebaseuiAngularLibraryService} from './firebaseui-angular-library.service';
import { DynamicLoaderService } from './dynamic-loader.service';

@NgModule({
imports: [
CommonModule
],
declarations: [FirebaseuiAngularLibraryComponent],
providers: [FirebaseuiAngularLibraryService],
providers: [FirebaseuiAngularLibraryService, DynamicLoaderService],
exports: [FirebaseuiAngularLibraryComponent]
})
export class FirebaseUIModule {
static forRoot(firebaseUiAuthConfig: NativeFirebaseUIAuthConfig): ModuleWithProviders<FirebaseUIModule> {
static forRoot(firebaseUiAuthConfig: ExtendedFirebaseUIAuthConfig): ModuleWithProviders<FirebaseUIModule> {
return {
ngModule: FirebaseUIModule,
providers: [
Expand All @@ -23,7 +24,7 @@ export class FirebaseUIModule {
};
}

static forFeature(firebaseUIAuthConfig: NativeFirebaseUIAuthConfig | any): ModuleWithProviders<FirebaseUIModule> {
static forFeature(firebaseUIAuthConfig: ExtendedFirebaseUIAuthConfig | any): ModuleWithProviders<FirebaseUIModule> {
return {
ngModule: FirebaseUIModule,
providers: [
Expand Down
Loading