Skip to content

Instantly share code, notes, and snippets.

@DavidFrahm
Last active January 22, 2020 00:18
Show Gist options
  • Save DavidFrahm/9c71fa2061640b9cb43b76582cd55d49 to your computer and use it in GitHub Desktop.
Save DavidFrahm/9c71fa2061640b9cb43b76582cd55d49 to your computer and use it in GitHub Desktop.
FirebaseUI with Stencil ionic-pwa starter (workaround for Rollup issues)
import { Component, h } from '@stencil/core';
import { initFirebase } from '../../helpers/firebase';
@Component({
tag: 'app-root',
styleUrl: 'app-root.css'
})
export class AppRoot {
componentWillLoad() {
initFirebase();
}
render() {
return (
<ion-app>
<ion-router useHash={false}>
<ion-route url="/" component="app-home" />
<ion-route url="/profile/:name" component="app-profile" />
<ion-route url="/sign-up" component="signup-page" />
</ion-router>
<ion-nav />
</ion-app>
);
}
}
/**
* This import loads the firebase namespace along with all its type information.
* Firebase App (the core Firebase SDK) is always required and must be listed first.
*/
import fb from 'firebase/app';
/**
* Add the Firebase products that you want to use.
* These imports load individual services into the firebase namespace.
*/
import 'firebase/auth';
import 'firebase/firestore';
import 'firebase/functions';
import 'firebase/performance';
import 'firebase/analytics';
import { injectJS, injectCSS } from './utils';
/**
* Initialize Firebase app here, so it happens early and only once.
*/
import { firebaseConfig } from './constants';
fb.initializeApp(firebaseConfig);
console.info("Firebase app initialized");
export const firebase = fb;
/**
* Convenience, so other scripts can use these exports instead of (correctly) importing firebase libraries
*/
export const firestore = firebase.firestore();
export const functions = firebase.functions();
export const performance = firebase.performance();
export const auth = firebase.auth();
export const analytics = firebase.analytics();
/**
* Needed for firebaseui
*/
(window as any).firebase = firebase;
/**
* Keep TypeScript happy with window/global variables.
*/
declare var firebaseui; // Set via initFirebaseUI function below
// TODO: Enable this or remove it.
// firestore.enablePersistence().catch(err => {
// if (err.code == 'failed-precondition') {
// // Multiple tabs open, persistence can only be enabled
// // in one tab at a a time.
// // ...
// } else if (err.code == 'unimplemented') {
// // The current browser does not support all of the
// // features required to enable persistence
// // ...
// }
// });
/**
* Actual Firebase app initilization happens when this script is first imported and ran.
* This is just a call to control when and from where that occurs.
* Also a good place to verify Firebase app.
* NB: I'm not thrilled about this basically empty function, so might move the app init into app-root before its @Component.
*/
export function initFirebase() {
console.info("initFirebase()");
try {
console.log("firebase", firebase);
} catch (error) {
console.error("Firebase app is not available");
}
}
/**
* Setup and initialize FirebaseUI on the page.
* Must be called after DOM exists for it to find the FirebaseUI widget element on the page.
*
* As of Dec 2019, FirebaseUI didn't work with Rolllup builds, so we have to add it separately.
* Option A: Standard <script ...> and <link ...> tags in index.html
* Option B: Programatically inject JS and CSS in the DOM
* I went with Option B, because then the scripts are only loaded when FirebaseUI is used, i.e., sign up and sign in.
*/
export async function initFirebaseUI(widgetElementId: string) {
console.info("initFirebaseUI()");
async function injectResources() {
await injectJS(
'firebase-ui-script',
'https://www.gstatic.com/firebasejs/ui/4.4.0/firebase-ui-auth.js'
);
await injectCSS(
'firebase-ui-css',
'https://www.gstatic.com/firebasejs/ui/4.4.0/firebase-ui-auth.css'
);
console.log("JS and CSS Injected");
}
function startFirebaseUI() {
console.info("startFirebaseUI()");
console.log("firebase", firebase);
console.log("firebaseui", firebaseui);
var uiConfig = {
signInSuccessUrl: '/my-post-login-route',
signInOptions: [
// Leave the lines as is for the providers you want to offer your users.
// firebase.auth.GoogleAuthProvider.PROVIDER_ID,
// firebase.auth.FacebookAuthProvider.PROVIDER_ID,
// firebase.auth.TwitterAuthProvider.PROVIDER_ID,
// firebase.auth.GithubAuthProvider.PROVIDER_ID,
firebase.auth.EmailAuthProvider.PROVIDER_ID
// firebase.auth.PhoneAuthProvider.PROVIDER_ID,
// firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID
],
tosUrl: '/my-terms-of-service-route',
privacyPolicyUrl: '/my-privacy-policy-route',
credentialHelper: firebaseui.auth.CredentialHelper.NONE,
callbacks: {
signInSuccessWithAuthResult: authResult => {
if (authResult.additionalUserInfo.isNewUser) {
authResult.user.sendEmailVerification();
}
return false;
}
}
};
// Initialize the FirebaseUI widget using Firebase.
const ui =
firebaseui.auth.AuthUI.getInstance() ||
new firebaseui.auth.AuthUI(firebase.auth());
// The start method will wait until the DOM is loaded.
ui.start(widgetElementId, uiConfig);
};
await injectResources();
startFirebaseUI();
}
...
"dependencies": {
"@ionic/core": "^4.11.7",
"firebase": "^7.6.1",
"firebaseui": "^4.4.0"
},
...
import { Component, h } from '@stencil/core';
import { initFirebaseUI } from '../../helpers/firebase';
@Component({
tag: 'signup-page',
styleUrl: 'signup-page.css'
})
export class SignupPage {
async componentDidLoad() {
await initFirebaseUI('#firebaseui-auth-container');
}
render() {
return (
<div>
<p>Hello SignupPage!</p>
<div id="firebaseui-auth-container"></div>
</div>
);
}
}
import { Config } from '@stencil/core';
// https://stenciljs.com/docs/config
export const config: Config = {
outputTargets: [{
type: 'www',
serviceWorker: null
}],
commonjs: {
namedExports: {
// NB: Left-hand side can be an absolute path, a path relative to the current directory, or the name of a module in node_modules.
// Firebase needs this:
'idb/build/idb.js': ['openDb'],
// FirebaseUI was needing this, but not anymore since loading FirebaseUI outside of Rollup; leaving for a while until other Firebase features are implemented.
// 'firebase/dist/index.cjs.js': ['initializeApp', 'auth', 'app', 'firebase'],
// 'firebase/app/dist/index.cjs.js': ['initializeApp', 'auth', 'app', 'firebase'],
}
},
globalScript: 'src/global/app.ts',
globalStyle: 'src/global/app.css'
};
export function injectJS(id: string, src: string) {
return new Promise((resolve, reject) => {
if (document.getElementById(id)) {
resolve(`JS already injected for id ${id}`);
return;
}
const script = document.createElement('script');
script.id = id;
script.async = true;
script.src = src;
script.addEventListener('load', resolve);
script.addEventListener('error', () => reject('Error loading script.'));
script.addEventListener('abort', () => reject('Script loading aborted.'));
document.head.appendChild(script);
});
}
export function injectCSS(id: string, src: string) {
return new Promise((resolve, reject) => {
if (document.getElementById(id)) {
resolve(`CSS already injected for id ${id}`);
return;
}
const script = document.createElement('link');
script.id = id;
script.setAttribute('rel', 'stylesheet');
script.setAttribute('href', src);
script.addEventListener('load', resolve);
script.addEventListener('error', () => reject('Error loading css.'));
script.addEventListener('abort', () => reject('CSS loading aborted.'));
document.head.appendChild(script);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment