diff --git a/.changeset/config.json b/.changeset/config.json index 27db2bd13ab..8a7e96564c6 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -15,7 +15,9 @@ "firebase-messaging-integration-test", "firebase-compat-interop-test", "firebase-compat-typings-test", + "@firebase/app-compat", "@firebase/app-exp", + "@firebase/app-check-compat", "@firebase/app-check-exp", "@firebase/analytics-compat", "@firebase/analytics-exp", @@ -32,7 +34,6 @@ "@firebase/remote-config-exp", "@firebase/remote-config-compat", "firebase-exp", - "@firebase/app-compat", "@firebase/changelog-generator", "firebase-size-analysis" ], diff --git a/packages-exp/app-check-compat/.eslintrc.js b/packages-exp/app-check-compat/.eslintrc.js new file mode 100644 index 00000000000..468a2ee6a34 --- /dev/null +++ b/packages-exp/app-check-compat/.eslintrc.js @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const path = require('path'); + +module.exports = { + 'extends': '../../config/.eslintrc.js', + 'parserOptions': { + 'project': 'tsconfig.json', + 'tsconfigRootDir': __dirname + }, + rules: { + 'import/no-extraneous-dependencies': [ + 'error', + { + 'packageDir': [path.resolve(__dirname, '../../'), __dirname] + } + ] + } +}; diff --git a/packages-exp/app-check-compat/README.md b/packages-exp/app-check-compat/README.md new file mode 100644 index 00000000000..fab8226da30 --- /dev/null +++ b/packages-exp/app-check-compat/README.md @@ -0,0 +1,5 @@ +# @firebase/app-check-compat + +This is the Firebase App Check component (compat version) of the Firebase JS SDK. + +**This package is not intended for direct usage, and should only be used via the officially supported [`firebase`](https://www.npmjs.com/package/firebase) package.** diff --git a/packages-exp/app-check-compat/karma.conf.js b/packages-exp/app-check-compat/karma.conf.js new file mode 100644 index 00000000000..324777bcd54 --- /dev/null +++ b/packages-exp/app-check-compat/karma.conf.js @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// eslint-disable-next-line @typescript-eslint/no-require-imports +const karmaBase = require('../../config/karma.base'); + +const files = [`**/*.test.ts`]; + +module.exports = function (config) { + config.set({ + ...karmaBase, + files, + preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] }, + frameworks: ['mocha'] + }); +}; + +module.exports.files = files; diff --git a/packages-exp/app-check-compat/package.json b/packages-exp/app-check-compat/package.json new file mode 100644 index 00000000000..2dc071e49e1 --- /dev/null +++ b/packages-exp/app-check-compat/package.json @@ -0,0 +1,60 @@ +{ + "name": "@firebase/app-check-compat", + "version": "0.0.900", + "private": true, + "description": "A compat App Check package for new firebase packages", + "author": "Firebase (https://firebase.google.com/)", + "main": "dist/index.cjs.js", + "browser": "dist/index.esm2017.js", + "module": "dist/index.esm2017.js", + "files": [ + "dist" + ], + "scripts": { + "lint": "eslint -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "lint:fix": "eslint --fix -c .eslintrc.js '**/*.ts' --ignore-path '../../.gitignore'", + "build": "rollup -c", + "build:release": "rollup -c rollup.config.release.js", + "build:deps": "lerna run --scope @firebase/app-check-compat --include-dependencies build", + "dev": "rollup -c -w", + "test": "run-p lint test:browser", + "test:ci": "node ../../scripts/run_tests_in_ci.js -s test:browser", + "test:browser": "karma start --single-run --nocache" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + }, + "dependencies": { + "@firebase/app-check-exp": "0.0.900", + "@firebase/logger": "0.2.6", + "@firebase/util": "1.1.0", + "@firebase/component": "0.5.3", + "tslib": "^2.1.0" + }, + "license": "Apache-2.0", + "devDependencies": { + "@firebase/app-compat": "0.0.900", + "rollup": "2.33.2", + "@rollup/plugin-commonjs": "17.1.0", + "@rollup/plugin-json": "4.1.0", + "@rollup/plugin-node-resolve": "11.2.0", + "rollup-plugin-typescript2": "0.29.0", + "typescript": "4.2.2" + }, + "repository": { + "directory": "packages/app-check", + "type": "git", + "url": "https://github.com/firebase/firebase-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/firebase/firebase-js-sdk/issues" + }, + "typings": "dist/src/index.d.ts", + "nyc": { + "extension": [ + ".ts" + ], + "reportDir": "./coverage/node" + }, + "esm5": "dist/index.esm.js" +} \ No newline at end of file diff --git a/packages-exp/app-check-compat/rollup.config.js b/packages-exp/app-check-compat/rollup.config.js new file mode 100644 index 00000000000..9b89b7772af --- /dev/null +++ b/packages-exp/app-check-compat/rollup.config.js @@ -0,0 +1,58 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import json from '@rollup/plugin-json'; +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript + }), + json() +]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + } + }), + json({ preferConst: true }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/app-check-compat/rollup.config.release.js b/packages-exp/app-check-compat/rollup.config.release.js new file mode 100644 index 00000000000..1896d7036f5 --- /dev/null +++ b/packages-exp/app-check-compat/rollup.config.release.js @@ -0,0 +1,67 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import typescriptPlugin from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import json from '@rollup/plugin-json'; +import { importPathTransformer } from '../../scripts/exp/ts-transform-import-path'; +import { es2017BuildsNoPlugin, es5BuildsNoPlugin } from './rollup.shared'; + +/** + * ES5 Builds + */ +const es5BuildPlugins = [ + typescriptPlugin({ + typescript, + clean: true, + abortOnError: false, + transformers: [importPathTransformer] + }), + json() +]; + +const es5Builds = es5BuildsNoPlugin.map(build => ({ + ...build, + plugins: es5BuildPlugins +})); + +/** + * ES2017 Builds + */ +const es2017BuildPlugins = [ + typescriptPlugin({ + typescript, + tsconfigOverride: { + compilerOptions: { + target: 'es2017' + } + }, + abortOnError: false, + clean: true, + transformers: [importPathTransformer] + }), + json({ + preferConst: true + }) +]; + +const es2017Builds = es2017BuildsNoPlugin.map(build => ({ + ...build, + plugins: es2017BuildPlugins +})); + +export default [...es5Builds, ...es2017Builds]; diff --git a/packages-exp/app-check-compat/rollup.shared.js b/packages-exp/app-check-compat/rollup.shared.js new file mode 100644 index 00000000000..24bbc5a28c7 --- /dev/null +++ b/packages-exp/app-check-compat/rollup.shared.js @@ -0,0 +1,54 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import pkg from './package.json'; + +const deps = [ + ...Object.keys(Object.assign({}, pkg.peerDependencies, pkg.dependencies)), + '@firebase/app' +]; + +export const es5BuildsNoPlugin = [ + /** + * Browser Builds + */ + { + input: 'src/index.ts', + output: [ + { file: pkg.main, format: 'cjs', sourcemap: true }, + { file: pkg.esm5, format: 'es', sourcemap: true } + ], + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; + +/** + * ES2017 Builds + */ +export const es2017BuildsNoPlugin = [ + { + /** + * Browser Build + */ + input: 'src/index.ts', + output: { + file: pkg.browser, + format: 'es', + sourcemap: true + }, + external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`)) + } +]; diff --git a/packages-exp/app-check-compat/src/index.ts b/packages-exp/app-check-compat/src/index.ts new file mode 100644 index 00000000000..d8aad99e612 --- /dev/null +++ b/packages-exp/app-check-compat/src/index.ts @@ -0,0 +1,69 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import firebase, { + _FirebaseNamespace, + FirebaseApp +} from '@firebase/app-compat'; +import { name, version } from '../package.json'; +import { + Component, + ComponentContainer, + ComponentType, + InstanceFactory +} from '@firebase/component'; +import { AppCheckService } from './service'; +import { FirebaseAppCheck } from '../../../packages/app-check-types'; + +declare module '@firebase/component' { + interface NameServiceMapping { + 'appCheck-compat': AppCheckService; + } +} + +const factory: InstanceFactory<'appCheck-compat'> = ( + container: ComponentContainer +) => { + // Dependencies + const app = container.getProvider('app-compat').getImmediate(); + const appCheckServiceExp = container + .getProvider('app-check-exp') + .getImmediate(); + + return new AppCheckService(app as FirebaseApp, appCheckServiceExp); +}; + +export function registerAppCheck(): void { + (firebase as _FirebaseNamespace).INTERNAL.registerComponent( + new Component('appCheck-compat', factory, ComponentType.PUBLIC) + ); +} + +registerAppCheck(); +firebase.registerVersion(name, version); + +/** + * Define extension behavior of `registerAppCheck` + */ +declare module '@firebase/app-compat' { + interface FirebaseNamespace { + appCheck(app?: FirebaseApp): FirebaseAppCheck; + } + interface FirebaseApp { + appCheck(): FirebaseAppCheck; + } +} diff --git a/packages-exp/app-check-compat/src/service.test.ts b/packages-exp/app-check-compat/src/service.test.ts new file mode 100644 index 00000000000..fe6cf762426 --- /dev/null +++ b/packages-exp/app-check-compat/src/service.test.ts @@ -0,0 +1,142 @@ +/** + * @license + * Copyright 2017 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, use } from 'chai'; +import { AppCheckService } from './service'; +import { firebase, FirebaseApp } from '@firebase/app-compat'; +import * as appCheckExp from '@firebase/app-check-exp'; +import { stub, match, SinonStub } from 'sinon'; +import * as sinonChai from 'sinon-chai'; +import { CustomProvider, ReCaptchaV3Provider } from '@firebase/app-check-exp'; +import { AppCheckTokenResult } from '../../../packages/app-check-types'; +import { PartialObserver } from '../../../packages/util/dist'; + +use(sinonChai); + +function createTestService(app: FirebaseApp): AppCheckService { + return new AppCheckService( + app, + appCheckExp.initializeAppCheck(app, { + provider: new ReCaptchaV3Provider('fake-site-key') + }) + ); +} + +describe('Firebase App Check > Service', () => { + let app: FirebaseApp; + let service: AppCheckService; + + beforeEach(() => { + app = firebase.initializeApp({ + apiKey: '456_LETTERS_AND_1234NUMBERS', + appId: '123lettersand:numbers', + projectId: 'my-project', + messagingSenderId: 'messaging-sender-id' + }); + }); + + afterEach(async () => { + await app.delete(); + }); + + it( + 'activate("string") calls modular initializeAppCheck() with a ' + + 'ReCaptchaV3Provider', + () => { + const initializeAppCheckStub = stub(appCheckExp, 'initializeAppCheck'); + service = new AppCheckService(app, {} as appCheckExp.AppCheck); + service.activate('my_site_key'); + expect(initializeAppCheckStub).to.be.calledWith(app, { + provider: match.instanceOf(ReCaptchaV3Provider), + isTokenAutoRefreshEnabled: undefined + }); + initializeAppCheckStub.restore(); + } + ); + + it( + 'activate(CustomProvider) calls modular initializeAppCheck() with' + + ' a CustomProvider', + () => { + const initializeAppCheckStub = stub(appCheckExp, 'initializeAppCheck'); + service = new AppCheckService(app, {} as appCheckExp.AppCheck); + const customGetTokenStub = stub(); + service.activate({ + getToken: customGetTokenStub + }); + expect(initializeAppCheckStub).to.be.calledWith(app, { + provider: match + .instanceOf(CustomProvider) + .and( + match.hasNested( + '_customProviderOptions.getToken', + customGetTokenStub + ) + ), + isTokenAutoRefreshEnabled: undefined + }); + initializeAppCheckStub.restore(); + } + ); + + it('setTokenAutoRefreshEnabled() calls modular setTokenAutoRefreshEnabled()', () => { + const setTokenAutoRefreshEnabledStub: SinonStub = stub( + appCheckExp, + 'setTokenAutoRefreshEnabled' + ); + service = createTestService(app); + service.setTokenAutoRefreshEnabled(true); + expect(setTokenAutoRefreshEnabledStub).to.be.calledWith( + service._delegate, + true + ); + setTokenAutoRefreshEnabledStub.restore(); + }); + + it('getToken() calls modular getToken()', async () => { + service = createTestService(app); + const getTokenStub = stub(appCheckExp, 'getToken'); + await service.getToken(true); + expect(getTokenStub).to.be.calledWith(service._delegate, true); + getTokenStub.restore(); + }); + + it('onTokenChanged() calls modular onTokenChanged() with observer', () => { + const onTokenChangedStub = stub(appCheckExp, 'onTokenChanged'); + service = createTestService(app); + const observer: PartialObserver = { + next: stub(), + error: stub() + }; + service.onTokenChanged(observer); + expect(onTokenChangedStub).to.be.calledWith(service._delegate, observer); + onTokenChangedStub.restore(); + }); + + it('onTokenChanged() calls modular onTokenChanged() with next/error fns', () => { + const onTokenChangedStub = stub(appCheckExp, 'onTokenChanged'); + service = createTestService(app); + const nextFn = stub(); + const errorFn = stub(); + service.onTokenChanged(nextFn, errorFn); + expect(onTokenChangedStub).to.be.calledWith( + service._delegate, + nextFn, + errorFn + ); + onTokenChangedStub.restore(); + }); +}); diff --git a/packages-exp/app-check-compat/src/service.ts b/packages-exp/app-check-compat/src/service.ts new file mode 100644 index 00000000000..a64e9c67b07 --- /dev/null +++ b/packages-exp/app-check-compat/src/service.ts @@ -0,0 +1,79 @@ +/** + * @license + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + AppCheckProvider, + AppCheckTokenResult, + FirebaseAppCheck +} from '@firebase/app-check-types'; +import { _FirebaseService, FirebaseApp } from '@firebase/app-compat'; +import { + AppCheck as AppCheckServiceExp, + CustomProvider, + initializeAppCheck, + ReCaptchaV3Provider, + setTokenAutoRefreshEnabled as setTokenAutoRefreshEnabledExp, + getToken as getTokenExp, + onTokenChanged as onTokenChangedExp +} from '@firebase/app-check-exp'; +import { PartialObserver, Unsubscribe } from '@firebase/util'; + +export class AppCheckService implements FirebaseAppCheck, _FirebaseService { + constructor( + public app: FirebaseApp, + readonly _delegate: AppCheckServiceExp + ) {} + activate( + siteKeyOrProvider: string | AppCheckProvider, + isTokenAutoRefreshEnabled?: boolean + ): void { + let provider: ReCaptchaV3Provider | CustomProvider; + if (typeof siteKeyOrProvider === 'string') { + provider = new ReCaptchaV3Provider(siteKeyOrProvider); + } else { + provider = new CustomProvider({ getToken: siteKeyOrProvider.getToken }); + } + initializeAppCheck(this.app, { + provider, + isTokenAutoRefreshEnabled + }); + } + setTokenAutoRefreshEnabled(isTokenAutoRefreshEnabled: boolean): void { + setTokenAutoRefreshEnabledExp(this._delegate, isTokenAutoRefreshEnabled); + } + getToken(forceRefresh?: boolean): Promise { + return getTokenExp(this._delegate, forceRefresh); + } + onTokenChanged( + onNextOrObserver: + | PartialObserver + | ((tokenResult: AppCheckTokenResult) => void), + onError?: (error: Error) => void, + onCompletion?: () => void + ): Unsubscribe { + return onTokenChangedExp( + this._delegate, + /** + * Exp onTokenChanged() will handle both overloads but we need + * to specify one to not confuse Typescript. + */ + onNextOrObserver as (tokenResult: AppCheckTokenResult) => void, + onError, + onCompletion + ); + } +} diff --git a/packages-exp/app-check-compat/tsconfig.json b/packages-exp/app-check-compat/tsconfig.json new file mode 100644 index 00000000000..356e7a53b8c --- /dev/null +++ b/packages-exp/app-check-compat/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../config/tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "strict": true + }, + "exclude": ["dist/**/*"] +}