From 7a72f620ea6fb54624e9a47455d4a157c8b152c8 Mon Sep 17 00:00:00 2001 From: Wojciech Date: Tue, 29 Sep 2020 05:15:16 +0200 Subject: [PATCH 01/15] fix: add customFunctionPath fixing custom domains --- .eslintrc.js | 1 + index.d.ts | 4 +- package.json | 2 + src/index.ts | 4 +- src/plugin.ts | 37 ++++--- src/utils/compute-root-paths.spec.ts | 132 +++++++++++++++++++++++++ src/utils/compute-root-paths.ts | 67 +++++++++++++ src/{ => utils}/parse-files.spec.ts | 0 src/{ => utils}/parse-files.ts | 0 src/utils/prepare-compare-path.spec.ts | 19 ++++ src/utils/prepare-compare-path.ts | 26 +++++ src/{ => utils}/routes.ts | 0 12 files changed, 271 insertions(+), 21 deletions(-) create mode 100644 src/utils/compute-root-paths.spec.ts create mode 100644 src/utils/compute-root-paths.ts rename src/{ => utils}/parse-files.spec.ts (100%) rename src/{ => utils}/parse-files.ts (100%) create mode 100644 src/utils/prepare-compare-path.spec.ts create mode 100644 src/utils/prepare-compare-path.ts rename src/{ => utils}/routes.ts (100%) diff --git a/.eslintrc.js b/.eslintrc.js index 02a0df8..3bbe379 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -36,6 +36,7 @@ module.exports = { '*.spec.ts', ], rules: { + 'mocha/no-mocha-arrows': 'off', 'no-unused-expressions': 'off', 'func-names': 'off', 'prefer-arrow-callback': 'off', diff --git a/index.d.ts b/index.d.ts index e5cf755..fcb073f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,3 +1 @@ -export * from './types/parse-files' -export * from './types/plugin' -export * from './types/routes' \ No newline at end of file +export * from './types'; diff --git a/package.json b/package.json index c2fcd33..04c29da 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "license": "MIT", "scripts": { "build": "tsc", + "dev": "yarn build --watch", + "check:all": "yarn lint && yarn test", "lint": "eslint './src/**/*.ts'", "test": "mocha -r ts-node/register src/**/*.spec.ts", "release": "semantic-release" diff --git a/src/index.ts b/src/index.ts index ec8cd66..3430201 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,3 @@ -export * from './parse-files'; +export * from './utils/parse-files'; export * from './plugin'; -export * from './routes'; +export * from './utils/routes'; diff --git a/src/plugin.ts b/src/plugin.ts index 93fe559..abdda04 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -7,9 +7,12 @@ 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'; + +import { AppRoutes, AppAssets } from './utils/routes'; +import { parseFiles, cleanFiles, File } from './utils/parse-files'; /** * @alias BuildHandlerReturn @@ -79,6 +82,8 @@ const DEFAULT_MAX_AGE = 900000; * AdminBro instance * @param {BuildHandlerOptions} options custom options for @admin-bro/firebase-functions * adapter + * @param {string} customFunctionPath "adjustment" path when you proxy domain to a created + * function * @return {BuildHandlerReturn} function which can be passed to firebase * @function * @memberof module:@admin-bro/firebase-functions @@ -86,16 +91,13 @@ const DEFAULT_MAX_AGE = 900000; export const buildHandler = ( adminOptions: AdminBroOptions, options: BuildHandlerOptions, + customFunctionPath?: string, ): 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 => { if (!admin) { @@ -105,17 +107,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, + }), customFunctionPath); } const { method, query } = req; - let path = req.originalUrl.replace(admin.options.rootPath, ''); - if (!path.startsWith('/')) { path = `/${path}`; } - + const path = prepareComparePath(req.path, rootPath); + const cookies = cookie.parse(req.headers.cookie || ''); const token = cookies && cookies.__session; @@ -199,7 +204,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; } diff --git a/src/utils/compute-root-paths.spec.ts b/src/utils/compute-root-paths.spec.ts new file mode 100644 index 0000000..f1289c0 --- /dev/null +++ b/src/utils/compute-root-paths.spec.ts @@ -0,0 +1,132 @@ +import { expect } from 'chai'; +import { + ComputeRootPathEnv, + AdminPathOptions, + computeRootPaths, + getLocalhostPathForEnv, +} from './compute-root-paths'; + +/* eslint-disable mocha/no-mocha-arrows */ +describe('routeMatch', () => { + let env: ComputeRootPathEnv = { + emulator: 'true', + project: 'admin-bro-app', + region: 'us-east-1', + target: 'admin', + }; + let options: AdminPathOptions; + let customFunctionPath: string | undefined; + + beforeEach(() => { + env = { + emulator: 'true', + project: 'admin-bro-app', + region: 'us-east-1', + target: 'admin', + }; + + options = { + rootPath: '/admin', + loginPath: '/admin/login', + logoutPath: '/admin/logout', + }; + customFunctionPath = undefined; + }); + + context('run on localhost', () => { + let functionLocalPath: string; + + beforeEach(() => { + env.emulator = 'true'; + functionLocalPath = getLocalhostPathForEnv(env); + }); + + it('fixes root admin paths that they have function url', () => { + const paths = computeRootPaths(options, env, customFunctionPath); + + expect(paths.rootPath).to.equal(`/${functionLocalPath}/admin`); + expect(paths.loginPath).to.equal(`/${functionLocalPath}/admin/login`); + expect(paths.logoutPath).to.equal(`/${functionLocalPath}/admin/logout`); + }); + + it('it is not affected by customFunctionPath', () => { + const paths = computeRootPaths(options, env, 'customDomain'); + + expect(paths.rootPath).to.equal(`/${functionLocalPath}/admin`); + expect(paths.loginPath).to.equal(`/${functionLocalPath}/admin/login`); + expect(paths.logoutPath).to.equal(`/${functionLocalPath}/admin/logout`); + }); + + it('works when adminRoot is set to "/"', () => { + options = { + rootPath: '/', + loginPath: '/login', + logoutPath: '/logout', + }; + + const paths = computeRootPaths(options, env); + + expect(paths.rootPath).to.equal(`/${functionLocalPath}`); + expect(paths.loginPath).to.equal(`/${functionLocalPath}/login`); + expect(paths.logoutPath).to.equal(`/${functionLocalPath}/logout`); + }); + }); + + context('run on default host without custom domain', () => { + beforeEach(() => { + env.emulator = undefined; + customFunctionPath = undefined; + }); + + it('changes urls by adding the target domain', () => { + const paths = computeRootPaths(options, env); + + expect(paths.rootPath).to.equal(`/${env.target}/admin`); + expect(paths.loginPath).to.equal(`/${env.target}/admin/login`); + expect(paths.logoutPath).to.equal(`/${env.target}/admin/logout`); + }); + + it('works when adminRoot is set to "/"', () => { + options = { + rootPath: '/', + loginPath: '/login', + logoutPath: '/logout', + }; + + const paths = computeRootPaths(options, env); + + expect(paths.rootPath).to.equal(`/${env.target}`); + expect(paths.loginPath).to.equal(`/${env.target}/login`); + expect(paths.logoutPath).to.equal(`/${env.target}/logout`); + }); + }); + + context('run on host with custom domain', () => { + beforeEach(() => { + env.emulator = undefined; + customFunctionPath = 'app'; + }); + + it('changes urls by adding the target domain', () => { + const paths = computeRootPaths(options, env, customFunctionPath); + + expect(paths.rootPath).to.equal(`/${customFunctionPath}/admin`); + expect(paths.loginPath).to.equal(`/${customFunctionPath}/admin/login`); + expect(paths.logoutPath).to.equal(`/${customFunctionPath}/admin/logout`); + }); + + it('works when adminRoot is set to "/"', () => { + options = { + rootPath: '/', + loginPath: '/login', + logoutPath: '/logout', + }; + + const paths = computeRootPaths(options, env, customFunctionPath); + + expect(paths.rootPath).to.equal(`/${customFunctionPath}`); + expect(paths.loginPath).to.equal(`/${customFunctionPath}/login`); + expect(paths.logoutPath).to.equal(`/${customFunctionPath}/logout`); + }); + }); +}); diff --git a/src/utils/compute-root-paths.ts b/src/utils/compute-root-paths.ts new file mode 100644 index 0000000..c59f1d8 --- /dev/null +++ b/src/utils/compute-root-paths.ts @@ -0,0 +1,67 @@ +import path from 'path'; +import { AdminBroOptionsWithDefault, Router } from 'admin-bro'; +/* eslint-disable no-param-reassign */ + +export type ComputeRootPathEnv = { + project: string; + region: string; + target: string; + emulator?: string; +} + +export type ComputedPaths = { + rootPath: string; + loginPath: string; + logoutPath: string; +} + +export type AdminPathOptions = Pick + +export const getLocalhostPathForEnv = (env: ComputeRootPathEnv): string => ( + `${env.project}/${env.region}/${env.target}` +); + +const joinPaths = (...paths: Array): string => { + // replace fixes windows paths + const targetPath = path.join('/', ...paths).replace(/\\/g, '/'); + if (targetPath.endsWith('/')) { + return targetPath.slice(0, -1); + } + return targetPath; +}; + +/** + * Function which takes admin bro options and fix paths depending on the environment where it is + * hosted. + * + * @param {AdminBroOptions} options options will can be mutated + */ +export const computeRootPaths = ( + options: AdminPathOptions, + env: ComputeRootPathEnv, + customFunctionPath?: string, +): ComputedPaths => { + let firebaseRootPath: string; + + if (env.emulator) { + firebaseRootPath = getLocalhostPathForEnv(env); + } else { + firebaseRootPath = customFunctionPath || env.target; + } + + const { rootPath, loginPath, logoutPath } = options; + + return { + rootPath: joinPaths('/', firebaseRootPath, rootPath), + loginPath: joinPaths('/', firebaseRootPath, loginPath), + logoutPath: joinPaths('/', firebaseRootPath, logoutPath), + }; +}; + +export type RouteMatchReturn = { + isLogin: boolean; + isLogout: boolean; + route: (typeof Router)['routes'][number] | null; + asset: (typeof Router)['assets'][number] | null; + // asset?: +} diff --git a/src/parse-files.spec.ts b/src/utils/parse-files.spec.ts similarity index 100% rename from src/parse-files.spec.ts rename to src/utils/parse-files.spec.ts diff --git a/src/parse-files.ts b/src/utils/parse-files.ts similarity index 100% rename from src/parse-files.ts rename to src/utils/parse-files.ts diff --git a/src/utils/prepare-compare-path.spec.ts b/src/utils/prepare-compare-path.spec.ts new file mode 100644 index 0000000..4c0d576 --- /dev/null +++ b/src/utils/prepare-compare-path.spec.ts @@ -0,0 +1,19 @@ +import { expect } from 'chai'; + +import { prepareComparePath } from './prepare-compare-path'; + +describe('prepareComparePath', () => { + it('strips path for standard admin path', () => { + const firebasePath = '/admin/login'; + const adminOriginalRootPath = '/admin'; + + expect(prepareComparePath(firebasePath, adminOriginalRootPath)).to.eq('/login'); + }); + + it('strips path admin path as "/"', () => { + const firebasePath = '/login'; + const adminOriginalRootPath = '/'; + + expect(prepareComparePath(firebasePath, adminOriginalRootPath)).to.eq('/login'); + }); +}); diff --git a/src/utils/prepare-compare-path.ts b/src/utils/prepare-compare-path.ts new file mode 100644 index 0000000..a86856d --- /dev/null +++ b/src/utils/prepare-compare-path.ts @@ -0,0 +1,26 @@ +/** + * When request hits the server it contains `req.path` equals to what was written in the URL, after + * the "function prefix" like this. + * + * ```json + * {"path": "/admin/api/resources/User/records/123/show"} + * ``` + * + * But when we compare path against particular route, we are comparing just the part after the + * {@link AdminBroOptions.rootPath} defined by the user. + * So this part: `/api/resources/User/records/123/show` is what interest us. + * + * This function takes one and converts it to another. + * + * + * @param firebasePath + * @param adminOriginalRootPath + */ +export const prepareComparePath = ( + firebasePath: string, + adminOriginalRootPath: string, +): string => { + let parsedPath = firebasePath.replace(adminOriginalRootPath, ''); + if (!parsedPath.startsWith('/')) { parsedPath = `/${parsedPath}`; } + return parsedPath; +}; diff --git a/src/routes.ts b/src/utils/routes.ts similarity index 100% rename from src/routes.ts rename to src/utils/routes.ts From 5b68c3d416ec251204e5396054cace7077852024 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 29 Sep 2020 03:23:18 +0000 Subject: [PATCH 02/15] chore(release): 3.0.2-beta.1 [skip ci] ## [3.0.2-beta.1](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/compare/v3.0.1...v3.0.2-beta.1) (2020-09-29) ### Bug Fixes * add customFunctionPath fixing custom domains ([7a72f62](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/commit/7a72f620ea6fb54624e9a47455d4a157c8b152c8)) * case when rootPath equals '/' ([c55850d](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/commit/c55850d4c6f34dc132aa9e8758492e54c9efbec9)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 04c29da..9b45270 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@admin-bro/firebase-functions", - "version": "3.0.1", + "version": "3.0.2-beta.1", "description": "Firebase plugin for AdminBro", "main": "index.js", "types": "index.d.ts", From 16b196991fb46e076455cc4e16ffce27d5c800bc Mon Sep 17 00:00:00 2001 From: Wojciech Date: Tue, 29 Sep 2020 05:25:55 +0200 Subject: [PATCH 03/15] feat: trigger build From c8ae0c5c422fa9364f7109c78fb76838b05f0214 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 29 Sep 2020 03:27:38 +0000 Subject: [PATCH 04/15] chore(release): 3.1.0-beta.1 [skip ci] # [3.1.0-beta.1](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/compare/v3.0.1...v3.1.0-beta.1) (2020-09-29) ### Bug Fixes * add customFunctionPath fixing custom domains ([7a72f62](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/commit/7a72f620ea6fb54624e9a47455d4a157c8b152c8)) * case when rootPath equals '/' ([c55850d](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/commit/c55850d4c6f34dc132aa9e8758492e54c9efbec9)) ### Features * trigger build ([16b1969](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/commit/16b196991fb46e076455cc4e16ffce27d5c800bc)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9b45270..27a4d31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@admin-bro/firebase-functions", - "version": "3.0.2-beta.1", + "version": "3.1.0-beta.1", "description": "Firebase plugin for AdminBro", "main": "index.js", "types": "index.d.ts", From 91989ea2eae260f126240327a3f1272b33c19f37 Mon Sep 17 00:00:00 2001 From: Wojciech Date: Tue, 29 Sep 2020 05:53:57 +0200 Subject: [PATCH 05/15] fix: adjust path according to the custom domain --- .github/workflows/push.yml | 2 +- src/plugin.ts | 2 +- src/utils/prepare-compare-path.spec.ts | 33 ++++++++++++++++++++++---- src/utils/prepare-compare-path.ts | 9 ++++++- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 95ad55b..51c6715 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -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 diff --git a/src/plugin.ts b/src/plugin.ts index abdda04..153a362 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -119,7 +119,7 @@ export const buildHandler = ( } const { method, query } = req; - const path = prepareComparePath(req.path, rootPath); + const path = prepareComparePath(req.path, rootPath, customFunctionPath); const cookies = cookie.parse(req.headers.cookie || ''); const token = cookies && cookies.__session; diff --git a/src/utils/prepare-compare-path.spec.ts b/src/utils/prepare-compare-path.spec.ts index 4c0d576..9dbacdd 100644 --- a/src/utils/prepare-compare-path.spec.ts +++ b/src/utils/prepare-compare-path.spec.ts @@ -3,17 +3,42 @@ import { expect } from 'chai'; import { prepareComparePath } from './prepare-compare-path'; describe('prepareComparePath', () => { + let firebasePath: string; + let adminOriginalRootPath: string; + let customFunctionPath: string; + it('strips path for standard admin path', () => { - const firebasePath = '/admin/login'; - const adminOriginalRootPath = '/admin'; + firebasePath = '/admin/login'; + adminOriginalRootPath = '/admin'; expect(prepareComparePath(firebasePath, adminOriginalRootPath)).to.eq('/login'); }); it('strips path admin path as "/"', () => { - const firebasePath = '/login'; - const adminOriginalRootPath = '/'; + firebasePath = '/login'; + adminOriginalRootPath = '/'; expect(prepareComparePath(firebasePath, adminOriginalRootPath)).to.eq('/login'); }); + + context('custom domain has been set with the proxy /debugFnc to the app', () => { + beforeEach('', () => { + firebasePath = '/debugFnc/login'; + adminOriginalRootPath = '/'; + customFunctionPath = 'debugFnc'; + }); + + it('returns correct path', () => { + const path = prepareComparePath(firebasePath, adminOriginalRootPath, customFunctionPath); + + expect(path).to.eq('/login'); + }); + + it('returns correct path for customPath containing dash', () => { + customFunctionPath = '/debugFnc'; + const path = prepareComparePath(firebasePath, adminOriginalRootPath, customFunctionPath); + + expect(path).to.eq('/login'); + }); + }); }); diff --git a/src/utils/prepare-compare-path.ts b/src/utils/prepare-compare-path.ts index a86856d..8278de1 100644 --- a/src/utils/prepare-compare-path.ts +++ b/src/utils/prepare-compare-path.ts @@ -15,12 +15,19 @@ * * @param firebasePath * @param adminOriginalRootPath + * @param customFunctionPath */ export const prepareComparePath = ( firebasePath: string, adminOriginalRootPath: string, + customFunctionPath?: string, ): string => { - let parsedPath = firebasePath.replace(adminOriginalRootPath, ''); + let parsedPath = firebasePath; + if (customFunctionPath) { + parsedPath = parsedPath.replace(customFunctionPath, ''); + } + parsedPath = parsedPath.replace(adminOriginalRootPath, ''); + if (!parsedPath.startsWith('/')) { parsedPath = `/${parsedPath}`; } return parsedPath; }; From b1ed1ce5ba14032a4a6a4b4cb5e0cbb90ae53a69 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 29 Sep 2020 03:55:22 +0000 Subject: [PATCH 06/15] chore(release): 3.1.0-beta.2 [skip ci] # [3.1.0-beta.2](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/compare/v3.1.0-beta.1...v3.1.0-beta.2) (2020-09-29) ### Bug Fixes * adjust path according to the custom domain ([91989ea](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/commit/91989ea2eae260f126240327a3f1272b33c19f37)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 27a4d31..69bc5d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@admin-bro/firebase-functions", - "version": "3.1.0-beta.1", + "version": "3.1.0-beta.2", "description": "Firebase plugin for AdminBro", "main": "index.js", "types": "index.d.ts", From 9825ea9a757f09abe8c6cde132e6b5ad2dd461b7 Mon Sep 17 00:00:00 2001 From: Wojciech Date: Tue, 29 Sep 2020 06:06:03 +0200 Subject: [PATCH 07/15] fix: move customFunctionPath to the options --- package.json | 2 +- src/plugin.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 27a4d31..be42d99 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "scripts": { "build": "tsc", "dev": "yarn build --watch", - "check:all": "yarn lint && yarn test", + "check:all": "yarn lint && yarn test && yarn build", "lint": "eslint './src/**/*.ts'", "test": "mocha -r ts-node/register src/**/*.spec.ts", "release": "semantic-release" diff --git a/src/plugin.ts b/src/plugin.ts index 153a362..384b543 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -57,6 +57,11 @@ export type BuildHandlerOptions = { */ maxAge?: number; }; + + /** + * Adjustment path when us use a proxy + */ + customFunctionPath?: string; } const DEFAULT_MAX_AGE = 900000; @@ -91,7 +96,6 @@ const DEFAULT_MAX_AGE = 900000; export const buildHandler = ( adminOptions: AdminBroOptions, options: BuildHandlerOptions, - customFunctionPath?: string, ): BuildHandlerReturn => { let admin: AdminBro; @@ -115,11 +119,11 @@ export const buildHandler = ( region: options.region, target: process.env.FUNCTION_TARGET as string, emulator: process.env.FUNCTIONS_EMULATOR, - }), customFunctionPath); + }), options.customFunctionPath); } const { method, query } = req; - const path = prepareComparePath(req.path, rootPath, customFunctionPath); + const path = prepareComparePath(req.path, rootPath, options.customFunctionPath); const cookies = cookie.parse(req.headers.cookie || ''); const token = cookies && cookies.__session; From 3ace70675c752c5688fe5d310c8cf2f9a49df7ea Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 29 Sep 2020 04:07:35 +0000 Subject: [PATCH 08/15] chore(release): 3.1.0-beta.3 [skip ci] # [3.1.0-beta.3](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/compare/v3.1.0-beta.2...v3.1.0-beta.3) (2020-09-29) ### Bug Fixes * move customFunctionPath to the options ([9825ea9](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/commit/9825ea9a757f09abe8c6cde132e6b5ad2dd461b7)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f146509..69d1be8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@admin-bro/firebase-functions", - "version": "3.1.0-beta.2", + "version": "3.1.0-beta.3", "description": "Firebase plugin for AdminBro", "main": "index.js", "types": "index.d.ts", From 72795ebc8ed11d995a1e2f8693652089c6ee241f Mon Sep 17 00:00:00 2001 From: Wojciech Date: Tue, 29 Sep 2020 06:20:11 +0200 Subject: [PATCH 09/15] fix: export all types --- src/index.ts | 3 +-- src/utils/index.ts | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 src/utils/index.ts diff --git a/src/index.ts b/src/index.ts index 3430201..4f38329 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,2 @@ -export * from './utils/parse-files'; export * from './plugin'; -export * from './utils/routes'; +export * from './utils'; diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..ed1f663 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,4 @@ +export * from './compute-root-paths'; +export * from './parse-files'; +export * from './routes'; +export * from './prepare-compare-path'; From 801e8523f69966834e2c1caacabb4ca6a6f82396 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 29 Sep 2020 04:21:40 +0000 Subject: [PATCH 10/15] chore(release): 3.1.0-beta.4 [skip ci] # [3.1.0-beta.4](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/compare/v3.1.0-beta.3...v3.1.0-beta.4) (2020-09-29) ### Bug Fixes * export all types ([72795eb](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/commit/72795ebc8ed11d995a1e2f8693652089c6ee241f)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 69d1be8..6ac0898 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@admin-bro/firebase-functions", - "version": "3.1.0-beta.3", + "version": "3.1.0-beta.4", "description": "Firebase plugin for AdminBro", "main": "index.js", "types": "index.d.ts", From fe071a45b8d82901af525a55659e80cbc3a781d1 Mon Sep 17 00:00:00 2001 From: Wojciech Date: Tue, 29 Sep 2020 10:01:41 +0200 Subject: [PATCH 11/15] fix: correct function invocation --- src/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin.ts b/src/plugin.ts index 384b543..b30ec3e 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -119,7 +119,7 @@ export const buildHandler = ( region: options.region, target: process.env.FUNCTION_TARGET as string, emulator: process.env.FUNCTIONS_EMULATOR, - }), options.customFunctionPath); + }, options.customFunctionPath)); } const { method, query } = req; From 9f949214c2cffee5bce1f7b70fbcdfc9fea91995 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 29 Sep 2020 08:03:07 +0000 Subject: [PATCH 12/15] chore(release): 3.1.0-beta.5 [skip ci] # [3.1.0-beta.5](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/compare/v3.1.0-beta.4...v3.1.0-beta.5) (2020-09-29) ### Bug Fixes * correct function invocation ([fe071a4](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/commit/fe071a45b8d82901af525a55659e80cbc3a781d1)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6ac0898..be50bfc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@admin-bro/firebase-functions", - "version": "3.1.0-beta.4", + "version": "3.1.0-beta.5", "description": "Firebase plugin for AdminBro", "main": "index.js", "types": "index.d.ts", From ad22ab26adca78034f4a08e2c2ea38f7ffd35d87 Mon Sep 17 00:00:00 2001 From: Wojciech Date: Tue, 29 Sep 2020 10:25:25 +0200 Subject: [PATCH 13/15] fix: wrong path param --- src/plugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugin.ts b/src/plugin.ts index b30ec3e..3aa6e9b 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -134,7 +134,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, @@ -160,7 +160,7 @@ export const buildHandler = ( } const matchLogout = match(logoutPath); - if (matchLogout(req.path)) { + if (matchLogout(path)) { res.cookie('__session', ''); res.redirect(admin.options.loginPath); return; From 6e095eba8e9d50aa94f686571cb58613779b8285 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 29 Sep 2020 08:26:52 +0000 Subject: [PATCH 14/15] chore(release): 3.1.0-beta.6 [skip ci] # [3.1.0-beta.6](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/compare/v3.1.0-beta.5...v3.1.0-beta.6) (2020-09-29) ### Bug Fixes * wrong path param ([ad22ab2](https://github.com/SoftwareBrothers/admin-bro-firebase-functions/commit/ad22ab26adca78034f4a08e2c2ea38f7ffd35d87)) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index be50bfc..0bb2679 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@admin-bro/firebase-functions", - "version": "3.1.0-beta.5", + "version": "3.1.0-beta.6", "description": "Firebase plugin for AdminBro", "main": "index.js", "types": "index.d.ts", From 99b1b4e01d3f6fed171d7f205af6d0a986d703a4 Mon Sep 17 00:00:00 2001 From: Wojciech Date: Tue, 29 Sep 2020 20:44:17 +0200 Subject: [PATCH 15/15] docs: update the documntation - add customFunctionPath - extract types to separate file - update readme with customFunctionPath and some styling --- .eslintrc.js | 1 + index.md | 58 ++++++++++++++++++++++++------ src/plugin.ts | 57 ++--------------------------- src/utils/build-handler-options.ts | 57 +++++++++++++++++++++++++++++ src/utils/compute-root-paths.ts | 1 + src/utils/index.ts | 1 + src/utils/prepare-compare-path.ts | 7 ++-- 7 files changed, 111 insertions(+), 71 deletions(-) create mode 100644 src/utils/build-handler-options.ts diff --git a/.eslintrc.js b/.eslintrc.js index 3bbe379..71e9eaf 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -29,6 +29,7 @@ module.exports = { "import/extensions": 'off', "import/prefer-default-export": 'off', "no-underscore-dangle": 'off', + "import/no-extraneous-dependencies": 'off' }, overrides: [ { diff --git a/index.md b/index.md index 6531b62..5d07e70 100644 --- a/index.md +++ b/index.md @@ -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 @@ -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'); @@ -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} \ No newline at end of file diff --git a/src/plugin.ts b/src/plugin.ts index 3aa6e9b..a87ba4c 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,8 +1,6 @@ // 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'; @@ -13,56 +11,7 @@ import { prepareComparePath } from './utils/prepare-compare-path'; import { AppRoutes, AppAssets } from './utils/routes'; import { parseFiles, cleanFiles, File } from './utils/parse-files'; - -/** - * @alias BuildHandlerReturn - * - * @memberof module:@admin-bro/firebase-functions - */ -export type BuildHandlerReturn = ((req: Request, resp: Response) => Promise) - -/** - * @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; - /** - * 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; - - /** - * For how long cookie session will be stored. - * Default to 900000 (15 minutes). - * In milliseconds. - */ - maxAge?: number; - }; - - /** - * Adjustment path when us use a proxy - */ - customFunctionPath?: string; -} +import { BuildHandlerOptions, BuildHandlerReturn } from './utils/build-handler-options'; const DEFAULT_MAX_AGE = 900000; @@ -87,8 +36,6 @@ const DEFAULT_MAX_AGE = 900000; * AdminBro instance * @param {BuildHandlerOptions} options custom options for @admin-bro/firebase-functions * adapter - * @param {string} customFunctionPath "adjustment" path when you proxy domain to a created - * function * @return {BuildHandlerReturn} function which can be passed to firebase * @function * @memberof module:@admin-bro/firebase-functions diff --git a/src/utils/build-handler-options.ts b/src/utils/build-handler-options.ts new file mode 100644 index 0000000..6035ff8 --- /dev/null +++ b/src/utils/build-handler-options.ts @@ -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); +/** + * @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; + /** + * 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; + + /** + * 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; +}; diff --git a/src/utils/compute-root-paths.ts b/src/utils/compute-root-paths.ts index c59f1d8..840e150 100644 --- a/src/utils/compute-root-paths.ts +++ b/src/utils/compute-root-paths.ts @@ -35,6 +35,7 @@ const joinPaths = (...paths: Array): string => { * hosted. * * @param {AdminBroOptions} options options will can be mutated + * @private */ export const computeRootPaths = ( options: AdminPathOptions, diff --git a/src/utils/index.ts b/src/utils/index.ts index ed1f663..25ce938 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -2,3 +2,4 @@ export * from './compute-root-paths'; export * from './parse-files'; export * from './routes'; export * from './prepare-compare-path'; +export * from './build-handler-options'; diff --git a/src/utils/prepare-compare-path.ts b/src/utils/prepare-compare-path.ts index 8278de1..361416b 100644 --- a/src/utils/prepare-compare-path.ts +++ b/src/utils/prepare-compare-path.ts @@ -11,11 +11,8 @@ * So this part: `/api/resources/User/records/123/show` is what interest us. * * This function takes one and converts it to another. - * - * - * @param firebasePath - * @param adminOriginalRootPath - * @param customFunctionPath + + * @private */ export const prepareComparePath = ( firebasePath: string,