Using module bundlers and dynamic config firebase - firebase

I have an issue where I cannot use init.js, which I have done in the past when importing firebase through the reserved hosting urls.
<script src="/__/firebase/init.js"></script>
This is the script that I am trying to use, and I am importing my firebase modules with:
import * as firebase from "firebase/app";
But I am trying to use webpack, with this. I have tried including init.js in my html, and my bundle as well, but without success.
Is there any way to use init.js with my module bundler?

Pretty late to the party, but better late than never if somebody needs it :)
We wanted to do the same at my company, for two reasons
not having to specify the firebase config ourself, as the init.js script contains it
automatic pickup of emulators when the useEmulator=true query param is specified (handy when you want to choose which emulators to start)
So we trick the script into doing what we want!
Basically the script looks for a global firebase variable which does not exist when using the modules. So we provide one for it to work.
Here's the code we use:
import { initializeApp } from 'firebase/app';
import { connectAuthEmulator, getAuth } from 'firebase/auth';
import { getFirestore, connectFirestoreEmulator } from 'firebase/firestore';
import { connectFunctionsEmulator, getFunctions } from 'firebase/functions';
export async function initFirebase(): Promise<void> {
// provide a fake firebase proxy object to the hosting init script
// to get the boilerplate done (firebase conf + automatic connection
// to started emulators)
const proxy: any = {
initializeApp: (c: any) => initializeApp(c),
};
// emulators done under development check to help with the tree-shaking
if (process.env.NODE_ENV === 'development') {
proxy.firestore = () => ({
useEmulator: (h: any, p: any) =>
connectFirestoreEmulator(getFirestore(), h, p),
});
proxy.functions = () => ({
useEmulator: (h: any, p: any) =>
connectFunctionsEmulator(getFunctions(), h, p),
});
proxy.auth = () => ({
useEmulator: (h: any, p: any) => connectAuthEmulator(getAuth(), h, p),
});
}
const _global = globalThis as any;
_global.firebase = proxy;
try {
await import(`/__/firebase/init.js${
process.env.NODE_ENV === 'development' ? '?useEmulator=true' : ''
}`
);
} finally {
delete _global.firebase;
}
}
You'll notice I only setup the proxies for firestore/functions/auth as those are the only ones we emulate at the moment. If you don't care for the emulators, you can skip the entire code paths when process.env.NODE is 'development'.

Related

RESTful API hosting on GCP

I have this multi layered application entirely hosted on GCP. At the moment, we only have the back-end part. Front-end and API are to be developed. For the front-end, the decision has been made - it will be a React.js app hosted on Firebase Hosting and the authentication method will be Email/password and users will be provisioned manually through the Firebase Hosting UI.
As we'd like to have a re-usable middle layer (API) we're in a process of making a decision what type of a solution to be used for our middle layer. The main request here is only logged in users to be able to call the API endpoints. Eventually, there will be also a native/mobile application which will have to also be able to make authenticated requests to the API.
My question here is, what type of GCP service is advised to pick here? I want it to be light, scalable and price optimized. Preferred programming language would be C# but Node.js would be also acceptable.
Firebase Functions would work well for this authenticated API. With a function, you can simply check for the existence of context.auth.uid before proceeding with the API call.
https://firebase.google.com/docs/functions/callable
You'll want to use the .onCall() method to access this context.auth object.
Here's an example I took from one of my active Firebase projects which uses this concept:
Inside your functions>src folder, create a new function doAuthenticatedThing.ts
/**
* A Firebase Function that can be called from your React Firebase client UI
*/
import * as functions from 'firebase-functions';
import { initializeApp } from 'firebase/app';
import { connectFirestoreEmulator, getFirestore, getDocs, query, where, collection } from 'firebase/firestore';
import firebaseConfig from './firebase-config.json';
let isEmulator = false;
const doAuthenticatedThing = functions
.region('us-west1')
.runWith({
enforceAppCheck: true,
memory: '256MB',
})
.https.onCall(async (_data, context) => {
// disable if you don't use app-check verify (you probably should)
if (context.app == undefined) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called from an App Check verified app.',
);
}
// checks for a firebase authenticated frontend user
if (context.auth == undefined) {
throw new functions.https.HttpsError(
'failed-precondition',
'The user must be authenticated.',
);
}
// establish firestore db for queries
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
// start the emulator
if (process.env.MODE === 'development' && !isEmulator) {
connectFirestoreEmulator(db, '127.0.0.1', 6060);
isEmulator = true;
}
// obtain the user's firebase auth UID
const uuid = context?.auth?.uid as string;
// do some database stuff
const ref = collection(db, 'collection-name');
const q = query(ref, where(uuid, '==', uuid));
const results = await getDocs(q);
if (results.empty) {
throw new functions.https.HttpsError(
'internal',
'There were no results found!',
);
}
// prepare document data
const data: Array<any> = [];
// gather chats, and an array of all chat uids
results.forEach((d) => {
data.push({ id: d.id, data: d.data() });
});
return data;
});
export default doAuthenticatedThing;
Make sure to reference this new Firebase Function in the functions/src/index.ts file.
import doAuthenticatedThingFn from './doAuthenticatedThing';
export const doAuthenticatedThing = doAuthenticatedThingFn;
Create a frontend React Hook so any component can use any function you make. Call it useGetFunction.ts
import { getApp } from 'firebase/app';
import { getFunctions, HttpsCallable, httpsCallable } from '#firebase/functions';
const useGetFunction = (functionName: string): HttpsCallable<unknown, unknown> => {
const app = getApp();
const region = 'us-west1';
const functions = getFunctions(app, region);
return httpsCallable(functions, functionName);
};
export default useGetFunction;
Now you can simply get this function and use it in any React component:
const SomeComponent = () => {
const doAuthenticatedThing = useGetFunction('doAuthenticatedThing');
useEffect(() => {
(async () => {
const results = await doAuthenticatedThing();
})();
}, []);
};

