Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7a72f62
fix: add customFunctionPath fixing custom domains
wojtek-krysiak Sep 29, 2020
5b68c3d
chore(release): 3.0.2-beta.1 [skip ci]
semantic-release-bot Sep 29, 2020
16b1969
feat: trigger build
wojtek-krysiak Sep 29, 2020
ae74f33
Merge branch 'beta' of github.com:SoftwareBrothers/admin-bro-firebase…
wojtek-krysiak Sep 29, 2020
c8ae0c5
chore(release): 3.1.0-beta.1 [skip ci]
semantic-release-bot Sep 29, 2020
91989ea
fix: adjust path according to the custom domain
wojtek-krysiak Sep 29, 2020
68acd6a
Merge branch 'beta' of github.com:SoftwareBrothers/admin-bro-firebase…
wojtek-krysiak Sep 29, 2020
b1ed1ce
chore(release): 3.1.0-beta.2 [skip ci]
semantic-release-bot Sep 29, 2020
9825ea9
fix: move customFunctionPath to the options
wojtek-krysiak Sep 29, 2020
25bc68f
Merge branch 'beta' of github.com:SoftwareBrothers/admin-bro-firebase…
wojtek-krysiak Sep 29, 2020
3ace706
chore(release): 3.1.0-beta.3 [skip ci]
semantic-release-bot Sep 29, 2020
72795eb
fix: export all types
wojtek-krysiak Sep 29, 2020
e1f6cc6
Merge branch 'beta' of github.com:SoftwareBrothers/admin-bro-firebase…
wojtek-krysiak Sep 29, 2020
801e852
chore(release): 3.1.0-beta.4 [skip ci]
semantic-release-bot Sep 29, 2020
fe071a4
fix: correct function invocation
wojtek-krysiak Sep 29, 2020
3d5da3e
Merge branch 'beta' of github.com:SoftwareBrothers/admin-bro-firebase…
wojtek-krysiak Sep 29, 2020
9f94921
chore(release): 3.1.0-beta.5 [skip ci]
semantic-release-bot Sep 29, 2020
ad22ab2
fix: wrong path param
wojtek-krysiak Sep 29, 2020
7dfbb93
Merge branch 'beta' of github.com:SoftwareBrothers/admin-bro-firebase…
wojtek-krysiak Sep 29, 2020
6e095eb
chore(release): 3.1.0-beta.6 [skip ci]
semantic-release-bot Sep 29, 2020
99b1b4e
docs: update the documntation
wojtek-krysiak Sep 29, 2020
53b5fa8
Merge branch 'beta' of github.com:SoftwareBrothers/admin-bro-firebase…
wojtek-krysiak Sep 29, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ module.exports = {
"import/extensions": 'off',
"import/prefer-default-export": 'off',
"no-underscore-dangle": 'off',
"import/no-extraneous-dependencies": 'off'
},
overrides: [
{
files: [
'*.spec.ts',
],
rules: {
'mocha/no-mocha-arrows': 'off',
'no-unused-expressions': 'off',
'func-names': 'off',
'prefer-arrow-callback': 'off',
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
run: yarn install
- name: Lint
run: yarn lint
- name: Lint
- name: Test
run: yarn test
- name: Build
run: yarn build
Expand Down
4 changes: 1 addition & 3 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
export * from './types/parse-files'
export * from './types/plugin'
export * from './types/routes'
export * from './types';
58 changes: 47 additions & 11 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ A plugin that allows you to render AdminBro by Firebase Cloud Functions
Before you start make sure you have the firebase app set up
(https://firebase.google.com/docs/functions/get-started):

```bash
```sh
yarn global add firebase-tools
firebase login
firebase init functions
Expand Down Expand Up @@ -99,25 +99,30 @@ and in a minute you will see your app on Google Cloud Functions for Firebase

## Do this even better.

AdminBro serves 4 assets:
AdminBro serves 4 major assets:

- global.bundle.js which contains react, redux, axios etc.
- design-system.bundle.js with AdminBro Design System
- app.bundle.js where the entire AdminBro frontend application resides
- components.bundle.js - this is the place for bundled (with {@link AdminBro.bundle})
custom components (admin.initialize(); creates it in `./adminbro/bundle.js`)

So it means that your function will have to serve these 4 assets every time the user
opens the page with a web browser.
And 2 less important: `logo` and `favicon` which can be changed in the {@link AdminBroOptions}.

So it means that your function will have to serve these all assets every time the user
opens the page with a web browser - meaning more function calls and cold start problems.

You can change that by setting {@link AdminBroOptions.assetsCDN} to bypass serving assets right
from AdminBro.

You can change that by setting {@link AdminBroOptions.assetsCDN}. So before the deployment
you can copy those files to the public directory and host this directory via firebase hosting.
Next point {@link AdminBroOptions.assetsCDN} to the hosting URL and you will save this 3
extra calls to your function.
Before the deployment you can copy those files to the /public directory and host this directory
via firebase hosting. Next point {@link AdminBroOptions.assetsCDN} to the hosting URL and you will
save these extra calls to your function.

First, you will need to add [firebase hosting]{@link https://firebase.google.com/docs/hosting}
to your app and set it up to host files from ./public directory
to your app and set it up to host files from ./public directory.

This is how updated ./bin/bundle.js could look like:
Next, we have to update ./bin/bundle.js to copy assets to the `/public` folder:

```javascript
const AdminBro = require('admin-bro');
Expand Down Expand Up @@ -149,8 +154,39 @@ admin.initialize().then(() => {
})
```

and updated deploy script:
> This script doesn't create a folder so you have to `mkdir` it manually.

Finally updated deploy script:

```sh
"deploy": "yarn bundle && firebase deploy --only functions,hosting"
```

Also, you will have to update your `firebase.json` with information about the hosting page.

## Custom domain

So let's assume that you have a `rootUrl` set to `/` in AdminBro. Your function target name,
(how you name your export) is `app`.

So the root of the page will be: `YOUR-FUNCTION-HOST/app/`.

Depending on the environment, (emulator or an actual firebase domain) `@admin-bro/firebase-functions`
will properly adjust the path, that AdminBro knows where to redirect users
(not to `/` but to `/app`).

In such a case, you don't need to do anything,

But now you are adding a reverse prox, which redirects traffic from `your-domain.com/app` to
`YOUR-FUNCTION-HOST/app`.

And now admin does not know how to build the URL because he thinks that, requests are not namespaces
(not from the firebase domain).

So we have to tell AdminBro that, to the `rootUrl` he has to prepend the `customFunctionPath`.

CustomPropertyPath should be `app` because `path` going from firebase to admin will be `app/` but
admin is waiting for `/` (rootUrl).


`customPropertyPath` is a member of {@link BuildHandlerOptions}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@admin-bro/firebase-functions",
"version": "3.0.1",
"version": "3.1.0-beta.6",
"description": "Firebase plugin for AdminBro",
"main": "index.js",
"types": "index.d.ts",
Expand All @@ -9,6 +9,8 @@
"license": "MIT",
"scripts": {
"build": "tsc",
"dev": "yarn build --watch",
"check:all": "yarn lint && yarn test && yarn build",
"lint": "eslint './src/**/*.ts'",
"test": "mocha -r ts-node/register src/**/*.spec.ts",
"release": "semantic-release"
Expand Down
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './parse-files';
export * from './plugin';
export * from './routes';
export * from './utils';
86 changes: 21 additions & 65 deletions src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,17 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import { Response } from 'firebase-functions';
// eslint-disable-next-line import/no-extraneous-dependencies
import { Request } from 'firebase-functions/lib/providers/https';
import AdminBro, { AdminBroOptions, CurrentAdmin } from 'admin-bro';
import AdminBro, { AdminBroOptions } from 'admin-bro';
import { resolve } from 'path';
import { match } from 'path-to-regexp';
import cookie from 'cookie';
import jwt from 'jsonwebtoken';
import { computeRootPaths } from './utils/compute-root-paths';
import { prepareComparePath } from './utils/prepare-compare-path';

import { AppRoutes, AppAssets } from './routes';
import { parseFiles, cleanFiles, File } from './parse-files';

/**
* @alias BuildHandlerReturn
*
* @memberof module:@admin-bro/firebase-functions
*/
export type BuildHandlerReturn = ((req: Request, resp: Response) => Promise<void>)

/**
* @alias BuildHandlerOptions
*
* @memberof module:@admin-bro/firebase-functions
*/
export type BuildHandlerOptions = {
/** Region where function is deployed */
region: string;
/**
* Optional before `async` hook which can be used to initialize database.
* if it returns something it will be used as AdminBroOptions.
*/
before?: () => Promise<AdminBroOptions | undefined | null> | AdminBroOptions | undefined | null;
/**
* custom authentication option. If given AdminBro will render login page
*/
auth?: {
/**
* secret which is used to encrypt the session cookie
*/
secret: string;
/**
* authenticate function
*/
authenticate: (
email: string,
password: string
) => Promise<CurrentAdmin | null> | CurrentAdmin | null;

/**
* For how long cookie session will be stored.
* Default to 900000 (15 minutes).
* In milliseconds.
*/
maxAge?: number;
};
}
import { AppRoutes, AppAssets } from './utils/routes';
import { parseFiles, cleanFiles, File } from './utils/parse-files';
import { BuildHandlerOptions, BuildHandlerReturn } from './utils/build-handler-options';

const DEFAULT_MAX_AGE = 900000;

Expand Down Expand Up @@ -89,13 +46,9 @@ export const buildHandler = (
): BuildHandlerReturn => {
let admin: AdminBro;

let rootPath: string;
let loginPath: string;
let logoutPath: string;

const domain = process.env.FUNCTIONS_EMULATOR
? `${process.env.GCLOUD_PROJECT}/${options.region}/${process.env.FUNCTION_TARGET}`
: process.env.FUNCTION_TARGET;
let rootPath: string;

return async (req, res): Promise<void> => {
if (!admin) {
Expand All @@ -105,17 +58,20 @@ export const buildHandler = (
}

admin = new AdminBro(beforeResult || adminOptions);
({ rootPath, loginPath, logoutPath } = admin.options);

admin.options.rootPath = `/${domain}${rootPath}`;
admin.options.loginPath = `/${domain}${loginPath}`;
admin.options.logoutPath = `/${domain}${logoutPath}`;
// we have to store original values
({ loginPath, logoutPath, rootPath } = admin.options);

Object.assign(admin.options, computeRootPaths(admin.options, {
project: process.env.GCLOUD_PROJECT as string,
region: options.region,
target: process.env.FUNCTION_TARGET as string,
emulator: process.env.FUNCTIONS_EMULATOR,
}, options.customFunctionPath));
}

const { method, query } = req;
let path = req.originalUrl.replace(admin.options.rootPath, '');
if (!path.startsWith('/')) { path = `/${path}`; }

const path = prepareComparePath(req.path, rootPath, options.customFunctionPath);

const cookies = cookie.parse(req.headers.cookie || '');
const token = cookies && cookies.__session;

Expand All @@ -125,7 +81,7 @@ export const buildHandler = (

if (options.auth) {
const matchLogin = match(loginPath);
if (matchLogin(req.path)) {
if (matchLogin(path)) {
if (method === 'GET') {
res.send(await admin.renderLogin({
action: admin.options.loginPath,
Expand All @@ -151,7 +107,7 @@ export const buildHandler = (
}

const matchLogout = match(logoutPath);
if (matchLogout(req.path)) {
if (matchLogout(path)) {
res.cookie('__session', '');
res.redirect(admin.options.loginPath);
return;
Expand Down Expand Up @@ -199,7 +155,7 @@ export const buildHandler = (
}

const asset = AppAssets.find((r) => r.match(path));
if (asset) {
if (asset && !admin.options.assetsCDN) {
res.status(200).sendFile(resolve(asset.src));
return;
}
Expand Down
57 changes: 57 additions & 0 deletions src/utils/build-handler-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Response } from 'firebase-functions';
import { Request } from 'firebase-functions/lib/providers/https';
import { AdminBroOptions, CurrentAdmin } from 'admin-bro';

/**
* @alias BuildHandlerReturn
*
* @memberof module:@admin-bro/firebase-functions
*/

export type BuildHandlerReturn = ((req: Request, resp: Response) => Promise<void>);
/**
* @alias BuildHandlerOptions
*
* @memberof module:@admin-bro/firebase-functions
*/

export type BuildHandlerOptions = {
/** Region where function is deployed */
region: string;
/**
* Optional before `async` hook which can be used to initialize database.
* if it returns something it will be used as AdminBroOptions.
*/
before?: () => Promise<AdminBroOptions | undefined | null> | AdminBroOptions | undefined | null;
/**
* custom authentication option. If given AdminBro will render login page
*/
auth?: {
/**
* secret which is used to encrypt the session cookie
*/
secret: string;
/**
* authenticate function
*/
authenticate: (
email: string,
password: string
) => Promise<CurrentAdmin | null> | CurrentAdmin | null;

/**
* For how long cookie session will be stored.
* Default to 900000 (15 minutes).
* In milliseconds.
*/
maxAge?: number;
};

/**
* Adjustment path when you proxy the domain. Use case: you proxy `your-domain.com/app` to admin
* firebase function with admin having `rootUrl=='/'` then you have to tell admin that all `paths`
* he receives are `/app` namespaced so he can properly resolve them. In such case
* `customFunctionPath` should be set to `app` because proxy path - rootUrl === 'app'.
*/
customFunctionPath?: string;
};
Loading