Next.js with Firebase Remote Config

I was trying to integrate Google's Firebase Remote config into my Next.js app.
When following Firebase's docs, and just inserted the functions directly into my component's code block, like so:
const remoteConfig = getRemoteConfig(app);
I keep getting the following error when following their documentation:
FirebaseError: Remote Config: Undefined window object. This SDK only supports usage in a browser environment.
I understand that it happens since Nextjs is rendered server-side, so there's no window object yet, so here's my solution:
import {
fetchAndActivate,
getRemoteConfig,
getString,
} from 'firebase/remote-config';
const Home: NextPage<Props> = (props) => {
const [title, setTitle] = useState<string | null>('Is It True?');
useEffect(() => {
if (typeof window !== 'undefined') {
const remoteConfig = getRemoteConfig(app);
remoteConfig.settings.minimumFetchIntervalMillis = 3600000;
fetchAndActivate(remoteConfig)
.then(() => {
const titleData = getString(remoteConfig, 'trueOrFalse');
setTitle(titleData);
})
.catch((err) => {
console.log(err);
});
}
});
return <h1>{title}</h1>}
Basically, the important part is the if statement that checks if the window object exists, then it execute the Remote Config functions according to Firebase documents.
Also, it worked outside a useEffect, but I think that's probably a bad idea to leave it outside, maybe even it should have a dependency, can't think of one at the moment.

Firebase Cloud Function unit test HTTP onCall

I would like to write some unit tests for a bunch of cloud functions. Now I'm facing the following issue. Using the firebase-functions-test I'm somehow not able to test HTTP triggered cloud functions. Here are some of my cloud functions that i'd like to test using jest:
export cloudFunctions = {
createUserByAdmin: functions.runWith({ timeoutSeconds: 30, memory: '256MB' }).https.onCall(UserService.createUserByAdmin),
updateUserByAdmin: functions.runWith({ timeoutSeconds: 30, memory: '256MB' }).https.onCall(UserService.updateUserByAdmin),
deleteUserByAdmin: functions.runWith({ timeoutSeconds: 30, memory: '256MB' }).https.onCall(UserService.deleteUserByAdmin)
}
They are all deployed on Firebase and they work without problems. But I couldn't find a way to call the using the firebase-functions-test package. Also, there are a few examples of how to write unit tests using that package but none of them test http triggered functions.
This is my test file:
import * as functions from 'firebase-functions-test'
import * as admin from 'firebase-admin'
import * as path from 'path'
const projectConfig = {
projectId: 'test-fb',
}
const testEnv = functions(
projectConfig,
path.resolve('DO-NOT-EDIT.dev.fb-admin-sdk-key.json'),
)
describe('[Cloud Functions] User Service', () => {
let cloudFunctions
beforeAll(() => {
cloudFunctions = require('../index')
})
afterAll(() => {
// delete made accounts/entrys
})
describe('Testing "createUserByAdmin"', () => {
it('Creating User does work', () => {
expect(1).toBe(0)
})
})
})
Does someone know how to test http cloud functions? I would really appreciate an example.
Thanks!
I actually found a way on how to test HTTP Cloud Functions using firebase-functions-test it all works with a wrapper function. Take a look at this reference page. Here is some code to make things a bit more clear.
this is a snippet from one of my tests
import * as functions from 'firebase-functions-test'
import * as admin from 'firebase-admin'
import * as path from 'path'
const projectConfig = {
projectId: 'myproject-id',
}
const testEnv = functions(
projectConfig,
path.resolve('fb-admin-sdk-key.json'),
)
// has to be after initializing functions
import * as cloudFunctions from '../index'
describe('Testing "createUserByAdmin"', () => {
const createUserByAdmin = testEnv.wrap(cloudFunctions.createUserByAdmin)
it('Creating User does work', async (done) => {
const data = {
displayName: 'Jest Unit Test',
email: 'unit#domain.com',
password: 'password',
uid: null,
}
const context = {
auth: {
token: {
access: 'somestring,
},
uid: 'mockuiddddd',
},
}
await createUserByAdmin(data, context)
.then(async (createdUser: any) => {
expect(createdUser.status).toBe('OK')
done()
})
.catch((error: any) => {
fail('createUserByAdmin failed with the following ' + error)
})
})
})
You'll see that after initializing our test environment using our projectConfig and our service account key file.
const testEnv = functions(
projectConfig,
path.resolve('fb-admin-sdk-key.json'),
)
you'll just have to wrap the appropriate cloud function with the .wrap() function.
const createUserByAdmin = testEnv.wrap(cloudFunctions.createUserByAdmin)
And you can call it like every other function (keep in mind that cloud functions usually expect a data parameter (with the variables you use in your cloud function) as well as a context parameter, depending on how you handle authentication/authorization you'll have to try and error to find the right context properties your function requests.
if your writing tests for your production cloud functions make sure to clean up after running tests - such as deleting created accounts or deleting data in either firestore or realtime-database

How to import Firebase only on client in Sapper?

I'm importing Firebase into my Sapper application, I do not want the imports to be evaluated on the server. How do I make sure imports are only on the client-side?
I am using Sapper to run sapper export which generates the static files. I have tried:
Creating the firebase instance in it's own file and exported the firebase.auth() and firebase.firestore() modules.
Trying to adjust the rollup.config.js to resolve the dependencies differently, as suggested from the error message below. This brings more headaches.
Creating the Firebase instance in client.js. Unsuccessful.
Creating the instance in stores.js. Unsuccessful.
Declaring the variable and assigning it in onMount(). This causes me to have to work in different block scopes. And feels a bit hacky.
The initialization of the app, works fine:
import firebase from 'firebase/app'
const config = {...}
firebase.initializeApp(config);
I have also discovered that if I change the import to just import firebase from 'firebase' I do not get this server error:
#firebase/app:
Warning: This is a browser-targeted Firebase bundle but it appears it is being run in a Node environment. If running in a Node environment, make sure you are using the bundle specified by the "main" field in package.json.
If you are using Webpack, you can specify "main" as the first item in
"resolve.mainFields": https://webpack.js.org/configuration/resolve/#resolvemainfields
If using Rollup, use the rollup-plugin-node-resolve plugin and set "module" to false and "main" to true: https://github.com/rollup/rollup-plugin-node-resolve
I expected to just export these firebase functionalities from a file and import them into my components like:
<script>
import { auth } from "../firebase";
</script>
But as soon as that import is include, the dev server crashes. I don't want to use it on the server, since I'm just generating the static files.
Does anyone have some ideas on how to achieve importing only on client side?
So I have spent too much time on this. There isn't really a more elegant solution than onMOunt.
However, I did realize that sapper really should be used for it's SSR capabilities. And I wrote an article about how to get set up on Firebase with Sapper SSR and Cloud Functions:
https://dev.to/eckhardtd/how-to-host-a-sapper-js-ssr-app-on-firebase-hmb
Another solution to original question is to put the Firebase CDN's in the global scope via the src/template.html file.
<body>
<!-- The application will be rendered inside this element,
because `app/client.js` references it -->
<div id='sapper'>%sapper.html%</div>
<!-- Sapper creates a <script> tag containing `app/client.js`
and anything else it needs to hydrate the app and
initialise the router -->
%sapper.scripts%
<!-- Insert these scripts at the bottom of the HTML, but before you use any Firebase services -->
<!-- Firebase App (the core Firebase SDK) is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/6.0.4/firebase-app.js"></script>
<!-- Add Firebase products that you want to use -->
<script src="https://www.gstatic.com/firebasejs/6.0.4/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/6.0.4/firebase-firestore.js"></script>
</body>
</html>
and in the component:
<script>
import { onMount } from 'svelte';
let database, authentication;
onMount(() => {
database = firebase.firestore();
authentication = firebase.auth();
});
const authHandler = () => {
if (process.browser) {
authentication
.createUserWithEmailAndPassword()
.catch(e => console.error(e));
}
}
</script>
<button on:click={authHandler}>Sign up</button>
I was able to import firebase using ES6. If you are using rollup you need to consfigure namedExports in commonjs plugin:
//--- rollup.config.js ---
...
commonjs({
namedExports: {
// left-hand side can be an absolute path, a path
// relative to the current directory, or the name
// of a module in node_modules
'node_modules/idb/build/idb.js': ['openDb'],
'node_modules/firebase/dist/index.cjs.js': ['initializeApp', 'firestore'],
},
}),
The you can use it like this:
//--- db.js ---
import * as firebase from 'firebase';
import 'firebase/database';
import { firebaseConfig } from '../config'; //<-- Firebase initialization config json
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
export { firebase };
// Initialize db
export const db = firebase.firestore();
and maybe use it in a service like such:
// --- userService.js ----
import { db } from './common';
const usersCol = db.collection('users');
export default {
async login(username, password) {
const userDoc = await usersCol.doc(username).get();
const user = userDoc.data();
if (user && user.password === password) {
return user;
}
return null;
},
};
EDITED
Full rollup config
/* eslint-disable global-require */
import resolve from 'rollup-plugin-node-resolve';
import replace from 'rollup-plugin-replace';
import commonjs from 'rollup-plugin-commonjs';
import svelte from 'rollup-plugin-svelte';
import babel from 'rollup-plugin-babel';
import { terser } from 'rollup-plugin-terser';
import config from 'sapper/config/rollup';
import { sass } from 'svelte-preprocess-sass';
import pkg from './package.json';
const mode = process.env.NODE_ENV;
const dev = mode === 'development';
const legacy = !!process.env.SAPPER_LEGACY_BUILD;
// eslint-disable-next-line no-shadow
const onwarn = (warning, onwarn) =>
(warning.code === 'CIRCULAR_DEPENDENCY' && warning.message.includes('/#sapper/')) || onwarn(warning);
export default {
client: {
input: config.client.input(),
output: config.client.output(),
plugins: [
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode),
}),
svelte({
dev,
hydratable: true,
emitCss: true,
preprocess: {
style: sass(),
},
}),
resolve({
browser: true,
}),
commonjs({
namedExports: {
// left-hand side can be an absolute path, a path
// relative to the current directory, or the name
// of a module in node_modules
'node_modules/idb/build/idb.js': ['openDb'],
'node_modules/firebase/dist/index.cjs.js': ['initializeApp', 'firestore'],
},
}),
legacy &&
babel({
extensions: ['.js', '.mjs', '.html', '.svelte'],
runtimeHelpers: true,
exclude: ['node_modules/#babel/**'],
presets: [
[
'#babel/preset-env',
{
targets: '> 0.25%, not dead',
},
],
],
plugins: [
'#babel/plugin-syntax-dynamic-import',
[
'#babel/plugin-transform-runtime',
{
useESModules: true,
},
],
],
}),
!dev &&
terser({
module: true,
}),
],
onwarn,
},
server: {
input: config.server.input(),
output: config.server.output(),
plugins: [
replace({
'process.browser': false,
'process.env.NODE_ENV': JSON.stringify(mode),
}),
svelte({
generate: 'ssr',
dev,
}),
resolve(),
commonjs(),
],
external: Object.keys(pkg.dependencies).concat(require('module').builtinModules || Object.keys(process.binding('natives'))),
onwarn,
},
serviceworker: {
input: config.serviceworker.input(),
output: config.serviceworker.output(),
plugins: [
resolve(),
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode),
}),
commonjs(),
!dev && terser(),
],
onwarn,
},
};
The clean way is to use the Dynamic Import as the documentation said: Making a component SSR compatible
The way to get around this is to use a dynamic import for your component, from within the onMount function (which is only called on the client), so that your import code is never called on the server.
So here for example we want to import the core of firebase and the authentication package too.
<script>
let firebase;
onMount(async () => {
const module = await import("firebase/app");
await import("firebase/auth");
firebase = module.default;
firebase.initializeApp(firebaseConfig);
});
<script>
And now you can use firebase object as you can, for example we want to login with email and password:
let email;
let password;
async function login() {
try {
let result = await firebase.auth().signInWithEmailAndPassword(
email,
password
);
console.log(result.user);
} catch (error) {
console.log(error.code, error.message);
}
}
In order to use Firebase with Sapper, you have to import firebase not firebase/app. You do want firebase to be able to load correctly with SSR on the backend, not just the frontend. If you have some metatags, for example, that would be stored in the database, you want them to load on the backend (UNTESTED).
You could just use firebase, but then you get the annoying console warning. Remember also firebase loads ALL firebase dependencies while firebase/app does not, that is why you don't want to use it on the frontend. There is probably a way with admin-firebase, but we want to have less dependencies.
Do not use rxfire at all. You don't need it. It causes errors with Sapper. Just plain Firebase.
firebase.ts
import firebase from 'firebase/app';
import "firebase/auth";
import "firebase/firestore";
import * as config from "./config.json";
const fb = (process as any).browser ? firebase : require('firebase');
fb.initializeApp(config);
export const auth = fb.auth();
export const googleProvider = new fb.auth.GoogleAuthProvider();
export const db = fb.firestore();
Firebase functions require an extra step and you must enable dynamic imports. (UNTESTED)
export const functions = (process as any).browser ? async () => {
await import("firebase/functions");
return fb.functions()
} : fb.functions();
While this compiles, I have not tried to run httpsCallable or confirmed it will load from the database on the backend for seo ssr from the db. Let me know if it works.
I suspect all of this will work with the new SvelteKit now that Sapper is dead.

What's the difference between the 'firebase' module, and the "Firebase" one from Ionic Native, in Ionic

Currently found this question (which also puzzled me), but I'm currently using "firebase" to authenticate, and "Firebase" from Ionic Native to get analytics data on the Firebase console. I think that one of these is redundant (since I have the Firebase initialization data once as an object in code, and another one in google-services.json).
So what is the difference, are these two packages substitutes for each other, or is there something else.
u talk about node-modules in ionic. im using if i understand to using it. and my experience tell me its not substitutes for each other. Lets talk about the modules.
First if using:
import firebase from 'firebase'
or
import * as firebase from "firebase";
working with dataSnapshot, snapshot, snap.
if i need object to array data from firebase example:
import firebase from 'firebase';
this.addProduct = firebase.database().ref('/product-List');
this.addProduct.on('value', snapshot => {
this.productList = [];
snapshot.forEach( snap => {
this.productList.push({
category: snap.val().category,
id: snap.key,
in_stock: snap.val().in_stock,
name: snap.val().name,
downloadURL: snap.val().downloadURL,
short_description: snap.val().short_description,
description: snap.val().description,
regular_price: snap.val().regular_price,
sale_price: snap.val().sale_price,
brand: snap.val().brand,
vendor: snap.val().vendor
});
});
});
}
another node-modules ionic-native/firebase
import {Firebase} from '#ionic-native/firebase';
plugin for push notifications, event tracking, crash reporting, analytics and more.
in my case. im using for login with phone and verifyPhoneNumber example:
import {Firebase} from '#ionic-native/firebase';
constructor(private firebasePlugin: Firebase) {
}
Private registerPhone(): void {
if (!this.phoneNumber.value) {
alert('Mohon isi nomor telepon anda');
return;
}
const appVerifier = this.recaptchaVerifier;
const phoneNo = '+62' + this.phoneNumber.value;
if (this.platform.is('cordova')) {
try {
this.firebasePlugin.verifyPhoneNumber(phoneNo, 60).then (credential=> {
// alert("SMS Kode Verifikasi Berhasil dikirim ke Nomor Telp anda");
console.log(credential);
this.showPrompt(credential.verificationId);
}).catch (error => {
console.error(error);
});
}catch(error){alert(error.message)}
}
}

Resources