From 1dd2035245dc8acbd7443332878bd7aabfefb807 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Thu, 31 Jan 2019 22:22:44 +0000 Subject: [PATCH 001/437] [firebase-release] Removed change log and reset repo after 2.2.0 release --- changelog.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 39a84cc4f..e69de29bb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,2 +0,0 @@ -feature - support for multiple regions on functions by passing extra region strings to functions.region() -fixed - validation and typing improvements From df27316a9e5c8d74f3aedc8fabca066ab9de53c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Mon, 4 Feb 2019 09:10:12 +0100 Subject: [PATCH 002/437] Improve types of the `Change` class (#403) Both `before` and `after` fields are now described as non-optional. --- changelog.txt | 1 + src/cloud-functions.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index e69de29bb..dae44d531 100644 --- a/changelog.txt +++ b/changelog.txt @@ -0,0 +1 @@ +fixed - improved types of the `Change` class to describe both `before` and `after` fields as non-optional diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 426c54bb8..e4873552f 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -90,7 +90,7 @@ export interface EventContext { * to the event, "after" represents the state after the event. */ export class Change { - constructor(public before?: T, public after?: T) {} + constructor(public before: T, public after: T) {} } /** ChangeJson is the JSON format used to construct a Change object. */ From 9a6b14be132f56529fbaf86f99212f5c1c590930 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 12 Feb 2019 11:12:35 -0800 Subject: [PATCH 003/437] Jh expose app (#400) * exposes app * actually expose app * changelog --- changelog.txt | 1 + src/apps.ts | 1 - src/index.ts | 3 +++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index dae44d531..5127b08b8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1 +1,2 @@ +feature - Adds functions.app() api to access the app instance used by functions fixed - improved types of the `Change` class to describe both `before` and `after` fields as non-optional diff --git a/src/apps.ts b/src/apps.ts index 15caff41c..e77497432 100644 --- a/src/apps.ts +++ b/src/apps.ts @@ -24,7 +24,6 @@ import * as _ from 'lodash'; import * as firebase from 'firebase-admin'; import { firebaseConfig } from './config'; -/** @internal */ export function apps(): apps.Apps { if (typeof apps.singleton === 'undefined') { apps.init(); diff --git a/src/index.ts b/src/index.ts index 1e60950ca..051212746 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,6 +24,7 @@ import * as analytics from './providers/analytics'; import * as auth from './providers/auth'; +import * as apps from './apps'; import * as crashlytics from './providers/crashlytics'; import * as database from './providers/database'; import * as firestore from './providers/firestore'; @@ -32,9 +33,11 @@ import * as pubsub from './providers/pubsub'; import * as remoteConfig from './providers/remoteConfig'; import * as storage from './providers/storage'; import { firebaseConfig } from './config'; +var app = apps.apps(); export { analytics, + app, auth, crashlytics, database, From ac94e4e984a03ab611d1cdc8cd09079969418a7b Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 12 Feb 2019 14:56:02 -0800 Subject: [PATCH 004/437] removing internal tags so integration tests run (#407) --- src/apps.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/apps.ts b/src/apps.ts index e77497432..3ddf1ea2e 100644 --- a/src/apps.ts +++ b/src/apps.ts @@ -31,7 +31,6 @@ export function apps(): apps.Apps { return apps.singleton; } -/** @internal */ export namespace apps { /** @internal */ export const garbageCollectionInterval = 2 * 60 * 1000; @@ -57,7 +56,6 @@ export namespace apps { [appName: string]: number; } - /** @internal */ export class Apps { private _refCounter: RefCounter; From 1183c6714a7e95b12126b57a213ffae9cf5f3b68 Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 15 Feb 2019 14:15:35 -0800 Subject: [PATCH 005/437] Toggling off remote config tests, toggling on storage tests, fixing https tests (#410) * toggling on storage tests, off remote config tests * minor changes * changing target of https function based on environment * removing unneeded config * adding clarifying comment --- integration_test/functions/src/index.ts | 52 +++++++++++++------------ 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index 66d637a16..cd6781f03 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -12,8 +12,8 @@ export * from './database-tests'; export * from './auth-tests'; export * from './firestore-tests'; export * from './https-tests'; -export * from './remoteConfig-tests'; -// export * from './storage-tests'; +// export * from './remoteConfig-tests'; +export * from './storage-tests'; const numTests = Object.keys(exports).length; // Assumption: every exported function is its own test. import 'firebase-functions'; // temporary shim until process.env.FIREBASE_CONFIG available natively in GCF(BUG 63586213) @@ -21,12 +21,12 @@ const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG); admin.initializeApp(); // TODO(klimt): Get rid of this once the JS client SDK supports callable triggers. -function callHttpsTrigger(name: string, data: any) { +function callHttpsTrigger(name: string, data: any, baseUrl) { return new Promise((resolve, reject) => { const request = https.request( { method: 'POST', - host: 'us-central1-' + firebaseConfig.projectId + '.cloudfunctions.net', + host: 'us-central1-' + firebaseConfig.projectId + '.' + baseUrl, path: '/' + name, headers: { 'Content-Type': 'application/json', @@ -51,8 +51,10 @@ export const integrationTests: any = functions timeoutSeconds: 540, }) .https.onRequest((req: Request, resp: Response) => { + // We take the base url for our https call (cloudfunctions.net, txckloud.net, etc) from the request + // so that it changes with the environment that the tests are run in + const baseUrl = req.hostname.split('.').slice(1).join('.'); let pubsub: any = require('@google-cloud/pubsub')(); - const testId = admin .database() .ref() @@ -88,33 +90,33 @@ export const integrationTests: any = functions .collection('tests') .doc(testId) .set({ test: testId }), + callHttpsTrigger('callableTests', { foo: 'bar', testId }, baseUrl), // A Remote Config update to trigger the Remote Config tests. - admin.credential - .applicationDefault() - .getAccessToken() - .then(accessToken => { - const options = { - hostname: 'firebaseremoteconfig.googleapis.com', - path: `/v1/projects/${firebaseConfig.projectId}/remoteConfig`, - method: 'PUT', - headers: { - Authorization: 'Bearer ' + accessToken.access_token, - 'Content-Type': 'application/json; UTF-8', - 'Accept-Encoding': 'gzip', - 'If-Match': '*', - }, - }; - const request = https.request(options, resp => {}); - request.write(JSON.stringify({ version: { description: testId } })); - request.end(); - }), + // admin.credential + // .applicationDefault() + // .getAccessToken() + // .then(accessToken => { + // const options = { + // hostname: 'firebaseremoteconfig.googleapis.com', + // path: `/v1/projects/${firebaseConfig.projectId}/remoteConfig`, + // method: 'PUT', + // headers: { + // Authorization: 'Bearer ' + accessToken.access_token, + // 'Content-Type': 'application/json; UTF-8', + // 'Accept-Encoding': 'gzip', + // 'If-Match': '*', + // }, + // }; + // const request = https.request(options, resp => {}); + // request.write(JSON.stringify({ version: { description: testId } })); + // request.end(); + // }), // A storage upload to trigger the Storage tests admin .storage() .bucket() .upload('/tmp/' + testId + '.txt'), // Invoke a callable HTTPS trigger. - callHttpsTrigger('callableTests', { foo: 'bar', testId }), ]) .then(() => { // On test completion, check that all tests pass and reply "PASS", or provide further details. From ea61f78ea61834d87de99471ec26037b64ca9e89 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 26 Feb 2019 11:55:02 -0800 Subject: [PATCH 006/437] extending express.request to include rawBody (#420) --- spec/providers/https.spec.ts | 6 +++--- src/function-builder.ts | 2 +- src/providers/https.ts | 23 +++++++++++++++-------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/spec/providers/https.spec.ts b/spec/providers/https.spec.ts index 966569609..9bd929767 100644 --- a/spec/providers/https.spec.ts +++ b/spec/providers/https.spec.ts @@ -71,7 +71,7 @@ interface RunHandlerResult { * and response are properly converted to their http equivalents. */ interface CallTest { - // An http request, mocking a subset of express.Request. + // An http request, mocking a subset of https.Request. httpRequest: any; // The expected format of the request passed to the handler. @@ -90,7 +90,7 @@ interface CallTest { */ function runHandler( handler: express.Handler, - request: express.Request + request: https.Request ): Promise { return new Promise((resolve, reject) => { // MockResponse mocks an express.Response. @@ -145,7 +145,7 @@ async function runTest(test: CallTest): Promise { expect(response.status).to.equal(test.expectedHttpResponse.status); } -// MockRequest mocks an express.Request. +// MockRequest mocks an https.Request. class MockRequest { public method: 'POST' | 'GET' | 'OPTIONS' = 'POST'; diff --git a/src/function-builder.ts b/src/function-builder.ts index 255c751cc..6c9a5508f 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -152,7 +152,7 @@ export class FunctionBuilder { * same signature as an Express app. */ onRequest: ( - handler: (req: express.Request, resp: express.Response) => void + handler: (req: https.Request, resp: express.Response) => void ) => https._onRequestWithOpts(handler, this.options), /** * Declares a callable method for clients to call using a Firebase SDK. diff --git a/src/providers/https.ts b/src/providers/https.ts index 56254efe1..59da0e85f 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -28,13 +28,20 @@ import { apps } from '../apps'; import { HttpsFunction, optsToTrigger, Runnable } from '../cloud-functions'; import { DeploymentOptions } from '../function-builder'; +/** + * + * + */ +export interface Request extends express.Request { + rawBody: Buffer; +} /** * Handle HTTP requests. * @param handler A function that takes a request and response object, * same signature as an Express app. */ export function onRequest( - handler: (req: express.Request, resp: express.Response) => void + handler: (req: Request, resp: express.Response) => void ) { return _onRequestWithOpts(handler, {}); } @@ -51,11 +58,11 @@ export function onCall( /** @internal */ export function _onRequestWithOpts( - handler: (req: express.Request, resp: express.Response) => void, + handler: (req: Request, resp: express.Response) => void, opts: DeploymentOptions ): HttpsFunction { // lets us add __trigger without altering handler: - let cloudFunction: any = (req: express.Request, res: express.Response) => { + let cloudFunction: any = (req: Request, res: express.Response) => { handler(req, res); }; cloudFunction.__trigger = _.assign(optsToTrigger(opts), { httpsTrigger: {} }); @@ -267,11 +274,11 @@ export interface CallableContext { /** * The raw request handled by the callable. */ - rawRequest: express.Request; + rawRequest: Request; } // The allowed interface for an http request for a callable function. -interface HttpRequest extends express.Request { +interface HttpRequest extends Request { body: { data: any; }; @@ -284,7 +291,7 @@ interface HttpResponseBody { } // Returns true if req is a properly formatted callable request. -function isValidRequest(req: express.Request): req is HttpRequest { +function isValidRequest(req: Request): req is HttpRequest { // The body must not be empty. if (!req.body) { console.warn('Request is missing body.'); @@ -416,7 +423,7 @@ export function _onCallWithOpts( handler: (data: any, context: CallableContext) => any | Promise, opts: DeploymentOptions ): HttpsFunction & Runnable { - const func = async (req: express.Request, res: express.Response) => { + const func = async (req: Request, res: express.Response) => { try { if (!isValidRequest(req)) { console.error('Invalid request', req); @@ -477,7 +484,7 @@ export function _onCallWithOpts( }; // Wrap the function with a cors handler. - const corsFunc: any = (req: express.Request, res: express.Response) => { + const corsFunc: any = (req: Request, res: express.Response) => { return corsHandler(req, res, () => func(req, res)); }; From 9e3bda13565454543b4c7b2fd10fb627a6a3ab97 Mon Sep 17 00:00:00 2001 From: Kevin Jian Date: Tue, 19 Mar 2019 12:03:24 -0700 Subject: [PATCH 007/437] Update firebase-admin dependency to allow minor revisions (#424) * Update admin dependency to allow minor updates * Run formatter * Update integration test dependencies --- integration_test/functions/src/index.ts | 5 ++++- integration_test/package.node6.json | 2 +- integration_test/package.node8.json | 2 +- package.json | 4 ++-- src/providers/https.ts | 4 ++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index cd6781f03..a7132e87d 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -53,7 +53,10 @@ export const integrationTests: any = functions .https.onRequest((req: Request, resp: Response) => { // We take the base url for our https call (cloudfunctions.net, txckloud.net, etc) from the request // so that it changes with the environment that the tests are run in - const baseUrl = req.hostname.split('.').slice(1).join('.'); + const baseUrl = req.hostname + .split('.') + .slice(1) + .join('.'); let pubsub: any = require('@google-cloud/pubsub')(); const testId = admin .database() diff --git a/integration_test/package.node6.json b/integration_test/package.node6.json index c7397753d..f7ae06056 100644 --- a/integration_test/package.node6.json +++ b/integration_test/package.node6.json @@ -8,7 +8,7 @@ "@google-cloud/pubsub": "~0.19.0", "@types/google-cloud__pubsub": "^0.18.0", "@types/lodash": "~4.14.41", - "firebase-admin": "~7.0.0", + "firebase-admin": "^7.0.0", "firebase-functions": "./firebase-functions.tgz", "lodash": "~4.17.2" }, diff --git a/integration_test/package.node8.json b/integration_test/package.node8.json index ae861c35b..18a6a9f60 100644 --- a/integration_test/package.node8.json +++ b/integration_test/package.node8.json @@ -8,7 +8,7 @@ "@google-cloud/pubsub": "~0.19.0", "@types/google-cloud__pubsub": "^0.18.0", "@types/lodash": "~4.14.41", - "firebase-admin": "~7.0.0", + "firebase-admin": "^7.0.0", "firebase-functions": "./firebase-functions.tgz", "lodash": "~4.17.2" }, diff --git a/package.json b/package.json index c9359955b..1d1f096f9 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@types/sinon": "^1.16.29", "chai": "^3.5.0", "chai-as-promised": "^5.2.0", - "firebase-admin": "~7.0.0", + "firebase-admin": "^7.0.0", "istanbul": "^0.4.2", "mocha": "^5.2.0", "mock-require": "^2.0.1", @@ -51,7 +51,7 @@ "typescript": "~3.1.0" }, "peerDependencies": { - "firebase-admin": "~7.0.0" + "firebase-admin": "^7.0.0" }, "dependencies": { "@types/cors": "^2.8.1", diff --git a/src/providers/https.ts b/src/providers/https.ts index 59da0e85f..2ad40a195 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -29,8 +29,8 @@ import { HttpsFunction, optsToTrigger, Runnable } from '../cloud-functions'; import { DeploymentOptions } from '../function-builder'; /** - * - * + * + * */ export interface Request extends express.Request { rawBody: Buffer; From 1ad8b388b3e189258211a9ea2e3244be0cc416cd Mon Sep 17 00:00:00 2001 From: Kevin Jian Date: Wed, 20 Mar 2019 11:42:55 -0700 Subject: [PATCH 008/437] Update changelog (#427) --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 5127b08b8..7e755d52f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,2 +1,3 @@ feature - Adds functions.app() api to access the app instance used by functions fixed - improved types of the `Change` class to describe both `before` and `after` fields as non-optional +fixed - Improve type of express.Request to include rawBody From 27c6c13ee7eab2d1f76bd2ddfb418f8b6f529a48 Mon Sep 17 00:00:00 2001 From: Tina Liang Date: Wed, 20 Mar 2019 13:02:05 -0700 Subject: [PATCH 009/437] firebase-functions/handler SDK (#219) * crashlytics * remove unnecessary import * throwing error if context.params accessed * reworded and removed unnecessary tests * initial commit * firestore unit tests * remove unnecessary imports * reworded tests * https and database providers for firebase-functions/handler sdk (#222) https and database providers and tests for firebase-functions/handler sdk * auth, pubsub, storage, and analytics providers for firebase-functions/handler sdk (#224) auth, pubsub, storage, and analytics providers * remote config provider for firebase-functions/handler SDK (#223) * remote config provider and unit tests --- spec/cloud-functions.spec.ts | 33 +++++ spec/providers/analytics.spec.ts | 38 +++++ spec/providers/auth.spec.ts | 109 ++++++++++++++ spec/providers/crashlytics.spec.ts | 53 +++++++ spec/providers/database.spec.ts | 110 ++++++++++++++ spec/providers/firestore.spec.ts | 152 ++++++++++++++----- spec/providers/https.spec.ts | 18 +++ spec/providers/pubsub.spec.ts | 35 +++++ spec/providers/remoteConfig.spec.ts | 26 ++++ spec/providers/storage.spec.ts | 116 +++++++++++++++ src/cloud-functions.ts | 23 ++- src/handler-builder.ts | 219 ++++++++++++++++++++++++++++ src/index.ts | 3 + src/providers/remoteConfig.ts | 47 ++++-- 14 files changed, 929 insertions(+), 53 deletions(-) create mode 100644 src/handler-builder.ts diff --git a/spec/cloud-functions.spec.ts b/spec/cloud-functions.spec.ts index 8bb213af5..7a6b6745f 100644 --- a/spec/cloud-functions.spec.ts +++ b/spec/cloud-functions.spec.ts @@ -158,6 +158,39 @@ describe('makeCloudFunction', () => { }, }); }); + + it('should throw error when context.params accessed in handler environment', () => { + let args: any = _.assign({}, cloudFunctionArgs, { + handler: (data: any, context: EventContext) => context, + triggerResource: () => null, + }); + let cf = makeCloudFunction(args); + let test: Event = { + context: { + eventId: '00000', + timestamp: '2016-11-04T21:29:03.496Z', + eventType: 'provider.event', + resource: { + service: 'provider', + name: 'resource', + }, + }, + data: 'test data', + }; + + return cf(test).then(result => { + expect(result).to.deep.equal({ + eventId: '00000', + timestamp: '2016-11-04T21:29:03.496Z', + eventType: 'provider.event', + resource: { + service: 'provider', + name: 'resource', + }, + }); + expect(() => result.params).to.throw(Error); + }); + }); }); describe('makeParams', () => { diff --git a/spec/providers/analytics.spec.ts b/spec/providers/analytics.spec.ts index e7a4532d5..48babba34 100644 --- a/spec/providers/analytics.spec.ts +++ b/spec/providers/analytics.spec.ts @@ -249,6 +249,44 @@ describe('Analytics Functions', () => { }); }); + describe('handler namespace', () => { + describe('#onLog', () => { + it('should return an empty trigger', () => { + const cloudFunction = functions.handler.analytics.event.onLog( + () => null + ); + expect(cloudFunction.__trigger).to.deep.equal({}); + }); + + it('should handle an event with the appropriate fields', () => { + const cloudFunction = functions.handler.analytics.event.onLog( + (data: analytics.AnalyticsEvent) => data + ); + + // The event data delivered over the wire will be the JSON for an AnalyticsEvent: + // https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data + let event: LegacyEvent = { + eventId: 'f2e2f0bf-2e47-4d92-b009-e7a375ecbd3e', + eventType: 'providers/google.firebase.analytics/eventTypes/event.log', + resource: 'projects/myUnitTestProject/events/first_open', + data: { + userDim: { + userId: 'hi!', + }, + }, + }; + + return expect(cloudFunction(event)).to.eventually.deep.equal({ + params: {}, + user: { + userId: 'hi!', + userProperties: {}, + }, + }); + }); + }); + }); + describe('process.env.GCLOUD_PROJECT not set', () => { it('should not throw if __trigger is not accessed', () => { expect(() => analytics.event('event').onLog(() => null)).to.not.throw( diff --git a/spec/providers/auth.spec.ts b/spec/providers/auth.spec.ts index f82c736e5..af175466e 100644 --- a/spec/providers/auth.spec.ts +++ b/spec/providers/auth.spec.ts @@ -201,6 +201,115 @@ describe('Auth Functions', () => { }); }); + describe('handler namespace', () => { + describe('#onCreate', () => { + let cloudFunctionCreate: CloudFunction< + firebase.auth.UserRecord + > = functions.handler.auth.user.onCreate( + (data: firebase.auth.UserRecord) => data + ); + + it('should return an empty trigger', () => { + const cloudFunction = functions.handler.auth.user.onCreate(() => null); + expect(cloudFunction.__trigger).to.deep.equal({}); + }); + + it('should transform wire format for UserRecord into v5.0.0 format', () => { + const event = { + data: { + metadata: { + createdAt: '2016-12-15T19:37:37.059Z', + lastSignedInAt: '2017-01-01T00:00:00.000Z', + }, + }, + }; + + return cloudFunctionCreate(event).then((data: any) => { + expect(data.metadata.creationTime).to.equal( + '2016-12-15T19:37:37.059Z' + ); + expect(data.metadata.lastSignInTime).to.equal( + '2017-01-01T00:00:00.000Z' + ); + }); + }); + + it('should handle new wire format if/when there is a change', () => { + const newEvent = { + data: { + metadata: { + creationTime: '2016-12-15T19:37:37.059Z', + lastSignInTime: '2017-01-01T00:00:00.000Z', + }, + }, + }; + + return cloudFunctionCreate(newEvent).then((data: any) => { + expect(data.metadata.creationTime).to.equal( + '2016-12-15T19:37:37.059Z' + ); + expect(data.metadata.lastSignInTime).to.equal( + '2017-01-01T00:00:00.000Z' + ); + }); + }); + }); + + describe('#onDelete', () => { + let cloudFunctionDelete: CloudFunction< + firebase.auth.UserRecord + > = functions.handler.auth.user.onDelete( + (data: firebase.auth.UserRecord) => data + ); + + it('should return an empty trigger', () => { + let handler: (user: firebase.auth.UserRecord) => PromiseLike | any; + const cloudFunction = functions.handler.auth.user.onDelete(handler); + expect(cloudFunction.__trigger).to.deep.equal({}); + }); + + it('should transform wire format for UserRecord into v5.0.0 format', () => { + const event = { + data: { + metadata: { + createdAt: '2016-12-15T19:37:37.059Z', + lastSignedInAt: '2017-01-01T00:00:00.000Z', + }, + }, + }; + + return cloudFunctionDelete(event).then((data: any) => { + expect(data.metadata.creationTime).to.equal( + '2016-12-15T19:37:37.059Z' + ); + expect(data.metadata.lastSignInTime).to.equal( + '2017-01-01T00:00:00.000Z' + ); + }); + }); + + it('should handle new wire format if/when there is a change', () => { + const newEvent = { + data: { + metadata: { + creationTime: '2016-12-15T19:37:37.059Z', + lastSignInTime: '2017-01-01T00:00:00.000Z', + }, + }, + }; + + return cloudFunctionDelete(newEvent).then((data: any) => { + expect(data.metadata.creationTime).to.equal( + '2016-12-15T19:37:37.059Z' + ); + expect(data.metadata.lastSignInTime).to.equal( + '2017-01-01T00:00:00.000Z' + ); + }); + }); + }); + }); + describe('process.env.GCLOUD_PROJECT not set', () => { it('should not throw if __trigger is not accessed', () => { expect(() => auth.user().onCreate(() => null)).to.not.throw(Error); diff --git a/spec/providers/crashlytics.spec.ts b/spec/providers/crashlytics.spec.ts index 7b2a78018..081fef807 100644 --- a/spec/providers/crashlytics.spec.ts +++ b/spec/providers/crashlytics.spec.ts @@ -93,6 +93,59 @@ describe('Crashlytics Functions', () => { }); }); }); + + describe('HandlerBuilder', () => { + const testIssue = { + issueId: '1234', + issueTitle: 'testIssue', + appInfo: { + appName: 'My Awesome Test App', + appPlatform: 'ios', + appId: '9876', + latestAppVersion: '1.2.3.4', + }, + createTime: '2018-12-18T23:05:55+00:00', + }; + + describe('#onNew', () => { + it('should return a CloudFunction with appropriate values', () => { + const cloudFunction = functions.handler.crashlytics.issue.onNew( + testIssue => { + return ( + testIssue.issueId + testIssue.issueTitle + testIssue.createTime + ); + } + ); + expect(cloudFunction.__trigger).to.deep.equal({}); + }); + }); + + describe('#onRegressed', () => { + it('should return a CloudFunction with appropriate values', () => { + const cloudFunction = functions.handler.crashlytics.issue.onRegressed( + testIssue => { + return ( + testIssue.issueId + testIssue.issueTitle + testIssue.createTime + ); + } + ); + expect(cloudFunction.__trigger).to.deep.equal({}); + }); + }); + + describe('#onVelocityAlert', () => { + it('should return a CloudFunction with appropriate values', () => { + const cloudFunction = functions.handler.crashlytics.issue.onVelocityAlert( + testIssue => { + return ( + testIssue.issueId + testIssue.issueTitle + testIssue.createTime + ); + } + ); + expect(cloudFunction.__trigger).to.deep.equal({}); + }); + }); + }); }); describe('process.env.GCLOUD_PROJECT not set', () => { diff --git a/spec/providers/database.spec.ts b/spec/providers/database.spec.ts index 47a068e85..f85225b1f 100644 --- a/spec/providers/database.spec.ts +++ b/spec/providers/database.spec.ts @@ -218,6 +218,116 @@ describe('Database Functions', () => { }); }); + describe('handler namespace', () => { + describe('#onWrite()', () => { + it('correctly sets trigger to {}', () => { + let cf = functions.handler.database.ref.onWrite(() => null); + expect(cf.__trigger).to.deep.equal({}); + }); + + it('should be able to use the instance entry point', () => { + let func = functions.handler.database.instance.ref.onWrite(() => null); + expect(func.__trigger).to.deep.equal({}); + }); + + it('should return a handler that emits events with a proper DataSnapshot', () => { + let handler = functions.handler.database.ref.onWrite(change => { + return expect(change.after.val()).to.deep.equal({ foo: 'bar' }); + }); + + return handler({ + data: { + data: null, + delta: { foo: 'bar' }, + }, + resource: 'projects/_/instances/subdomains/refs/users', + eventType: 'providers/google.firebase.database/eventTypes/ref.write', + }); + }); + }); + + describe('#onCreate()', () => { + it('correctly sets trigger to {}', () => { + let cf = functions.handler.database.ref.onCreate(() => null); + return expect(cf.__trigger).to.deep.equal({}); + }); + + it('should be able to use the instance entry point', () => { + let func = functions.handler.database.instance.ref.onCreate(() => null); + expect(func.__trigger).to.deep.equal({}); + }); + + it('should return a handler that emits events with a proper DataSnapshot', () => { + let handler = functions.handler.database.ref.onCreate(data => { + return expect(data.val()).to.deep.equal({ foo: 'bar' }); + }); + + return handler({ + data: { + data: null, + delta: { foo: 'bar' }, + }, + resource: 'projects/_/instances/subdomains/refs/users', + eventType: 'providers/google.firebase.database/eventTypes/ref.create', + }); + }); + }); + + describe('#onUpdate()', () => { + it('correctly sets trigger to {}', () => { + let cf = functions.handler.database.ref.onUpdate(() => null); + return expect(cf.__trigger).to.deep.equal({}); + }); + + it('should be able to use the instance entry point', () => { + let func = functions.handler.database.instance.ref.onUpdate(() => null); + expect(func.__trigger).to.deep.equal({}); + }); + + it('should return a handler that emits events with a proper DataSnapshot', () => { + let handler = functions.handler.database.ref.onUpdate(change => { + return expect(change.after.val()).to.deep.equal({ foo: 'bar' }); + }); + + return handler({ + data: { + data: null, + delta: { foo: 'bar' }, + }, + resource: 'projects/_/instances/subdomains/refs/users', + eventType: 'providers/google.firebase.database/eventTypes/ref.update', + }); + }); + }); + + describe('#onDelete()', () => { + it('correctly sets trigger to {}', () => { + let cf = functions.handler.database.ref.onDelete(() => null); + return expect(cf.__trigger).to.deep.equal({}); + }); + + it('should be able to use the instance entry point', () => { + let func = functions.handler.database.instance.ref.onDelete(() => null); + expect(func.__trigger).to.deep.equal({}); + }); + + it('should return a handler that emits events with a proper DataSnapshot', () => { + let handler = functions.handler.database.ref.onDelete(data => { + return expect(data.val()).to.deep.equal({ foo: 'bar' }); + }); + + return handler({ + data: { + data: { foo: 'bar' }, + delta: null, + }, + resource: 'projects/_/instances/subdomains/refs/users', + eventType: 'providers/google.firebase.database/eventTypes/ref.delete', + }); + }); + }); + }); + describe('process.env.FIREBASE_CONFIG not set', () => { it('should not throw if __trigger is not accessed', () => { expect(() => database.ref('/path').onWrite(() => null)).to.not.throw( diff --git a/spec/providers/firestore.spec.ts b/spec/providers/firestore.spec.ts index 34e7ab24c..16901fe88 100644 --- a/spec/providers/firestore.spec.ts +++ b/spec/providers/firestore.spec.ts @@ -56,6 +56,42 @@ describe('Firestore Functions', () => { }; } + function constructEvent(oldValue: object, value: object, eventType: string) { + return { + data: { + oldValue: oldValue, + value: value, + }, + context: { + resource: { + name: 'resource', + }, + }, + }; + } + + function createOldValue() { + return constructValue({ + key1: { + booleanValue: false, + }, + key2: { + integerValue: '111', + }, + }); + } + + function createValue() { + return constructValue({ + key1: { + booleanValue: true, + }, + key2: { + integerValue: '123', + }, + }); + } + describe('document builders and event types', () => { function expectedTrigger(resource: string, eventType: string) { return { @@ -186,46 +222,6 @@ describe('Firestore Functions', () => { }); describe('dataConstructor', () => { - function constructEvent( - oldValue: object, - value: object, - eventType: string - ) { - return { - data: { - oldValue: oldValue, - value: value, - }, - context: { - resource: { - name: 'resource', - }, - }, - }; - } - - function createOldValue() { - return constructValue({ - key1: { - booleanValue: false, - }, - key2: { - integerValue: '111', - }, - }); - } - - function createValue() { - return constructValue({ - key1: { - booleanValue: true, - }, - key2: { - integerValue: '123', - }, - }); - } - before(() => { process.env.GCLOUD_PROJECT = 'project1'; }); @@ -287,6 +283,82 @@ describe('Firestore Functions', () => { }).timeout(5000); }); + describe('handler namespace', () => { + before(() => { + process.env.GCLOUD_PROJECT = 'project1'; + }); + + after(() => { + delete process.env.GCLOUD_PROJECT; + }); + + it('constructs correct data type and sets trigger to {} on "document.write" events', () => { + let testFunction = functions.handler.firestore.document.onWrite( + change => { + expect(change.before.data()).to.deep.equal({ + key1: false, + key2: 111, + }); + expect(change.before.get('key1')).to.equal(false); + expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); + expect(change.after.get('key1')).to.equal(true); + return true; // otherwise will get warning about returning undefined + } + ); + expect(testFunction.__trigger).to.deep.equal({}); + let data = constructEvent( + createOldValue(), + createValue(), + 'document.write' + ); + return testFunction(data); + }).timeout(5000); + + it('constructs correct data type and sets trigger to {} on "document.create" events', () => { + let testFunction = functions.handler.firestore.document.onCreate(data => { + expect(data.data()).to.deep.equal({ key1: true, key2: 123 }); + expect(data.get('key1')).to.equal(true); + return true; // otherwise will get warning about returning undefined + }); + expect(testFunction.__trigger).to.deep.equal({}); + let data = constructEvent({}, createValue(), 'document.create'); + return testFunction(data); + }).timeout(5000); + + it('constructs correct data type and sets trigger to {} on "document.update" events', () => { + let testFunction = functions.handler.firestore.document.onUpdate( + change => { + expect(change.before.data()).to.deep.equal({ + key1: false, + key2: 111, + }); + expect(change.before.get('key1')).to.equal(false); + expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); + expect(change.after.get('key1')).to.equal(true); + return true; // otherwise will get warning about returning undefined + } + ); + expect(testFunction.__trigger).to.deep.equal({}); + let data = constructEvent( + createOldValue(), + createValue(), + 'document.update' + ); + return testFunction(data); + }).timeout(5000); + + it('constructs correct data type and sets trigger to {} on "document.delete" events', () => { + let testFunction = functions.handler.firestore.document.onDelete(data => { + expect(data.data()).to.deep.equal({ key1: false, key2: 111 }); + expect(data.get('key1')).to.equal(false); + return true; // otherwise will get warning about returning undefined + }); + let data = constructEvent(createOldValue(), {}, 'document.delete'); + expect(testFunction.__trigger).to.deep.equal({}); + return testFunction(data); + }).timeout(5000); + }); + describe('SnapshotConstructor', () => { describe('#data()', () => { it('should parse int values', () => { diff --git a/spec/providers/https.spec.ts b/spec/providers/https.spec.ts index 9bd929767..48d93dbea 100644 --- a/spec/providers/https.spec.ts +++ b/spec/providers/https.spec.ts @@ -56,6 +56,24 @@ describe('CloudHttpsBuilder', () => { }); }); +describe('handler namespace', () => { + describe('#onRequest', () => { + it('should return an empty trigger', () => { + let result = functions.handler.https.onRequest((req, res) => { + res.send(200); + }); + expect(result.__trigger).to.deep.equal({}); + }); + }); + + describe('#onCall', () => { + it('should return an empty trigger', () => { + let result = functions.handler.https.onCall(() => null); + expect(result.__trigger).to.deep.equal({}); + }); + }); +}); + /** * RunHandlerResult contains the data from an express.Response. */ diff --git a/spec/providers/pubsub.spec.ts b/spec/providers/pubsub.spec.ts index b1d7e2918..3538e91f6 100644 --- a/spec/providers/pubsub.spec.ts +++ b/spec/providers/pubsub.spec.ts @@ -131,6 +131,41 @@ describe('Pubsub Functions', () => { }); }); + describe('handler namespace', () => { + describe('#onPublish', () => { + it('should return an empty trigger', () => { + const result = functions.handler.pubsub.topic.onPublish(() => null); + expect(result.__trigger).to.deep.equal({}); + }); + + it('should properly handle a new-style event', () => { + const raw = new Buffer('{"hello":"world"}', 'utf8').toString('base64'); + const event = { + data: { + data: raw, + attributes: { + foo: 'bar', + }, + }, + }; + + const result = functions.handler.pubsub.topic.onPublish(data => { + return { + raw: data.data, + json: data.json, + attributes: data.attributes, + }; + }); + + return expect(result(event)).to.eventually.deep.equal({ + raw, + json: { hello: 'world' }, + attributes: { foo: 'bar' }, + }); + }); + }); + }); + describe('process.env.GCLOUD_PROJECT not set', () => { it('should not throw if __trigger is not accessed', () => { expect(() => pubsub.topic('toppy').onPublish(() => null)).to.not.throw( diff --git a/spec/providers/remoteConfig.spec.ts b/spec/providers/remoteConfig.spec.ts index 07da151a8..5ff3d9e69 100644 --- a/spec/providers/remoteConfig.spec.ts +++ b/spec/providers/remoteConfig.spec.ts @@ -125,4 +125,30 @@ describe('RemoteConfig Functions', () => { ]); }); }); + + describe('handler namespace', () => { + describe('#onUpdate', () => { + it('should have an empty trigger', () => { + const cloudFunction = functions.handler.remoteConfig.onUpdate( + () => null + ); + expect(cloudFunction.__trigger).to.deep.equal({}); + }); + + it('should correctly unwrap the event', () => { + let cloudFunctionUpdate = functions.handler.remoteConfig.onUpdate( + (version: remoteConfig.TemplateVersion) => version + ); + let event = { + data: constructVersion(), + }; + + return Promise.all([ + cloudFunctionUpdate(event).then((data: any) => { + expect(data).to.deep.equal(constructVersion()); + }), + ]); + }); + }); + }); }); diff --git a/spec/providers/storage.spec.ts b/spec/providers/storage.spec.ts index 2aa7f7145..36339743d 100644 --- a/spec/providers/storage.spec.ts +++ b/spec/providers/storage.spec.ts @@ -324,6 +324,122 @@ describe('Storage Functions', () => { }); }); + describe('namespace handler', () => { + before(() => { + process.env.FIREBASE_CONFIG = JSON.stringify({ + storageBucket: 'bucket', + }); + }); + + after(() => { + delete process.env.FIREBASE_CONFIG; + }); + + describe('#onArchive', () => { + it('should return an empty trigger', () => { + let cloudFunction = functions.handler.storage.bucket.onArchive( + () => null + ); + expect(cloudFunction.__trigger).to.deep.equal({}); + }); + + it('should not mess with media links using non-literal slashes', () => { + let cloudFunction = functions.handler.storage.object.onArchive(data => { + return data.mediaLink; + }); + let goodMediaLinkEvent = { + data: { + mediaLink: + 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', + }, + }; + return cloudFunction(goodMediaLinkEvent).then((result: any) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + }); + }); + }); + + describe('#onDelete', () => { + it('should return an empty trigger', () => { + let cloudFunction = functions.handler.storage.bucket.onDelete( + () => null + ); + expect(cloudFunction.__trigger).to.deep.equal({}); + }); + + it('should not mess with media links using non-literal slashes', () => { + let cloudFunction = functions.handler.storage.object.onDelete(data => { + return data.mediaLink; + }); + let goodMediaLinkEvent = { + data: { + mediaLink: + 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', + }, + }; + return cloudFunction(goodMediaLinkEvent).then((result: any) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + }); + }); + }); + + describe('#onFinalize', () => { + it('should return an empty trigger', () => { + let cloudFunction = functions.handler.storage.bucket.onFinalize( + () => null + ); + expect(cloudFunction.__trigger).to.deep.equal({}); + }); + + it('should not mess with media links using non-literal slashes', () => { + let cloudFunction = functions.handler.storage.object.onFinalize( + data => { + return data.mediaLink; + } + ); + let goodMediaLinkEvent = { + data: { + mediaLink: + 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', + }, + }; + return cloudFunction(goodMediaLinkEvent).then((result: any) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + }); + }); + }); + + describe('#onMetadataUpdate', () => { + it('should return an empty trigger', () => { + let cloudFunction = functions.handler.storage.bucket.onMetadataUpdate( + () => null + ); + expect(cloudFunction.__trigger).to.deep.equal({}); + }); + + it('should not mess with media links using non-literal slashes', () => { + let cloudFunction = functions.handler.storage.object.onMetadataUpdate( + data => { + return data.mediaLink; + } + ); + let goodMediaLinkEvent = { + data: { + mediaLink: + 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', + }, + }; + return cloudFunction(goodMediaLinkEvent).then((result: any) => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); + }); + }); + }); + }); + describe('process.env.FIREBASE_CONFIG not set', () => { it('should not throw if __trigger is not accessed', () => { expect(() => storage.object().onArchive(() => null)).to.not.throw(Error); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index e4873552f..b2d0e30e0 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -70,8 +70,10 @@ export interface EventContext { eventType: string; /** Resource that triggered the event */ resource: Resource; - /** Key-value pairs that represent the values of wildcards in a database reference */ - params: { [option: string]: any }; // added by SDK, but may be {} + /** Key-value pairs that represent the values of wildcards in a database reference + * Cannot be accessed while inside the handler namespace. + */ + params: { [option: string]: any }; /** Type of authentication for the triggering action, valid value are: 'ADMIN', 'USER', * 'UNAUTHENTICATED'. Only available for database functions. */ @@ -256,7 +258,18 @@ export function makeCloudFunction({ delete context.auth; } } - context.params = context.params || _makeParams(context, triggerResource); + + if (triggerResource() == null) { + Object.defineProperty(context, 'params', { + get: () => { + throw new Error( + 'context.params is not available when using the handler namespace.' + ); + }, + }); + } else { + context.params = context.params || _makeParams(context, triggerResource); + } before(event); @@ -296,6 +309,10 @@ export function makeCloudFunction({ Object.defineProperty(cloudFunction, '__trigger', { get: () => { + if (triggerResource() == null) { + return {}; + } + let trigger: any = _.assign(optsToTrigger(opts), { eventTrigger: { resource: triggerResource(), diff --git a/src/handler-builder.ts b/src/handler-builder.ts new file mode 100644 index 000000000..a3ed867d8 --- /dev/null +++ b/src/handler-builder.ts @@ -0,0 +1,219 @@ +// The MIT License (MIT) +// +// Copyright (c) 2017 Firebase +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import * as express from 'express'; + +import { apps } from './apps'; +import * as analytics from './providers/analytics'; +import * as auth from './providers/auth'; +import * as crashlytics from './providers/crashlytics'; +import * as database from './providers/database'; +import * as firestore from './providers/firestore'; +import * as https from './providers/https'; +import * as pubsub from './providers/pubsub'; +import * as remoteConfig from './providers/remoteConfig'; +import * as storage from './providers/storage'; +import { CloudFunction, EventContext, HttpsFunction } from './cloud-functions'; + +/** @internal */ +export class HandlerBuilder { + constructor() {} + + get https() { + return { + /** + * Handle HTTP requests. + * @param handler A function that takes a request and response object, + * same signature as an Express app. + */ + onRequest: ( + handler: (req: express.Request, resp: express.Response) => void + ): HttpsFunction => { + const func = https._onRequestWithOpts(handler, {}); + func.__trigger = {}; + return func; + }, + /** + * Declares a callable method for clients to call using a Firebase SDK. + * @param handler A method that takes a data and context and returns a value. + */ + onCall: ( + handler: ( + data: any, + context: https.CallableContext + ) => any | Promise + ): HttpsFunction => { + const func = https._onCallWithOpts(handler, {}); + func.__trigger = {}; + return func; + }, + }; + } + + get database() { + return { + /** + * Selects a database instance that will trigger the function. + * If omitted, will pick the default database for your project. + */ + get instance() { + return { + get ref() { + return new database.RefBuilder(apps(), () => null, {}); + }, + }; + }, + + /** + * Select Firebase Realtime Database Reference to listen to. + * + * This method behaves very similarly to the method of the same name in the + * client and Admin Firebase SDKs. Any change to the Database that affects the + * data at or below the provided `path` will fire an event in Cloud Functions. + * + * There are three important differences between listening to a Realtime + * Database event in Cloud Functions and using the Realtime Database in the + * client and Admin SDKs: + * 1. Cloud Functions allows wildcards in the `path` name. Any `path` component + * in curly brackets (`{}`) is a wildcard that matches all strings. The value + * that matched a certain invocation of a Cloud Function is returned as part + * of the `context.params` object. For example, `ref("messages/{messageId}")` + * matches changes at `/messages/message1` or `/messages/message2`, resulting + * in `context.params.messageId` being set to `"message1"` or `"message2"`, + * respectively. + * 2. Cloud Functions do not fire an event for data that already existed before + * the Cloud Function was deployed. + * 3. Cloud Function events have access to more information, including information + * about the user who triggered the Cloud Function. + */ + get ref() { + return new database.RefBuilder(apps(), () => null, {}); + }, + }; + } + + get firestore() { + return { + /** + * Listen for events on a Firestore document. A Firestore document contains a set of + * key-value pairs and may contain subcollections and nested objects. + */ + get document() { + return new firestore.DocumentBuilder(() => null, {}); + }, + /** @internal */ + get namespace() { + return new firestore.DocumentBuilder(() => null, {}); + }, + /** @internal */ + get database() { + return new firestore.DocumentBuilder(() => null, {}); + }, + }; + } + + get crashlytics() { + return { + /** + * Handle events related to Crashlytics issues. An issue in Crashlytics is an + * aggregation of crashes which have a shared root cause. + */ + get issue() { + return new crashlytics.IssueBuilder(() => null, {}); + }, + }; + } + + get remoteConfig() { + return { + /** + * Handle all updates (including rollbacks) that affect a Remote Config + * project. + * @param handler A function that takes the updated Remote Config template + * version metadata as an argument. + */ + onUpdate: ( + handler: ( + version: remoteConfig.TemplateVersion, + context: EventContext + ) => PromiseLike | any + ): CloudFunction => { + return new remoteConfig.UpdateBuilder(() => null, {}).onUpdate(handler); + }, + }; + } + + get analytics() { + return { + /** + * Select analytics events to listen to for events. + */ + get event() { + return new analytics.AnalyticsEventBuilder(() => null, {}); + }, + }; + } + + get storage() { + return { + /** + * The optional bucket function allows you to choose which buckets' events to handle. + * This step can be bypassed by calling object() directly, which will use the default + * Cloud Storage for Firebase bucket. + */ + get bucket() { + return new storage.BucketBuilder(() => null, {}).object(); + }, + + /** + * Handle events related to Cloud Storage objects. + */ + get object() { + return new storage.ObjectBuilder(() => null, {}); + }, + }; + } + + get pubsub() { + return { + /** + * Select Cloud Pub/Sub topic to listen to. + */ + get topic() { + return new pubsub.TopicBuilder(() => null, {}); + }, + }; + } + + get auth() { + return { + /** + * Handle events related to Firebase authentication users. + */ + get user() { + return new auth.UserBuilder(() => null, {}); + }, + }; + } +} + +export let handler = new HandlerBuilder(); diff --git a/src/index.ts b/src/index.ts index 051212746..8c3901f7d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,6 +33,8 @@ import * as pubsub from './providers/pubsub'; import * as remoteConfig from './providers/remoteConfig'; import * as storage from './providers/storage'; import { firebaseConfig } from './config'; +import { handler } from './handler-builder'; + var app = apps.apps(); export { @@ -42,6 +44,7 @@ export { crashlytics, database, firestore, + handler, https, pubsub, remoteConfig, diff --git a/src/providers/remoteConfig.ts b/src/providers/remoteConfig.ts index ca8ef3139..00fb8b877 100644 --- a/src/providers/remoteConfig.ts +++ b/src/providers/remoteConfig.ts @@ -57,17 +57,44 @@ export function _onUpdateWithOpts( ) => PromiseLike | any, opts: DeploymentOptions ): CloudFunction { - if (!process.env.GCLOUD_PROJECT) { - throw new Error('process.env.GCLOUD_PROJECT is not set.'); + const triggerResource = () => { + if (!process.env.GCLOUD_PROJECT) { + throw new Error('process.env.GCLOUD_PROJECT is not set.'); + } + return `projects/${process.env.GCLOUD_PROJECT}`; + }; + return new UpdateBuilder(triggerResource, opts).onUpdate(handler); +} + +/** Builder used to create Cloud Functions for Remote Config. */ +export class UpdateBuilder { + /** @internal */ + constructor( + private triggerResource: () => string, + private opts: DeploymentOptions + ) {} + + /** + * Handle all updates (including rollbacks) that affect a Remote Config + * project. + * @param handler A function that takes the updated Remote Config template + * version metadata as an argument. + */ + onUpdate( + handler: ( + version: TemplateVersion, + context: EventContext + ) => PromiseLike | any + ): CloudFunction { + return makeCloudFunction({ + handler, + provider, + service, + triggerResource: this.triggerResource, + eventType: 'update', + opts: this.opts, + }); } - return makeCloudFunction({ - handler, - provider, - service, - triggerResource: () => `projects/${process.env.GCLOUD_PROJECT}`, - eventType: 'update', - opts: opts, - }); } /** From d7fd3d14a880fe085a05cd6eec2d0dc628c63ed1 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 20 Mar 2019 13:37:09 -0700 Subject: [PATCH 010/437] remove @internal flag from handlker-build type (#228) --- src/handler-builder.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/handler-builder.ts b/src/handler-builder.ts index a3ed867d8..5c466fabc 100644 --- a/src/handler-builder.ts +++ b/src/handler-builder.ts @@ -34,7 +34,6 @@ import * as remoteConfig from './providers/remoteConfig'; import * as storage from './providers/storage'; import { CloudFunction, EventContext, HttpsFunction } from './cloud-functions'; -/** @internal */ export class HandlerBuilder { constructor() {} From 4adcbf895efda4dcd07fa01c130a62538cef13d7 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Wed, 20 Mar 2019 20:44:14 +0000 Subject: [PATCH 011/437] [firebase-release] Updated SDK for Cloud Functions to 2.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1d1f096f9..228b09c88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "2.2.0", + "version": "2.2.1", "description": "Firebase SDK for Cloud Functions", "main": "lib/index.js", "scripts": { From cd81469149b31a69c5a6e24518cf42abe6a30dad Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Wed, 20 Mar 2019 20:44:25 +0000 Subject: [PATCH 012/437] [firebase-release] Removed change log and reset repo after 2.2.1 release --- changelog.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index 7e755d52f..e69de29bb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +0,0 @@ -feature - Adds functions.app() api to access the app instance used by functions -fixed - improved types of the `Change` class to describe both `before` and `after` fields as non-optional -fixed - Improve type of express.Request to include rawBody From a68325292e935ccf8fd9c56fee5fc8d8be4e6eb9 Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 15 Apr 2019 11:43:18 -0700 Subject: [PATCH 013/437] Scheduled functions via pubsub (#227) * Scheduler * unit tests * Adds integration tests for scheduled functions * removing uninented commit * fixing version number * adding labels and test for labels * trigger tests off of call to cloud scheduler:run * adding region/runtime opts tests * Predictions Functions (#220) * Firebase Predictions Integration with Functions - Initial check in * Add predictions getter * Make API match what was in the review, and add unit tests. * Fix imports. * Add error handling. * Handle process.env.GCLOUD_PROJECT correctly. * Fix region. * Upper case RiskToleranceName. * Add changelog message. * formatting * cleaning up * Revert "Predictions Functions (#220)" (#226) This reverts commit 838597aa6df973462a50484ec1f50d625da3f633. * pr fixes * adding clarifying comments * starting on pubsub impl * adds pusbub.schedule() * switching tests over from https to pubsub * Change timezone to a method instead of an optional argument * use real eventType * remove extra slash * changelog * switch signature to onRun(context) * switches changelog to present tense --- changelog.txt | 4 +- integration_test/functions/src/index.ts | 36 ++++- .../functions/src/pubsub-tests.ts | 24 ++- integration_test/functions/src/testing.ts | 2 +- spec/providers/https.spec.ts | 6 +- spec/providers/pubsub.spec.ts | 152 ++++++++++++++++++ src/cloud-functions.ts | 44 ++++- src/function-builder.ts | 4 + src/providers/pubsub.ts | 52 ++++++ 9 files changed, 308 insertions(+), 16 deletions(-) diff --git a/changelog.txt b/changelog.txt index 7e755d52f..d556f232e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1 @@ -feature - Adds functions.app() api to access the app instance used by functions -fixed - improved types of the `Change` class to describe both `before` and `after` fields as non-optional -fixed - Improve type of express.Request to include rawBody +feature - Adds pubsub.schedule() \ No newline at end of file diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index a7132e87d..72c3e5099 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -46,6 +46,33 @@ function callHttpsTrigger(name: string, data: any, baseUrl) { }); } +function callScheduleTrigger(functionName: string, region: string) { + return new Promise((resolve, reject) => { + const request = https.request( + { + method: 'POST', + host: 'cloudscheduler.googleapis.com', + path: `projects/${ + firebaseConfig.projectId + }/locations/us-central1/jobs/firebase-schedule-${functionName}-${region}:run`, + headers: { + 'Content-Type': 'application/json', + }, + }, + response => { + let body = ''; + response.on('data', chunk => { + body += chunk; + }); + response.on('end', () => resolve(body)); + } + ); + request.on('error', reject); + request.write('{}'); + request.end(); + }); +} + export const integrationTests: any = functions .runWith({ timeoutSeconds: 540, @@ -62,6 +89,10 @@ export const integrationTests: any = functions .database() .ref() .push().key; + admin + .database() + .ref(`testRuns/${testId}/timestamp`) + .set(Date.now()); console.log('testId is: ', testId); fs.writeFile('/tmp/' + testId + '.txt', 'test', () => {}); return Promise.all([ @@ -120,6 +151,9 @@ export const integrationTests: any = functions .bucket() .upload('/tmp/' + testId + '.txt'), // Invoke a callable HTTPS trigger. + callHttpsTrigger('callableTests', { foo: 'bar', testId }), + // Invoke the schedule for our scheduled function to fire + callScheduleTrigger('schedule', 'us-central1'), ]) .then(() => { // On test completion, check that all tests pass and reply "PASS", or provide further details. @@ -129,7 +163,7 @@ export const integrationTests: any = functions let testsExecuted = 0; ref.on('child_added', snapshot => { testsExecuted += 1; - if (!snapshot.val().passed) { + if (snapshot.key != 'timestamp' && !snapshot.val().passed) { reject( new Error( `test ${snapshot.key} failed; see database for details.` diff --git a/integration_test/functions/src/pubsub-tests.ts b/integration_test/functions/src/pubsub-tests.ts index 3ce63251e..e657eedc3 100644 --- a/integration_test/functions/src/pubsub-tests.ts +++ b/integration_test/functions/src/pubsub-tests.ts @@ -1,5 +1,6 @@ import * as functions from 'firebase-functions'; -import { TestSuite, expectEq, evaluate } from './testing'; +import * as admin from 'firebase-admin'; +import { TestSuite, expectEq, evaluate, success } from './testing'; import PubsubMessage = functions.pubsub.Message; // TODO(inlined) use multiple queues to run inline. @@ -57,3 +58,24 @@ export const pubsubTests: any = functions.pubsub .run(testId, m, c); }); + +export const schedule: any = functions.pubsub + .schedule('every 10 hours') // This is a dummy schedule, since we need to put a valid one in. + // For the test, the job is triggered by the jobs:run api + .onRun(context => { + let testId; + let db = admin.database(); + return new Promise(async (resolve, reject) => { + await db + .ref('testRuns') + .orderByChild('timestamp') + .limitToLast(1) + .on('value', snap => { + testId = Object.keys(snap.val())[0]; + new TestSuite('pubsub scheduleOnRun') + .it('should trigger when the scheduler fires', () => success()) + .run(testId, null); + }); + resolve(); + }); + }); diff --git a/integration_test/functions/src/testing.ts b/integration_test/functions/src/testing.ts index 3a34fd8a1..c3aaf05f0 100644 --- a/integration_test/functions/src/testing.ts +++ b/integration_test/functions/src/testing.ts @@ -56,7 +56,7 @@ export class TestSuite { } } -function success() { +export function success() { return Promise.resolve().then(() => true); } diff --git a/spec/providers/https.spec.ts b/spec/providers/https.spec.ts index 48d93dbea..0b3c23c38 100644 --- a/spec/providers/https.spec.ts +++ b/spec/providers/https.spec.ts @@ -23,13 +23,13 @@ import { expect } from 'chai'; import * as express from 'express'; import * as firebase from 'firebase-admin'; -import * as https from '../../src/providers/https'; import * as jwt from 'jsonwebtoken'; -import * as mocks from '../fixtures/credential/key.json'; -import * as nock from 'nock'; import * as _ from 'lodash'; +import * as nock from 'nock'; import { apps as appsNamespace } from '../../src/apps'; import * as functions from '../../src/index'; +import * as https from '../../src/providers/https'; +import * as mocks from '../fixtures/credential/key.json'; describe('CloudHttpsBuilder', () => { describe('#onRequest', () => { diff --git a/spec/providers/pubsub.spec.ts b/spec/providers/pubsub.spec.ts index 3538e91f6..006421974 100644 --- a/spec/providers/pubsub.spec.ts +++ b/spec/providers/pubsub.spec.ts @@ -129,6 +129,158 @@ describe('Pubsub Functions', () => { }); }); }); + describe('#schedule', () => { + it('should return a trigger with schedule', () => { + let result = pubsub.schedule('every 5 minutes').onRun(context => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + }); + }); + it('should return a trigger with schedule and timeZone when one is chosen', () => { + let result = pubsub + .schedule('every 5 minutes') + .timeZone('America/New_York') + .onRun(context => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + timeZone: 'America/New_York', + }); + }); + it('should return a trigger with schedule and retry config when called with retryConfig', () => { + let retryConfig = { + retryCount: 3, + maxRetryDuration: '10 minutes', + minBackoffDuration: '10 minutes', + maxBackoffDuration: '10 minutes', + maxDoublings: 5, + }; + let result = pubsub + .schedule('every 5 minutes') + .retryConfig(retryConfig) + .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + retryConfig: retryConfig, + }); + expect(result.__trigger.labels).to.deep.equal({ + 'deployment-scheduled': 'true', + }); + }); + it('should return a trigger with schedule, timeZone and retry config when called with retryConfig and timeout', () => { + let retryConfig = { + retryCount: 3, + maxRetryDuration: '10 minutes', + minBackoffDuration: '10 minutes', + maxBackoffDuration: '10 minutes', + maxDoublings: 5, + }; + let result = pubsub + .schedule('every 5 minutes') + .timeZone('America/New_York') + .retryConfig(retryConfig) + .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + retryConfig: retryConfig, + timeZone: 'America/New_York', + }); + expect(result.__trigger.labels).to.deep.equal({ + 'deployment-scheduled': 'true', + }); + }); + it('should return an appropriate trigger when called with region and options', () => { + let result = functions + .region('us-east1') + .runWith({ + timeoutSeconds: 90, + memory: '256MB', + }) + .pubsub.schedule('every 5 minutes') + .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + }); + expect(result.__trigger.regions).to.deep.equal(['us-east1']); + expect(result.__trigger.availableMemoryMb).to.deep.equal(256); + expect(result.__trigger.timeout).to.deep.equal('90s'); + }); + it('should return an appropriate trigger when called with region, timeZone, and options', () => { + let result = functions + .region('us-east1') + .runWith({ + timeoutSeconds: 90, + memory: '256MB', + }) + .pubsub.schedule('every 5 minutes') + .timeZone('America/New_York') + .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + timeZone: 'America/New_York', + }); + expect(result.__trigger.regions).to.deep.equal(['us-east1']); + expect(result.__trigger.availableMemoryMb).to.deep.equal(256); + expect(result.__trigger.timeout).to.deep.equal('90s'); + }); + it('should return an appropriate trigger when called with region, options and retryConfig', () => { + let retryConfig = { + retryCount: 3, + maxRetryDuration: '10 minutes', + minBackoffDuration: '10 minutes', + maxBackoffDuration: '10 minutes', + maxDoublings: 5, + }; + let result = functions + .region('us-east1') + .runWith({ + timeoutSeconds: 90, + memory: '256MB', + }) + .pubsub.schedule('every 5 minutes') + .retryConfig(retryConfig) + .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + retryConfig: retryConfig, + }); + expect(result.__trigger.labels).to.deep.equal({ + 'deployment-scheduled': 'true', + }); + expect(result.__trigger.regions).to.deep.equal(['us-east1']); + expect(result.__trigger.availableMemoryMb).to.deep.equal(256); + expect(result.__trigger.timeout).to.deep.equal('90s'); + }); + it('should return an appropriate trigger when called with region, options, retryConfig, and timeZone', () => { + let retryConfig = { + retryCount: 3, + maxRetryDuration: '10 minutes', + minBackoffDuration: '10 minutes', + maxBackoffDuration: '10 minutes', + maxDoublings: 5, + }; + let result = functions + .region('us-east1') + .runWith({ + timeoutSeconds: 90, + memory: '256MB', + }) + .pubsub.schedule('every 5 minutes') + .timeZone('America/New_York') + .retryConfig(retryConfig) + .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + timeZone: 'America/New_York', + retryConfig: retryConfig, + }); + expect(result.__trigger.labels).to.deep.equal({ + 'deployment-scheduled': 'true', + }); + expect(result.__trigger.regions).to.deep.equal(['us-east1']); + expect(result.__trigger.availableMemoryMb).to.deep.equal(256); + expect(result.__trigger.timeout).to.deep.equal('90s'); + }); + }); }); describe('handler namespace', () => { diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index b2d0e30e0..af4e97ba2 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -20,9 +20,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import { apps } from './apps'; -import * as _ from 'lodash'; import { Request, Response } from 'express'; +import * as _ from 'lodash'; +import { apps } from './apps'; import { DeploymentOptions } from './function-builder'; export { Request, Response }; @@ -177,9 +177,24 @@ export interface TriggerAnnotated { regions?: string[]; timeout?: string; availableMemoryMb?: number; + schedule?: Schedule; }; } +export interface ScheduleRetryConfig { + retryCount?: number; + maxRetryDuration?: string; + minBackoffDuration?: string; + maxBackoffDuration?: string; + maxDoublings?: number; +} + +export interface Schedule { + schedule: string; + timeZone?: string; + retryConfig?: ScheduleRetryConfig; +} + /** A Runnable has a `run` method which directly invokes the user-defined function - useful for unit testing. */ export interface Runnable { run: (data: T, context: any) => PromiseLike | any; @@ -209,11 +224,13 @@ export interface MakeCloudFunctionArgs { triggerResource: () => string; service: string; dataConstructor?: (raw: Event) => EventData; - handler: (data: EventData, context: EventContext) => PromiseLike | any; + handler?: (data: EventData, context: EventContext) => PromiseLike | any; + contextOnlyHandler?: (context: EventContext) => PromiseLike | any; before?: (raw: Event) => void; after?: (raw: Event) => void; legacyEventType?: string; opts?: { [key: string]: any }; + labels?: { [key: string]: any }; } /** @internal */ @@ -224,6 +241,7 @@ export function makeCloudFunction({ service, dataConstructor = (raw: Event) => raw.data, handler, + contextOnlyHandler, before = () => { return; }, @@ -232,6 +250,7 @@ export function makeCloudFunction({ }, legacyEventType, opts = {}, + labels = {}, }: MakeCloudFunctionArgs): CloudFunction { let cloudFunction; @@ -273,8 +292,14 @@ export function makeCloudFunction({ before(event); - let dataOrChange = dataConstructor(event); - let promise = handler(dataOrChange, context); + let promise; + if (labels && labels['deployment-scheduled']) { + // Scheduled function do not have meaningful data, so exclude it + promise = contextOnlyHandler(context); + } else { + const dataOrChange = dataConstructor(event); + promise = handler(dataOrChange, context); + } if (typeof promise === 'undefined') { console.warn('Function returned undefined, expected Promise or value'); } @@ -320,12 +345,14 @@ export function makeCloudFunction({ service, }, }); - + if (!_.isEmpty(labels)) { + trigger.labels = labels; + } return trigger; }, }); - cloudFunction.run = handler; + cloudFunction.run = handler || contextOnlyHandler; return cloudFunction; } @@ -398,5 +425,8 @@ export function optsToTrigger(opts: DeploymentOptions) { }; trigger.availableMemoryMb = _.get(memoryLookup, opts.memory); } + if (opts.schedule) { + trigger.schedule = opts.schedule; + } return trigger; } diff --git a/src/function-builder.ts b/src/function-builder.ts index 6c9a5508f..e3ccb159e 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -37,6 +37,7 @@ import { EventContext, Runnable, TriggerAnnotated, + Schedule, } from './cloud-functions'; /** @@ -97,6 +98,7 @@ export interface DeploymentOptions { regions?: string[]; timeoutSeconds?: number; memory?: string; + schedule?: Schedule; } export class FunctionBuilder { @@ -288,6 +290,8 @@ export class FunctionBuilder { * @param topic Name of Pub/Sub topic, must belong to the same project as the function. */ topic: (topic: string) => pubsub._topicWithOpts(topic, this.options), + schedule: (schedule: string) => + pubsub._scheduleWithOpts(schedule, this.options), }; } diff --git a/src/providers/pubsub.ts b/src/providers/pubsub.ts index 43dad1fc1..e734c0ba3 100644 --- a/src/providers/pubsub.ts +++ b/src/providers/pubsub.ts @@ -24,6 +24,8 @@ import { CloudFunction, makeCloudFunction, EventContext, + Schedule, + ScheduleRetryConfig, } from '../cloud-functions'; import { DeploymentOptions } from '../function-builder'; @@ -56,6 +58,56 @@ export function _topicWithOpts( }, opts); } +export function schedule(schedule: string): ScheduleBuilder { + return _scheduleWithOpts(schedule, {}); +} + +export class ScheduleBuilder { + private _opts: DeploymentOptions; + + /** @internal */ + constructor(private schedule: Schedule, private opts: DeploymentOptions) { + this._opts = { schedule, ...opts }; + } + + retryConfig(config: ScheduleRetryConfig): ScheduleBuilder { + this._opts.schedule.retryConfig = config; + return this; + } + + timeZone(timeZone: string): ScheduleBuilder { + this._opts.schedule.timeZone = timeZone; + return this; + } + + onRun(handler: (context: EventContext) => PromiseLike | any) { + const triggerResource = () => { + if (!process.env.GCLOUD_PROJECT) { + throw new Error('process.env.GCLOUD_PROJECT is not set.'); + } + return `projects/${process.env.GCLOUD_PROJECT}/topics`; // The CLI will append the correct topic name based on region and function name + }; + const cloudFunction = makeCloudFunction({ + contextOnlyHandler: handler, + provider, + service, + triggerResource: triggerResource, + eventType: 'topic.publish', + opts: this._opts, + labels: { 'deployment-scheduled': 'true' }, + }); + return cloudFunction; + } +} + +/** @internal */ +export function _scheduleWithOpts( + schedule: string, + opts: DeploymentOptions +): ScheduleBuilder { + return new ScheduleBuilder({ schedule }, opts); +} + /** Builder used to create Cloud Functions for Google Pub/Sub topics. */ export class TopicBuilder { /** @internal */ From 27e4586e57ff66e7f899be938b0c1f819ad61c59 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Thu, 18 Apr 2019 22:46:56 +0000 Subject: [PATCH 014/437] [firebase-release] Updated SDK for Cloud Functions to 2.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 228b09c88..7ac979235 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "2.2.1", + "version": "2.3.0", "description": "Firebase SDK for Cloud Functions", "main": "lib/index.js", "scripts": { From e1f0f8a30d904bca50a715ed4936fae21bcd5d79 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Thu, 18 Apr 2019 22:47:09 +0000 Subject: [PATCH 015/437] [firebase-release] Removed change log and reset repo after 2.3.0 release --- changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index bfdfee31c..e69de29bb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1 +0,0 @@ -feature - Adds pubsub.schedule() From ecfb4ad93c6cc881d09806bd428b661afc13a90d Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 19 Apr 2019 11:44:29 -0700 Subject: [PATCH 016/437] removes extra broken call of callHttpsTrigger (#431) --- integration_test/functions/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index 72c3e5099..1ee1a903f 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -124,6 +124,7 @@ export const integrationTests: any = functions .collection('tests') .doc(testId) .set({ test: testId }), + // Invoke a callable HTTPS trigger. callHttpsTrigger('callableTests', { foo: 'bar', testId }, baseUrl), // A Remote Config update to trigger the Remote Config tests. // admin.credential @@ -150,8 +151,6 @@ export const integrationTests: any = functions .storage() .bucket() .upload('/tmp/' + testId + '.txt'), - // Invoke a callable HTTPS trigger. - callHttpsTrigger('callableTests', { foo: 'bar', testId }), // Invoke the schedule for our scheduled function to fire callScheduleTrigger('schedule', 'us-central1'), ]) From 7b5ac4b9101adc3d0936188dd9881809a96f63a9 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Thu, 9 May 2019 00:55:07 -0700 Subject: [PATCH 017/437] Add region support for europe-west2 and asia-east2 (#435) * Add new regions, fix bugs, refactor * change from ...region to region array * add test for specifying all regions, add memory type to catch compilcation errors * capitalize memory type * better check of error being thrown * add changelog * lower case feature in changelog --- changelog.txt | 1 + spec/function-builder.spec.ts | 81 ++++++++++++----- src/function-builder.ts | 164 +++++++++++++++++++--------------- 3 files changed, 155 insertions(+), 91 deletions(-) diff --git a/changelog.txt b/changelog.txt index e69de29bb..3f6624f47 100644 --- a/changelog.txt +++ b/changelog.txt @@ -0,0 +1 @@ +feature - Adds region support for europe-west2 and asia-east2 \ No newline at end of file diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index 6472720bb..615d9fe9c 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -33,7 +33,7 @@ describe('FunctionBuilder', () => { delete process.env.GCLOUD_PROJECT; }); - it('should allow region to be set', () => { + it('should allow supported region to be set', () => { let fn = functions .region('us-east1') .auth.user() @@ -42,7 +42,7 @@ describe('FunctionBuilder', () => { expect(fn.__trigger.regions).to.deep.equal(['us-east1']); }); - it('should allow multiple regions to be set', () => { + it('should allow multiple supported regions to be set', () => { let fn = functions .region('us-east1', 'us-central1') .auth.user() @@ -51,7 +51,30 @@ describe('FunctionBuilder', () => { expect(fn.__trigger.regions).to.deep.equal(['us-east1', 'us-central1']); }); - it('should allow runtime options to be set', () => { + it('should allow all supported regions to be set', () => { + let fn = functions + .region( + 'us-central1', + 'us-east1', + 'europe-west1', + 'europe-west2', + 'asia-east2', + 'asia-northeast1' + ) + .auth.user() + .onCreate(user => user); + + expect(fn.__trigger.regions).to.deep.equal([ + 'us-central1', + 'us-east1', + 'europe-west1', + 'europe-west2', + 'asia-east2', + 'asia-northeast1', + ]); + }); + + it('should allow valid runtime options to be set', () => { let fn = functions .runWith({ timeoutSeconds: 90, @@ -64,9 +87,9 @@ describe('FunctionBuilder', () => { expect(fn.__trigger.timeout).to.deep.equal('90s'); }); - it('should allow both region and runtime options to be set', () => { + it('should allow both supported region and valid runtime options to be set', () => { let fn = functions - .region('us-east1') + .region('europe-west2') .runWith({ timeoutSeconds: 90, memory: '256MB', @@ -74,38 +97,54 @@ describe('FunctionBuilder', () => { .auth.user() .onCreate(user => user); - expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.regions).to.deep.equal(['europe-west2']); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); expect(fn.__trigger.timeout).to.deep.equal('90s'); }); - it('should allow both region and runtime options to be set (reverse order)', () => { + it('should allow both valid runtime options and supported region to be set in reverse order', () => { let fn = functions .runWith({ timeoutSeconds: 90, memory: '256MB', }) - .region('us-east1') + .region('europe-west1') .auth.user() .onCreate(user => user); - expect(fn.__trigger.regions).to.deep.equal(['us-east1']); + expect(fn.__trigger.regions).to.deep.equal(['europe-west1']); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); expect(fn.__trigger.timeout).to.deep.equal('90s'); }); - it('should throw an error if user chooses an unsupported memory allocation', () => { + it('should fail if valid runtime options but unsupported region are set (reverse order)', () => { + expect(() => { + functions + .runWith({ timeoutSeconds: 90, memory: '256MB' }) + .region('unsupported'); + }).to.throw(Error, 'region'); + }); + + it('should fail if supported region but invalid runtime options are set (reverse order)', () => { + expect(() => { + functions + .region('asia-northeast1') + .runWith({ timeoutSeconds: 600, memory: '256MB' }); + }).to.throw(Error, 'TimeoutSeconds'); + }); + + it('should throw an error if user chooses an invalid memory allocation', () => { expect(() => { return functions.runWith({ memory: 'unsupported', } as any); - }).to.throw(Error); + }).to.throw(Error, 'memory'); expect(() => { return functions.region('us-east1').runWith({ memory: 'unsupported', } as any); - }).to.throw(Error); + }).to.throw(Error, 'memory'); }); it('should throw an error if user chooses an invalid timeoutSeconds', () => { @@ -113,46 +152,46 @@ describe('FunctionBuilder', () => { return functions.runWith({ timeoutSeconds: 1000000, } as any); - }).to.throw(Error); + }).to.throw(Error, 'TimeoutSeconds'); expect(() => { - return functions.region('us-east1').runWith({ + return functions.region('asia-east2').runWith({ timeoutSeconds: 1000000, } as any); - }).to.throw(Error); + }).to.throw(Error, 'TimeoutSeconds'); }); it('should throw an error if user chooses an invalid region', () => { expect(() => { return functions.region('unsupported'); - }).to.throw(Error); + }).to.throw(Error, 'region'); expect(() => { return functions.region('unsupported').runWith({ timeoutSeconds: 500, } as any); - }).to.throw(Error); + }).to.throw(Error, 'region'); expect(() => { return functions.region('unsupported', 'us-east1'); - }).to.throw(Error); + }).to.throw(Error, 'region'); expect(() => { return functions.region('unsupported', 'us-east1').runWith({ timeoutSeconds: 500, } as any); - }).to.throw(Error); + }).to.throw(Error, 'region'); }); it('should throw an error if user chooses no region when using .region()', () => { expect(() => { return functions.region(); - }).to.throw(Error); + }).to.throw(Error, 'at least one region'); expect(() => { return functions.region().runWith({ timeoutSeconds: 500, } as any); - }).to.throw(Error); + }).to.throw(Error, 'at least one region'); }); }); diff --git a/src/function-builder.ts b/src/function-builder.ts index e3ccb159e..42de739eb 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -32,37 +32,90 @@ import * as https from './providers/https'; import * as pubsub from './providers/pubsub'; import * as remoteConfig from './providers/remoteConfig'; import * as storage from './providers/storage'; -import { - CloudFunction, - EventContext, - Runnable, - TriggerAnnotated, - Schedule, -} from './cloud-functions'; +import { CloudFunction, EventContext, Schedule } from './cloud-functions'; /** - * Configure the regions that the function is deployed to. - * @param regions One of more region strings. - * For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')` + * List of all regions supported by Cloud Functions. */ -export function region(...regions: string[]) { - if (!regions.length) { - throw new Error('You must specify at least one region'); +const SUPPORTED_REGIONS = [ + 'us-central1', + 'us-east1', + 'europe-west1', + 'europe-west2', + 'asia-east2', + 'asia-northeast1', +]; + +/** + * List of available memory options supported by Cloud Functions. + */ +const VALID_MEMORY_OPTS = ['128MB', '256MB', '512MB', '1GB', '2GB']; + +// Adding this memory type here to error on compile for TS users. +// Unfortunately I have not found a way to merge this with VALID_MEMORY_OPS +// without it being super ugly. But here they are right next to each other at least. +type Memory = '128MB' | '256MB' | '512MB' | '1GB' | '2GB'; + +/** + * Cloud Functions max timeout value. + */ +const MAX_TIMEOUT_SECONDS = 540; + +/** + * Assert that the runtime options passed in are valid. + * @param runtimeOptions object containing memory and timeout information. + * @throws { Error } Memory and TimeoutSeconds values must be valid. + */ +function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean { + if ( + runtimeOptions.memory && + !_.includes(VALID_MEMORY_OPTS, runtimeOptions.memory) + ) { + throw new Error( + `The only valid memory allocation values are: ${VALID_MEMORY_OPTS.join( + ', ' + )}` + ); } if ( - _.difference(regions, [ - 'us-central1', - 'us-east1', - 'europe-west1', - 'asia-northeast1', - ]).length + runtimeOptions.timeoutSeconds > MAX_TIMEOUT_SECONDS || + runtimeOptions.timeoutSeconds < 0 ) { throw new Error( - "The only valid regions are 'us-central1', 'us-east1', 'europe-west1', and 'asia-northeast1'" + `TimeoutSeconds must be between 0 and ${MAX_TIMEOUT_SECONDS}` ); } - return new FunctionBuilder({ regions }); + return true; } + +/** + * Assert regions specified are valid. + * @param regions list of regions. + * @throws { Error } Regions must be in list of supported regions. + */ +function assertRegionsAreValid(regions: string[]): boolean { + if (!regions.length) { + throw new Error('You must specify at least one region'); + } + if (_.difference(regions, SUPPORTED_REGIONS).length) { + throw new Error( + `The only valid regions are: ${SUPPORTED_REGIONS.join(', ')}` + ); + } + return true; +} + +/** + * Configure the regions that the function is deployed to. + * @param regions One of more region strings. + * For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')` + */ +export function region(...regions: string[]): FunctionBuilder { + if (assertRegionsAreValid(regions)) { + return new FunctionBuilder({ regions }); + } +} + /** * Configure runtime options for the function. * @param runtimeOptions Object with 2 optional fields: @@ -70,34 +123,21 @@ export function region(...regions: string[]) { * 2. `memory`: amount of memory to allocate to the function, * possible values are: '128MB', '256MB', '512MB', '1GB', and '2GB'. */ -export function runWith(runtimeOptions: { - timeoutSeconds?: number; - memory?: '128MB' | '256MB' | '512MB' | '1GB' | '2GB'; -}) { - if ( - runtimeOptions.memory && - !_.includes( - ['128MB', '256MB', '512MB', '1GB', '2GB'], - runtimeOptions.memory - ) - ) { - throw new Error( - "The only valid memory allocation values are: '128MB', '256MB', '512MB', '1GB', and '2GB'" - ); - } - if ( - runtimeOptions.timeoutSeconds > 540 || - runtimeOptions.timeoutSeconds < 0 - ) { - throw new Error('TimeoutSeconds must be between 0 and 540'); +export function runWith(runtimeOptions: RuntimeOptions): FunctionBuilder { + if (assertRuntimeOptionsValid(runtimeOptions)) { + return new FunctionBuilder(runtimeOptions); } - return new FunctionBuilder(runtimeOptions); +} + +export interface RuntimeOptions { + timeoutSeconds?: number; + memory?: Memory; } export interface DeploymentOptions { regions?: string[]; timeoutSeconds?: number; - memory?: string; + memory?: Memory; schedule?: Schedule; } @@ -109,10 +149,12 @@ export class FunctionBuilder { * @param regions One or more region strings. * For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')` */ - region = (...regions: string[]) => { - this.options.regions = regions; - return this; - }; + region(...regions: string[]): FunctionBuilder { + if (assertRegionsAreValid(regions)) { + this.options.regions = regions; + return this; + } + } /** * Configure runtime options for the function. @@ -121,30 +163,12 @@ export class FunctionBuilder { * 2. memory: amount of memory to allocate to the function, possible values are: * '128MB', '256MB', '512MB', '1GB', and '2GB'. */ - runWith = (runtimeOptions: { - timeoutSeconds?: number; - memory?: '128MB' | '256MB' | '512MB' | '1GB' | '2GB'; - }) => { - if ( - runtimeOptions.memory && - !_.includes( - ['128MB', '256MB', '512MB', '1GB', '2GB'], - runtimeOptions.memory - ) - ) { - throw new Error( - "The only valid memory allocation values are: '128MB', '256MB', '512MB', '1GB', and '2GB'" - ); - } - if ( - runtimeOptions.timeoutSeconds > 540 || - runtimeOptions.timeoutSeconds < 0 - ) { - throw new Error('TimeoutSeconds must be between 0 and 540'); + runWith(runtimeOptions: RuntimeOptions): FunctionBuilder { + if (assertRuntimeOptionsValid(runtimeOptions)) { + this.options = _.assign(this.options, runtimeOptions); + return this; } - this.options = _.assign(this.options, runtimeOptions); - return this; - }; + } get https() { return { From 430bb2b7aa15754e42e6efc01f8ceeedc153dd62 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Thu, 9 May 2019 18:19:23 +0000 Subject: [PATCH 018/437] [firebase-release] Updated SDK for Cloud Functions to 2.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7ac979235..f1e312ffd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "2.3.0", + "version": "2.3.1", "description": "Firebase SDK for Cloud Functions", "main": "lib/index.js", "scripts": { From 18c01cbdc25d8f4fccb8847f6a4be86b6c7d4960 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Thu, 9 May 2019 18:19:35 +0000 Subject: [PATCH 019/437] [firebase-release] Removed change log and reset repo after 2.3.1 release --- changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 3f6624f47..e69de29bb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1 +0,0 @@ -feature - Adds region support for europe-west2 and asia-east2 \ No newline at end of file From ff1d203e1f9e8e190d7c80dcc119556d6fd2eeb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Thu, 6 Jun 2019 19:37:24 +0200 Subject: [PATCH 020/437] Lay the groundwork for v3 (#450) * Update dependencies to latest versions * Revert ordering alphabetically * Add `dom` library to `package.json` This is required by `@types/protobufjs`. * Update dependencies once more * Update dependencies again * Sort package.json using sort-package-json * Breaking changes to dependencies * Remove upgrade warning * Compile to ES2017 * Simplify test scripts * Remove class prototype overrides * Drop support for FIREBASE_PROJECT env variable * Add changelog entries * Adjust Travis config to run on Node.js 8 and Node.js 10 * Remove tests for FIREBASE_PROJECT env variable * Remove v1 migration entry from the readme * Specify keywords in a correct order * Revert adding a newline before an if statement * Remove an always truthy condition * Remove DOM library * Update dependency @types/lodash to ^4.14.133 * Add a changelog entry for FIREBASE_PROJECT * Add typeRoots to tsconfig.release.json * Require a version of firebase-tools that supports Node.js 10 * Adjust firebase-tools version in changelog entry * Remove firebase-tool peer dependency. --- .travis.yml | 5 +- README.md | 6 +-- changelog.txt | 3 ++ package.json | 95 +++++++++++++++++------------------- spec/config.spec.ts | 50 ------------------- spec/fixtures/http.ts | 65 ++++++++++-------------- spec/providers/https.spec.ts | 8 +-- src/config.ts | 5 +- src/providers/https.ts | 9 +--- tsconfig.json | 13 ++--- tsconfig.release.json | 13 ++--- upgrade-warning | 15 ------ 12 files changed, 94 insertions(+), 193 deletions(-) delete mode 100644 upgrade-warning diff --git a/.travis.yml b/.travis.yml index 8f5bf9f17..987e158e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: node_js node_js: -- '6.14.0' -- '8' -- stable + - '8' + - '10' sudo: false diff --git a/README.md b/README.md index d044c60f5..59c5c0936 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,7 @@ Here are some resources to get help: If the official documentation doesn't help, try asking through our official support channels: https://firebase.google.com/support/ -*Please avoid double posting across multiple channels!* - -## Migrating to v1 - -To migrate from a beta version of firebase-functions to v1, please refer to the [migration guide](https://firebase.google.com/docs/functions/beta-v1-diff). +_Please avoid double posting across multiple channels!_ ## Usage diff --git a/changelog.txt b/changelog.txt index e69de29bb..6af6b5e23 100644 --- a/changelog.txt +++ b/changelog.txt @@ -0,0 +1,3 @@ +- important - [breaking change] The Firebase Functions SDK no longer supports Node 6. Developers must use Node 8.13.0 or higher. +- important - [breaking change] The Firebase Functions SDK no longer supports Firebase Tools SDK below version 6.8.0. +- important - [breaking change] FIREBASE_PROJECT environment variable is no longer supported and has been supplanted by FIREBASE_CONFIG. diff --git a/package.json b/package.json index f1e312ffd..d1deef78b 100644 --- a/package.json +++ b/package.json @@ -2,69 +2,66 @@ "name": "firebase-functions", "version": "2.3.1", "description": "Firebase SDK for Cloud Functions", - "main": "lib/index.js", - "scripts": { - "build": "node_modules/.bin/tsc -p tsconfig.release.json", - "build:pack": "rm -rf lib && npm install && node_modules/.bin/tsc -p tsconfig.release.json && npm pack", - "build:release": "npm install --production && npm install typescript firebase-admin && node_modules/.bin/tsc -p tsconfig.release.json", - "format": "prettier --write '**/*.ts'", - "pretest": "node_modules/.bin/tsc && cp -r spec/fixtures .tmp/spec", - "test": "npm run mocha", - "mocha": "mocha .tmp/spec/index.spec.js", - "posttest": "npm run format && rm -rf .tmp", - "postinstall": "node ./upgrade-warning" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/firebase/firebase-functions.git" - }, "keywords": [ "firebase", "functions", "google", "cloud" ], - "author": "Firebase Team", - "license": "MIT", + "homepage": "https://github.com/firebase/firebase-functions#readme", "bugs": { "url": "https://github.com/firebase/firebase-functions/issues" }, - "homepage": "https://github.com/firebase/firebase-functions#readme", - "devDependencies": { - "@types/chai": "^3.4.32", - "@types/chai-as-promised": "0.0.28", - "@types/mocha": "^5.2.5", - "@types/mock-require": "^1.3.3", - "@types/nock": "^0.54.32", - "@types/node": "^6.0.38", - "@types/sinon": "^1.16.29", - "chai": "^3.5.0", - "chai-as-promised": "^5.2.0", - "firebase-admin": "^7.0.0", - "istanbul": "^0.4.2", - "mocha": "^5.2.0", - "mock-require": "^2.0.1", - "nock": "^9.0.0", - "prettier": "^1.13.7", - "sinon": "^1.17.4", - "ts-node": "^7.0.1", - "typescript": "~3.1.0" + "repository": { + "type": "git", + "url": "git+https://github.com/firebase/firebase-functions.git" }, - "peerDependencies": { - "firebase-admin": "^7.0.0" + "license": "MIT", + "author": "Firebase Team", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "scripts": { + "build:pack": "rm -rf lib && npm install && tsc -p tsconfig.release.json && npm pack", + "build:release": "npm install --production && npm install typescript firebase-admin && tsc -p tsconfig.release.json", + "build": "tsc -p tsconfig.release.json", + "format": "prettier --write '**/*.ts'", + "posttest": "npm run format", + "test": "mocha -r ts-node/register ./spec/index.spec.ts" }, "dependencies": { - "@types/cors": "^2.8.1", - "@types/express": "^4.11.1", - "@types/jsonwebtoken": "^7.2.6", - "@types/lodash": "^4.14.34", "cors": "^2.8.4", - "express": "^4.16.2", - "jsonwebtoken": "^8.2.1", + "express": "^4.17.1", + "jsonwebtoken": "^8.3.2", "lodash": "^4.6.1" }, - "engines": { - "node": ">=6.0.0" + "devDependencies": { + "@types/chai": "^4.1.7", + "@types/chai-as-promised": "^7.1.0", + "@types/cors": "^2.8.5", + "@types/express": "^4.11.1", + "@types/jsonwebtoken": "^8.3.2", + "@types/lodash": "^4.14.133", + "@types/mocha": "^5.2.7", + "@types/mock-require": "^2.0.0", + "@types/nock": "^10.0.2", + "@types/node": "^8.10.49", + "@types/sinon": "^7.0.12", + "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", + "firebase-admin": "^8.0.0", + "istanbul": "^0.4.2", + "mocha": "^6.1.4", + "mock-require": "^3.0.3", + "nock": "^10.0.6", + "prettier": "^1.17.1", + "sinon": "^7.3.2", + "ts-node": "^8.2.0", + "typescript": "^3.5.1" }, - "typings": "lib/index.d.ts" + "peerDependencies": { + "firebase-admin": "^8.0.0" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } } diff --git a/spec/config.spec.ts b/spec/config.spec.ts index c72efa633..90ff64620 100644 --- a/spec/config.spec.ts +++ b/spec/config.spec.ts @@ -29,7 +29,6 @@ describe('config()', () => { mockRequire.stopAll(); delete config.singleton; delete process.env.FIREBASE_CONFIG; - delete process.env.FIREBASE_PROJECT; delete process.env.CLOUD_RUNTIME_CONFIG; }); @@ -50,16 +49,6 @@ describe('config()', () => { expect(firebaseConfig()).to.be.null; }); - it('loads Firebase configs from FIREBASE_PROJECT env variable', () => { - process.env.FIREBASE_PROJECT = JSON.stringify({ - databaseURL: 'foo@firebaseio.com', - }); - expect(firebaseConfig()).to.have.property( - 'databaseURL', - 'foo@firebaseio.com' - ); - }); - it('loads Firebase configs from FIREBASE_CONFIG env variable', () => { process.env.FIREBASE_CONFIG = JSON.stringify({ databaseURL: 'foo@firebaseio.com', @@ -70,33 +59,6 @@ describe('config()', () => { ); }); - it('prefers FIREBASE_CONFIG over FIREBASE_PROJECT', () => { - process.env.FIREBASE_CONFIG = JSON.stringify({ - databaseURL: 'firebase_config', - }); - process.env.FIREBASE_PROJECT = JSON.stringify({ - databaseURL: 'firebase_project', - }); - expect(firebaseConfig()).to.have.property('databaseURL', 'firebase_config'); - }); - - it('behaves well when both FIREBASE_PROJECT and .runtimeconfig.json present', () => { - process.env.FIREBASE_PROJECT = JSON.stringify({ - databaseURL: 'foo@firebaseio.com', - }); - mockRequire('../../../.runtimeconfig.json', { - firebase: { - databaseURL: 'foo@firebaseio.com', - }, - foo: 'bar', - }); - expect(firebaseConfig()).to.have.property( - 'databaseURL', - 'foo@firebaseio.com' - ); - expect(config()).to.have.property('foo', 'bar'); - }); - it('accepts alternative locations for config file', () => { process.env.CLOUD_RUNTIME_CONFIG = 'another.json'; mockRequire('another.json', { foo: 'bar', firebase: {} }); @@ -112,16 +74,4 @@ describe('config()', () => { expect(firebaseConfig()).to.not.be.null; expect(config()).to.have.property('foo', 'bar'); }); - - it('behaves well when both env.CLOUD_RUNTIME_CONFIG and env.FIREBASE_PROJECT are set', () => { - process.env.CLOUD_RUNTIME_CONFIG = JSON.stringify({ foo: 'bar' }); - process.env.FIREBASE_PROJECT = JSON.stringify({ - databaseURL: 'foo@firebaseio.com', - }); - expect(firebaseConfig()).to.have.property( - 'databaseURL', - 'foo@firebaseio.com' - ); - expect(config()).to.have.property('foo', 'bar'); - }); }); diff --git a/spec/fixtures/http.ts b/spec/fixtures/http.ts index ae9545780..efda2a501 100644 --- a/spec/fixtures/http.ts +++ b/spec/fixtures/http.ts @@ -39,15 +39,10 @@ export function mockRCVariableFetch( data: any, token: string = 'thetoken' ): nock.Scope { - let mock: nock.Scope = nock('https://runtimeconfig.googleapis.com').get( - `/v1beta1/projects/${projectId}/configs/firebase/variables/${varName}` - ); - - if (token) { - mock = mock.matchHeader('Authorization', `Bearer ${token}`); - } - - return mock.reply(200, { text: JSON.stringify(data) }); + return nock('https://runtimeconfig.googleapis.com') + .get(`/v1beta1/projects/${projectId}/configs/firebase/variables/${varName}`) + .matchHeader('Authorization', `Bearer ${token}`) + .reply(200, { text: JSON.stringify(data) }); } export function mockMetaVariableWatch( @@ -56,19 +51,16 @@ export function mockMetaVariableWatch( token: string = 'thetoken', updateTime: string = new Date().toISOString() ): nock.Scope { - let mock: nock.Scope = nock('https://runtimeconfig.googleapis.com').post( - `/v1beta1/projects/${projectId}/configs/firebase/variables/meta:watch` - ); - - if (token) { - mock = mock.matchHeader('Authorization', `Bearer ${token}`); - } - - return mock.reply(200, { - updateTime, - state: 'UPDATED', - text: JSON.stringify(data), - }); + return nock('https://runtimeconfig.googleapis.com') + .post( + `/v1beta1/projects/${projectId}/configs/firebase/variables/meta:watch` + ) + .matchHeader('Authorization', `Bearer ${token}`) + .reply(200, { + updateTime, + state: 'UPDATED', + text: JSON.stringify(data), + }); } export function mockMetaVariableWatchTimeout( @@ -76,40 +68,37 @@ export function mockMetaVariableWatchTimeout( delay: number, token?: string ): nock.Scope { - let mock: nock.Scope = nock('https://runtimeconfig.googleapis.com').post( + let interceptor = nock('https://runtimeconfig.googleapis.com').post( `/v1beta1/projects/${projectId}/configs/firebase/variables/meta:watch` ); - if (token) { - mock = mock.matchHeader('Authorization', `Bearer ${token}`); + if (interceptor) { + interceptor = interceptor.matchHeader('Authorization', `Bearer ${token}`); } - return mock.delay(delay).reply(502); + return interceptor.delay(delay).reply(502); } export function mockCreateToken( token: AccessToken = { access_token: 'aToken', expires_in: 3600 } ): nock.Scope { - let mock: nock.Scope = nock('https://accounts.google.com').post( - '/o/oauth2/token' - ); - return mock.reply(200, token); + return nock('https://accounts.google.com') + .post('/o/oauth2/token') + .reply(200, token); } export function mockRefreshToken( token: AccessToken = { access_token: 'aToken', expires_in: 3600 } ): nock.Scope { - let mock: nock.Scope = nock('https://www.googleapis.com').post( - '/oauth2/v4/token' - ); - return mock.reply(200, token); + return nock('https://www.googleapis.com') + .post('/oauth2/v4/token') + .reply(200, token); } export function mockMetadataServiceToken( token: AccessToken = { access_token: 'aToken', expires_in: 3600 } ): nock.Scope { - let mock: nock.Scope = nock('http://metadata.google.internal').get( - '/computeMetadata/v1beta1/instance/service-accounts/default/token' - ); - return mock.reply(200, token); + return nock('http://metadata.google.internal') + .get('/computeMetadata/v1beta1/instance/service-accounts/default/token') + .reply(200, token); } diff --git a/spec/providers/https.spec.ts b/spec/providers/https.spec.ts index 0b3c23c38..ec1a191e5 100644 --- a/spec/providers/https.spec.ts +++ b/spec/providers/https.spec.ts @@ -213,14 +213,14 @@ const expectedResponseHeaders = { * verifying an id token. */ function mockFetchPublicKeys(): nock.Scope { - let mock: nock.Scope = nock('https://www.googleapis.com:443').get( - '/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com' - ); const mockedResponse = { [mocks.key_id]: mocks.public_key }; const headers = { 'cache-control': 'public, max-age=1, must-revalidate, no-transform', }; - return mock.reply(200, mockedResponse, headers); + + return nock('https://www.googleapis.com:443') + .get('/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com') + .reply(200, mockedResponse, headers); } /** diff --git a/src/config.ts b/src/config.ts index 2ff026d2a..b7bb05b93 100644 --- a/src/config.ts +++ b/src/config.ts @@ -40,10 +40,7 @@ export namespace config { /* @internal */ export function firebaseConfig(): firebase.AppOptions | null { - // The FIREBASE_PROJECT environment variable was introduced to help local emulation with `firebase-tools` 3.18 - // Unfortunately, API review decided that the name should be FIREBASE_CONFIG to avoid confusions that Firebase has - // a separate project from Google Cloud. This accepts both versions, preferring the documented name. - const env = process.env.FIREBASE_CONFIG || process.env.FIREBASE_PROJECT; + const env = process.env.FIREBASE_CONFIG; if (env) { return JSON.parse(env); } diff --git a/src/providers/https.ts b/src/providers/https.ts index 2ad40a195..315ce0768 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -169,16 +169,11 @@ export class HttpsError extends Error { /** * Extra data to be converted to JSON and included in the error response. */ - readonly details?: any; + readonly details?: unknown; - constructor(code: FunctionsErrorCode, message: string, details?: any) { + constructor(code: FunctionsErrorCode, message: string, details?: unknown) { super(message); - // This is a workaround for a bug in TypeScript when extending Error: - // tslint:disable-next-line - // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, HttpsError.prototype); - if (!errorCodeMap[code]) { throw new Error('Unknown error status: ' + code); } diff --git a/tsconfig.json b/tsconfig.json index 04fc40239..b99abf115 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,12 @@ { "compilerOptions": { - "lib": ["es6"], + "lib": ["es2017"], "module": "commonjs", "noImplicitAny": false, "outDir": ".tmp", + "resolveJsonModule": true, "sourceMap": true, - "target": "es6", - "typeRoots": [ - "node_modules/@types" - ] + "target": "es2017" }, - "include": [ - "src/**/*.ts", - "spec/**/*.ts" - ] + "include": ["src/**/*.ts", "spec/**/*.ts"] } diff --git a/tsconfig.release.json b/tsconfig.release.json index b114d4697..58d0d8e8f 100644 --- a/tsconfig.release.json +++ b/tsconfig.release.json @@ -1,18 +1,13 @@ { "compilerOptions": { "declaration": true, - "lib": ["es6"], + "lib": ["es2017"], "module": "commonjs", "noImplicitAny": false, "outDir": "lib", "stripInternal": true, - "target": "es6", - "typeRoots": [ - "node_modules/@types" - ] + "target": "es2017", + "typeRoots": ["node_modules/@types"] }, - "files": [ - "src/index.ts", - "src/testing.ts" - ] + "files": ["src/index.ts", "src/testing.ts"] } diff --git a/upgrade-warning b/upgrade-warning deleted file mode 100644 index 9f2ecc220..000000000 --- a/upgrade-warning +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env node - -'use strict'; - -const message = ` -======== WARNING! ======== - -This upgrade of firebase-functions contains breaking changes if you are upgrading from a version below v1.0.0. - -To see a complete list of these breaking changes, please go to: - -https://firebase.google.com/docs/functions/beta-v1-diff -`; - -console.log(message); From c4ff97293e1409bbec25447a8d16d1d3cae6bd8a Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Fri, 7 Jun 2019 16:41:17 -0700 Subject: [PATCH 021/437] Remove Node 6 from Integration Tests (#459) * update integration tests: remove node6, add node10 * add tracking bug for Node 10, move functions around * nit fix --- ...package.node6.json => package.node10.json} | 6 +- integration_test/package.node8.json | 4 +- integration_test/run_tests.sh | 65 ++++++++++--------- 3 files changed, 38 insertions(+), 37 deletions(-) rename integration_test/{package.node6.json => package.node10.json} (91%) diff --git a/integration_test/package.node6.json b/integration_test/package.node10.json similarity index 91% rename from integration_test/package.node6.json rename to integration_test/package.node10.json index f7ae06056..b90ff8272 100644 --- a/integration_test/package.node6.json +++ b/integration_test/package.node10.json @@ -8,7 +8,7 @@ "@google-cloud/pubsub": "~0.19.0", "@types/google-cloud__pubsub": "^0.18.0", "@types/lodash": "~4.14.41", - "firebase-admin": "^7.0.0", + "firebase-admin": "^8.0.0", "firebase-functions": "./firebase-functions.tgz", "lodash": "~4.17.2" }, @@ -17,7 +17,7 @@ "typescript": "~3.1.0" }, "engines": { - "node": "6" + "node": "10" }, "private": true -} +} \ No newline at end of file diff --git a/integration_test/package.node8.json b/integration_test/package.node8.json index 18a6a9f60..31a46b8aa 100644 --- a/integration_test/package.node8.json +++ b/integration_test/package.node8.json @@ -8,7 +8,7 @@ "@google-cloud/pubsub": "~0.19.0", "@types/google-cloud__pubsub": "^0.18.0", "@types/lodash": "~4.14.41", - "firebase-admin": "^7.0.0", + "firebase-admin": "^8.0.0", "firebase-functions": "./firebase-functions.tgz", "lodash": "~4.17.2" }, @@ -20,4 +20,4 @@ "node": "8" }, "private": true -} +} \ No newline at end of file diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index 8721842c7..5da3a4bfb 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -4,24 +4,24 @@ set -e function usage { - echo "Usage: $0 []" + echo "Usage: $0 []" exit 1 } -# This script takes 1 or 2 params, both of which are Firebase project ids. -# If there is only one given, that project will be used for both node6 and node8 -# Otherwise, param1 will be used for node6 -# and param2 will be used for node8 -# The first parameter is required and is the Firebase project id. +# This script takes 1 or 2 arguments, both of which are Firebase project ids. +# If only one argument is given, that project will be used for both node 8 and node 10 +# Otherwise, first argument will be used for node 8 and second argument will be used +# for node 10. +# Note that at least one argument is required. if [[ $1 == "" ]]; then usage fi if [[ $2 == "" ]]; then - PROJECT_ID_NODE_6=$1 PROJECT_ID_NODE_8=$1 + PROJECT_ID_NODE_10=$1 else - PROJECT_ID_NODE_6=$1 - PROJECT_ID_NODE_8=$2 + PROJECT_ID_NODE_8=$1 + PROJECT_ID_NODE_10=$2 fi # Directory where this script lives. @@ -39,18 +39,18 @@ function build_sdk { mv firebase-functions-*.tgz integration_test/functions/firebase-functions.tgz } -function pick_node6 { - cd $DIR - PROJECT_ID=$PROJECT_ID_NODE_6 - cp package.node6.json functions/package.json -} - function pick_node8 { cd $DIR PROJECT_ID=$PROJECT_ID_NODE_8 cp package.node8.json functions/package.json } +function pick_node10 { + cd $DIR + PROJECT_ID=$PROJECT_ID_NODE_10 + cp package.node10.json functions/package.json +} + function install_deps { announce "Installing dependencies..." cd $DIR/functions @@ -63,9 +63,9 @@ function delete_all_functions { cd $DIR # Try to delete, if there are errors it is because the project is already empty, # in that case do nothing. - firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID_NODE_6 || : & - if ! [[ $PROJECT_ID_NODE_6 == $PROJECT_ID_NODE_8 ]]; then - firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID_NODE_8 || : & + firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID_NODE_8 || : & + if ! [[ $PROJECT_ID_NODE_8 == $PROJECT_ID_NODE_10 ]]; then + firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID_NODE_10 || : & fi wait announce "Project emptied." @@ -96,14 +96,14 @@ function run_all_tests { if [[ $FIREBASE_FUNCTIONS_URL == "https://preprod-cloudfunctions.sandbox.googleapis.com" ]]; then TEST_DOMAIN="txcloud.net" fi - TEST_URL_NODE_6="https://us-central1-$PROJECT_ID_NODE_6.$TEST_DOMAIN/integrationTests" TEST_URL_NODE_8="https://us-central1-$PROJECT_ID_NODE_8.$TEST_DOMAIN/integrationTests" - echo $TEST_URL_NODE_6 + TEST_URL_NODE_10="https://us-central1-$PROJECT_ID_NODE_10.$TEST_DOMAIN/integrationTests" echo $TEST_URL_NODE_8 - curl --fail $TEST_URL_NODE_6 & NODE6PID=$! + echo $TEST_URL_NODE_10 curl --fail $TEST_URL_NODE_8 & NODE8PID=$! - wait $NODE6PID && echo 'node 6 passed' || (announce 'Node 6 tests failed'; cleanup; announce 'Tests failed'; exit 1) + curl --fail $TEST_URL_NODE_10 & NODE10PID=$! wait $NODE8PID && echo 'node 8 passed' || (announce 'Node 8 tests failed'; cleanup; announce 'Tests failed'; exit 1) + wait $NODE10PID && echo 'node 10 passed' || (announce 'Node 10 tests failed'; cleanup; announce 'Tests failed'; exit 1) } function run_tests { @@ -137,18 +137,19 @@ install_deps delete_all_functions announce "Deploying functions to Node 8 runtime ..." deploy -if [[ $PROJECT_ID_NODE_6 == $PROJECT_ID_NODE_8 ]]; then +if [[ $PROJECT_ID_NODE_8 == $PROJECT_ID_NODE_10 ]]; then waitForPropagation run_tests fi -pick_node6 -announce "Re-deploying the same functions to Node 6 runtime ..." -deploy -waitForPropagation -if [[ $PROJECT_ID_NODE_6 == $PROJECT_ID_NODE_8 ]]; then - run_tests -else - run_all_tests -fi +# TODO(b/134418760): Uncomment this when Node 10 issues are fixed +# pick_node10 +# announce "Re-deploying the same functions to Node 10 runtime ..." +# deploy +# waitForPropagation +# if [[ $PROJECT_ID_NODE_8 == $PROJECT_ID_NODE_10 ]]; then +# run_tests +# else +# run_all_tests +# fi cleanup announce "All tests pass!" From e6bd9a18c19a261c474422c1dce4c594c043177c Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Wed, 12 Jun 2019 14:26:07 -0700 Subject: [PATCH 022/437] Remove LegacyEvent, change all tests to use new Event format (#460) * remove LegacyEvent flows * fix context in v1beta1 as it looks different - this fixes integration tests also * remove LegacyEvent since it's no longer supported * fixes from review comments, removing unused tests * adding changelog entry --- changelog.txt | 7 +- spec/cloud-functions.spec.ts | 128 +++++------------ spec/providers/analytics.spec.input.ts | 13 +- spec/providers/analytics.spec.ts | 106 +++++++++++--- spec/providers/auth.spec.ts | 174 ++++++----------------- spec/providers/database.spec.ts | 186 +++++++++++++++++-------- spec/providers/firestore.spec.ts | 104 ++++++++------ spec/providers/pubsub.spec.ts | 39 +++++- spec/providers/remoteConfig.spec.ts | 72 +++++++--- spec/providers/storage.spec.ts | 115 +++++++++++++-- src/cloud-functions.ts | 39 +----- src/providers/auth.ts | 6 +- 12 files changed, 553 insertions(+), 436 deletions(-) diff --git a/changelog.txt b/changelog.txt index 6af6b5e23..c6d5ee1dd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,4 @@ -- important - [breaking change] The Firebase Functions SDK no longer supports Node 6. Developers must use Node 8.13.0 or higher. -- important - [breaking change] The Firebase Functions SDK no longer supports Firebase Tools SDK below version 6.8.0. -- important - [breaking change] FIREBASE_PROJECT environment variable is no longer supported and has been supplanted by FIREBASE_CONFIG. +important - [breaking change] The Firebase Functions SDK no longer supports Node 6. Developers must use Node 8.13.0 or higher. +important - [breaking change] The Firebase Functions SDK no longer supports Firebase Tools SDK below version 6.8.0. +important - [breaking change] FIREBASE_PROJECT environment variable is no longer supported and has been supplanted by FIREBASE_CONFIG. +fixed - Fixed bug in Node 10 where events from Node 10 were not parsed properly resulting in undefined. \ No newline at end of file diff --git a/spec/cloud-functions.spec.ts b/spec/cloud-functions.spec.ts index 7a6b6745f..194ce790b 100644 --- a/spec/cloud-functions.spec.ts +++ b/spec/cloud-functions.spec.ts @@ -25,7 +25,6 @@ import { expect } from 'chai'; import { Event, EventContext, - LegacyEvent, makeCloudFunction, MakeCloudFunctionArgs, Change, @@ -69,32 +68,7 @@ describe('makeCloudFunction', () => { }); }); - it('should construct the right context for legacy event format', () => { - let args: any = _.assign({}, cloudFunctionArgs, { - handler: (data: any, context: EventContext) => context, - }); - let cf = makeCloudFunction(args); - let test: LegacyEvent = { - eventId: '00000', - timestamp: '2016-11-04T21:29:03.496Z', - eventType: 'providers/provider/eventTypes/event', - resource: 'resource', - data: 'data', - }; - - return expect(cf(test)).to.eventually.deep.equal({ - eventId: '00000', - timestamp: '2016-11-04T21:29:03.496Z', - eventType: 'mock.provider.mock.event', - resource: { - service: 'service', - name: 'resource', - }, - params: {}, - }); - }); - - it('should construct the right context for new event format', () => { + it('should construct the right context for event', () => { let args: any = _.assign({}, cloudFunctionArgs, { handler: (data: any, context: EventContext) => context, }); @@ -112,7 +86,7 @@ describe('makeCloudFunction', () => { data: 'data', }; - return expect(cf(test)).to.eventually.deep.equal({ + return expect(cf(test.data, test.context)).to.eventually.deep.equal({ eventId: '00000', timestamp: '2016-11-04T21:29:03.496Z', eventType: 'provider.event', @@ -124,41 +98,6 @@ describe('makeCloudFunction', () => { }); }); - it('should handle Node 8 function signature', () => { - let args: any = _.assign({}, cloudFunctionArgs, { - handler: (data: any, context: EventContext) => { - return { data, context }; - }, - }); - process.env.X_GOOGLE_NEW_FUNCTION_SIGNATURE = 'true'; - let cf = makeCloudFunction(args); - delete process.env.X_GOOGLE_NEW_FUNCTION_SIGNATURE; - let testContext = { - eventId: '00000', - timestamp: '2016-11-04T21:29:03.496Z', - eventType: 'provider.event', - resource: { - service: 'provider', - name: 'resource', - }, - }; - let testData = 'data'; - - return expect(cf(testData, testContext)).to.eventually.deep.equal({ - data: 'data', - context: { - eventId: '00000', - timestamp: '2016-11-04T21:29:03.496Z', - eventType: 'provider.event', - resource: { - service: 'provider', - name: 'resource', - }, - params: {}, - }, - }); - }); - it('should throw error when context.params accessed in handler environment', () => { let args: any = _.assign({}, cloudFunctionArgs, { handler: (data: any, context: EventContext) => context, @@ -178,7 +117,7 @@ describe('makeCloudFunction', () => { data: 'test data', }; - return cf(test).then(result => { + return cf(test.data, test.context).then(result => { expect(result).to.deep.equal({ eventId: '00000', timestamp: '2016-11-04T21:29:03.496Z', @@ -204,20 +143,7 @@ describe('makeParams', () => { }; const cf = makeCloudFunction(args); - it('should construct params from the event resource of legacy events', () => { - const testEvent: LegacyEvent = { - resource: 'projects/_/instances/pid/ref/a/nested/b', - eventType: 'legacyEvent', - data: 'data', - }; - - return expect(cf(testEvent)).to.eventually.deep.equal({ - foo: 'a', - bar: 'b', - }); - }); - - it('should construct params from the event resource of new format events', () => { + it('should construct params from the event resource of events', () => { const testEvent: Event = { context: { eventId: '111', @@ -231,7 +157,9 @@ describe('makeParams', () => { data: 'data', }; - return expect(cf(testEvent)).to.eventually.deep.equal({ + return expect( + cf(testEvent.data, testEvent.context) + ).to.eventually.deep.equal({ foo: 'a', bar: 'b', }); @@ -256,12 +184,16 @@ describe('makeAuth and makeAuthType', () => { it('should construct correct auth and authType for admin user', () => { const testEvent = { data: 'data', - auth: { - admin: true, + context: { + auth: { + admin: true, + }, }, }; - return expect(cf(testEvent)).to.eventually.deep.equal({ + return expect( + cf(testEvent.data, testEvent.context) + ).to.eventually.deep.equal({ auth: undefined, authType: 'ADMIN', }); @@ -270,33 +202,41 @@ describe('makeAuth and makeAuthType', () => { it('should construct correct auth and authType for unauthenticated user', () => { const testEvent = { data: 'data', - auth: { - admin: false, + context: { + auth: { + admin: false, + }, }, }; - return expect(cf(testEvent)).to.eventually.deep.equal({ + return expect( + cf(testEvent.data, testEvent.context) + ).to.eventually.deep.equal({ auth: null, authType: 'UNAUTHENTICATED', }); }); it('should construct correct auth and authType for a user', () => { - const testEvent: LegacyEvent = { + const testEvent = { data: 'data', - auth: { - admin: false, - variable: { - uid: 'user', - provider: 'google', - token: { - sub: 'user', + context: { + auth: { + admin: false, + variable: { + uid: 'user', + provider: 'google', + token: { + sub: 'user', + }, }, }, }, }; - return expect(cf(testEvent)).to.eventually.deep.equal({ + return expect( + cf(testEvent.data, testEvent.context) + ).to.eventually.deep.equal({ auth: { uid: 'user', token: { diff --git a/spec/providers/analytics.spec.input.ts b/spec/providers/analytics.spec.input.ts index 84ac4c3ce..bd9768b16 100644 --- a/spec/providers/analytics.spec.input.ts +++ b/spec/providers/analytics.spec.input.ts @@ -117,10 +117,15 @@ export const fullPayload = JSON.parse(`{ } } }, - "eventId": "1486080145623867projects/analytics-integration-fd82a/events/i_made_this_upproviders/google.firebase.analytics/eventTypes/event.sendprojects/f949d1bb9ef782579-tp/topics/cloud-functions-u54ejabpzs4prfjh7433eklhae", - "eventType": "providers/google.firebase.analytics/eventTypes/event.send", - "resource": "projects/analytics-integration-fd82a/events/i_made_this_up", - "timestamp": "2017-03-29T23:59:59.986371388Z" + "context": { + "eventId": "1486080145623867projects/analytics-integration-fd82a/events/i_made_this_upproviders/google.firebase.analytics/eventTypes/event.sendprojects/f949d1bb9ef782579-tp/topics/cloud-functions-u54ejabpzs4prfjh7433eklhae", + "eventType": "providers/google.firebase.analytics/eventTypes/event.send", + "timestamp": "2017-03-29T23:59:59.986371388Z", + "resource": { + "service": "app-measurement.com", + "name": "projects/analytics-integration-fd82a/events/i_made_this_up" + } + } }`); // The event data that we expect would be constructed if the payload above were to arrive. diff --git a/spec/providers/analytics.spec.ts b/spec/providers/analytics.spec.ts index 48babba34..cdb0ba02c 100644 --- a/spec/providers/analytics.spec.ts +++ b/spec/providers/analytics.spec.ts @@ -22,7 +22,7 @@ import * as analytics from '../../src/providers/analytics'; import { expect } from 'chai'; -import { LegacyEvent } from '../../src/cloud-functions'; +import { EventContext, Event } from '../../src/cloud-functions'; import * as analytics_spec_input from './analytics.spec.input'; import * as functions from '../../src/index'; @@ -69,22 +69,33 @@ describe('Analytics Functions', () => { it('should handle an event with the appropriate fields', () => { const cloudFunction = analytics .event('first_open') - .onLog((data: analytics.AnalyticsEvent) => data); + .onLog( + (data: analytics.AnalyticsEvent, context: EventContext) => data + ); // The event data delivered over the wire will be the JSON for an AnalyticsEvent: // https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data - let event: LegacyEvent = { - eventId: 'f2e2f0bf-2e47-4d92-b009-e7a375ecbd3e', - eventType: 'providers/google.firebase.analytics/eventTypes/event.log', - resource: 'projects/myUnitTestProject/events/first_open', + let event: Event = { data: { userDim: { userId: 'hi!', }, }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: + 'providers/google.firebase.analytics/eventTypes/event.log', + resource: { + service: 'app-measurement.com', + name: 'projects/project1/events/first_open', + }, + }, }; - return expect(cloudFunction(event)).to.eventually.deep.equal({ + return expect( + cloudFunction(event.data, event.context) + ).to.eventually.deep.equal({ params: {}, user: { userId: 'hi!', @@ -96,12 +107,14 @@ describe('Analytics Functions', () => { it('should remove xValues', () => { const cloudFunction = analytics .event('first_open') - .onLog((data: analytics.AnalyticsEvent) => data); + .onLog( + (data: analytics.AnalyticsEvent, context: EventContext) => data + ); // Incoming events will have four kinds of "xValue" fields: "intValue", // "stringValue", "doubleValue" and "floatValue". We expect those to get // flattened away, leaving just their values. - let event: LegacyEvent = { + let event: Event = { data: { eventDim: [ { @@ -133,9 +146,21 @@ describe('Analytics Functions', () => { }, }, }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: + 'providers/google.firebase.analytics/eventTypes/event.log', + resource: { + service: 'app-measurement.com', + name: 'projects/project1/events/first_open', + }, + }, }; - return expect(cloudFunction(event)).to.eventually.deep.equal({ + return expect( + cloudFunction(event.data, event.context) + ).to.eventually.deep.equal({ reportingDate: '20170202', name: 'Loaded_In_Background', params: { @@ -159,7 +184,7 @@ describe('Analytics Functions', () => { .event('first_open') .onLog((data: analytics.AnalyticsEvent) => data); - let event: LegacyEvent = { + let event: Event = { data: { eventDim: [ { @@ -181,9 +206,21 @@ describe('Analytics Functions', () => { }, }, }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: + 'providers/google.firebase.analytics/eventTypes/event.log', + resource: { + service: 'app-measurement.com', + name: 'projects/project1/events/first_open', + }, + }, }; - return expect(cloudFunction(event)).to.eventually.deep.equal({ + return expect( + cloudFunction(event.data, event.context) + ).to.eventually.deep.equal({ reportingDate: '20170202', name: 'Loaded_In_Background', params: {}, @@ -217,7 +254,7 @@ describe('Analytics Functions', () => { // // Separately, the input has a number of microsecond timestamps that we'd // like to rename and scale down to milliseconds. - let event: LegacyEvent = { + let event: Event = { data: { eventDim: [ { @@ -227,9 +264,21 @@ describe('Analytics Functions', () => { }, ], }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: + 'providers/google.firebase.analytics/eventTypes/event.log', + resource: { + service: 'app-measurement.com', + name: 'projects/project1/events/first_open', + }, + }, }; - return expect(cloudFunction(event)).to.eventually.deep.equal({ + return expect( + cloudFunction(event.data, event.context) + ).to.eventually.deep.equal({ reportingDate: '20170202', name: 'Loaded_In_Background', params: {}, @@ -242,9 +291,11 @@ describe('Analytics Functions', () => { .event('first_open') .onLog((data: analytics.AnalyticsEvent) => data); // The payload in analytics_spec_input contains all possible fields at least once. - return expect( - cloudFunction(analytics_spec_input.fullPayload) - ).to.eventually.deep.equal(analytics_spec_input.data); + const data = analytics_spec_input.fullPayload.data; + const context = analytics_spec_input.fullPayload.context; + return expect(cloudFunction(data, context)).to.eventually.deep.equal( + analytics_spec_input.data + ); }); }); }); @@ -260,23 +311,32 @@ describe('Analytics Functions', () => { it('should handle an event with the appropriate fields', () => { const cloudFunction = functions.handler.analytics.event.onLog( - (data: analytics.AnalyticsEvent) => data + (data: analytics.AnalyticsEvent, context: EventContext) => data ); // The event data delivered over the wire will be the JSON for an AnalyticsEvent: // https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data - let event: LegacyEvent = { - eventId: 'f2e2f0bf-2e47-4d92-b009-e7a375ecbd3e', - eventType: 'providers/google.firebase.analytics/eventTypes/event.log', - resource: 'projects/myUnitTestProject/events/first_open', + let event: Event = { data: { userDim: { userId: 'hi!', }, }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: + 'providers/google.firebase.analytics/eventTypes/event.log', + resource: { + service: 'app-measurement.com', + name: 'projects/project1/events/first_open', + }, + }, }; - return expect(cloudFunction(event)).to.eventually.deep.equal({ + return expect( + cloudFunction(event.data, event.context) + ).to.eventually.deep.equal({ params: {}, user: { userId: 'hi!', diff --git a/spec/providers/auth.spec.ts b/spec/providers/auth.spec.ts index af175466e..c5300364d 100644 --- a/spec/providers/auth.spec.ts +++ b/spec/providers/auth.spec.ts @@ -23,10 +23,28 @@ import { expect } from 'chai'; import * as firebase from 'firebase-admin'; import * as auth from '../../src/providers/auth'; -import { CloudFunction } from '../../src/cloud-functions'; +import { CloudFunction, EventContext, Event } from '../../src/cloud-functions'; import * as functions from '../../src/index'; describe('Auth Functions', () => { + const event: Event = { + data: { + metadata: { + creationTime: '2016-12-15T19:37:37.059Z', + lastSignInTime: '2017-01-01T00:00:00.000Z', + }, + }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'providers/firebase.auth/eventTypes/user.delete', + resource: { + service: 'firebaseauth.googleapis.com', + name: 'projects/project1', + }, + }, + }; + describe('AuthBuilder', () => { let handler: (user: firebase.auth.UserRecord) => PromiseLike | any; @@ -82,74 +100,31 @@ describe('Auth Functions', () => { describe('#_dataConstructor', () => { let cloudFunctionCreate: CloudFunction; let cloudFunctionDelete: CloudFunction; - let event: any; before(() => { cloudFunctionCreate = auth .user() - .onCreate((data: firebase.auth.UserRecord) => data); + .onCreate( + (data: firebase.auth.UserRecord, context: EventContext) => data + ); cloudFunctionDelete = auth .user() - .onDelete((data: firebase.auth.UserRecord) => data); - event = { - data: { - metadata: { - createdAt: '2016-12-15T19:37:37.059Z', - lastSignedInAt: '2017-01-01T00:00:00.000Z', - }, - }, - }; - }); - - it('should transform wire format for UserRecord into v5.0.0 format', () => { - return Promise.all([ - cloudFunctionCreate(event).then((data: any) => { - expect(data.metadata.creationTime).to.equal( - '2016-12-15T19:37:37.059Z' - ); - expect(data.metadata.lastSignInTime).to.equal( - '2017-01-01T00:00:00.000Z' - ); - }), - cloudFunctionDelete(event).then((data: any) => { - expect(data.metadata.creationTime).to.equal( - '2016-12-15T19:37:37.059Z' - ); - expect(data.metadata.lastSignInTime).to.equal( - '2017-01-01T00:00:00.000Z' - ); - }), - ]); + .onDelete( + (data: firebase.auth.UserRecord, context: EventContext) => data + ); }); - it('should handle new wire format if/when there is a change', () => { - const newEvent = { - data: { - metadata: { - creationTime: '2016-12-15T19:37:37.059Z', - lastSignInTime: '2017-01-01T00:00:00.000Z', - }, - }, - }; - - return Promise.all([ - cloudFunctionCreate(newEvent).then((data: any) => { - expect(data.metadata.creationTime).to.equal( - '2016-12-15T19:37:37.059Z' - ); - expect(data.metadata.lastSignInTime).to.equal( - '2017-01-01T00:00:00.000Z' - ); - }), - cloudFunctionDelete(newEvent).then((data: any) => { + it('should handle wire format as of v5.0.0 of firebase-admin', () => { + return cloudFunctionDelete(event.data, event.context).then( + (data: any) => { expect(data.metadata.creationTime).to.equal( '2016-12-15T19:37:37.059Z' ); expect(data.metadata.lastSignInTime).to.equal( '2017-01-01T00:00:00.000Z' ); - }), - ]); + } + ); }); }); }); @@ -213,46 +188,6 @@ describe('Auth Functions', () => { const cloudFunction = functions.handler.auth.user.onCreate(() => null); expect(cloudFunction.__trigger).to.deep.equal({}); }); - - it('should transform wire format for UserRecord into v5.0.0 format', () => { - const event = { - data: { - metadata: { - createdAt: '2016-12-15T19:37:37.059Z', - lastSignedInAt: '2017-01-01T00:00:00.000Z', - }, - }, - }; - - return cloudFunctionCreate(event).then((data: any) => { - expect(data.metadata.creationTime).to.equal( - '2016-12-15T19:37:37.059Z' - ); - expect(data.metadata.lastSignInTime).to.equal( - '2017-01-01T00:00:00.000Z' - ); - }); - }); - - it('should handle new wire format if/when there is a change', () => { - const newEvent = { - data: { - metadata: { - creationTime: '2016-12-15T19:37:37.059Z', - lastSignInTime: '2017-01-01T00:00:00.000Z', - }, - }, - }; - - return cloudFunctionCreate(newEvent).then((data: any) => { - expect(data.metadata.creationTime).to.equal( - '2016-12-15T19:37:37.059Z' - ); - expect(data.metadata.lastSignInTime).to.equal( - '2017-01-01T00:00:00.000Z' - ); - }); - }); }); describe('#onDelete', () => { @@ -268,44 +203,17 @@ describe('Auth Functions', () => { expect(cloudFunction.__trigger).to.deep.equal({}); }); - it('should transform wire format for UserRecord into v5.0.0 format', () => { - const event = { - data: { - metadata: { - createdAt: '2016-12-15T19:37:37.059Z', - lastSignedInAt: '2017-01-01T00:00:00.000Z', - }, - }, - }; - - return cloudFunctionDelete(event).then((data: any) => { - expect(data.metadata.creationTime).to.equal( - '2016-12-15T19:37:37.059Z' - ); - expect(data.metadata.lastSignInTime).to.equal( - '2017-01-01T00:00:00.000Z' - ); - }); - }); - - it('should handle new wire format if/when there is a change', () => { - const newEvent = { - data: { - metadata: { - creationTime: '2016-12-15T19:37:37.059Z', - lastSignInTime: '2017-01-01T00:00:00.000Z', - }, - }, - }; - - return cloudFunctionDelete(newEvent).then((data: any) => { - expect(data.metadata.creationTime).to.equal( - '2016-12-15T19:37:37.059Z' - ); - expect(data.metadata.lastSignInTime).to.equal( - '2017-01-01T00:00:00.000Z' - ); - }); + it('should handle wire format as of v5.0.0 of firebase-admin', () => { + return cloudFunctionDelete(event.data, event.context).then( + (data: any) => { + expect(data.metadata.creationTime).to.equal( + '2016-12-15T19:37:37.059Z' + ); + expect(data.metadata.lastSignInTime).to.equal( + '2017-01-01T00:00:00.000Z' + ); + } + ); }); }); }); diff --git a/spec/providers/database.spec.ts b/spec/providers/database.spec.ts index f85225b1f..5fa1307fd 100644 --- a/spec/providers/database.spec.ts +++ b/spec/providers/database.spec.ts @@ -25,6 +25,7 @@ import { expect } from 'chai'; import { apps as appsNamespace } from '../../src/apps'; import { applyChange } from '../../src/utils'; import * as functions from '../../src/index'; +import { Event } from '../../src/index'; describe('Database Functions', () => { describe('DatabaseBuilder', () => { @@ -82,18 +83,24 @@ describe('Database Functions', () => { }); it('should return a handler that emits events with a proper DataSnapshot', () => { - let handler = database.ref('/users/{id}').onWrite(change => { - expect(change.after.val()).to.deep.equal({ foo: 'bar' }); - }); - - return handler({ + const event = { data: { data: null, delta: { foo: 'bar' }, }, - resource: 'projects/_/instances/subdomains/refs/users', - eventType: 'providers/google.firebase.database/eventTypes/ref.write', + context: { + eventId: '70172329041928', + eventType: + 'providers/google.firebase.database/eventTypes/ref.write', + timestamp: '2018-04-09T07:56:12.975Z', + resource: 'projects/_/instances/subdomains/refs/users', + }, + }; + let handler = database.ref('/users/{id}').onWrite((change, context) => { + expect(change.after.val()).to.deep.equal({ foo: 'bar' }); }); + + return handler(event.data, event.context); }); }); @@ -122,18 +129,25 @@ describe('Database Functions', () => { }); it('should return a handler that emits events with a proper DataSnapshot', () => { - let handler = database.ref('/users/{id}').onCreate(data => { - expect(data.val()).to.deep.equal({ foo: 'bar' }); - }); - - return handler({ + const event = { data: { data: null, delta: { foo: 'bar' }, }, - resource: 'projects/_/instances/subdomains/refs/users', - eventType: 'providers/google.firebase.database/eventTypes/ref.create', + context: { + eventId: '70172329041928', + eventType: + 'providers/google.firebase.database/eventTypes/ref.create', + timestamp: '2018-04-09T07:56:12.975Z', + resource: 'projects/_/instances/subdomains/refs/users', + }, + }; + + let handler = database.ref('/users/{id}').onCreate((data, context) => { + expect(data.val()).to.deep.equal({ foo: 'bar' }); }); + + return handler(event.data, event.context); }); }); @@ -162,18 +176,27 @@ describe('Database Functions', () => { }); it('should return a handler that emits events with a proper DataSnapshot', () => { - let handler = database.ref('/users/{id}').onUpdate(change => { - expect(change.after.val()).to.deep.equal({ foo: 'bar' }); - }); - - return handler({ + const event = { data: { data: null, delta: { foo: 'bar' }, }, - resource: 'projects/_/instances/subdomains/refs/users', - eventType: 'providers/google.firebase.database/eventTypes/ref.update', - }); + context: { + eventId: '70172329041928', + eventType: + 'providers/google.firebase.database/eventTypes/ref.update', + timestamp: '2018-04-09T07:56:12.975Z', + resource: 'projects/_/instances/subdomains/refs/users', + }, + }; + + let handler = database + .ref('/users/{id}') + .onUpdate((change, context) => { + expect(change.after.val()).to.deep.equal({ foo: 'bar' }); + }); + + return handler(event.data, event.context); }); }); @@ -202,18 +225,25 @@ describe('Database Functions', () => { }); it('should return a handler that emits events with a proper DataSnapshot', () => { - let handler = database.ref('/users/{id}').onDelete(data => { - expect(data.val()).to.deep.equal({ foo: 'bar' }); - }); - - return handler({ + const event = { data: { data: { foo: 'bar' }, delta: null, }, - resource: 'projects/_/instances/subdomains/refs/users', - eventType: 'providers/google.firebase.database/eventTypes/ref.delete', + context: { + eventId: '70172329041928', + eventType: + 'providers/google.firebase.database/eventTypes/ref.delete', + timestamp: '2018-04-09T07:56:12.975Z', + resource: 'projects/_/instances/subdomains/refs/users', + }, + }; + + let handler = database.ref('/users/{id}').onDelete((data, context) => { + expect(data.val()).to.deep.equal({ foo: 'bar' }); }); + + return handler(event.data, event.context); }); }); }); @@ -231,18 +261,27 @@ describe('Database Functions', () => { }); it('should return a handler that emits events with a proper DataSnapshot', () => { - let handler = functions.handler.database.ref.onWrite(change => { - return expect(change.after.val()).to.deep.equal({ foo: 'bar' }); - }); - - return handler({ + const event = { data: { data: null, delta: { foo: 'bar' }, }, - resource: 'projects/_/instances/subdomains/refs/users', - eventType: 'providers/google.firebase.database/eventTypes/ref.write', - }); + context: { + eventId: '70172329041928', + eventType: + 'providers/google.firebase.database/eventTypes/ref.write', + timestamp: '2018-04-09T07:56:12.975Z', + resource: 'projects/_/instances/subdomains/refs/users', + }, + }; + + let handler = functions.handler.database.ref.onWrite( + (change, context) => { + return expect(change.after.val()).to.deep.equal({ foo: 'bar' }); + } + ); + + return handler(event.data, event.context); }); }); @@ -258,18 +297,26 @@ describe('Database Functions', () => { }); it('should return a handler that emits events with a proper DataSnapshot', () => { - let handler = functions.handler.database.ref.onCreate(data => { - return expect(data.val()).to.deep.equal({ foo: 'bar' }); - }); - - return handler({ + const event = { data: { data: null, delta: { foo: 'bar' }, }, - resource: 'projects/_/instances/subdomains/refs/users', - eventType: 'providers/google.firebase.database/eventTypes/ref.create', - }); + context: { + eventId: '70172329041928', + eventType: + 'providers/google.firebase.database/eventTypes/ref.create', + timestamp: '2018-04-09T07:56:12.975Z', + resource: 'projects/_/instances/subdomains/refs/users', + }, + }; + let handler = functions.handler.database.ref.onCreate( + (data, context) => { + return expect(data.val()).to.deep.equal({ foo: 'bar' }); + } + ); + + return handler(event.data, event.context); }); }); @@ -285,18 +332,26 @@ describe('Database Functions', () => { }); it('should return a handler that emits events with a proper DataSnapshot', () => { - let handler = functions.handler.database.ref.onUpdate(change => { - return expect(change.after.val()).to.deep.equal({ foo: 'bar' }); - }); - - return handler({ + const event = { data: { data: null, delta: { foo: 'bar' }, }, - resource: 'projects/_/instances/subdomains/refs/users', - eventType: 'providers/google.firebase.database/eventTypes/ref.update', - }); + context: { + eventId: '70172329041928', + eventType: + 'providers/google.firebase.database/eventTypes/ref.update', + timestamp: '2018-04-09T07:56:12.975Z', + resource: 'projects/_/instances/subdomains/refs/users', + }, + }; + let handler = functions.handler.database.ref.onUpdate( + (change, context) => { + return expect(change.after.val()).to.deep.equal({ foo: 'bar' }); + } + ); + + return handler(event.data, event.context); }); }); @@ -312,18 +367,27 @@ describe('Database Functions', () => { }); it('should return a handler that emits events with a proper DataSnapshot', () => { - let handler = functions.handler.database.ref.onDelete(data => { - return expect(data.val()).to.deep.equal({ foo: 'bar' }); - }); - - return handler({ + const event = { data: { data: { foo: 'bar' }, delta: null, }, - resource: 'projects/_/instances/subdomains/refs/users', - eventType: 'providers/google.firebase.database/eventTypes/ref.delete', - }); + context: { + eventId: '70172329041928', + eventType: + 'providers/google.firebase.database/eventTypes/ref.delete', + timestamp: '2018-04-09T07:56:12.975Z', + resource: 'projects/_/instances/subdomains/refs/users', + }, + }; + + let handler = functions.handler.database.ref.onDelete( + (data, context) => { + return expect(data.val()).to.deep.equal({ foo: 'bar' }); + } + ); + + return handler(event.data, event.context); }); }); }); diff --git a/spec/providers/firestore.spec.ts b/spec/providers/firestore.spec.ts index 16901fe88..e43c1f2d7 100644 --- a/spec/providers/firestore.spec.ts +++ b/spec/providers/firestore.spec.ts @@ -231,55 +231,69 @@ describe('Firestore Functions', () => { }); it('constructs appropriate fields and getters for event.data on "document.write" events', () => { - let testFunction = firestore.document('path').onWrite(change => { - expect(change.before.data()).to.deep.equal({ key1: false, key2: 111 }); - expect(change.before.get('key1')).to.equal(false); - expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); - expect(change.after.get('key1')).to.equal(true); - return true; // otherwise will get warning about returning undefined - }); + let testFunction = firestore + .document('path') + .onWrite((change, context) => { + expect(change.before.data()).to.deep.equal({ + key1: false, + key2: 111, + }); + expect(change.before.get('key1')).to.equal(false); + expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); + expect(change.after.get('key1')).to.equal(true); + return true; // otherwise will get warning about returning undefined + }); let data = constructEvent( createOldValue(), createValue(), 'document.write' ); - return testFunction(data); + return testFunction(data.data, data.context); }).timeout(5000); it('constructs appropriate fields and getters for event.data on "document.create" events', () => { - let testFunction = firestore.document('path').onCreate(data => { - expect(data.data()).to.deep.equal({ key1: true, key2: 123 }); - expect(data.get('key1')).to.equal(true); - return true; // otherwise will get warning about returning undefined - }); + let testFunction = firestore + .document('path') + .onCreate((data, context) => { + expect(data.data()).to.deep.equal({ key1: true, key2: 123 }); + expect(data.get('key1')).to.equal(true); + return true; // otherwise will get warning about returning undefined + }); let data = constructEvent({}, createValue(), 'document.create'); - return testFunction(data); + return testFunction(data.data, data.context); }).timeout(5000); it('constructs appropriate fields and getters for event.data on "document.update" events', () => { - let testFunction = firestore.document('path').onUpdate(change => { - expect(change.before.data()).to.deep.equal({ key1: false, key2: 111 }); - expect(change.before.get('key1')).to.equal(false); - expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); - expect(change.after.get('key1')).to.equal(true); - return true; // otherwise will get warning about returning undefined - }); + let testFunction = firestore + .document('path') + .onUpdate((change, context) => { + expect(change.before.data()).to.deep.equal({ + key1: false, + key2: 111, + }); + expect(change.before.get('key1')).to.equal(false); + expect(change.after.data()).to.deep.equal({ key1: true, key2: 123 }); + expect(change.after.get('key1')).to.equal(true); + return true; // otherwise will get warning about returning undefined + }); let data = constructEvent( createOldValue(), createValue(), 'document.update' ); - return testFunction(data); + return testFunction(data.data, data.context); }).timeout(5000); it('constructs appropriate fields and getters for event.data on "document.delete" events', () => { - let testFunction = firestore.document('path').onDelete(data => { - expect(data.data()).to.deep.equal({ key1: false, key2: 111 }); - expect(data.get('key1')).to.equal(false); - return true; // otherwise will get warning about returning undefined - }); + let testFunction = firestore + .document('path') + .onDelete((data, context) => { + expect(data.data()).to.deep.equal({ key1: false, key2: 111 }); + expect(data.get('key1')).to.equal(false); + return true; // otherwise will get warning about returning undefined + }); let data = constructEvent(createOldValue(), {}, 'document.delete'); - return testFunction(data); + return testFunction(data.data, data.context); }).timeout(5000); }); @@ -294,7 +308,7 @@ describe('Firestore Functions', () => { it('constructs correct data type and sets trigger to {} on "document.write" events', () => { let testFunction = functions.handler.firestore.document.onWrite( - change => { + (change, context) => { expect(change.before.data()).to.deep.equal({ key1: false, key2: 111, @@ -311,18 +325,20 @@ describe('Firestore Functions', () => { createValue(), 'document.write' ); - return testFunction(data); + return testFunction(data.data, data.context); }).timeout(5000); it('constructs correct data type and sets trigger to {} on "document.create" events', () => { - let testFunction = functions.handler.firestore.document.onCreate(data => { - expect(data.data()).to.deep.equal({ key1: true, key2: 123 }); - expect(data.get('key1')).to.equal(true); - return true; // otherwise will get warning about returning undefined - }); + let testFunction = functions.handler.firestore.document.onCreate( + (data, context) => { + expect(data.data()).to.deep.equal({ key1: true, key2: 123 }); + expect(data.get('key1')).to.equal(true); + return true; // otherwise will get warning about returning undefined + } + ); expect(testFunction.__trigger).to.deep.equal({}); let data = constructEvent({}, createValue(), 'document.create'); - return testFunction(data); + return testFunction(data.data, data.context); }).timeout(5000); it('constructs correct data type and sets trigger to {} on "document.update" events', () => { @@ -344,18 +360,20 @@ describe('Firestore Functions', () => { createValue(), 'document.update' ); - return testFunction(data); + return testFunction(data.data, data.context); }).timeout(5000); it('constructs correct data type and sets trigger to {} on "document.delete" events', () => { - let testFunction = functions.handler.firestore.document.onDelete(data => { - expect(data.data()).to.deep.equal({ key1: false, key2: 111 }); - expect(data.get('key1')).to.equal(false); - return true; // otherwise will get warning about returning undefined - }); + let testFunction = functions.handler.firestore.document.onDelete( + (data, context) => { + expect(data.data()).to.deep.equal({ key1: false, key2: 111 }); + expect(data.get('key1')).to.equal(false); + return true; // otherwise will get warning about returning undefined + } + ); let data = constructEvent(createOldValue(), {}, 'document.delete'); expect(testFunction.__trigger).to.deep.equal({}); - return testFunction(data); + return testFunction(data.data, data.context); }).timeout(5000); }); diff --git a/spec/providers/pubsub.spec.ts b/spec/providers/pubsub.spec.ts index 006421974..345e790a6 100644 --- a/spec/providers/pubsub.spec.ts +++ b/spec/providers/pubsub.spec.ts @@ -23,6 +23,7 @@ import { expect } from 'chai'; import * as pubsub from '../../src/providers/pubsub'; import * as functions from '../../src/index'; +import { Event } from '../../src/index'; describe('Pubsub Functions', () => { describe('pubsub.Message', () => { @@ -105,16 +106,25 @@ describe('Pubsub Functions', () => { it('should properly handle a new-style event', () => { const raw = new Buffer('{"hello":"world"}', 'utf8').toString('base64'); - const event = { + const event: Event = { data: { data: raw, attributes: { foo: 'bar', }, }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.pubsub.topic.publish', + resource: { + service: 'pubsub.googleapis.com', + name: 'projects/project1/topics/toppy', + }, + }, }; - const result = pubsub.topic('toppy').onPublish(data => { + const result = pubsub.topic('toppy').onPublish((data, context) => { return { raw: data.data, json: data.json, @@ -122,13 +132,16 @@ describe('Pubsub Functions', () => { }; }); - return expect(result(event)).to.eventually.deep.equal({ + return expect( + result(event.data, event.context) + ).to.eventually.deep.equal({ raw, json: { hello: 'world' }, attributes: { foo: 'bar' }, }); }); }); + describe('#schedule', () => { it('should return a trigger with schedule', () => { let result = pubsub.schedule('every 5 minutes').onRun(context => null); @@ -136,6 +149,7 @@ describe('Pubsub Functions', () => { schedule: 'every 5 minutes', }); }); + it('should return a trigger with schedule and timeZone when one is chosen', () => { let result = pubsub .schedule('every 5 minutes') @@ -146,6 +160,7 @@ describe('Pubsub Functions', () => { timeZone: 'America/New_York', }); }); + it('should return a trigger with schedule and retry config when called with retryConfig', () => { let retryConfig = { retryCount: 3, @@ -166,6 +181,7 @@ describe('Pubsub Functions', () => { 'deployment-scheduled': 'true', }); }); + it('should return a trigger with schedule, timeZone and retry config when called with retryConfig and timeout', () => { let retryConfig = { retryCount: 3, @@ -188,6 +204,7 @@ describe('Pubsub Functions', () => { 'deployment-scheduled': 'true', }); }); + it('should return an appropriate trigger when called with region and options', () => { let result = functions .region('us-east1') @@ -204,6 +221,7 @@ describe('Pubsub Functions', () => { expect(result.__trigger.availableMemoryMb).to.deep.equal(256); expect(result.__trigger.timeout).to.deep.equal('90s'); }); + it('should return an appropriate trigger when called with region, timeZone, and options', () => { let result = functions .region('us-east1') @@ -222,6 +240,7 @@ describe('Pubsub Functions', () => { expect(result.__trigger.availableMemoryMb).to.deep.equal(256); expect(result.__trigger.timeout).to.deep.equal('90s'); }); + it('should return an appropriate trigger when called with region, options and retryConfig', () => { let retryConfig = { retryCount: 3, @@ -250,6 +269,7 @@ describe('Pubsub Functions', () => { expect(result.__trigger.availableMemoryMb).to.deep.equal(256); expect(result.__trigger.timeout).to.deep.equal('90s'); }); + it('should return an appropriate trigger when called with region, options, retryConfig, and timeZone', () => { let retryConfig = { retryCount: 3, @@ -299,6 +319,15 @@ describe('Pubsub Functions', () => { foo: 'bar', }, }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.pubsub.topic.publish', + resource: { + service: 'pubsub.googleapis.com', + name: 'projects/project1/topics/toppy', + }, + }, }; const result = functions.handler.pubsub.topic.onPublish(data => { @@ -309,7 +338,9 @@ describe('Pubsub Functions', () => { }; }); - return expect(result(event)).to.eventually.deep.equal({ + return expect( + result(event.data, event.context) + ).to.eventually.deep.equal({ raw, json: { hello: 'world' }, attributes: { foo: 'bar' }, diff --git a/spec/providers/remoteConfig.spec.ts b/spec/providers/remoteConfig.spec.ts index 5ff3d9e69..97e74b593 100644 --- a/spec/providers/remoteConfig.spec.ts +++ b/spec/providers/remoteConfig.spec.ts @@ -20,10 +20,14 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import { expect } from 'chai'; -import * as admin from 'firebase-admin'; import * as _ from 'lodash'; -import { CloudFunction } from '../../src/cloud-functions'; +import { + CloudFunction, + Event, + TriggerAnnotated, + EventContext, +} from '../../src/cloud-functions'; import * as functions from '../../src/index'; import * as remoteConfig from '../../src/providers/remoteConfig'; @@ -42,7 +46,7 @@ describe('RemoteConfig Functions', () => { }; } - function makeEvent(data: any, context?: { [key: string]: any }) { + function makeEvent(data: any, context: { [key: string]: any }): Event { context = context || {}; return { data: data, @@ -62,12 +66,14 @@ describe('RemoteConfig Functions', () => { } describe('#onUpdate', () => { - function expectedTrigger() { + function expectedTrigger(): TriggerAnnotated { return { - eventTrigger: { - resource: 'projects/project1', - eventType: 'google.firebase.remoteconfig.update', - service: 'firebaseremoteconfig.googleapis.com', + __trigger: { + eventTrigger: { + resource: 'projects/project1', + eventType: 'google.firebase.remoteconfig.update', + service: 'firebaseremoteconfig.googleapis.com', + }, }, }; } @@ -82,7 +88,9 @@ describe('RemoteConfig Functions', () => { it('should have the correct trigger', () => { const cloudFunction = remoteConfig.onUpdate(() => null); - expect(cloudFunction.__trigger).to.deep.equal(expectedTrigger()); + expect(cloudFunction.__trigger).to.deep.equal( + expectedTrigger().__trigger + ); }); it('should allow both region and runtime options to be set', () => { @@ -102,14 +110,26 @@ describe('RemoteConfig Functions', () => { describe('unwraps TemplateVersion', () => { let cloudFunctionUpdate: CloudFunction; - let event: any; + let event: Event; + before(() => { process.env.GCLOUD_PROJECT = 'project1'; cloudFunctionUpdate = remoteConfig.onUpdate( - (version: remoteConfig.TemplateVersion) => version + (version: remoteConfig.TemplateVersion, context: EventContext) => + version ); + event = { data: constructVersion(), + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.firebase.remoteconfig.update', + resource: { + service: 'firebaseremoteconfig.googleapis.com', + name: 'projects/project1', + }, + }, }; }); @@ -119,9 +139,11 @@ describe('RemoteConfig Functions', () => { it('should unwrap the version in the event', () => { return Promise.all([ - cloudFunctionUpdate(event).then((data: any) => { - expect(data).to.deep.equal(constructVersion()); - }), + cloudFunctionUpdate(event.data, event.context).then( + (data: any, context: any) => { + expect(data).to.deep.equal(constructVersion()); + } + ), ]); }); }); @@ -137,16 +159,28 @@ describe('RemoteConfig Functions', () => { it('should correctly unwrap the event', () => { let cloudFunctionUpdate = functions.handler.remoteConfig.onUpdate( - (version: remoteConfig.TemplateVersion) => version + (version: remoteConfig.TemplateVersion, context: EventContext) => + version ); - let event = { + let event: Event = { data: constructVersion(), + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.firebase.remoteconfig.update', + resource: { + service: 'firebaseremoteconfig.googleapis.com', + name: 'projects/project1', + }, + }, }; return Promise.all([ - cloudFunctionUpdate(event).then((data: any) => { - expect(data).to.deep.equal(constructVersion()); - }), + cloudFunctionUpdate(event.data, event.context).then( + (data: any, context: any) => { + expect(data).to.deep.equal(constructVersion()); + } + ), ]); }); }); diff --git a/spec/providers/storage.spec.ts b/spec/providers/storage.spec.ts index 36339743d..744946ca6 100644 --- a/spec/providers/storage.spec.ts +++ b/spec/providers/storage.spec.ts @@ -23,6 +23,7 @@ import { expect } from 'chai'; import * as storage from '../../src/providers/storage'; import * as functions from '../../src/index'; +import { Event, EventContext } from '../../src/index'; describe('Storage Functions', () => { describe('ObjectBuilder', () => { @@ -106,14 +107,26 @@ describe('Storage Functions', () => { let cloudFunction = storage.object().onArchive(data => { return data.mediaLink; }); - let goodMediaLinkEvent = { + let goodMediaLinkEvent: Event = { data: { mediaLink: 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.storage.object.archive', + resource: { + service: 'storage.googleapis.com', + name: 'projects/_/buckets/bucky', + }, + }, }; - return cloudFunction(goodMediaLinkEvent).then((result: any) => { + return cloudFunction( + goodMediaLinkEvent.data, + goodMediaLinkEvent.context + ).then((result: any, context: EventContext) => { expect(result).equals(goodMediaLinkEvent.data.mediaLink); }); }); @@ -180,8 +193,20 @@ describe('Storage Functions', () => { 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.storage.object.delete', + resource: { + service: 'storage.googleapis.com', + name: 'projects/_/buckets/bucky', + }, + }, }; - return cloudFunction(goodMediaLinkEvent).then((result: any) => { + return cloudFunction( + goodMediaLinkEvent.data, + goodMediaLinkEvent.context + ).then((result: any, context: EventContext) => { expect(result).equals(goodMediaLinkEvent.data.mediaLink); }); }); @@ -248,8 +273,20 @@ describe('Storage Functions', () => { 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.storage.object.finalize', + resource: { + service: 'storage.googleapis.com', + name: 'projects/_/buckets/bucky', + }, + }, }; - return cloudFunction(goodMediaLinkEvent).then((result: any) => { + return cloudFunction( + goodMediaLinkEvent.data, + goodMediaLinkEvent.context + ).then((result: any, context: EventContext) => { expect(result).equals(goodMediaLinkEvent.data.mediaLink); }); }); @@ -316,8 +353,20 @@ describe('Storage Functions', () => { 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.storage.object.metadataUpdate', + resource: { + service: 'storage.googleapis.com', + name: 'projects/_/buckets/bucky', + }, + }, }; - return cloudFunction(goodMediaLinkEvent).then((result: any) => { + return cloudFunction( + goodMediaLinkEvent.data, + goodMediaLinkEvent.context + ).then((result: any, context: EventContext) => { expect(result).equals(goodMediaLinkEvent.data.mediaLink); }); }); @@ -353,8 +402,20 @@ describe('Storage Functions', () => { 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.storage.object.archive', + resource: { + service: 'storage.googleapis.com', + name: 'projects/_/buckets/bucky', + }, + }, }; - return cloudFunction(goodMediaLinkEvent).then((result: any) => { + return cloudFunction( + goodMediaLinkEvent.data, + goodMediaLinkEvent.context + ).then((result: any, context: EventContext) => { expect(result).equals(goodMediaLinkEvent.data.mediaLink); }); }); @@ -378,8 +439,20 @@ describe('Storage Functions', () => { 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.storage.object.delete', + resource: { + service: 'storage.googleapis.com', + name: 'projects/_/buckets/bucky', + }, + }, }; - return cloudFunction(goodMediaLinkEvent).then((result: any) => { + return cloudFunction( + goodMediaLinkEvent.data, + goodMediaLinkEvent.context + ).then((result: any, context: EventContext) => { expect(result).equals(goodMediaLinkEvent.data.mediaLink); }); }); @@ -405,8 +478,20 @@ describe('Storage Functions', () => { 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.storage.object.finalize', + resource: { + service: 'storage.googleapis.com', + name: 'projects/_/buckets/bucky', + }, + }, }; - return cloudFunction(goodMediaLinkEvent).then((result: any) => { + return cloudFunction( + goodMediaLinkEvent.data, + goodMediaLinkEvent.context + ).then((result: any, context: EventContext) => { expect(result).equals(goodMediaLinkEvent.data.mediaLink); }); }); @@ -432,8 +517,20 @@ describe('Storage Functions', () => { 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.storage.object.metadataUpdate', + resource: { + service: 'storage.googleapis.com', + name: 'projects/_/buckets/bucky', + }, + }, }; - return cloudFunction(goodMediaLinkEvent).then((result: any) => { + return cloudFunction( + goodMediaLinkEvent.data, + goodMediaLinkEvent.context + ).then((result: any, context: EventContext) => { expect(result).equals(goodMediaLinkEvent.data.mediaLink); }); }); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index af4e97ba2..9cce3c3bb 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -28,19 +28,6 @@ export { Request, Response }; const WILDCARD_REGEX = new RegExp('{[^/{}]*}', 'g'); -/** Legacy wire format for an event - * @internal - */ -export interface LegacyEvent { - data: any; - eventType?: string; - resource?: string; - eventId?: string; - timestamp?: string; - params?: { [option: string]: any }; - auth?: apps.AuthMode; -} - /** Wire format for an event * @internal */ @@ -252,9 +239,7 @@ export function makeCloudFunction({ opts = {}, labels = {}, }: MakeCloudFunctionArgs): CloudFunction { - let cloudFunction; - - let cloudFunctionNewSignature: any = (data: any, context: any) => { + let cloudFunction: any = (data: any, context: any) => { if (legacyEventType && context.eventType === legacyEventType) { // v1beta1 event flow has different format for context, transform them to new format. context.eventType = provider + '.' + eventType; @@ -314,24 +299,6 @@ export function makeCloudFunction({ }); }; - if (process.env.X_GOOGLE_NEW_FUNCTION_SIGNATURE === 'true') { - cloudFunction = cloudFunctionNewSignature; - } else { - cloudFunction = (raw: Event | LegacyEvent) => { - let context; - // In Node 6 runtime, function called with single event param - let data = _.get(raw, 'data'); - if (isEvent(raw)) { - // new eventflow v1beta2 format - context = _.cloneDeep(raw.context); - } else { - // eventflow v1beta1 format - context = _.omit(raw, 'data'); - } - return cloudFunctionNewSignature(data, context); - }; - } - Object.defineProperty(cloudFunction, '__trigger', { get: () => { if (triggerResource() == null) { @@ -356,10 +323,6 @@ export function makeCloudFunction({ return cloudFunction; } -function isEvent(event: Event | LegacyEvent): event is Event { - return _.has(event, 'context'); -} - function _makeParams( context: EventContext, triggerResourceGetter: () => string diff --git a/src/providers/auth.ts b/src/providers/auth.ts index d271f2384..0cf7a579d 100644 --- a/src/providers/auth.ts +++ b/src/providers/auth.ts @@ -140,11 +140,7 @@ export function userRecordConstructor( _.set( record, 'metadata', - new UserRecordMetadata( - // Transform payload to firebase-admin v5.0.0 format because wire format is different (BUG 63167395) - meta.createdAt || meta.creationTime, - meta.lastSignedInAt || meta.lastSignInTime - ) + new UserRecordMetadata(meta.creationTime, meta.lastSignInTime) ); } else { _.set(record, 'metadata', new UserRecordMetadata(null, null)); From 59d6a7e056a7244e700dc7b6a180e25b38b647fd Mon Sep 17 00:00:00 2001 From: Kevin Jian Date: Wed, 12 Jun 2019 14:44:58 -0700 Subject: [PATCH 023/437] Set GCLOUD_PROJECT from FIREBASE_CONFIG when missing (#462) * Set GCLOUD_PROJECT from FIREBASE_CONFIG when missing * Update changelog * Separated test and updated messages * Moved to setup file --- changelog.txt | 3 +- integration_test/functions/src/index.ts | 8 +-- spec/index.spec.ts | 1 + spec/setup.spec.ts | 50 +++++++++++++++++++ src/index.ts | 31 ++---------- src/setup.ts | 65 +++++++++++++++++++++++++ 6 files changed, 123 insertions(+), 35 deletions(-) create mode 100644 spec/setup.spec.ts create mode 100644 src/setup.ts diff --git a/changelog.txt b/changelog.txt index c6d5ee1dd..414ba2bcd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,5 @@ important - [breaking change] The Firebase Functions SDK no longer supports Node 6. Developers must use Node 8.13.0 or higher. important - [breaking change] The Firebase Functions SDK no longer supports Firebase Tools SDK below version 6.8.0. important - [breaking change] FIREBASE_PROJECT environment variable is no longer supported and has been supplanted by FIREBASE_CONFIG. -fixed - Fixed bug in Node 10 where events from Node 10 were not parsed properly resulting in undefined. \ No newline at end of file +fixed - Fixed bug in Node 10 where events from Node 10 were not parsed properly resulting in undefined. +fixed - Set GCLOUD_PROJECT from FIREBASE_CONFIG when missing. \ No newline at end of file diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index 1ee1a903f..e2a109a2c 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -52,9 +52,7 @@ function callScheduleTrigger(functionName: string, region: string) { { method: 'POST', host: 'cloudscheduler.googleapis.com', - path: `projects/${ - firebaseConfig.projectId - }/locations/us-central1/jobs/firebase-schedule-${functionName}-${region}:run`, + path: `projects/${firebaseConfig.projectId}/locations/us-central1/jobs/firebase-schedule-${functionName}-${region}:run`, headers: { 'Content-Type': 'application/json', }, @@ -199,9 +197,7 @@ export const integrationTests: any = functions resp .status(500) .send( - `FAIL - details at https://${ - process.env.GCLOUD_PROJECT - }.firebaseio.com/testRuns/${testId}` + `FAIL - details at https://${process.env.GCLOUD_PROJECT}.firebaseio.com/testRuns/${testId}` ); }); }); diff --git a/spec/index.spec.ts b/spec/index.spec.ts index a42ed16a8..5efe47fce 100644 --- a/spec/index.spec.ts +++ b/spec/index.spec.ts @@ -32,6 +32,7 @@ import './utils.spec'; import './apps.spec'; import './cloud-functions.spec'; import './config.spec'; +import './setup.spec'; import './testing.spec'; import './function-builder.spec'; import './providers/analytics.spec'; diff --git a/spec/setup.spec.ts b/spec/setup.spec.ts new file mode 100644 index 000000000..289b90fc1 --- /dev/null +++ b/spec/setup.spec.ts @@ -0,0 +1,50 @@ +// The MIT License (MIT) +// +// Copyright (c) 2017 Firebase +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import { expect } from 'chai'; +import { setup } from '../src/setup'; + +describe('setup()', () => { + afterEach(() => { + delete process.env.FIREBASE_CONFIG; + delete process.env.GCLOUD_PROJECT; + }); + + it('sets GCLOUD_PROJECT from FIREBASE_CONFIG', () => { + const testProject = 'test-project'; + process.env.FIREBASE_CONFIG = JSON.stringify({ + projectId: testProject, + }); + setup(); + expect(process.env.GCLOUD_PROJECT).to.equal(testProject); + }); + + it('does not set GCLOUD_PROJECT if already defined', () => { + const existingProject = 'test-project'; + process.env.GCLOUD_PROJECT = existingProject; + process.env.FIREBASE_CONFIG = JSON.stringify({ + projectId: 'new-project', + }); + setup(); + expect(process.env.GCLOUD_PROJECT).to.equal(existingProject); + }); +}); diff --git a/src/index.ts b/src/index.ts index 8c3901f7d..e82b05c1e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,8 +32,8 @@ import * as https from './providers/https'; import * as pubsub from './providers/pubsub'; import * as remoteConfig from './providers/remoteConfig'; import * as storage from './providers/storage'; -import { firebaseConfig } from './config'; import { handler } from './handler-builder'; +import { setup } from './setup'; var app = apps.apps(); @@ -51,34 +51,9 @@ export { storage, }; -// // Exported root types: +// Exported root types: export * from './config'; export * from './cloud-functions'; export * from './function-builder'; -// TEMPORARY WORKAROUND (BUG 63586213): -// Until the Cloud Functions builder can publish FIREBASE_CONFIG, automatically provide it on import based on what -// we can deduce. -if (!process.env.FIREBASE_CONFIG) { - const cfg = firebaseConfig(); - if (cfg) { - process.env.FIREBASE_CONFIG = JSON.stringify(cfg); - } else if (process.env.GCLOUD_PROJECT) { - console.warn( - 'Warning, estimating Firebase Config based on GCLOUD_PROJECT. Initializing firebase-admin may fail' - ); - process.env.FIREBASE_CONFIG = JSON.stringify({ - databaseURL: - `${process.env.DATABASE_URL}` || - `https://${process.env.GCLOUD_PROJECT}.firebaseio.com`, - storageBucket: - `${process.env.STORAGE_BUCKET_URL}` || - `${process.env.GCLOUD_PROJECT}.appspot.com`, - projectId: process.env.GCLOUD_PROJECT, - }); - } else { - console.warn( - 'Warning, FIREBASE_CONFIG environment variable is missing. Initializing firebase-admin will fail' - ); - } -} +setup(); diff --git a/src/setup.ts b/src/setup.ts new file mode 100644 index 000000000..6a0796348 --- /dev/null +++ b/src/setup.ts @@ -0,0 +1,65 @@ +// The MIT License (MIT) +// +// Copyright (c) 2017 Firebase +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import { firebaseConfig } from './config'; + +// Set up for config and vars +export function setup() { + // TEMPORARY WORKAROUND (BUG 63586213): + // Until the Cloud Functions builder can publish FIREBASE_CONFIG, automatically provide it on import based on what + // we can deduce. + if (!process.env.FIREBASE_CONFIG) { + const cfg = firebaseConfig(); + if (cfg) { + process.env.FIREBASE_CONFIG = JSON.stringify(cfg); + } + } + + // WORKAROUND (BUG 134416569): GCLOUD_PROJECT missing in Node 10 + if (!process.env.GCLOUD_PROJECT && process.env.FIREBASE_CONFIG) { + process.env.GCLOUD_PROJECT = JSON.parse( + process.env.FIREBASE_CONFIG + ).projectId; + } + + // If FIREBASE_CONFIG is still not found, try using GCLOUD_PROJECT to estimate + if (!process.env.FIREBASE_CONFIG) { + if (process.env.GCLOUD_PROJECT) { + console.warn( + 'Warning, estimating Firebase Config based on GCLOUD_PROJECT. Initializing firebase-admin may fail' + ); + process.env.FIREBASE_CONFIG = JSON.stringify({ + databaseURL: + `${process.env.DATABASE_URL}` || + `https://${process.env.GCLOUD_PROJECT}.firebaseio.com`, + storageBucket: + `${process.env.STORAGE_BUCKET_URL}` || + `${process.env.GCLOUD_PROJECT}.appspot.com`, + projectId: process.env.GCLOUD_PROJECT, + }); + } else { + console.warn( + 'Warning, FIREBASE_CONFIG and GCLOUD_PROJECT environment variables are missing. Initializing firebase-admin will fail' + ); + } + } +} From 6068832a760734d76c2171cc9d91769d0e3d0575 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Wed, 12 Jun 2019 15:07:53 -0700 Subject: [PATCH 024/437] uncomment integration tests for node 10 (#464) --- integration_test/run_tests.sh | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index 5da3a4bfb..1aaa39859 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -141,15 +141,14 @@ if [[ $PROJECT_ID_NODE_8 == $PROJECT_ID_NODE_10 ]]; then waitForPropagation run_tests fi -# TODO(b/134418760): Uncomment this when Node 10 issues are fixed -# pick_node10 -# announce "Re-deploying the same functions to Node 10 runtime ..." -# deploy -# waitForPropagation -# if [[ $PROJECT_ID_NODE_8 == $PROJECT_ID_NODE_10 ]]; then -# run_tests -# else -# run_all_tests -# fi +pick_node10 +announce "Re-deploying the same functions to Node 10 runtime ..." +deploy +waitForPropagation +if [[ $PROJECT_ID_NODE_8 == $PROJECT_ID_NODE_10 ]]; then + run_tests +else + run_all_tests +fi cleanup announce "All tests pass!" From 55a4bd2a61f01dc8d5c2019c88701494e46c3312 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko Date: Wed, 12 Jun 2019 15:29:12 -0700 Subject: [PATCH 025/437] 3.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d1deef78b..d71079277 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "2.3.1", + "version": "3.0.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From 75fb7627acc3a7e6d7bd65dc77d5b24188bd822e Mon Sep 17 00:00:00 2001 From: Diana Tkachenko Date: Wed, 12 Jun 2019 15:59:16 -0700 Subject: [PATCH 026/437] Clear changelog after v3.0.0 release --- changelog.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/changelog.txt b/changelog.txt index 414ba2bcd..e69de29bb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +0,0 @@ -important - [breaking change] The Firebase Functions SDK no longer supports Node 6. Developers must use Node 8.13.0 or higher. -important - [breaking change] The Firebase Functions SDK no longer supports Firebase Tools SDK below version 6.8.0. -important - [breaking change] FIREBASE_PROJECT environment variable is no longer supported and has been supplanted by FIREBASE_CONFIG. -fixed - Fixed bug in Node 10 where events from Node 10 were not parsed properly resulting in undefined. -fixed - Set GCLOUD_PROJECT from FIREBASE_CONFIG when missing. \ No newline at end of file From e1c612901a3a09654d2b4d8afc46661860437b26 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Wed, 12 Jun 2019 16:28:54 -0700 Subject: [PATCH 027/437] Add required types to Functions SDK (#465) * add necessary types back into dependencies * remove from devdeps * add changelog --- changelog.txt | 1 + package.json | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index e69de29bb..73188f736 100644 --- a/changelog.txt +++ b/changelog.txt @@ -0,0 +1 @@ +* Includes required types for functions SDK. \ No newline at end of file diff --git a/package.json b/package.json index d71079277..76c767fb9 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,10 @@ "test": "mocha -r ts-node/register ./spec/index.spec.ts" }, "dependencies": { + "@types/cors": "^2.8.5", + "@types/express": "^4.11.1", + "@types/jsonwebtoken": "^8.3.2", + "@types/lodash": "^4.14.133", "cors": "^2.8.4", "express": "^4.17.1", "jsonwebtoken": "^8.3.2", @@ -37,10 +41,6 @@ "devDependencies": { "@types/chai": "^4.1.7", "@types/chai-as-promised": "^7.1.0", - "@types/cors": "^2.8.5", - "@types/express": "^4.11.1", - "@types/jsonwebtoken": "^8.3.2", - "@types/lodash": "^4.14.133", "@types/mocha": "^5.2.7", "@types/mock-require": "^2.0.0", "@types/nock": "^10.0.2", From a9b4eb63cb10dc1e80cb3f4dc27f6493ada2d20e Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Wed, 12 Jun 2019 16:33:27 -0700 Subject: [PATCH 028/437] changelog change (#466) --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 73188f736..c8d64a5f3 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1 +1 @@ -* Includes required types for functions SDK. \ No newline at end of file +fixed - Includes required types for functions SDK. \ No newline at end of file From ed9e141cc55fb2dabce879d0b7d8ef8129293ce9 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Wed, 12 Jun 2019 23:34:55 +0000 Subject: [PATCH 029/437] [firebase-release] Updated SDK for Cloud Functions to 3.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 76c767fb9..fc9e0feb1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.0.0", + "version": "3.0.1", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From 9753e6a397c823ade428d066d75a84309dc1099f Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Wed, 12 Jun 2019 23:35:06 +0000 Subject: [PATCH 030/437] [firebase-release] Removed change log and reset repo after 3.0.1 release --- changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index c8d64a5f3..e69de29bb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1 +0,0 @@ -fixed - Includes required types for functions SDK. \ No newline at end of file From 8712ee957efa07e7da56100d86ba078f5f57f115 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Thu, 13 Jun 2019 20:19:08 -0700 Subject: [PATCH 031/437] Lint spec/* files (#468) --- spec/apps.spec.ts | 36 ++++++++++++++++++----------------- spec/cloud-functions.spec.ts | 27 +++++++++++++------------- spec/config.spec.ts | 4 ++-- spec/function-builder.spec.ts | 12 ++++++------ spec/index.spec.ts | 9 +++++---- spec/testing.spec.ts | 2 +- spec/utils.spec.ts | 8 ++++---- 7 files changed, 51 insertions(+), 47 deletions(-) diff --git a/spec/apps.spec.ts b/spec/apps.spec.ts index 9cf0a35cc..13a7213cd 100644 --- a/spec/apps.spec.ts +++ b/spec/apps.spec.ts @@ -22,6 +22,7 @@ import { expect } from 'chai'; import { apps as appsNamespace } from '../src/apps'; + import * as firebase from 'firebase-admin'; import * as _ from 'lodash'; import * as sinon from 'sinon'; @@ -29,6 +30,7 @@ import * as sinon from 'sinon'; describe('apps', () => { let apps: appsNamespace.Apps; let claims; + beforeEach(() => { apps = new appsNamespace.Apps(); // mock claims intentionally contains dots, square brackets, and nested paths @@ -52,37 +54,37 @@ describe('apps', () => { clock.restore(); }); - it('should retain/release ref counters appropriately', function() { + it('should retain/release ref counters appropriately', () => { apps.retain(); - expect(apps['_refCounter']).to.deep.equal({ + expect(_.get(apps, '_refCounter')).to.deep.equal({ __admin__: 1, }); apps.release(); clock.tick(appsNamespace.garbageCollectionInterval); return Promise.resolve().then(() => { - expect(apps['_refCounter']).to.deep.equal({ + expect(_.get(apps, '_refCounter')).to.deep.equal({ __admin__: 0, }); }); }); - it('should only decrement counter after garbageCollectionInterval is up', function() { + it('should only decrement counter after garbageCollectionInterval is up', () => { apps.retain(); apps.release(); clock.tick(appsNamespace.garbageCollectionInterval / 2); - expect(apps['_refCounter']).to.deep.equal({ + expect(_.get(apps, '_refCounter')).to.deep.equal({ __admin__: 1, }); clock.tick(appsNamespace.garbageCollectionInterval / 2); return Promise.resolve().then(() => { - expect(apps['_refCounter']).to.deep.equal({ + expect(_.get(apps, '_refCounter')).to.deep.equal({ __admin__: 0, }); }); }); - it('should call _destroyApp if app no longer used', function() { - let spy = sinon.spy(apps, '_destroyApp'); + it('should call _destroyApp if app no longer used', () => { + const spy = sinon.spy(apps, '_destroyApp'); apps.retain(); apps.release(); clock.tick(appsNamespace.garbageCollectionInterval); @@ -91,8 +93,8 @@ describe('apps', () => { }); }); - it('should not call _destroyApp if app used again while waiting for release', function() { - let spy = sinon.spy(apps, '_destroyApp'); + it('should not call _destroyApp if app used again while waiting for release', () => { + const spy = sinon.spy(apps, '_destroyApp'); apps.retain(); apps.release(); clock.tick(appsNamespace.garbageCollectionInterval / 2); @@ -103,22 +105,22 @@ describe('apps', () => { }); }); - it('should increment ref counter for each subsequent retain', function() { + it('should increment ref counter for each subsequent retain', () => { apps.retain(); - expect(apps['_refCounter']).to.deep.equal({ + expect(_.get(apps, '_refCounter')).to.deep.equal({ __admin__: 1, }); apps.retain(); - expect(apps['_refCounter']).to.deep.equal({ + expect(_.get(apps, '_refCounter')).to.deep.equal({ __admin__: 2, }); apps.retain(); - expect(apps['_refCounter']).to.deep.equal({ + expect(_.get(apps, '_refCounter')).to.deep.equal({ __admin__: 3, }); }); - it('should work with staggering sets of retain/release', function() { + it('should work with staggering sets of retain/release', () => { apps.retain(); apps.release(); clock.tick(appsNamespace.garbageCollectionInterval / 2); @@ -128,14 +130,14 @@ describe('apps', () => { return Promise.resolve() .then(() => { // Counters are still 1 due second set of retain/release - expect(apps['_refCounter']).to.deep.equal({ + expect(_.get(apps, '_refCounter')).to.deep.equal({ __admin__: 1, }); clock.tick(appsNamespace.garbageCollectionInterval / 2); }) .then(() => { // It's now been a full interval since the second set of retain/release - expect(apps['_refCounter']).to.deep.equal({ + expect(_.get(apps, '_refCounter')).to.deep.equal({ __admin__: 0, }); }); diff --git a/spec/cloud-functions.spec.ts b/spec/cloud-functions.spec.ts index 194ce790b..c0b0e518c 100644 --- a/spec/cloud-functions.spec.ts +++ b/spec/cloud-functions.spec.ts @@ -20,14 +20,15 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import * as _ from 'lodash'; import { expect } from 'chai'; +import * as _ from 'lodash'; + import { + Change, Event, EventContext, makeCloudFunction, MakeCloudFunctionArgs, - Change, } from '../src/cloud-functions'; describe('makeCloudFunction', () => { @@ -41,7 +42,7 @@ describe('makeCloudFunction', () => { }; it('should put a __trigger on the returned CloudFunction', () => { - let cf = makeCloudFunction({ + const cf = makeCloudFunction({ provider: 'mock.provider', eventType: 'mock.event', service: 'service', @@ -58,7 +59,7 @@ describe('makeCloudFunction', () => { }); it('should have legacy event type in __trigger if provided', () => { - let cf = makeCloudFunction(cloudFunctionArgs); + const cf = makeCloudFunction(cloudFunctionArgs); expect(cf.__trigger).to.deep.equal({ eventTrigger: { eventType: 'providers/provider/eventTypes/event', @@ -69,11 +70,11 @@ describe('makeCloudFunction', () => { }); it('should construct the right context for event', () => { - let args: any = _.assign({}, cloudFunctionArgs, { + const args: any = _.assign({}, cloudFunctionArgs, { handler: (data: any, context: EventContext) => context, }); - let cf = makeCloudFunction(args); - let test: Event = { + const cf = makeCloudFunction(args); + const test: Event = { context: { eventId: '00000', timestamp: '2016-11-04T21:29:03.496Z', @@ -99,12 +100,12 @@ describe('makeCloudFunction', () => { }); it('should throw error when context.params accessed in handler environment', () => { - let args: any = _.assign({}, cloudFunctionArgs, { + const args: any = _.assign({}, cloudFunctionArgs, { handler: (data: any, context: EventContext) => context, triggerResource: () => null, }); - let cf = makeCloudFunction(args); - let test: Event = { + const cf = makeCloudFunction(args); + const test: Event = { context: { eventId: '00000', timestamp: '2016-11-04T21:29:03.496Z', @@ -179,7 +180,7 @@ describe('makeAuth and makeAuthType', () => { }; }, }; - let cf = makeCloudFunction(args); + const cf = makeCloudFunction(args); it('should construct correct auth and authType for admin user', () => { const testEvent = { @@ -311,7 +312,7 @@ describe('Change', () => { describe('fromJSON', () => { it('should create a Change object with a `before` and `after`', () => { - let created = Change.fromJSON({ + const created = Change.fromJSON({ before: { foo: 'bar' }, after: { foo: 'faz' }, }); @@ -325,7 +326,7 @@ describe('Change', () => { _.set(input, 'another', 'value'); return input as T; } - let created = Change.fromJSON( + const created = Change.fromJSON( { before: { foo: 'bar' }, after: { foo: 'faz' }, diff --git a/spec/config.spec.ts b/spec/config.spec.ts index 90ff64620..64963be4e 100644 --- a/spec/config.spec.ts +++ b/spec/config.spec.ts @@ -20,8 +20,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import * as mockRequire from 'mock-require'; import { expect } from 'chai'; +import * as mockRequire from 'mock-require'; import { config, firebaseConfig } from '../src/config'; describe('config()', () => { @@ -34,7 +34,7 @@ describe('config()', () => { it('loads config values from .runtimeconfig.json', () => { mockRequire('../../../.runtimeconfig.json', { foo: 'bar', firebase: {} }); - let loaded = config(); + const loaded = config(); expect(loaded).to.not.have.property('firebase'); expect(loaded).to.have.property('foo', 'bar'); }); diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index 615d9fe9c..cddb2d10b 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -34,7 +34,7 @@ describe('FunctionBuilder', () => { }); it('should allow supported region to be set', () => { - let fn = functions + const fn = functions .region('us-east1') .auth.user() .onCreate(user => user); @@ -43,7 +43,7 @@ describe('FunctionBuilder', () => { }); it('should allow multiple supported regions to be set', () => { - let fn = functions + const fn = functions .region('us-east1', 'us-central1') .auth.user() .onCreate(user => user); @@ -52,7 +52,7 @@ describe('FunctionBuilder', () => { }); it('should allow all supported regions to be set', () => { - let fn = functions + const fn = functions .region( 'us-central1', 'us-east1', @@ -75,7 +75,7 @@ describe('FunctionBuilder', () => { }); it('should allow valid runtime options to be set', () => { - let fn = functions + const fn = functions .runWith({ timeoutSeconds: 90, memory: '256MB', @@ -88,7 +88,7 @@ describe('FunctionBuilder', () => { }); it('should allow both supported region and valid runtime options to be set', () => { - let fn = functions + const fn = functions .region('europe-west2') .runWith({ timeoutSeconds: 90, @@ -103,7 +103,7 @@ describe('FunctionBuilder', () => { }); it('should allow both valid runtime options and supported region to be set in reverse order', () => { - let fn = functions + const fn = functions .runWith({ timeoutSeconds: 90, memory: '256MB', diff --git a/spec/index.spec.ts b/spec/index.spec.ts index 5efe47fce..6d56878da 100644 --- a/spec/index.spec.ts +++ b/spec/index.spec.ts @@ -28,19 +28,20 @@ import * as nock from 'nock'; nock.disableNetConnect(); import 'mocha'; -import './utils.spec'; + import './apps.spec'; import './cloud-functions.spec'; import './config.spec'; -import './setup.spec'; -import './testing.spec'; import './function-builder.spec'; import './providers/analytics.spec'; import './providers/auth.spec'; +import './providers/crashlytics.spec'; import './providers/database.spec'; import './providers/firestore.spec'; import './providers/https.spec'; import './providers/pubsub.spec'; import './providers/remoteConfig.spec'; import './providers/storage.spec'; -import './providers/crashlytics.spec'; +import './setup.spec'; +import './testing.spec'; +import './utils.spec'; diff --git a/spec/testing.spec.ts b/spec/testing.spec.ts index 4df5362df..e4bc73d55 100644 --- a/spec/testing.spec.ts +++ b/spec/testing.spec.ts @@ -26,7 +26,7 @@ import * as testing from '../src/testing'; // TODO(rjh): As actual testing methods become available, replace this with actual tests. describe('testing', () => { - it('should be accessible through the entrypoint', function() { + it('should be accessible through the entrypoint', () => { expect(testing.whereAreTheBugs()).to.not.equal('Earth'); }); }); diff --git a/spec/utils.spec.ts b/spec/utils.spec.ts index cedf43597..57634dd72 100644 --- a/spec/utils.spec.ts +++ b/spec/utils.spec.ts @@ -20,8 +20,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import { normalizePath, pathParts, valAt, applyChange } from '../src/utils'; import { expect } from 'chai'; +import { applyChange, normalizePath, pathParts, valAt } from '../src/utils'; describe('utils', () => { describe('.normalizePath(path: string)', () => { @@ -71,9 +71,9 @@ describe('utils', () => { }); it('should return the merged value of two objects', () => { - let from = { a: { b: 'foo', c: 23, d: 444 }, d: { e: 42 } }; - let to: any = { a: { b: 'bar', c: null }, d: null, e: { f: 'g' } }; - let result = { a: { b: 'bar', d: 444 }, e: { f: 'g' } }; + const from = { a: { b: 'foo', c: 23, d: 444 }, d: { e: 42 } }; + const to: any = { a: { b: 'bar', c: null }, d: null, e: { f: 'g' } }; + const result = { a: { b: 'bar', d: 444 }, e: { f: 'g' } }; expect(applyChange(from, to)).to.deep.equal(result); }); }); From f989e8c1a2e03e4658c6723aaf130612e5f4886e Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Fri, 14 Jun 2019 13:21:58 -0700 Subject: [PATCH 032/437] Adds npm lint command and updates lint options (#467) * adding lint command and modifying lint options * one additional format fix * order deps in package.json, remove unnecessary [] from tslint * Lint spec/* files (#468) * adding lint command and modifying lint options * one additional format fix * order deps in package.json, remove unnecessary [] from tslint --- integration_test/functions/src/index.ts | 8 ++++++-- package.json | 4 ++++ tslint.json | 14 ++++++++------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index e2a109a2c..1ee1a903f 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -52,7 +52,9 @@ function callScheduleTrigger(functionName: string, region: string) { { method: 'POST', host: 'cloudscheduler.googleapis.com', - path: `projects/${firebaseConfig.projectId}/locations/us-central1/jobs/firebase-schedule-${functionName}-${region}:run`, + path: `projects/${ + firebaseConfig.projectId + }/locations/us-central1/jobs/firebase-schedule-${functionName}-${region}:run`, headers: { 'Content-Type': 'application/json', }, @@ -197,7 +199,9 @@ export const integrationTests: any = functions resp .status(500) .send( - `FAIL - details at https://${process.env.GCLOUD_PROJECT}.firebaseio.com/testRuns/${testId}` + `FAIL - details at https://${ + process.env.GCLOUD_PROJECT + }.firebaseio.com/testRuns/${testId}` ); }); }); diff --git a/package.json b/package.json index fc9e0feb1..2f2b38325 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "build:release": "npm install --production && npm install typescript firebase-admin && tsc -p tsconfig.release.json", "build": "tsc -p tsconfig.release.json", "format": "prettier --write '**/*.ts'", + "lint": "tslint --project tsconfig.json --config tslint.json", "posttest": "npm run format", "test": "mocha -r ts-node/register ./spec/index.spec.ts" }, @@ -56,6 +57,9 @@ "prettier": "^1.17.1", "sinon": "^7.3.2", "ts-node": "^8.2.0", + "tslint": "^5.17.0", + "tslint-no-unused-expression-chai": "^0.1.4", + "tslint-plugin-prettier": "^2.0.0", "typescript": "^3.5.1" }, "peerDependencies": { diff --git a/tslint.json b/tslint.json index 72dce24d8..9b6dff93b 100644 --- a/tslint.json +++ b/tslint.json @@ -1,13 +1,15 @@ { - "extends": "tslint:recommended", + "defaultSeverity": "warning", + "extends": ["tslint:recommended", "tslint-no-unused-expression-chai"], "rules": { "quotemark": [true, "single", "avoid-escape"], - "interface-name": [false], + "interface-name": false, "variable-name": [true, "check-format", "allow-leading-underscore"], "object-literal-sort-keys": false, - "whitespace": [true], - "member-access": [false], - "no-console": [false], - "no-namespace": [false] + "whitespace": true, + "member-access": false, + "no-console": false, + "no-namespace": false, + "trailing-comma": [true, { "functions": "never" }] } } From 1ccd569702c41eb7e198f86f02d77462f51b3928 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Fri, 14 Jun 2019 13:41:28 -0700 Subject: [PATCH 033/437] Fix lint errors for auth, analytics, crashlytics, remoteconfig tests (#469) * fix lint errors for auth, analytics, crashlytics, remoteconfig * fix handler function in auth test * renaming event back to data --- spec/providers/analytics.spec.ts | 34 ++++++++++++++++------------- spec/providers/auth.spec.ts | 26 +++++++++++----------- spec/providers/crashlytics.spec.ts | 18 +++------------ spec/providers/remoteConfig.spec.ts | 8 +++---- 4 files changed, 39 insertions(+), 47 deletions(-) diff --git a/spec/providers/analytics.spec.ts b/spec/providers/analytics.spec.ts index cdb0ba02c..67e7a700b 100644 --- a/spec/providers/analytics.spec.ts +++ b/spec/providers/analytics.spec.ts @@ -20,11 +20,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import * as analytics from '../../src/providers/analytics'; import { expect } from 'chai'; -import { EventContext, Event } from '../../src/cloud-functions'; -import * as analytics_spec_input from './analytics.spec.input'; + +import { Event, EventContext } from '../../src/cloud-functions'; import * as functions from '../../src/index'; +import * as analytics from '../../src/providers/analytics'; +import * as analytics_spec_input from './analytics.spec.input'; describe('Analytics Functions', () => { describe('EventBuilder', () => { @@ -37,7 +38,7 @@ describe('Analytics Functions', () => { }); it('should allow both region and runtime options to be set', () => { - let fn = functions + const fn = functions .region('us-east1') .runWith({ timeoutSeconds: 90, @@ -54,6 +55,7 @@ describe('Analytics Functions', () => { describe('#onLog', () => { it('should return a TriggerDefinition with appropriate values', () => { const cloudFunction = analytics.event('first_open').onLog(() => null); + expect(cloudFunction.__trigger).to.deep.equal({ eventTrigger: { eventType: @@ -75,7 +77,7 @@ describe('Analytics Functions', () => { // The event data delivered over the wire will be the JSON for an AnalyticsEvent: // https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data - let event: Event = { + const event: Event = { data: { userDim: { userId: 'hi!', @@ -114,7 +116,7 @@ describe('Analytics Functions', () => { // Incoming events will have four kinds of "xValue" fields: "intValue", // "stringValue", "doubleValue" and "floatValue". We expect those to get // flattened away, leaving just their values. - let event: Event = { + const event: Event = { data: { eventDim: [ { @@ -184,7 +186,7 @@ describe('Analytics Functions', () => { .event('first_open') .onLog((data: analytics.AnalyticsEvent) => data); - let event: Event = { + const event: Event = { data: { eventDim: [ { @@ -254,7 +256,7 @@ describe('Analytics Functions', () => { // // Separately, the input has a number of microsecond timestamps that we'd // like to rename and scale down to milliseconds. - let event: Event = { + const event: Event = { data: { eventDim: [ { @@ -291,11 +293,12 @@ describe('Analytics Functions', () => { .event('first_open') .onLog((data: analytics.AnalyticsEvent) => data); // The payload in analytics_spec_input contains all possible fields at least once. - const data = analytics_spec_input.fullPayload.data; - const context = analytics_spec_input.fullPayload.context; - return expect(cloudFunction(data, context)).to.eventually.deep.equal( - analytics_spec_input.data - ); + const payloadData = analytics_spec_input.fullPayload.data; + const payloadContext = analytics_spec_input.fullPayload.context; + + return expect( + cloudFunction(payloadData, payloadContext) + ).to.eventually.deep.equal(analytics_spec_input.data); }); }); }); @@ -316,7 +319,7 @@ describe('Analytics Functions', () => { // The event data delivered over the wire will be the JSON for an AnalyticsEvent: // https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data - let event: Event = { + const event: Event = { data: { userDim: { userId: 'hi!', @@ -361,7 +364,8 @@ describe('Analytics Functions', () => { }); it('should not throw when #run is called', () => { - let cf = analytics.event('event').onLog(() => null); + const cf = analytics.event('event').onLog(() => null); + expect(cf.run).to.not.throw(Error); }); }); diff --git a/spec/providers/auth.spec.ts b/spec/providers/auth.spec.ts index c5300364d..676e973ce 100644 --- a/spec/providers/auth.spec.ts +++ b/spec/providers/auth.spec.ts @@ -22,9 +22,11 @@ import { expect } from 'chai'; import * as firebase from 'firebase-admin'; -import * as auth from '../../src/providers/auth'; -import { CloudFunction, EventContext, Event } from '../../src/cloud-functions'; + +import { CloudFunction, Event, EventContext } from '../../src/cloud-functions'; import * as functions from '../../src/index'; +import * as auth from '../../src/providers/auth'; +import { Resolver } from 'dns'; describe('Auth Functions', () => { const event: Event = { @@ -46,7 +48,9 @@ describe('Auth Functions', () => { }; describe('AuthBuilder', () => { - let handler: (user: firebase.auth.UserRecord) => PromiseLike | any; + const handler = (user: firebase.auth.UserRecord) => { + return Promise.resolve(); + }; before(() => { process.env.GCLOUD_PROJECT = 'project1'; @@ -57,7 +61,7 @@ describe('Auth Functions', () => { }); it('should allow both region and runtime options to be set', () => { - let fn = functions + const fn = functions .region('us-east1') .runWith({ timeoutSeconds: 90, @@ -178,12 +182,6 @@ describe('Auth Functions', () => { describe('handler namespace', () => { describe('#onCreate', () => { - let cloudFunctionCreate: CloudFunction< - firebase.auth.UserRecord - > = functions.handler.auth.user.onCreate( - (data: firebase.auth.UserRecord) => data - ); - it('should return an empty trigger', () => { const cloudFunction = functions.handler.auth.user.onCreate(() => null); expect(cloudFunction.__trigger).to.deep.equal({}); @@ -191,14 +189,16 @@ describe('Auth Functions', () => { }); describe('#onDelete', () => { - let cloudFunctionDelete: CloudFunction< + const cloudFunctionDelete: CloudFunction< firebase.auth.UserRecord > = functions.handler.auth.user.onDelete( (data: firebase.auth.UserRecord) => data ); it('should return an empty trigger', () => { - let handler: (user: firebase.auth.UserRecord) => PromiseLike | any; + const handler = (user: firebase.auth.UserRecord) => { + return Promise.resolve(); + }; const cloudFunction = functions.handler.auth.user.onDelete(handler); expect(cloudFunction.__trigger).to.deep.equal({}); }); @@ -228,7 +228,7 @@ describe('Auth Functions', () => { }); it('should not throw when #run is called', () => { - let cf = auth.user().onCreate(() => null); + const cf = auth.user().onCreate(() => null); expect(cf.run).to.not.throw(Error); }); }); diff --git a/spec/providers/crashlytics.spec.ts b/spec/providers/crashlytics.spec.ts index 081fef807..7a724213b 100644 --- a/spec/providers/crashlytics.spec.ts +++ b/spec/providers/crashlytics.spec.ts @@ -22,9 +22,9 @@ import { expect } from 'chai'; -import * as crashlytics from '../../src/providers/crashlytics'; import { apps as appsNamespace } from '../../src/apps'; import * as functions from '../../src/index'; +import * as crashlytics from '../../src/providers/crashlytics'; describe('Crashlytics Functions', () => { describe('Issue Builder', () => { @@ -39,7 +39,7 @@ describe('Crashlytics Functions', () => { }); it('should allow both region and runtime options to be set', () => { - let fn = functions + const fn = functions .region('us-east1') .runWith({ timeoutSeconds: 90, @@ -95,18 +95,6 @@ describe('Crashlytics Functions', () => { }); describe('HandlerBuilder', () => { - const testIssue = { - issueId: '1234', - issueTitle: 'testIssue', - appInfo: { - appName: 'My Awesome Test App', - appPlatform: 'ios', - appId: '9876', - latestAppVersion: '1.2.3.4', - }, - createTime: '2018-12-18T23:05:55+00:00', - }; - describe('#onNew', () => { it('should return a CloudFunction with appropriate values', () => { const cloudFunction = functions.handler.crashlytics.issue.onNew( @@ -160,7 +148,7 @@ describe('Crashlytics Functions', () => { }); it('should not throw when #run is called', () => { - let cf = crashlytics.issue().onNew(() => null); + const cf = crashlytics.issue().onNew(() => null); expect(cf.run).to.not.throw(Error); }); }); diff --git a/spec/providers/remoteConfig.spec.ts b/spec/providers/remoteConfig.spec.ts index 97e74b593..d6e553cf4 100644 --- a/spec/providers/remoteConfig.spec.ts +++ b/spec/providers/remoteConfig.spec.ts @@ -25,8 +25,8 @@ import * as _ from 'lodash'; import { CloudFunction, Event, - TriggerAnnotated, EventContext, + TriggerAnnotated, } from '../../src/cloud-functions'; import * as functions from '../../src/index'; import * as remoteConfig from '../../src/providers/remoteConfig'; @@ -49,7 +49,7 @@ describe('RemoteConfig Functions', () => { function makeEvent(data: any, context: { [key: string]: any }): Event { context = context || {}; return { - data: data, + data, context: _.merge( { eventId: '123', @@ -158,11 +158,11 @@ describe('RemoteConfig Functions', () => { }); it('should correctly unwrap the event', () => { - let cloudFunctionUpdate = functions.handler.remoteConfig.onUpdate( + const cloudFunctionUpdate = functions.handler.remoteConfig.onUpdate( (version: remoteConfig.TemplateVersion, context: EventContext) => version ); - let event: Event = { + const event: Event = { data: constructVersion(), context: { eventId: '70172329041928', From 48194fb6e22f9a64e7f18ad7b81f547cce181663 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Fri, 14 Jun 2019 13:52:46 -0700 Subject: [PATCH 034/437] fix lint errors for pubsub and storage tests (#471) --- spec/providers/pubsub.spec.ts | 92 ++++++++++++++++++---------------- spec/providers/storage.spec.ts | 92 ++++++++++++++++++---------------- 2 files changed, 97 insertions(+), 87 deletions(-) diff --git a/spec/providers/pubsub.spec.ts b/spec/providers/pubsub.spec.ts index 345e790a6..7586302ad 100644 --- a/spec/providers/pubsub.spec.ts +++ b/spec/providers/pubsub.spec.ts @@ -21,15 +21,15 @@ // SOFTWARE. import { expect } from 'chai'; -import * as pubsub from '../../src/providers/pubsub'; -import * as functions from '../../src/index'; import { Event } from '../../src/index'; +import * as functions from '../../src/index'; +import * as pubsub from '../../src/providers/pubsub'; describe('Pubsub Functions', () => { describe('pubsub.Message', () => { describe('#json', () => { it('should return json decoded from base64', () => { - let message = new pubsub.Message({ + const message = new pubsub.Message({ data: new Buffer('{"hello":"world"}', 'utf8').toString('base64'), }); @@ -37,7 +37,7 @@ describe('Pubsub Functions', () => { }); it('should preserve passed in json', () => { - let message = new pubsub.Message({ + const message = new pubsub.Message({ data: new Buffer('{"hello":"world"}', 'utf8').toString('base64'), json: { goodbye: 'world' }, }); @@ -48,10 +48,10 @@ describe('Pubsub Functions', () => { describe('#toJSON', () => { it('should be JSON stringify-able', () => { - let encoded = new Buffer('{"hello":"world"}', 'utf8').toString( + const encoded = new Buffer('{"hello":"world"}', 'utf8').toString( 'base64' ); - let message = new pubsub.Message({ + const message = new pubsub.Message({ data: encoded, }); @@ -73,7 +73,7 @@ describe('Pubsub Functions', () => { }); it('should allow both region and runtime options to be set', () => { - let fn = functions + const fn = functions .region('us-east1') .runWith({ timeoutSeconds: 90, @@ -144,14 +144,16 @@ describe('Pubsub Functions', () => { describe('#schedule', () => { it('should return a trigger with schedule', () => { - let result = pubsub.schedule('every 5 minutes').onRun(context => null); + const result = pubsub + .schedule('every 5 minutes') + .onRun(context => null); expect(result.__trigger.schedule).to.deep.equal({ schedule: 'every 5 minutes', }); }); it('should return a trigger with schedule and timeZone when one is chosen', () => { - let result = pubsub + const result = pubsub .schedule('every 5 minutes') .timeZone('America/New_York') .onRun(context => null); @@ -162,51 +164,55 @@ describe('Pubsub Functions', () => { }); it('should return a trigger with schedule and retry config when called with retryConfig', () => { - let retryConfig = { + const retryConfig = { retryCount: 3, maxRetryDuration: '10 minutes', minBackoffDuration: '10 minutes', maxBackoffDuration: '10 minutes', maxDoublings: 5, }; - let result = pubsub + const result = pubsub .schedule('every 5 minutes') .retryConfig(retryConfig) .onRun(() => null); expect(result.__trigger.schedule).to.deep.equal({ schedule: 'every 5 minutes', - retryConfig: retryConfig, + retryConfig, }); expect(result.__trigger.labels).to.deep.equal({ 'deployment-scheduled': 'true', }); }); - it('should return a trigger with schedule, timeZone and retry config when called with retryConfig and timeout', () => { - let retryConfig = { - retryCount: 3, - maxRetryDuration: '10 minutes', - minBackoffDuration: '10 minutes', - maxBackoffDuration: '10 minutes', - maxDoublings: 5, - }; - let result = pubsub - .schedule('every 5 minutes') - .timeZone('America/New_York') - .retryConfig(retryConfig) - .onRun(() => null); - expect(result.__trigger.schedule).to.deep.equal({ - schedule: 'every 5 minutes', - retryConfig: retryConfig, - timeZone: 'America/New_York', - }); - expect(result.__trigger.labels).to.deep.equal({ - 'deployment-scheduled': 'true', - }); - }); + it( + 'should return a trigger with schedule, timeZone and retry config' + + 'when called with retryConfig and timeout', + () => { + const retryConfig = { + retryCount: 3, + maxRetryDuration: '10 minutes', + minBackoffDuration: '10 minutes', + maxBackoffDuration: '10 minutes', + maxDoublings: 5, + }; + const result = pubsub + .schedule('every 5 minutes') + .timeZone('America/New_York') + .retryConfig(retryConfig) + .onRun(() => null); + expect(result.__trigger.schedule).to.deep.equal({ + schedule: 'every 5 minutes', + retryConfig, + timeZone: 'America/New_York', + }); + expect(result.__trigger.labels).to.deep.equal({ + 'deployment-scheduled': 'true', + }); + } + ); it('should return an appropriate trigger when called with region and options', () => { - let result = functions + const result = functions .region('us-east1') .runWith({ timeoutSeconds: 90, @@ -223,7 +229,7 @@ describe('Pubsub Functions', () => { }); it('should return an appropriate trigger when called with region, timeZone, and options', () => { - let result = functions + const result = functions .region('us-east1') .runWith({ timeoutSeconds: 90, @@ -242,14 +248,14 @@ describe('Pubsub Functions', () => { }); it('should return an appropriate trigger when called with region, options and retryConfig', () => { - let retryConfig = { + const retryConfig = { retryCount: 3, maxRetryDuration: '10 minutes', minBackoffDuration: '10 minutes', maxBackoffDuration: '10 minutes', maxDoublings: 5, }; - let result = functions + const result = functions .region('us-east1') .runWith({ timeoutSeconds: 90, @@ -260,7 +266,7 @@ describe('Pubsub Functions', () => { .onRun(() => null); expect(result.__trigger.schedule).to.deep.equal({ schedule: 'every 5 minutes', - retryConfig: retryConfig, + retryConfig, }); expect(result.__trigger.labels).to.deep.equal({ 'deployment-scheduled': 'true', @@ -271,14 +277,14 @@ describe('Pubsub Functions', () => { }); it('should return an appropriate trigger when called with region, options, retryConfig, and timeZone', () => { - let retryConfig = { + const retryConfig = { retryCount: 3, maxRetryDuration: '10 minutes', minBackoffDuration: '10 minutes', maxBackoffDuration: '10 minutes', maxDoublings: 5, }; - let result = functions + const result = functions .region('us-east1') .runWith({ timeoutSeconds: 90, @@ -291,7 +297,7 @@ describe('Pubsub Functions', () => { expect(result.__trigger.schedule).to.deep.equal({ schedule: 'every 5 minutes', timeZone: 'America/New_York', - retryConfig: retryConfig, + retryConfig, }); expect(result.__trigger.labels).to.deep.equal({ 'deployment-scheduled': 'true', @@ -363,7 +369,7 @@ describe('Pubsub Functions', () => { }); it('should not throw when #run is called', () => { - let cf = pubsub.topic('toppy').onPublish(() => null); + const cf = pubsub.topic('toppy').onPublish(() => null); expect(cf.run).to.not.throw(Error); }); }); diff --git a/spec/providers/storage.spec.ts b/spec/providers/storage.spec.ts index 744946ca6..2a7566142 100644 --- a/spec/providers/storage.spec.ts +++ b/spec/providers/storage.spec.ts @@ -21,9 +21,9 @@ // SOFTWARE. import { expect } from 'chai'; -import * as storage from '../../src/providers/storage'; -import * as functions from '../../src/index'; import { Event, EventContext } from '../../src/index'; +import * as functions from '../../src/index'; +import * as storage from '../../src/providers/storage'; describe('Storage Functions', () => { describe('ObjectBuilder', () => { @@ -38,7 +38,7 @@ describe('Storage Functions', () => { }); it('should allow both region and runtime options to be set', () => { - let fn = functions + const fn = functions .region('us-east1') .runWith({ timeoutSeconds: 90, @@ -54,7 +54,7 @@ describe('Storage Functions', () => { describe('#onArchive', () => { it('should return a TriggerDefinition with appropriate values', () => { - let cloudFunction = storage + const cloudFunction = storage .bucket('bucky') .object() .onArchive(() => null); @@ -68,7 +68,7 @@ describe('Storage Functions', () => { }); it('should use the default bucket when none is provided', () => { - let cloudFunction = storage.object().onArchive(() => null); + const cloudFunction = storage.object().onArchive(() => null); expect(cloudFunction.__trigger).to.deep.equal({ eventTrigger: { eventType: 'google.storage.object.archive', @@ -79,11 +79,11 @@ describe('Storage Functions', () => { }); it('should allow fully qualified bucket names', () => { - let subjectQualified = new storage.ObjectBuilder( + const subjectQualified = new storage.ObjectBuilder( () => 'projects/_/buckets/bucky', {} ); - let result = subjectQualified.onArchive(() => null); + const result = subjectQualified.onArchive(() => null); expect(result.__trigger).to.deep.equal({ eventTrigger: { eventType: 'google.storage.object.archive', @@ -104,10 +104,10 @@ describe('Storage Functions', () => { }); it('should not mess with media links using non-literal slashes', () => { - let cloudFunction = storage.object().onArchive(data => { + const cloudFunction = storage.object().onArchive(data => { return data.mediaLink; }); - let goodMediaLinkEvent: Event = { + const goodMediaLinkEvent: Event = { data: { mediaLink: 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + @@ -134,7 +134,7 @@ describe('Storage Functions', () => { describe('#onDelete', () => { it('should return a TriggerDefinition with appropriate values', () => { - let cloudFunction = storage + const cloudFunction = storage .bucket('bucky') .object() .onDelete(() => null); @@ -148,7 +148,7 @@ describe('Storage Functions', () => { }); it('should use the default bucket when none is provided', () => { - let cloudFunction = storage.object().onDelete(() => null); + const cloudFunction = storage.object().onDelete(() => null); expect(cloudFunction.__trigger).to.deep.equal({ eventTrigger: { eventType: 'google.storage.object.delete', @@ -159,11 +159,11 @@ describe('Storage Functions', () => { }); it('should allow fully qualified bucket names', () => { - let subjectQualified = new storage.ObjectBuilder( + const subjectQualified = new storage.ObjectBuilder( () => 'projects/_/buckets/bucky', {} ); - let result = subjectQualified.onDelete(() => null); + const result = subjectQualified.onDelete(() => null); expect(result.__trigger).to.deep.equal({ eventTrigger: { eventType: 'google.storage.object.delete', @@ -184,10 +184,10 @@ describe('Storage Functions', () => { }); it('should not mess with media links using non-literal slashes', () => { - let cloudFunction = storage.object().onDelete(data => { + const cloudFunction = storage.object().onDelete(data => { return data.mediaLink; }); - let goodMediaLinkEvent = { + const goodMediaLinkEvent = { data: { mediaLink: 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + @@ -214,7 +214,7 @@ describe('Storage Functions', () => { describe('#onFinalize', () => { it('should return a TriggerDefinition with appropriate values', () => { - let cloudFunction = storage + const cloudFunction = storage .bucket('bucky') .object() .onFinalize(() => null); @@ -228,7 +228,7 @@ describe('Storage Functions', () => { }); it('should use the default bucket when none is provided', () => { - let cloudFunction = storage.object().onFinalize(() => null); + const cloudFunction = storage.object().onFinalize(() => null); expect(cloudFunction.__trigger).to.deep.equal({ eventTrigger: { eventType: 'google.storage.object.finalize', @@ -239,11 +239,11 @@ describe('Storage Functions', () => { }); it('should allow fully qualified bucket names', () => { - let subjectQualified = new storage.ObjectBuilder( + const subjectQualified = new storage.ObjectBuilder( () => 'projects/_/buckets/bucky', {} ); - let result = subjectQualified.onFinalize(() => null); + const result = subjectQualified.onFinalize(() => null); expect(result.__trigger).to.deep.equal({ eventTrigger: { eventType: 'google.storage.object.finalize', @@ -264,10 +264,10 @@ describe('Storage Functions', () => { }); it('should not mess with media links using non-literal slashes', () => { - let cloudFunction = storage.object().onFinalize(data => { + const cloudFunction = storage.object().onFinalize(data => { return data.mediaLink; }); - let goodMediaLinkEvent = { + const goodMediaLinkEvent = { data: { mediaLink: 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + @@ -294,7 +294,7 @@ describe('Storage Functions', () => { describe('#onMetadataUpdate', () => { it('should return a TriggerDefinition with appropriate values', () => { - let cloudFunction = storage + const cloudFunction = storage .bucket('bucky') .object() .onMetadataUpdate(() => null); @@ -308,7 +308,7 @@ describe('Storage Functions', () => { }); it('should use the default bucket when none is provided', () => { - let cloudFunction = storage.object().onMetadataUpdate(() => null); + const cloudFunction = storage.object().onMetadataUpdate(() => null); expect(cloudFunction.__trigger).to.deep.equal({ eventTrigger: { eventType: 'google.storage.object.metadataUpdate', @@ -319,11 +319,11 @@ describe('Storage Functions', () => { }); it('should allow fully qualified bucket names', () => { - let subjectQualified = new storage.ObjectBuilder( + const subjectQualified = new storage.ObjectBuilder( () => 'projects/_/buckets/bucky', {} ); - let result = subjectQualified.onMetadataUpdate(() => null); + const result = subjectQualified.onMetadataUpdate(() => null); expect(result.__trigger).to.deep.equal({ eventTrigger: { eventType: 'google.storage.object.metadataUpdate', @@ -344,10 +344,10 @@ describe('Storage Functions', () => { }); it('should not mess with media links using non-literal slashes', () => { - let cloudFunction = storage.object().onMetadataUpdate(data => { + const cloudFunction = storage.object().onMetadataUpdate(data => { return data.mediaLink; }); - let goodMediaLinkEvent = { + const goodMediaLinkEvent = { data: { mediaLink: 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + @@ -386,17 +386,19 @@ describe('Storage Functions', () => { describe('#onArchive', () => { it('should return an empty trigger', () => { - let cloudFunction = functions.handler.storage.bucket.onArchive( + const cloudFunction = functions.handler.storage.bucket.onArchive( () => null ); expect(cloudFunction.__trigger).to.deep.equal({}); }); it('should not mess with media links using non-literal slashes', () => { - let cloudFunction = functions.handler.storage.object.onArchive(data => { - return data.mediaLink; - }); - let goodMediaLinkEvent = { + const cloudFunction = functions.handler.storage.object.onArchive( + data => { + return data.mediaLink; + } + ); + const goodMediaLinkEvent = { data: { mediaLink: 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + @@ -423,17 +425,19 @@ describe('Storage Functions', () => { describe('#onDelete', () => { it('should return an empty trigger', () => { - let cloudFunction = functions.handler.storage.bucket.onDelete( + const cloudFunction = functions.handler.storage.bucket.onDelete( () => null ); expect(cloudFunction.__trigger).to.deep.equal({}); }); it('should not mess with media links using non-literal slashes', () => { - let cloudFunction = functions.handler.storage.object.onDelete(data => { - return data.mediaLink; - }); - let goodMediaLinkEvent = { + const cloudFunction = functions.handler.storage.object.onDelete( + data => { + return data.mediaLink; + } + ); + const goodMediaLinkEvent = { data: { mediaLink: 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + @@ -460,19 +464,19 @@ describe('Storage Functions', () => { describe('#onFinalize', () => { it('should return an empty trigger', () => { - let cloudFunction = functions.handler.storage.bucket.onFinalize( + const cloudFunction = functions.handler.storage.bucket.onFinalize( () => null ); expect(cloudFunction.__trigger).to.deep.equal({}); }); it('should not mess with media links using non-literal slashes', () => { - let cloudFunction = functions.handler.storage.object.onFinalize( + const cloudFunction = functions.handler.storage.object.onFinalize( data => { return data.mediaLink; } ); - let goodMediaLinkEvent = { + const goodMediaLinkEvent = { data: { mediaLink: 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + @@ -499,19 +503,19 @@ describe('Storage Functions', () => { describe('#onMetadataUpdate', () => { it('should return an empty trigger', () => { - let cloudFunction = functions.handler.storage.bucket.onMetadataUpdate( + const cloudFunction = functions.handler.storage.bucket.onMetadataUpdate( () => null ); expect(cloudFunction.__trigger).to.deep.equal({}); }); it('should not mess with media links using non-literal slashes', () => { - let cloudFunction = functions.handler.storage.object.onMetadataUpdate( + const cloudFunction = functions.handler.storage.object.onMetadataUpdate( data => { return data.mediaLink; } ); - let goodMediaLinkEvent = { + const goodMediaLinkEvent = { data: { mediaLink: 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + @@ -549,7 +553,7 @@ describe('Storage Functions', () => { }); it('should not throw when #run is called', () => { - let cf = storage.object().onArchive(() => null); + const cf = storage.object().onArchive(() => null); expect(cf.run).to.not.throw(Error); }); }); From 860dd00fb81148bd600ffe6b44d9bb6657fd3586 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Fri, 14 Jun 2019 19:43:30 -0700 Subject: [PATCH 035/437] Fix lint errors for database, firestore, https tests (#470) * fix lint errors for database, firestore, https tests * consistency changes and missed lint errors, separate out mockrequest class * format strings --- spec/fixtures/mockrequest.ts | 82 +++++++++++++++++ spec/providers/database.spec.ts | 111 +++++++++++++---------- spec/providers/firestore.spec.ts | 144 ++++++++++++++--------------- spec/providers/https.spec.ts | 151 ++++++++----------------------- 4 files changed, 257 insertions(+), 231 deletions(-) create mode 100644 spec/fixtures/mockrequest.ts diff --git a/spec/fixtures/mockrequest.ts b/spec/fixtures/mockrequest.ts new file mode 100644 index 000000000..2139c71ce --- /dev/null +++ b/spec/fixtures/mockrequest.ts @@ -0,0 +1,82 @@ +import * as jwt from 'jsonwebtoken'; +import * as _ from 'lodash'; +import * as nock from 'nock'; +import * as mocks from '../fixtures/credential/key.json'; + +// MockRequest mocks an https.Request. +export class MockRequest { + public method: 'POST' | 'GET' | 'OPTIONS' = 'POST'; + + constructor( + readonly body: any, + readonly headers: { [name: string]: string } + ) { + // This block intentionally left blank. + } + + public header(name: string): string { + return this.headers[name.toLowerCase()]; + } +} + +// Creates a mock request with the given data and content-type. +export function mockRequest( + data: any, + contentType: string = 'application/json', + context: { + authorization?: string; + instanceIdToken?: string; + } = {} +) { + const body: any = {}; + if (!_.isUndefined(data)) { + body.data = data; + } + + const headers = { + 'content-type': contentType, + authorization: context.authorization, + 'firebase-instance-id-token': context.instanceIdToken, + origin: 'example.com', + }; + + return new MockRequest(body, headers); +} + +export const expectedResponseHeaders = { + 'Access-Control-Allow-Origin': 'example.com', + Vary: 'Origin', +}; + +/** + * Mocks out the http request used by the firebase-admin SDK to get the key for + * verifying an id token. + */ +export function mockFetchPublicKeys(): nock.Scope { + const mockedResponse = { [mocks.key_id]: mocks.public_key }; + const headers = { + 'cache-control': 'public, max-age=1, must-revalidate, no-transform', + }; + + return nock('https://www.googleapis.com:443') + .get('/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com') + .reply(200, mockedResponse, headers); +} + +/** + * Generates a mocked Firebase ID token. + */ +export function generateIdToken(projectId: string): string { + const claims = {}; + const options = { + audience: projectId, + expiresIn: 60 * 60, // 1 hour in seconds + issuer: 'https://securetoken.google.com/' + projectId, + subject: mocks.user_id, + algorithm: 'RS256', + header: { + kid: mocks.key_id, + }, + }; + return jwt.sign(claims, mocks.private_key, options); +} diff --git a/spec/providers/database.spec.ts b/spec/providers/database.spec.ts index 5fa1307fd..25293ac11 100644 --- a/spec/providers/database.spec.ts +++ b/spec/providers/database.spec.ts @@ -20,12 +20,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import * as database from '../../src/providers/database'; import { expect } from 'chai'; import { apps as appsNamespace } from '../../src/apps'; -import { applyChange } from '../../src/utils'; import * as functions from '../../src/index'; -import { Event } from '../../src/index'; +import * as database from '../../src/providers/database'; +import { applyChange } from '../../src/utils'; describe('Database Functions', () => { describe('DatabaseBuilder', () => { @@ -44,14 +43,14 @@ describe('Database Functions', () => { }); it('should allow both region and runtime options to be set', () => { - let fn = functions + const fn = functions .region('us-east1') .runWith({ timeoutSeconds: 90, memory: '256MB', }) .database.ref('/') - .onCreate(snap => snap); + .onCreate((snap) => snap); expect(fn.__trigger.regions).to.deep.equal(['us-east1']); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); @@ -60,7 +59,7 @@ describe('Database Functions', () => { describe('#onWrite()', () => { it('should return "ref.write" as the event type', () => { - let eventType = database.ref('foo').onWrite(() => null).__trigger + const eventType = database.ref('foo').onWrite(() => null).__trigger .eventTrigger.eventType; expect(eventType).to.eq( 'providers/google.firebase.database/eventTypes/ref.write' @@ -68,17 +67,17 @@ describe('Database Functions', () => { }); it('should construct a proper resource path', () => { - let resource = database.ref('foo').onWrite(() => null).__trigger + const resource = database.ref('foo').onWrite(() => null).__trigger .eventTrigger.resource; expect(resource).to.eq('projects/_/instances/subdomain/refs/foo'); }); it('should let developers choose a database instance', () => { - let func = database + const func = database .instance('custom') .ref('foo') .onWrite(() => null); - let resource = func.__trigger.eventTrigger.resource; + const resource = func.__trigger.eventTrigger.resource; expect(resource).to.eq('projects/_/instances/custom/refs/foo'); }); @@ -96,9 +95,11 @@ describe('Database Functions', () => { resource: 'projects/_/instances/subdomains/refs/users', }, }; - let handler = database.ref('/users/{id}').onWrite((change, context) => { - expect(change.after.val()).to.deep.equal({ foo: 'bar' }); - }); + const handler = database + .ref('/users/{id}') + .onWrite((change, context) => { + expect(change.after.val()).to.deep.equal({ foo: 'bar' }); + }); return handler(event.data, event.context); }); @@ -106,7 +107,7 @@ describe('Database Functions', () => { describe('#onCreate()', () => { it('should return "ref.create" as the event type', () => { - let eventType = database.ref('foo').onCreate(() => null).__trigger + const eventType = database.ref('foo').onCreate(() => null).__trigger .eventTrigger.eventType; expect(eventType).to.eq( 'providers/google.firebase.database/eventTypes/ref.create' @@ -114,17 +115,17 @@ describe('Database Functions', () => { }); it('should construct a proper resource path', () => { - let resource = database.ref('foo').onCreate(() => null).__trigger + const resource = database.ref('foo').onCreate(() => null).__trigger .eventTrigger.resource; expect(resource).to.eq('projects/_/instances/subdomain/refs/foo'); }); it('should let developers choose a database instance', () => { - let func = database + const func = database .instance('custom') .ref('foo') .onCreate(() => null); - let resource = func.__trigger.eventTrigger.resource; + const resource = func.__trigger.eventTrigger.resource; expect(resource).to.eq('projects/_/instances/custom/refs/foo'); }); @@ -143,9 +144,11 @@ describe('Database Functions', () => { }, }; - let handler = database.ref('/users/{id}').onCreate((data, context) => { - expect(data.val()).to.deep.equal({ foo: 'bar' }); - }); + const handler = database + .ref('/users/{id}') + .onCreate((data, context) => { + expect(data.val()).to.deep.equal({ foo: 'bar' }); + }); return handler(event.data, event.context); }); @@ -153,7 +156,7 @@ describe('Database Functions', () => { describe('#onUpdate()', () => { it('should return "ref.update" as the event type', () => { - let eventType = database.ref('foo').onUpdate(() => null).__trigger + const eventType = database.ref('foo').onUpdate(() => null).__trigger .eventTrigger.eventType; expect(eventType).to.eq( 'providers/google.firebase.database/eventTypes/ref.update' @@ -161,17 +164,17 @@ describe('Database Functions', () => { }); it('should construct a proper resource path', () => { - let resource = database.ref('foo').onUpdate(() => null).__trigger + const resource = database.ref('foo').onUpdate(() => null).__trigger .eventTrigger.resource; expect(resource).to.eq('projects/_/instances/subdomain/refs/foo'); }); it('should let developers choose a database instance', () => { - let func = database + const func = database .instance('custom') .ref('foo') .onUpdate(() => null); - let resource = func.__trigger.eventTrigger.resource; + const resource = func.__trigger.eventTrigger.resource; expect(resource).to.eq('projects/_/instances/custom/refs/foo'); }); @@ -190,7 +193,7 @@ describe('Database Functions', () => { }, }; - let handler = database + const handler = database .ref('/users/{id}') .onUpdate((change, context) => { expect(change.after.val()).to.deep.equal({ foo: 'bar' }); @@ -202,7 +205,7 @@ describe('Database Functions', () => { describe('#onDelete()', () => { it('should return "ref.delete" as the event type', () => { - let eventType = database.ref('foo').onDelete(() => null).__trigger + const eventType = database.ref('foo').onDelete(() => null).__trigger .eventTrigger.eventType; expect(eventType).to.eq( 'providers/google.firebase.database/eventTypes/ref.delete' @@ -210,17 +213,17 @@ describe('Database Functions', () => { }); it('should construct a proper resource path', () => { - let resource = database.ref('foo').onDelete(() => null).__trigger + const resource = database.ref('foo').onDelete(() => null).__trigger .eventTrigger.resource; expect(resource).to.eq('projects/_/instances/subdomain/refs/foo'); }); it('should let developers choose a database instance', () => { - let func = database + const func = database .instance('custom') .ref('foo') .onDelete(() => null); - let resource = func.__trigger.eventTrigger.resource; + const resource = func.__trigger.eventTrigger.resource; expect(resource).to.eq('projects/_/instances/custom/refs/foo'); }); @@ -239,9 +242,11 @@ describe('Database Functions', () => { }, }; - let handler = database.ref('/users/{id}').onDelete((data, context) => { - expect(data.val()).to.deep.equal({ foo: 'bar' }); - }); + const handler = database + .ref('/users/{id}') + .onDelete((data, context) => { + expect(data.val()).to.deep.equal({ foo: 'bar' }); + }); return handler(event.data, event.context); }); @@ -251,12 +256,14 @@ describe('Database Functions', () => { describe('handler namespace', () => { describe('#onWrite()', () => { it('correctly sets trigger to {}', () => { - let cf = functions.handler.database.ref.onWrite(() => null); + const cf = functions.handler.database.ref.onWrite(() => null); expect(cf.__trigger).to.deep.equal({}); }); it('should be able to use the instance entry point', () => { - let func = functions.handler.database.instance.ref.onWrite(() => null); + const func = functions.handler.database.instance.ref.onWrite( + () => null + ); expect(func.__trigger).to.deep.equal({}); }); @@ -275,7 +282,7 @@ describe('Database Functions', () => { }, }; - let handler = functions.handler.database.ref.onWrite( + const handler = functions.handler.database.ref.onWrite( (change, context) => { return expect(change.after.val()).to.deep.equal({ foo: 'bar' }); } @@ -287,12 +294,14 @@ describe('Database Functions', () => { describe('#onCreate()', () => { it('correctly sets trigger to {}', () => { - let cf = functions.handler.database.ref.onCreate(() => null); + const cf = functions.handler.database.ref.onCreate(() => null); return expect(cf.__trigger).to.deep.equal({}); }); it('should be able to use the instance entry point', () => { - let func = functions.handler.database.instance.ref.onCreate(() => null); + const func = functions.handler.database.instance.ref.onCreate( + () => null + ); expect(func.__trigger).to.deep.equal({}); }); @@ -310,7 +319,7 @@ describe('Database Functions', () => { resource: 'projects/_/instances/subdomains/refs/users', }, }; - let handler = functions.handler.database.ref.onCreate( + const handler = functions.handler.database.ref.onCreate( (data, context) => { return expect(data.val()).to.deep.equal({ foo: 'bar' }); } @@ -322,12 +331,14 @@ describe('Database Functions', () => { describe('#onUpdate()', () => { it('correctly sets trigger to {}', () => { - let cf = functions.handler.database.ref.onUpdate(() => null); + const cf = functions.handler.database.ref.onUpdate(() => null); return expect(cf.__trigger).to.deep.equal({}); }); it('should be able to use the instance entry point', () => { - let func = functions.handler.database.instance.ref.onUpdate(() => null); + const func = functions.handler.database.instance.ref.onUpdate( + () => null + ); expect(func.__trigger).to.deep.equal({}); }); @@ -345,7 +356,7 @@ describe('Database Functions', () => { resource: 'projects/_/instances/subdomains/refs/users', }, }; - let handler = functions.handler.database.ref.onUpdate( + const handler = functions.handler.database.ref.onUpdate( (change, context) => { return expect(change.after.val()).to.deep.equal({ foo: 'bar' }); } @@ -357,12 +368,14 @@ describe('Database Functions', () => { describe('#onDelete()', () => { it('correctly sets trigger to {}', () => { - let cf = functions.handler.database.ref.onDelete(() => null); + const cf = functions.handler.database.ref.onDelete(() => null); return expect(cf.__trigger).to.deep.equal({}); }); it('should be able to use the instance entry point', () => { - let func = functions.handler.database.instance.ref.onDelete(() => null); + const func = functions.handler.database.instance.ref.onDelete( + () => null + ); expect(func.__trigger).to.deep.equal({}); }); @@ -381,7 +394,7 @@ describe('Database Functions', () => { }, }; - let handler = functions.handler.database.ref.onDelete( + const handler = functions.handler.database.ref.onDelete( (data, context) => { return expect(data.val()).to.deep.equal({ foo: 'bar' }); } @@ -406,14 +419,14 @@ describe('Database Functions', () => { }); it('should not throw when #run is called', () => { - let cf = database.ref('/path').onWrite(() => null); + const cf = database.ref('/path').onWrite(() => null); expect(cf.run).to.not.throw(Error); }); }); describe('resourceToInstanceAndPath', () => { it('should return the correct instance and path strings', () => { - let [instance, path] = database.resourceToInstanceAndPath( + const [instance, path] = database.resourceToInstanceAndPath( 'projects/_/instances/foo/refs/bar' ); expect(instance).to.equal('https://foo.firebaseio.com'); @@ -443,8 +456,8 @@ describe('Database Functions', () => { let subject: any; const apps = new appsNamespace.Apps(); - let populate = (data: any) => { - let [instance, path] = database.resourceToInstanceAndPath( + const populate = (data: any) => { + const [instance, path] = database.resourceToInstanceAndPath( 'projects/_/instances/other-subdomain/refs/foo' ); subject = new database.DataSnapshot(data, path, apps.admin, instance); @@ -562,7 +575,7 @@ describe('Database Functions', () => { it('should not execute for leaf or null nodes', () => { populate(23); let count = 0; - let counter = (snap: any) => count++; + const counter = (snap: any) => count++; expect(subject.forEach(counter)).to.equal(false); expect(count).to.eq(0); @@ -635,7 +648,7 @@ describe('Database Functions', () => { }); it('should return null for the root', () => { - let [instance, path] = database.resourceToInstanceAndPath( + const [instance, path] = database.resourceToInstanceAndPath( 'projects/_/instances/foo/refs/' ); const snapshot = new database.DataSnapshot( diff --git a/spec/providers/firestore.spec.ts b/spec/providers/firestore.spec.ts index e43c1f2d7..42cdf97f7 100644 --- a/spec/providers/firestore.spec.ts +++ b/spec/providers/firestore.spec.ts @@ -20,17 +20,17 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import { expect } from 'chai'; import * as admin from 'firebase-admin'; - -import * as firestore from '../../src/providers/firestore'; import * as _ from 'lodash'; -import { expect } from 'chai'; + import * as functions from '../../src/index'; +import * as firestore from '../../src/providers/firestore'; describe('Firestore Functions', () => { function constructValue(fields: any) { return { - fields: fields, + fields, name: 'projects/pid/databases/(default)/documents/collection/123', createTime: '2017-06-02T18:48:58.920638Z', updateTime: '2017-07-02T18:48:58.920638Z', @@ -40,7 +40,7 @@ describe('Firestore Functions', () => { function makeEvent(data: any, context?: { [key: string]: any }) { context = context || {}; return { - data: data, + data, context: _.merge( { eventId: '123', @@ -59,8 +59,8 @@ describe('Firestore Functions', () => { function constructEvent(oldValue: object, value: object, eventType: string) { return { data: { - oldValue: oldValue, - value: value, + oldValue, + value, }, context: { resource: { @@ -112,18 +112,20 @@ describe('Firestore Functions', () => { }); it('should allow terse constructors', () => { - let resource = + const resource = 'projects/project1/databases/(default)/documents/users/{uid}'; - let cloudFunction = firestore.document('users/{uid}').onWrite(() => null); + const cloudFunction = firestore + .document('users/{uid}') + .onWrite(() => null); expect(cloudFunction.__trigger).to.deep.equal( expectedTrigger(resource, 'document.write') ); }); it('should allow custom namespaces', () => { - let resource = + const resource = 'projects/project1/databases/(default)/documents@v2/users/{uid}'; - let cloudFunction = firestore + const cloudFunction = firestore .namespace('v2') .document('users/{uid}') .onWrite(() => null); @@ -133,8 +135,8 @@ describe('Firestore Functions', () => { }); it('should allow custom databases', () => { - let resource = 'projects/project1/databases/myDB/documents/users/{uid}'; - let cloudFunction = firestore + const resource = 'projects/project1/databases/myDB/documents/users/{uid}'; + const cloudFunction = firestore .database('myDB') .document('users/{uid}') .onWrite(() => null); @@ -144,9 +146,9 @@ describe('Firestore Functions', () => { }); it('should allow both custom database and namespace', () => { - let resource = + const resource = 'projects/project1/databases/myDB/documents@v2/users/{uid}'; - let cloudFunction = firestore + const cloudFunction = firestore .database('myDB') .namespace('v2') .document('users/{uid}') @@ -157,14 +159,14 @@ describe('Firestore Functions', () => { }); it('should allow both region and runtime options to be set', () => { - let fn = functions + const fn = functions .region('us-east1') .runWith({ timeoutSeconds: 90, memory: '256MB', }) .firestore.document('doc') - .onCreate(snap => snap); + .onCreate((snap) => snap); expect(fn.__trigger.regions).to.deep.equal(['us-east1']); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); @@ -172,9 +174,9 @@ describe('Firestore Functions', () => { }); it('onCreate should have the "document.create" eventType', () => { - let resource = + const resource = 'projects/project1/databases/(default)/documents/users/{uid}'; - let eventType = firestore.document('users/{uid}').onCreate(() => null) + const eventType = firestore.document('users/{uid}').onCreate(() => null) .__trigger.eventTrigger.eventType; expect(eventType).to.eq( expectedTrigger(resource, 'document.create').eventTrigger.eventType @@ -182,9 +184,9 @@ describe('Firestore Functions', () => { }); it('onUpdate should have the "document.update" eventType', () => { - let resource = + const resource = 'projects/project1/databases/(default)/documents/users/{uid}'; - let eventType = firestore.document('users/{uid}').onUpdate(() => null) + const eventType = firestore.document('users/{uid}').onUpdate(() => null) .__trigger.eventTrigger.eventType; expect(eventType).to.eq( expectedTrigger(resource, 'document.update').eventTrigger.eventType @@ -192,9 +194,9 @@ describe('Firestore Functions', () => { }); it('onDelete should have the "document.delete" eventType', () => { - let resource = + const resource = 'projects/project1/databases/(default)/documents/users/{uid}'; - let eventType = firestore.document('users/{uid}').onDelete(() => null) + const eventType = firestore.document('users/{uid}').onDelete(() => null) .__trigger.eventTrigger.eventType; expect(eventType).to.eq( expectedTrigger(resource, 'document.delete').eventTrigger.eventType @@ -216,7 +218,7 @@ describe('Firestore Functions', () => { }); it('should not throw when #run is called', () => { - let cf = firestore.document('input').onCreate(() => null); + const cf = firestore.document('input').onCreate(() => null); expect(cf.run).to.not.throw(Error); }); }); @@ -231,7 +233,7 @@ describe('Firestore Functions', () => { }); it('constructs appropriate fields and getters for event.data on "document.write" events', () => { - let testFunction = firestore + const testFunction = firestore .document('path') .onWrite((change, context) => { expect(change.before.data()).to.deep.equal({ @@ -243,28 +245,28 @@ describe('Firestore Functions', () => { expect(change.after.get('key1')).to.equal(true); return true; // otherwise will get warning about returning undefined }); - let data = constructEvent( + const event = constructEvent( createOldValue(), createValue(), 'document.write' ); - return testFunction(data.data, data.context); + return testFunction(event.data, event.context); }).timeout(5000); it('constructs appropriate fields and getters for event.data on "document.create" events', () => { - let testFunction = firestore + const testFunction = firestore .document('path') .onCreate((data, context) => { expect(data.data()).to.deep.equal({ key1: true, key2: 123 }); expect(data.get('key1')).to.equal(true); return true; // otherwise will get warning about returning undefined }); - let data = constructEvent({}, createValue(), 'document.create'); - return testFunction(data.data, data.context); + const event = constructEvent({}, createValue(), 'document.create'); + return testFunction(event.data, event.context); }).timeout(5000); it('constructs appropriate fields and getters for event.data on "document.update" events', () => { - let testFunction = firestore + const testFunction = firestore .document('path') .onUpdate((change, context) => { expect(change.before.data()).to.deep.equal({ @@ -276,24 +278,24 @@ describe('Firestore Functions', () => { expect(change.after.get('key1')).to.equal(true); return true; // otherwise will get warning about returning undefined }); - let data = constructEvent( + const event = constructEvent( createOldValue(), createValue(), 'document.update' ); - return testFunction(data.data, data.context); + return testFunction(event.data, event.context); }).timeout(5000); it('constructs appropriate fields and getters for event.data on "document.delete" events', () => { - let testFunction = firestore + const testFunction = firestore .document('path') .onDelete((data, context) => { expect(data.data()).to.deep.equal({ key1: false, key2: 111 }); expect(data.get('key1')).to.equal(false); return true; // otherwise will get warning about returning undefined }); - let data = constructEvent(createOldValue(), {}, 'document.delete'); - return testFunction(data.data, data.context); + const event = constructEvent(createOldValue(), {}, 'document.delete'); + return testFunction(event.data, event.context); }).timeout(5000); }); @@ -307,7 +309,7 @@ describe('Firestore Functions', () => { }); it('constructs correct data type and sets trigger to {} on "document.write" events', () => { - let testFunction = functions.handler.firestore.document.onWrite( + const testFunction = functions.handler.firestore.document.onWrite( (change, context) => { expect(change.before.data()).to.deep.equal({ key1: false, @@ -320,16 +322,16 @@ describe('Firestore Functions', () => { } ); expect(testFunction.__trigger).to.deep.equal({}); - let data = constructEvent( + const event = constructEvent( createOldValue(), createValue(), 'document.write' ); - return testFunction(data.data, data.context); + return testFunction(event.data, event.context); }).timeout(5000); it('constructs correct data type and sets trigger to {} on "document.create" events', () => { - let testFunction = functions.handler.firestore.document.onCreate( + const testFunction = functions.handler.firestore.document.onCreate( (data, context) => { expect(data.data()).to.deep.equal({ key1: true, key2: 123 }); expect(data.get('key1')).to.equal(true); @@ -337,13 +339,13 @@ describe('Firestore Functions', () => { } ); expect(testFunction.__trigger).to.deep.equal({}); - let data = constructEvent({}, createValue(), 'document.create'); - return testFunction(data.data, data.context); + const event = constructEvent({}, createValue(), 'document.create'); + return testFunction(event.data, event.context); }).timeout(5000); it('constructs correct data type and sets trigger to {} on "document.update" events', () => { - let testFunction = functions.handler.firestore.document.onUpdate( - change => { + const testFunction = functions.handler.firestore.document.onUpdate( + (change) => { expect(change.before.data()).to.deep.equal({ key1: false, key2: 111, @@ -355,32 +357,32 @@ describe('Firestore Functions', () => { } ); expect(testFunction.__trigger).to.deep.equal({}); - let data = constructEvent( + const event = constructEvent( createOldValue(), createValue(), 'document.update' ); - return testFunction(data.data, data.context); + return testFunction(event.data, event.context); }).timeout(5000); it('constructs correct data type and sets trigger to {} on "document.delete" events', () => { - let testFunction = functions.handler.firestore.document.onDelete( + const testFunction = functions.handler.firestore.document.onDelete( (data, context) => { expect(data.data()).to.deep.equal({ key1: false, key2: 111 }); expect(data.get('key1')).to.equal(false); return true; // otherwise will get warning about returning undefined } ); - let data = constructEvent(createOldValue(), {}, 'document.delete'); + const event = constructEvent(createOldValue(), {}, 'document.delete'); expect(testFunction.__trigger).to.deep.equal({}); - return testFunction(data.data, data.context); + return testFunction(event.data, event.context); }).timeout(5000); }); describe('SnapshotConstructor', () => { describe('#data()', () => { it('should parse int values', () => { - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent({ value: constructValue({ key: { integerValue: '123' } }), }) @@ -389,7 +391,7 @@ describe('Firestore Functions', () => { }); it('should parse double values', () => { - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent({ value: constructValue({ key: { doubleValue: 12.34 } }), }) @@ -398,7 +400,7 @@ describe('Firestore Functions', () => { }); it('should parse null values', () => { - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent({ value: constructValue({ key: { nullValue: null } }), }) @@ -407,7 +409,7 @@ describe('Firestore Functions', () => { }); it('should parse boolean values', () => { - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent({ value: constructValue({ key: { booleanValue: true } }), }) @@ -416,7 +418,7 @@ describe('Firestore Functions', () => { }); it('should parse string values', () => { - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent({ value: constructValue({ key: { stringValue: 'foo' } }), }) @@ -425,14 +427,14 @@ describe('Firestore Functions', () => { }); it('should parse array values', () => { - let raw = constructValue({ + const raw = constructValue({ key: { arrayValue: { values: [{ integerValue: '1' }, { integerValue: '2' }], }, }, }); - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent({ value: raw, }) @@ -441,7 +443,7 @@ describe('Firestore Functions', () => { }); it('should parse object values', () => { - let raw = constructValue({ + const raw = constructValue({ keyParent: { mapValue: { fields: { @@ -455,7 +457,7 @@ describe('Firestore Functions', () => { }, }, }); - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent({ value: raw, }) @@ -466,7 +468,7 @@ describe('Firestore Functions', () => { }); it('should parse GeoPoint values', () => { - let raw = constructValue({ + const raw = constructValue({ geoPointValue: { mapValue: { fields: { @@ -480,7 +482,7 @@ describe('Firestore Functions', () => { }, }, }); - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent({ value: raw, }) @@ -494,27 +496,27 @@ describe('Firestore Functions', () => { }); it('should parse reference values', () => { - let raw = constructValue({ + const raw = constructValue({ referenceVal: { referenceValue: 'projects/proj1/databases/(default)/documents/doc1/id', }, }); - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent({ value: raw, }) ); - expect(snapshot.data()['referenceVal'].path).to.equal('doc1/id'); + expect(_.get(snapshot.data(), 'referenceVal').path).to.equal('doc1/id'); }); it('should parse timestamp values with precision to the millisecond', () => { - let raw = constructValue({ + const raw = constructValue({ timestampVal: { timestampValue: '2017-06-13T00:58:40.349Z', }, }); - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent({ value: raw, }) @@ -527,12 +529,12 @@ describe('Firestore Functions', () => { }); it('should parse timestamp values with precision to the second', () => { - let raw = constructValue({ + const raw = constructValue({ timestampVal: { timestampValue: '2017-06-13T00:58:40Z', }, }); - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent({ value: raw, }) @@ -546,12 +548,12 @@ describe('Firestore Functions', () => { it('should parse binary values', () => { // Format defined in https://developers.google.com/discovery/v1/type-format - let raw = constructValue({ + const raw = constructValue({ binaryVal: { bytesValue: 'Zm9vYmFy', }, }); - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent({ value: raw, }) @@ -609,7 +611,7 @@ describe('Firestore Functions', () => { describe('Handle empty and non-existent documents', () => { it('constructs non-existent DocumentSnapshot when whole document deleted', () => { - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent( { value: {}, // value is empty when the whole document is deleted @@ -627,7 +629,7 @@ describe('Firestore Functions', () => { }); it('constructs existent DocumentSnapshot with empty data when all fields of document deleted', () => { - let snapshot = firestore.snapshotConstructor( + const snapshot = firestore.snapshotConstructor( makeEvent({ value: { // value is not empty when document still exists diff --git a/spec/providers/https.spec.ts b/spec/providers/https.spec.ts index ec1a191e5..f858d5923 100644 --- a/spec/providers/https.spec.ts +++ b/spec/providers/https.spec.ts @@ -23,25 +23,30 @@ import { expect } from 'chai'; import * as express from 'express'; import * as firebase from 'firebase-admin'; -import * as jwt from 'jsonwebtoken'; import * as _ from 'lodash'; -import * as nock from 'nock'; import { apps as appsNamespace } from '../../src/apps'; import * as functions from '../../src/index'; import * as https from '../../src/providers/https'; import * as mocks from '../fixtures/credential/key.json'; +import { + expectedResponseHeaders, + generateIdToken, + mockFetchPublicKeys, + MockRequest, + mockRequest, +} from '../fixtures/mockrequest'; describe('CloudHttpsBuilder', () => { describe('#onRequest', () => { it('should return a Trigger with appropriate values', () => { - let result = https.onRequest((req, resp) => { + const result = https.onRequest((req, resp) => { resp.send(200); }); expect(result.__trigger).to.deep.equal({ httpsTrigger: {} }); }); it('should allow both region and runtime options to be set', () => { - let fn = functions + const fn = functions .region('us-east1') .runWith({ timeoutSeconds: 90, @@ -59,7 +64,7 @@ describe('CloudHttpsBuilder', () => { describe('handler namespace', () => { describe('#onRequest', () => { it('should return an empty trigger', () => { - let result = functions.handler.https.onRequest((req, res) => { + const result = functions.handler.https.onRequest((req, res) => { res.send(200); }); expect(result.__trigger).to.deep.equal({}); @@ -68,7 +73,7 @@ describe('handler namespace', () => { describe('#onCall', () => { it('should return an empty trigger', () => { - let result = functions.handler.https.onCall(() => null); + const result = functions.handler.https.onCall(() => null); expect(result.__trigger).to.deep.equal({}); }); }); @@ -163,89 +168,11 @@ async function runTest(test: CallTest): Promise { expect(response.status).to.equal(test.expectedHttpResponse.status); } -// MockRequest mocks an https.Request. -class MockRequest { - public method: 'POST' | 'GET' | 'OPTIONS' = 'POST'; - - constructor( - readonly body: any, - readonly headers: { [name: string]: string } - ) { - // This block intentionally left blank. - } - - public header(name: string): string { - return this.headers[name.toLowerCase()]; - } -} - -// Creates a mock request with the given data and content-type. -function request( - data: any, - contentType: string = 'application/json', - context: { - authorization?: string; - instanceIdToken?: string; - } = {} -) { - const body: any = {}; - if (!_.isUndefined(data)) { - body.data = data; - } - - const headers = { - 'content-type': contentType, - authorization: context.authorization, - 'firebase-instance-id-token': context.instanceIdToken, - origin: 'example.com', - }; - - return new MockRequest(body, headers); -} - -const expectedResponseHeaders = { - 'Access-Control-Allow-Origin': 'example.com', - Vary: 'Origin', -}; - -/** - * Mocks out the http request used by the firebase-admin SDK to get the key for - * verifying an id token. - */ -function mockFetchPublicKeys(): nock.Scope { - const mockedResponse = { [mocks.key_id]: mocks.public_key }; - const headers = { - 'cache-control': 'public, max-age=1, must-revalidate, no-transform', - }; - - return nock('https://www.googleapis.com:443') - .get('/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com') - .reply(200, mockedResponse, headers); -} - -/** - * Generates a mocked Firebase ID token. - */ -export function generateIdToken(projectId: string): string { - const claims = {}; - const options = { - audience: projectId, - expiresIn: 60 * 60, // 1 hour in seconds - issuer: 'https://securetoken.google.com/' + projectId, - subject: mocks.user_id, - algorithm: 'RS256', - header: { - kid: mocks.key_id, - }, - }; - return jwt.sign(claims, mocks.private_key, options); -} - describe('callable.FunctionBuilder', () => { let app: firebase.app.App; before(() => { - let credential = { + const credential = { getAccessToken: () => { return Promise.resolve({ expires_in: 1000, @@ -260,7 +187,7 @@ describe('callable.FunctionBuilder', () => { }; app = firebase.initializeApp({ projectId: 'aProjectId', - credential: credential, + credential, }); Object.defineProperty(appsNamespace(), 'admin', { get: () => app }); }); @@ -272,7 +199,7 @@ describe('callable.FunctionBuilder', () => { describe('#onCall', () => { it('should return a Trigger with appropriate values', () => { - const result = https.onCall(data => { + const result = https.onCall((data) => { return 'response'; }); expect(result.__trigger).to.deep.equal({ @@ -283,7 +210,7 @@ describe('callable.FunctionBuilder', () => { it('should handle success', () => { return runTest({ - httpRequest: request({ foo: 'bar' }), + httpRequest: mockRequest({ foo: 'bar' }), expectedData: { foo: 'bar' }, callableFunction: (data, context) => ({ baz: 'qux' }), expectedHttpResponse: { @@ -296,7 +223,7 @@ describe('callable.FunctionBuilder', () => { it('should handle null data and return', () => { return runTest({ - httpRequest: request(null), + httpRequest: mockRequest(null), expectedData: null, callableFunction: (data, context) => null, expectedHttpResponse: { @@ -309,7 +236,7 @@ describe('callable.FunctionBuilder', () => { it('should handle void return', () => { return runTest({ - httpRequest: request(null), + httpRequest: mockRequest(null), expectedData: null, callableFunction: (data, context) => { return; @@ -323,7 +250,7 @@ describe('callable.FunctionBuilder', () => { }); it('should reject bad method', () => { - let req = request(null); + const req = mockRequest(null); req.method = 'GET'; return runTest({ httpRequest: req, @@ -343,7 +270,7 @@ describe('callable.FunctionBuilder', () => { it('should ignore charset', () => { return runTest({ - httpRequest: request(null, 'application/json; charset=utf-8'), + httpRequest: mockRequest(null, 'application/json; charset=utf-8'), expectedData: null, callableFunction: (data, context) => { return; @@ -358,7 +285,7 @@ describe('callable.FunctionBuilder', () => { it('should reject bad content type', () => { return runTest({ - httpRequest: request(null, 'text/plain'), + httpRequest: mockRequest(null, 'text/plain'), expectedData: null, callableFunction: (data, context) => { return; @@ -374,7 +301,7 @@ describe('callable.FunctionBuilder', () => { }); it('should reject extra body fields', () => { - const req = request(null); + const req = mockRequest(null); req.body.extra = 'bad'; return runTest({ httpRequest: req, @@ -394,10 +321,10 @@ describe('callable.FunctionBuilder', () => { it('should handle unhandled error', () => { return runTest({ - httpRequest: request(null), + httpRequest: mockRequest(null), expectedData: null, callableFunction: (data, context) => { - throw 'ceci n\'est pas une error'; + throw new Error(`ceci n'est pas une error`); }, expectedHttpResponse: { status: 500, @@ -409,7 +336,7 @@ describe('callable.FunctionBuilder', () => { it('should handle unknown error status', () => { return runTest({ - httpRequest: request(null), + httpRequest: mockRequest(null), expectedData: null, callableFunction: (data, context) => { throw new https.HttpsError('THIS_IS_NOT_VALID' as any, 'nope'); @@ -424,7 +351,7 @@ describe('callable.FunctionBuilder', () => { it('should handle well-formed error', () => { return runTest({ - httpRequest: request(null), + httpRequest: mockRequest(null), expectedData: null, callableFunction: (data, context) => { throw new https.HttpsError('not-found', 'i am error'); @@ -442,7 +369,7 @@ describe('callable.FunctionBuilder', () => { const projectId = appsNamespace().admin.options.projectId; const idToken = generateIdToken(projectId); await runTest({ - httpRequest: request(null, 'application/json', { + httpRequest: mockRequest(null, 'application/json', { authorization: 'Bearer ' + idToken, }), expectedData: null, @@ -467,7 +394,7 @@ describe('callable.FunctionBuilder', () => { it('should reject bad auth', async () => { await runTest({ - httpRequest: request(null, 'application/json', { + httpRequest: mockRequest(null, 'application/json', { authorization: 'Bearer FAKE', }), expectedData: null, @@ -489,7 +416,7 @@ describe('callable.FunctionBuilder', () => { it('should handle instance id', async () => { await runTest({ - httpRequest: request(null, 'application/json', { + httpRequest: mockRequest(null, 'application/json', { instanceIdToken: 'iid-token', }), expectedData: null, @@ -507,13 +434,13 @@ describe('callable.FunctionBuilder', () => { }); it('should expose raw request', async () => { - const mockRequest = request(null, 'application/json', {}); + const mockReq = mockRequest(null, 'application/json', {}); await runTest({ - httpRequest: mockRequest, + httpRequest: mockReq, expectedData: null, callableFunction: (data, context) => { expect(context.rawRequest).to.not.be.undefined; - expect(context.rawRequest).to.equal(mockRequest); + expect(context.rawRequest).to.equal(mockReq); return null; }, expectedHttpResponse: { @@ -525,7 +452,7 @@ describe('callable.FunctionBuilder', () => { }); it('should allow both region and runtime options to be set', () => { - let fn = functions + const fn = functions .region('us-east1') .runWith({ timeoutSeconds: 90, @@ -539,8 +466,8 @@ describe('callable.FunctionBuilder', () => { }); it('has a .run method', () => { - const cf = https.onCall((data, context) => { - return { data, context }; + const cf = https.onCall((d, c) => { + return { data: d, context: c }; }); const data = 'data'; @@ -559,10 +486,12 @@ describe('callable.FunctionBuilder', () => { describe('callable CORS', () => { it('handles OPTIONS preflight', async () => { const func = https.onCall((data, context) => { - throw "This shouldn't have gotten called for an OPTIONS preflight."; + throw new Error( + `This shouldn't have gotten called for an OPTIONS preflight.` + ); }); - const request = new MockRequest( + const req = new MockRequest( {}, { 'Access-Control-Request-Method': 'POST', @@ -570,9 +499,9 @@ describe('callable CORS', () => { Origin: 'example.com', } ); - request.method = 'OPTIONS'; + req.method = 'OPTIONS'; - const response = await runHandler(func, request as any); + const response = await runHandler(func, req as any); expect(response.status).to.equal(204); expect(response.body).to.be.undefined; From 775fd35109a5e172d33550ee9d38a156161186d9 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Mon, 17 Jun 2019 17:47:24 -0700 Subject: [PATCH 036/437] prettier to enforce pareens in arrow functions (#474) --- .prettierrc | 1 + integration_test/functions/src/database-tests.ts | 4 ++-- .../functions/src/firestore-tests.ts | 2 +- integration_test/functions/src/https-tests.ts | 4 ++-- integration_test/functions/src/index.ts | 16 ++++++++-------- integration_test/functions/src/pubsub-tests.ts | 8 ++++---- integration_test/functions/src/testing.ts | 8 ++++---- spec/apps.spec.ts | 2 +- spec/cloud-functions.spec.ts | 2 +- spec/function-builder.spec.ts | 12 ++++++------ spec/providers/analytics.spec.ts | 2 +- spec/providers/crashlytics.spec.ts | 16 +++++++++------- spec/providers/pubsub.spec.ts | 6 +++--- spec/providers/storage.spec.ts | 16 ++++++++-------- src/apps.ts | 2 +- src/cloud-functions.ts | 8 ++++---- src/providers/analytics.ts | 10 +++++----- src/providers/auth.ts | 4 ++-- src/providers/database.ts | 4 ++-- src/providers/pubsub.ts | 2 +- tslint.json | 13 +++++++------ 21 files changed, 73 insertions(+), 69 deletions(-) diff --git a/.prettierrc b/.prettierrc index c1a6f6671..491929e45 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,5 @@ { + "arrowParens": "always", "singleQuote": true, "trailingComma": "es5" } diff --git a/integration_test/functions/src/database-tests.ts b/integration_test/functions/src/database-tests.ts index e31dacf79..403c85bc7 100644 --- a/integration_test/functions/src/database-tests.ts +++ b/integration_test/functions/src/database-tests.ts @@ -24,14 +24,14 @@ export const databaseTests: any = functions.database (change, context) => !(context as any).app ) - .it('should give refs access to admin data', change => + .it('should give refs access to admin data', (change) => change.after.ref.parent .child('adminOnly') .update({ allowed: 1 }) .then(() => true) ) - .it('should have a correct ref url', change => { + .it('should have a correct ref url', (change) => { const url = change.after.ref.toString(); return Promise.resolve() .then(() => { diff --git a/integration_test/functions/src/firestore-tests.ts b/integration_test/functions/src/firestore-tests.ts index 1a9dd1d7e..de6581392 100644 --- a/integration_test/functions/src/firestore-tests.ts +++ b/integration_test/functions/src/firestore-tests.ts @@ -15,7 +15,7 @@ export const firestoreTests: any = functions .it('should not have event.app', (snap, context) => !(context as any).app) - .it('should give refs write access', snap => + .it('should give refs write access', (snap) => snap.ref.set({ allowed: 1 }, { merge: true }).then(() => true) ) diff --git a/integration_test/functions/src/https-tests.ts b/integration_test/functions/src/https-tests.ts index 4184f124d..7a0889ef6 100644 --- a/integration_test/functions/src/https-tests.ts +++ b/integration_test/functions/src/https-tests.ts @@ -2,9 +2,9 @@ import * as functions from 'firebase-functions'; import * as _ from 'lodash'; import { TestSuite, expectEq } from './testing'; -export const callableTests: any = functions.https.onCall(d => { +export const callableTests: any = functions.https.onCall((d) => { return new TestSuite('https onCall') - .it('should have the correct data', data => + .it('should have the correct data', (data) => expectEq(_.get(data, 'foo'), 'bar') ) .run(d.testId, d); diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index 1ee1a903f..6541dee23 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -32,9 +32,9 @@ function callHttpsTrigger(name: string, data: any, baseUrl) { 'Content-Type': 'application/json', }, }, - response => { + (response) => { let body = ''; - response.on('data', chunk => { + response.on('data', (chunk) => { body += chunk; }); response.on('end', () => resolve(body)); @@ -59,9 +59,9 @@ function callScheduleTrigger(functionName: string, region: string) { 'Content-Type': 'application/json', }, }, - response => { + (response) => { let body = ''; - response.on('data', chunk => { + response.on('data', (chunk) => { body += chunk; }); response.on('end', () => resolve(body)); @@ -114,7 +114,7 @@ export const integrationTests: any = functions password: 'secret', displayName: `${testId}`, }) - .then(userRecord => { + .then((userRecord) => { // A user deletion to trigger the Firebase Auth user deletion tests. admin.auth().deleteUser(userRecord.uid); }), @@ -160,7 +160,7 @@ export const integrationTests: any = functions let ref = admin.database().ref(`testRuns/${testId}`); return new Promise((resolve, reject) => { let testsExecuted = 0; - ref.on('child_added', snapshot => { + ref.on('child_added', (snapshot) => { testsExecuted += 1; if (snapshot.key != 'timestamp' && !snapshot.val().passed) { reject( @@ -185,7 +185,7 @@ export const integrationTests: any = functions ref.off(); // No more need to listen. return Promise.resolve(); }) - .catch(err => { + .catch((err) => { ref.off(); // No more need to listen. return Promise.reject(err); }); @@ -194,7 +194,7 @@ export const integrationTests: any = functions console.log('All tests pass!'); resp.status(200).send('PASS \n'); }) - .catch(err => { + .catch((err) => { console.log(`Some tests failed: ${err}`); resp .status(500) diff --git a/integration_test/functions/src/pubsub-tests.ts b/integration_test/functions/src/pubsub-tests.ts index e657eedc3..ebac609dc 100644 --- a/integration_test/functions/src/pubsub-tests.ts +++ b/integration_test/functions/src/pubsub-tests.ts @@ -43,7 +43,7 @@ export const pubsubTests: any = functions.pubsub expectEq((context as any).action, undefined) ) - .it('should have pubsub data', message => { + .it('should have pubsub data', (message) => { const decoded = new Buffer(message.data, 'base64').toString(); const parsed = JSON.parse(decoded); return evaluate( @@ -52,7 +52,7 @@ export const pubsubTests: any = functions.pubsub ); }) - .it('should decode JSON payloads with the json helper', message => + .it('should decode JSON payloads with the json helper', (message) => evaluate(message.json.hasOwnProperty('testId'), message.json) ) @@ -62,7 +62,7 @@ export const pubsubTests: any = functions.pubsub export const schedule: any = functions.pubsub .schedule('every 10 hours') // This is a dummy schedule, since we need to put a valid one in. // For the test, the job is triggered by the jobs:run api - .onRun(context => { + .onRun((context) => { let testId; let db = admin.database(); return new Promise(async (resolve, reject) => { @@ -70,7 +70,7 @@ export const schedule: any = functions.pubsub .ref('testRuns') .orderByChild('timestamp') .limitToLast(1) - .on('value', snap => { + .on('value', (snap) => { testId = Object.keys(snap.val())[0]; new TestSuite('pubsub scheduleOnRun') .it('should trigger when the scheduler fires', () => success()) diff --git a/integration_test/functions/src/testing.ts b/integration_test/functions/src/testing.ts index c3aaf05f0..d3782a981 100644 --- a/integration_test/functions/src/testing.ts +++ b/integration_test/functions/src/testing.ts @@ -28,22 +28,22 @@ export class TestSuite { const run = Promise.resolve() .then(() => this.tests[testName](data, context)) .then( - result => { + (result) => { console.log( `${result ? 'Passed' : 'Failed with successful op'}: ${testName}` ); return { name: testName, passed: !!result }; }, - error => { + (error) => { console.error(`Failed: ${testName}`, error); return { name: testName, passed: 0, error: error }; } ); running.push(run); } - return Promise.all(running).then(results => { + return Promise.all(running).then((results) => { let sum = 0; - results.forEach(val => (sum = sum + val.passed)); + results.forEach((val) => (sum = sum + val.passed)); const summary = `passed ${sum} of ${running.length}`; const passed = sum === running.length; console.log(summary); diff --git a/spec/apps.spec.ts b/spec/apps.spec.ts index 13a7213cd..209a20b72 100644 --- a/spec/apps.spec.ts +++ b/spec/apps.spec.ts @@ -38,7 +38,7 @@ describe('apps', () => { }); afterEach(() => { - _.forEach(firebase.apps, app => { + _.forEach(firebase.apps, (app) => { app.delete(); }); }); diff --git a/spec/cloud-functions.spec.ts b/spec/cloud-functions.spec.ts index c0b0e518c..ab17fbc3e 100644 --- a/spec/cloud-functions.spec.ts +++ b/spec/cloud-functions.spec.ts @@ -118,7 +118,7 @@ describe('makeCloudFunction', () => { data: 'test data', }; - return cf(test.data, test.context).then(result => { + return cf(test.data, test.context).then((result) => { expect(result).to.deep.equal({ eventId: '00000', timestamp: '2016-11-04T21:29:03.496Z', diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index cddb2d10b..2ddbb40df 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -37,7 +37,7 @@ describe('FunctionBuilder', () => { const fn = functions .region('us-east1') .auth.user() - .onCreate(user => user); + .onCreate((user) => user); expect(fn.__trigger.regions).to.deep.equal(['us-east1']); }); @@ -46,7 +46,7 @@ describe('FunctionBuilder', () => { const fn = functions .region('us-east1', 'us-central1') .auth.user() - .onCreate(user => user); + .onCreate((user) => user); expect(fn.__trigger.regions).to.deep.equal(['us-east1', 'us-central1']); }); @@ -62,7 +62,7 @@ describe('FunctionBuilder', () => { 'asia-northeast1' ) .auth.user() - .onCreate(user => user); + .onCreate((user) => user); expect(fn.__trigger.regions).to.deep.equal([ 'us-central1', @@ -81,7 +81,7 @@ describe('FunctionBuilder', () => { memory: '256MB', }) .auth.user() - .onCreate(user => user); + .onCreate((user) => user); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); expect(fn.__trigger.timeout).to.deep.equal('90s'); @@ -95,7 +95,7 @@ describe('FunctionBuilder', () => { memory: '256MB', }) .auth.user() - .onCreate(user => user); + .onCreate((user) => user); expect(fn.__trigger.regions).to.deep.equal(['europe-west2']); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); @@ -110,7 +110,7 @@ describe('FunctionBuilder', () => { }) .region('europe-west1') .auth.user() - .onCreate(user => user); + .onCreate((user) => user); expect(fn.__trigger.regions).to.deep.equal(['europe-west1']); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); diff --git a/spec/providers/analytics.spec.ts b/spec/providers/analytics.spec.ts index 67e7a700b..4a5e84a0c 100644 --- a/spec/providers/analytics.spec.ts +++ b/spec/providers/analytics.spec.ts @@ -45,7 +45,7 @@ describe('Analytics Functions', () => { memory: '256MB', }) .analytics.event('event') - .onLog(event => event); + .onLog((event) => event); expect(fn.__trigger.regions).to.deep.equal(['us-east1']); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); diff --git a/spec/providers/crashlytics.spec.ts b/spec/providers/crashlytics.spec.ts index 7a724213b..59e745dfa 100644 --- a/spec/providers/crashlytics.spec.ts +++ b/spec/providers/crashlytics.spec.ts @@ -46,7 +46,7 @@ describe('Crashlytics Functions', () => { memory: '256MB', }) .crashlytics.issue() - .onNew(issue => issue); + .onNew((issue) => issue); expect(fn.__trigger.regions).to.deep.equal(['us-east1']); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); @@ -55,7 +55,7 @@ describe('Crashlytics Functions', () => { describe('#onNew', () => { it('should return a TriggerDefinition with appropriate values', () => { - const cloudFunction = crashlytics.issue().onNew(data => null); + const cloudFunction = crashlytics.issue().onNew((data) => null); expect(cloudFunction.__trigger).to.deep.equal({ eventTrigger: { eventType: 'providers/firebase.crashlytics/eventTypes/issue.new', @@ -68,7 +68,7 @@ describe('Crashlytics Functions', () => { describe('#onRegressed', () => { it('should return a TriggerDefinition with appropriate values', () => { - const cloudFunction = crashlytics.issue().onRegressed(data => null); + const cloudFunction = crashlytics.issue().onRegressed((data) => null); expect(cloudFunction.__trigger).to.deep.equal({ eventTrigger: { eventType: @@ -82,7 +82,9 @@ describe('Crashlytics Functions', () => { describe('#onVelocityAlert', () => { it('should return a TriggerDefinition with appropriate values', () => { - const cloudFunction = crashlytics.issue().onVelocityAlert(data => null); + const cloudFunction = crashlytics + .issue() + .onVelocityAlert((data) => null); expect(cloudFunction.__trigger).to.deep.equal({ eventTrigger: { eventType: @@ -98,7 +100,7 @@ describe('Crashlytics Functions', () => { describe('#onNew', () => { it('should return a CloudFunction with appropriate values', () => { const cloudFunction = functions.handler.crashlytics.issue.onNew( - testIssue => { + (testIssue) => { return ( testIssue.issueId + testIssue.issueTitle + testIssue.createTime ); @@ -111,7 +113,7 @@ describe('Crashlytics Functions', () => { describe('#onRegressed', () => { it('should return a CloudFunction with appropriate values', () => { const cloudFunction = functions.handler.crashlytics.issue.onRegressed( - testIssue => { + (testIssue) => { return ( testIssue.issueId + testIssue.issueTitle + testIssue.createTime ); @@ -124,7 +126,7 @@ describe('Crashlytics Functions', () => { describe('#onVelocityAlert', () => { it('should return a CloudFunction with appropriate values', () => { const cloudFunction = functions.handler.crashlytics.issue.onVelocityAlert( - testIssue => { + (testIssue) => { return ( testIssue.issueId + testIssue.issueTitle + testIssue.createTime ); diff --git a/spec/providers/pubsub.spec.ts b/spec/providers/pubsub.spec.ts index 7586302ad..53776c357 100644 --- a/spec/providers/pubsub.spec.ts +++ b/spec/providers/pubsub.spec.ts @@ -146,7 +146,7 @@ describe('Pubsub Functions', () => { it('should return a trigger with schedule', () => { const result = pubsub .schedule('every 5 minutes') - .onRun(context => null); + .onRun((context) => null); expect(result.__trigger.schedule).to.deep.equal({ schedule: 'every 5 minutes', }); @@ -156,7 +156,7 @@ describe('Pubsub Functions', () => { const result = pubsub .schedule('every 5 minutes') .timeZone('America/New_York') - .onRun(context => null); + .onRun((context) => null); expect(result.__trigger.schedule).to.deep.equal({ schedule: 'every 5 minutes', timeZone: 'America/New_York', @@ -336,7 +336,7 @@ describe('Pubsub Functions', () => { }, }; - const result = functions.handler.pubsub.topic.onPublish(data => { + const result = functions.handler.pubsub.topic.onPublish((data) => { return { raw: data.data, json: data.json, diff --git a/spec/providers/storage.spec.ts b/spec/providers/storage.spec.ts index 2a7566142..18326eab8 100644 --- a/spec/providers/storage.spec.ts +++ b/spec/providers/storage.spec.ts @@ -104,7 +104,7 @@ describe('Storage Functions', () => { }); it('should not mess with media links using non-literal slashes', () => { - const cloudFunction = storage.object().onArchive(data => { + const cloudFunction = storage.object().onArchive((data) => { return data.mediaLink; }); const goodMediaLinkEvent: Event = { @@ -184,7 +184,7 @@ describe('Storage Functions', () => { }); it('should not mess with media links using non-literal slashes', () => { - const cloudFunction = storage.object().onDelete(data => { + const cloudFunction = storage.object().onDelete((data) => { return data.mediaLink; }); const goodMediaLinkEvent = { @@ -264,7 +264,7 @@ describe('Storage Functions', () => { }); it('should not mess with media links using non-literal slashes', () => { - const cloudFunction = storage.object().onFinalize(data => { + const cloudFunction = storage.object().onFinalize((data) => { return data.mediaLink; }); const goodMediaLinkEvent = { @@ -344,7 +344,7 @@ describe('Storage Functions', () => { }); it('should not mess with media links using non-literal slashes', () => { - const cloudFunction = storage.object().onMetadataUpdate(data => { + const cloudFunction = storage.object().onMetadataUpdate((data) => { return data.mediaLink; }); const goodMediaLinkEvent = { @@ -394,7 +394,7 @@ describe('Storage Functions', () => { it('should not mess with media links using non-literal slashes', () => { const cloudFunction = functions.handler.storage.object.onArchive( - data => { + (data) => { return data.mediaLink; } ); @@ -433,7 +433,7 @@ describe('Storage Functions', () => { it('should not mess with media links using non-literal slashes', () => { const cloudFunction = functions.handler.storage.object.onDelete( - data => { + (data) => { return data.mediaLink; } ); @@ -472,7 +472,7 @@ describe('Storage Functions', () => { it('should not mess with media links using non-literal slashes', () => { const cloudFunction = functions.handler.storage.object.onFinalize( - data => { + (data) => { return data.mediaLink; } ); @@ -511,7 +511,7 @@ describe('Storage Functions', () => { it('should not mess with media links using non-literal slashes', () => { const cloudFunction = functions.handler.storage.object.onMetadataUpdate( - data => { + (data) => { return data.mediaLink; } ); diff --git a/src/apps.ts b/src/apps.ts index 3ddf1ea2e..a3a5b3483 100644 --- a/src/apps.ts +++ b/src/apps.ts @@ -37,7 +37,7 @@ export namespace apps { /** @internal */ export function delay(delay: number) { - return new Promise(resolve => { + return new Promise((resolve) => { setTimeout(resolve, delay); }); } diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 9cce3c3bb..1a5c6e214 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -129,7 +129,7 @@ export namespace Change { ) { let before = _.assign({}, after); let masks = fieldMask.split(','); - _.forEach(masks, mask => { + _.forEach(masks, (mask) => { const val = _.get(sparseBefore, mask); if (typeof val === 'undefined') { _.unset(before, mask); @@ -289,11 +289,11 @@ export function makeCloudFunction({ console.warn('Function returned undefined, expected Promise or value'); } return Promise.resolve(promise) - .then(result => { + .then((result) => { after(event); return result; }) - .catch(err => { + .catch((err) => { after(event); return Promise.reject(err); }); @@ -341,7 +341,7 @@ function _makeParams( if (wildcards) { let triggerResourceParts = _.split(triggerResource, '/'); let eventResourceParts = _.split(context.resource.name, '/'); - _.forEach(wildcards, wildcard => { + _.forEach(wildcards, (wildcard) => { let wildcardNoBraces = wildcard.slice(1, -1); let position = _.indexOf(triggerResourceParts, wildcard); params[wildcardNoBraces] = eventResourceParts[position]; diff --git a/src/providers/analytics.ts b/src/providers/analytics.ts index b41ae479a..3287264b3 100644 --- a/src/providers/analytics.ts +++ b/src/providers/analytics.ts @@ -143,7 +143,7 @@ export class AnalyticsEvent { // If there's an eventDim, there'll always be exactly one. let eventDim = wireFormat.eventDim[0]; copyField(eventDim, this, 'name'); - copyField(eventDim, this, 'params', p => _.mapValues(p, unwrapValue)); + copyField(eventDim, this, 'params', (p) => _.mapValues(p, unwrapValue)); copyFieldTo(eventDim, this, 'valueInUsd', 'valueInUSD'); copyFieldTo(eventDim, this, 'date', 'reportingDate'); copyTimestampToString(eventDim, this, 'timestampMicros', 'logTime'); @@ -159,7 +159,7 @@ export class AnalyticsEvent { this, 'userDim', 'user', - dim => new UserDimensions(dim) + (dim) => new UserDimensions(dim) ); } } @@ -218,10 +218,10 @@ export class UserDimensions { 'firstOpenTime' ); this.userProperties = {}; // With no entries in the wire format, present an empty (as opposed to absent) map. - copyField(wireFormat, this, 'userProperties', r => - _.mapValues(r, p => new UserPropertyValue(p)) + copyField(wireFormat, this, 'userProperties', (r) => + _.mapValues(r, (p) => new UserPropertyValue(p)) ); - copyField(wireFormat, this, 'bundleInfo', r => new ExportBundleInfo(r)); + copyField(wireFormat, this, 'bundleInfo', (r) => new ExportBundleInfo(r)); // BUG(36000368) Remove when no longer necessary /* tslint:disable:no-string-literal */ diff --git a/src/providers/auth.ts b/src/providers/auth.ts index 0cf7a579d..708aca5ad 100644 --- a/src/providers/auth.ts +++ b/src/providers/auth.ts @@ -145,7 +145,7 @@ export function userRecordConstructor( } else { _.set(record, 'metadata', new UserRecordMetadata(null, null)); } - _.forEach(record.providerData, entry => { + _.forEach(record.providerData, (entry) => { _.set(entry, 'toJSON', () => { return entry; }); @@ -165,7 +165,7 @@ export function userRecordConstructor( ]); json.metadata = _.get(record, 'metadata').toJSON(); json.customClaims = _.cloneDeep(record.customClaims); - json.providerData = _.map(record.providerData, entry => entry.toJSON()); + json.providerData = _.map(record.providerData, (entry) => entry.toJSON()); return json; }); return record as firebase.auth.UserRecord; diff --git a/src/providers/database.ts b/src/providers/database.ts index 342379c81..4b0aaf938 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -208,8 +208,8 @@ export class RefBuilder { legacyEventType: `providers/${provider}/eventTypes/${eventType}`, triggerResource: this.triggerResource, dataConstructor: dataConstructor, - before: event => this.apps.retain(), - after: event => this.apps.release(), + before: (event) => this.apps.retain(), + after: (event) => this.apps.release(), opts: this.opts, }); } diff --git a/src/providers/pubsub.ts b/src/providers/pubsub.ts index e734c0ba3..194ecda9c 100644 --- a/src/providers/pubsub.ts +++ b/src/providers/pubsub.ts @@ -126,7 +126,7 @@ export class TopicBuilder { service, triggerResource: this.triggerResource, eventType: 'topic.publish', - dataConstructor: raw => new Message(raw.data), + dataConstructor: (raw) => new Message(raw.data), opts: this.opts, }); } diff --git a/tslint.json b/tslint.json index 9b6dff93b..f8f669f21 100644 --- a/tslint.json +++ b/tslint.json @@ -2,14 +2,15 @@ "defaultSeverity": "warning", "extends": ["tslint:recommended", "tslint-no-unused-expression-chai"], "rules": { - "quotemark": [true, "single", "avoid-escape"], "interface-name": false, - "variable-name": [true, "check-format", "allow-leading-underscore"], - "object-literal-sort-keys": false, - "whitespace": true, "member-access": false, - "no-console": false, "no-namespace": false, - "trailing-comma": [true, { "functions": "never" }] + "no-console": false, + "object-literal-key-quotes": [true, "as-needed"], + "object-literal-sort-keys": false, + "quotemark": [true, "single", "avoid-escape"], + "trailing-comma": [true, { "functions": "never" }], + "variable-name": [true, "check-format", "allow-leading-underscore"], + "whitespace": true } } From 9862b86f639d10efc3520a191534701384ad5cbc Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Mon, 24 Jun 2019 11:27:23 -0700 Subject: [PATCH 037/437] remove delay from integration tests, remove parallellization (#480) --- integration_test/run_tests.sh | 75 ++++++++--------------------------- 1 file changed, 17 insertions(+), 58 deletions(-) diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index 1aaa39859..8333c9158 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -4,25 +4,18 @@ set -e function usage { - echo "Usage: $0 []" + echo "Usage: $0 " exit 1 } -# This script takes 1 or 2 arguments, both of which are Firebase project ids. -# If only one argument is given, that project will be used for both node 8 and node 10 -# Otherwise, first argument will be used for node 8 and second argument will be used -# for node 10. -# Note that at least one argument is required. +# This script takes in one argument specifying a project_id +# Example usage (from root dir): +# ./integration_test/run_tests.sh chenky-test-proj if [[ $1 == "" ]]; then usage fi -if [[ $2 == "" ]]; then - PROJECT_ID_NODE_8=$1 - PROJECT_ID_NODE_10=$1 -else - PROJECT_ID_NODE_8=$1 - PROJECT_ID_NODE_10=$2 -fi + +PROJECT_ID=$1 # Directory where this script lives. DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" @@ -41,13 +34,11 @@ function build_sdk { function pick_node8 { cd $DIR - PROJECT_ID=$PROJECT_ID_NODE_8 cp package.node8.json functions/package.json } function pick_node10 { cd $DIR - PROJECT_ID=$PROJECT_ID_NODE_10 cp package.node10.json functions/package.json } @@ -63,10 +54,7 @@ function delete_all_functions { cd $DIR # Try to delete, if there are errors it is because the project is already empty, # in that case do nothing. - firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID_NODE_8 || : & - if ! [[ $PROJECT_ID_NODE_8 == $PROJECT_ID_NODE_10 ]]; then - firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID_NODE_10 || : & - fi + firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID || : & wait announce "Project emptied." } @@ -78,34 +66,6 @@ function deploy { for i in 1 2 3; do firebase deploy --project=$PROJECT_ID --only functions,database,firestore && break; done } -# At the moment, functions take 30-40 seconds AFTER firebase deploy returns successfully to go live -# This needs to be fixed separately -# However, so that we have working integration tests in the interim, waitForPropagation is a workaround -function waitForPropagation { - announce "Waiting 50 seconds for functions changes to propagate" - sleep 50 -} - -function run_all_tests { - announce "Running the integration tests..." - - # Constructs the URLs for both test functions. This may change in the future, - # causing this script to start failing, but currently we don't have a very - # reliable way of determining the URL dynamically. - TEST_DOMAIN="cloudfunctions.net" - if [[ $FIREBASE_FUNCTIONS_URL == "https://preprod-cloudfunctions.sandbox.googleapis.com" ]]; then - TEST_DOMAIN="txcloud.net" - fi - TEST_URL_NODE_8="https://us-central1-$PROJECT_ID_NODE_8.$TEST_DOMAIN/integrationTests" - TEST_URL_NODE_10="https://us-central1-$PROJECT_ID_NODE_10.$TEST_DOMAIN/integrationTests" - echo $TEST_URL_NODE_8 - echo $TEST_URL_NODE_10 - curl --fail $TEST_URL_NODE_8 & NODE8PID=$! - curl --fail $TEST_URL_NODE_10 & NODE10PID=$! - wait $NODE8PID && echo 'node 8 passed' || (announce 'Node 8 tests failed'; cleanup; announce 'Tests failed'; exit 1) - wait $NODE10PID && echo 'node 10 passed' || (announce 'Node 10 tests failed'; cleanup; announce 'Tests failed'; exit 1) -} - function run_tests { announce "Running the integration tests..." @@ -131,24 +91,23 @@ function cleanup { rm -rf $DIR/functions/node_modules/firebase-functions } +# Setup build_sdk -pick_node8 install_deps delete_all_functions + +# Node 8 tests +pick_node8 announce "Deploying functions to Node 8 runtime ..." deploy -if [[ $PROJECT_ID_NODE_8 == $PROJECT_ID_NODE_10 ]]; then - waitForPropagation - run_tests -fi +run_tests + +# Node 10 tests pick_node10 announce "Re-deploying the same functions to Node 10 runtime ..." deploy -waitForPropagation -if [[ $PROJECT_ID_NODE_8 == $PROJECT_ID_NODE_10 ]]; then - run_tests -else - run_all_tests -fi +run_tests + +# Cleanup cleanup announce "All tests pass!" From d53c7b041ea353edb6e3ce4b104d939ce985ff13 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Mon, 24 Jun 2019 14:02:22 -0700 Subject: [PATCH 038/437] Add RC integration tests back in (#479) * add RC integration tests back in * formatting --- integration_test/functions/src/index.ts | 40 ++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index 6541dee23..679ba3a5a 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -12,7 +12,7 @@ export * from './database-tests'; export * from './auth-tests'; export * from './firestore-tests'; export * from './https-tests'; -// export * from './remoteConfig-tests'; +export * from './remoteConfig-tests'; export * from './storage-tests'; const numTests = Object.keys(exports).length; // Assumption: every exported function is its own test. @@ -127,25 +127,25 @@ export const integrationTests: any = functions // Invoke a callable HTTPS trigger. callHttpsTrigger('callableTests', { foo: 'bar', testId }, baseUrl), // A Remote Config update to trigger the Remote Config tests. - // admin.credential - // .applicationDefault() - // .getAccessToken() - // .then(accessToken => { - // const options = { - // hostname: 'firebaseremoteconfig.googleapis.com', - // path: `/v1/projects/${firebaseConfig.projectId}/remoteConfig`, - // method: 'PUT', - // headers: { - // Authorization: 'Bearer ' + accessToken.access_token, - // 'Content-Type': 'application/json; UTF-8', - // 'Accept-Encoding': 'gzip', - // 'If-Match': '*', - // }, - // }; - // const request = https.request(options, resp => {}); - // request.write(JSON.stringify({ version: { description: testId } })); - // request.end(); - // }), + admin.credential + .applicationDefault() + .getAccessToken() + .then((accessToken) => { + const options = { + hostname: 'firebaseremoteconfig.googleapis.com', + path: `/v1/projects/${firebaseConfig.projectId}/remoteConfig`, + method: 'PUT', + headers: { + Authorization: 'Bearer ' + accessToken.access_token, + 'Content-Type': 'application/json; UTF-8', + 'Accept-Encoding': 'gzip', + 'If-Match': '*', + }, + }; + const request = https.request(options, (resp) => {}); + request.write(JSON.stringify({ version: { description: testId } })); + request.end(); + }), // A storage upload to trigger the Storage tests admin .storage() From 6c5ffdc41ec48d3171a563980a14e940cfef230f Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Mon, 24 Jun 2019 16:38:53 -0700 Subject: [PATCH 039/437] fix bug in integration tests where deps not installed (#485) --- integration_test/run_tests.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index 8333c9158..52d354f95 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -93,17 +93,18 @@ function cleanup { # Setup build_sdk -install_deps delete_all_functions # Node 8 tests pick_node8 +install_deps announce "Deploying functions to Node 8 runtime ..." deploy run_tests # Node 10 tests pick_node10 +install_deps announce "Re-deploying the same functions to Node 10 runtime ..." deploy run_tests From 2563b99a95fa4081efe3afaecfa45581f71e44df Mon Sep 17 00:00:00 2001 From: Ananda Rao H Date: Tue, 25 Jun 2019 01:58:16 +0200 Subject: [PATCH 040/437] Update run_tests.sh to accept CI tokens (#484) * Update run_tests.sh to accept CI tokens The integration tests now accept CI tokens obtained from firebase login:ci. * Update run_tests.sh --- integration_test/run_tests.sh | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index 52d354f95..8ab0071ca 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -4,17 +4,26 @@ set -e function usage { - echo "Usage: $0 " + echo "Usage: $0 []" exit 1 } -# This script takes in one argument specifying a project_id -# Example usage (from root dir): +# This script takes in one required argument specifying a project_id and an +# optional arguement for a CI token that can be obtained by running +# `firebase login:ci` +# Example usage (from root dir) without token: # ./integration_test/run_tests.sh chenky-test-proj +# Example usage (from root dir) with token: +# ./integration_test/run_tests.sh chenky-test-proj $TOKEN if [[ $1 == "" ]]; then usage fi +TOKEN="" +if [[ $2 != "" ]]; then + TOKEN=$2 +fi + PROJECT_ID=$1 # Directory where this script lives. @@ -63,7 +72,11 @@ function deploy { cd $DIR ./functions/node_modules/.bin/tsc -p functions/ # Deploy functions, and security rules for database and Firestore. If the deploy fails, retry twice - for i in 1 2 3; do firebase deploy --project=$PROJECT_ID --only functions,database,firestore && break; done + if [[ $TOKEN == "" ]]; then + for i in 1 2 3; do firebase deploy --project=$PROJECT_ID --only functions,database,firestore && break; done + else + for i in 1 2 3; do firebase deploy --project=$PROJECT_ID --token=$TOKEN --only functions,database,firestore && break; done + fi } function run_tests { From 298f66f9f3ecf6228103766662d443d2875ecc00 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Mon, 24 Jun 2019 17:07:56 -0700 Subject: [PATCH 041/437] Fix auth UserRecord.metadata regression (#481) * fix auth regression * adding unit and integration tests to test the wire format conversion for auth * add changelog for release --- changelog.txt | 1 + integration_test/functions/src/auth-tests.ts | 2 ++ spec/providers/auth.spec.ts | 15 +++++++++++++++ src/providers/auth.ts | 5 ++++- 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index e69de29bb..03f1fb9d3 100644 --- a/changelog.txt +++ b/changelog.txt @@ -0,0 +1 @@ +* Fixes bug where auth.UserRecord.metadata was undefined. \ No newline at end of file diff --git a/integration_test/functions/src/auth-tests.ts b/integration_test/functions/src/auth-tests.ts index 7b7f8686c..aaef21e6a 100644 --- a/integration_test/functions/src/auth-tests.ts +++ b/integration_test/functions/src/auth-tests.ts @@ -32,6 +32,8 @@ export const createUserTests: any = functions.auth.user().onCreate((u, c) => { expectEq((context as any).action, undefined) ) + .it('should have properly defined meta', (user, context) => user.metadata) + .run(testId, u, c); }); diff --git a/spec/providers/auth.spec.ts b/spec/providers/auth.spec.ts index 676e973ce..bee3435e0 100644 --- a/spec/providers/auth.spec.ts +++ b/spec/providers/auth.spec.ts @@ -178,6 +178,21 @@ describe('Auth Functions', () => { const record = auth.userRecordConstructor(raw); expect(record.toJSON()).to.deep.equal(raw); }); + + it('will convert raw wire fields createdAt and lastSignedInAt to creationTime and lastSignInTime', () => { + const raw: any = { + uid: '123', + metadata: { + createdAt: '2017-02-02T23:06:26.124Z', + lastSignedInAt: '2017-02-02T23:01:19.797Z', + }, + }; + const record = auth.userRecordConstructor(raw); + expect(record.metadata).to.deep.equal({ + creationTime: '2017-02-02T23:06:26.124Z', + lastSignInTime: '2017-02-02T23:01:19.797Z', + }); + }); }); describe('handler namespace', () => { diff --git a/src/providers/auth.ts b/src/providers/auth.ts index 708aca5ad..95747ede5 100644 --- a/src/providers/auth.ts +++ b/src/providers/auth.ts @@ -140,7 +140,10 @@ export function userRecordConstructor( _.set( record, 'metadata', - new UserRecordMetadata(meta.creationTime, meta.lastSignInTime) + new UserRecordMetadata( + meta.createdAt || meta.creationTime, + meta.lastSignedInAt || meta.lastSignInTime + ) ); } else { _.set(record, 'metadata', new UserRecordMetadata(null, null)); From 18af1e2262e5308efed857a56da862872acee49e Mon Sep 17 00:00:00 2001 From: Ananda Rao H Date: Tue, 25 Jun 2019 22:34:45 +0200 Subject: [PATCH 042/437] Add support for token to firebase functions:delete (#487) This was missed in the PR: https://github.com/firebase/firebase-functions/pull/484 --- integration_test/run_tests.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index 8ab0071ca..b90cb4ebb 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -63,7 +63,10 @@ function delete_all_functions { cd $DIR # Try to delete, if there are errors it is because the project is already empty, # in that case do nothing. - firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID || : & + if [[ $TOKEN == "" ]]; then + firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID || : & + else + firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID --token=$TOKEN || : & wait announce "Project emptied." } From 5babae89ff4372d255d8fa7cdeabee6c268e87da Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Tue, 25 Jun 2019 13:48:53 -0700 Subject: [PATCH 043/437] add cleanup to integration tests (#486) --- integration_test/run_tests.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index b90cb4ebb..e9ccbac00 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -104,7 +104,8 @@ function cleanup { rm $DIR/functions/firebase-functions.tgz rm $DIR/functions/package.json rm -f $DIR/functions/firebase-debug.log - rm -rf $DIR/functions/node_modules/firebase-functions + rm -rf $DIR/functions/lib + rm -rf $DIR/functions/node_modules } # Setup From e9030882954456cc842f3d9ee728ae884d5cb880 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Tue, 25 Jun 2019 14:54:28 -0700 Subject: [PATCH 044/437] add fi to end of if else (#489) --- integration_test/run_tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index e9ccbac00..ec5de6b74 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -67,6 +67,7 @@ function delete_all_functions { firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID || : & else firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID --token=$TOKEN || : & + fi wait announce "Project emptied." } From 85de1e09cf03f936c5ed987e4e545ec97f794fc3 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Tue, 25 Jun 2019 16:34:11 -0700 Subject: [PATCH 045/437] Update changelog with syntax fix (#490) --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 03f1fb9d3..61451a5fb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1 +1 @@ -* Fixes bug where auth.UserRecord.metadata was undefined. \ No newline at end of file +fixed - bug where auth.UserRecord.metadata was undefined. From d86ec8774c73e747a22f7fcb15a9eadba3fd3d9e Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Tue, 25 Jun 2019 23:38:46 +0000 Subject: [PATCH 046/437] [firebase-release] Updated SDK for Cloud Functions to 3.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2f2b38325..de899ea52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.0.1", + "version": "3.0.2", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From 1f2b9e3a4d58e3db48b97173c097554500a99cf2 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Tue, 25 Jun 2019 23:38:57 +0000 Subject: [PATCH 047/437] [firebase-release] Removed change log and reset repo after 3.0.2 release --- changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 61451a5fb..e69de29bb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1 +0,0 @@ -fixed - bug where auth.UserRecord.metadata was undefined. From 81764d6d9867ba30dcbd081eae61a37648b4ae48 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Tue, 2 Jul 2019 12:26:50 -0700 Subject: [PATCH 048/437] Add support for us-east4 region (#458) * add new region support * remove europe-west4 as it's delayed, update changelog --- changelog.txt | 1 + spec/function-builder.spec.ts | 2 ++ src/function-builder.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/changelog.txt b/changelog.txt index e69de29bb..1385ce18b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -0,0 +1 @@ +feature - Adds region support for us-east4. \ No newline at end of file diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index 2ddbb40df..04575e28b 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -56,6 +56,7 @@ describe('FunctionBuilder', () => { .region( 'us-central1', 'us-east1', + 'us-east4', 'europe-west1', 'europe-west2', 'asia-east2', @@ -67,6 +68,7 @@ describe('FunctionBuilder', () => { expect(fn.__trigger.regions).to.deep.equal([ 'us-central1', 'us-east1', + 'us-east4', 'europe-west1', 'europe-west2', 'asia-east2', diff --git a/src/function-builder.ts b/src/function-builder.ts index 42de739eb..83e9a00b0 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -40,6 +40,7 @@ import { CloudFunction, EventContext, Schedule } from './cloud-functions'; const SUPPORTED_REGIONS = [ 'us-central1', 'us-east1', + 'us-east4', 'europe-west1', 'europe-west2', 'asia-east2', From 9694dcabb621f763c18139e2872a9976bfeec0f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Wed, 3 Jul 2019 20:39:34 +0200 Subject: [PATCH 049/437] Run Prettier and TSLint in the CI (#493) * Run Prettier and TSLint in the CI * Specify parameters in an alphabetical order * Reformat --- .travis.yml | 9 +++ .../functions/src/database-tests.ts | 4 +- .../functions/src/firestore-tests.ts | 4 +- integration_test/functions/src/index.ts | 8 +-- package.json | 8 ++- spec/providers/auth.spec.ts | 2 +- src/apps.ts | 8 +-- src/cloud-functions.ts | 28 ++++---- src/config.ts | 6 +- src/encoder.ts | 4 +- src/function-builder.ts | 4 +- src/handler-builder.ts | 2 +- src/index.ts | 4 +- src/providers/analytics.ts | 16 ++--- src/providers/auth.ts | 14 ++-- src/providers/crashlytics.ts | 2 +- src/providers/database.ts | 69 ++++++++++--------- src/providers/firestore.ts | 20 +++--- src/providers/https.ts | 6 +- src/providers/pubsub.ts | 4 +- src/utils.ts | 6 +- tslint.json | 6 +- 22 files changed, 125 insertions(+), 109 deletions(-) diff --git a/.travis.yml b/.travis.yml index 987e158e2..18dff4114 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,12 @@ +cache: npm +jobs: + include: + - name: lint + script: npm run lint + stage: verify + - name: format + script: npm run format + stage: verify language: node_js node_js: - '8' diff --git a/integration_test/functions/src/database-tests.ts b/integration_test/functions/src/database-tests.ts index 403c85bc7..215e6bd3b 100644 --- a/integration_test/functions/src/database-tests.ts +++ b/integration_test/functions/src/database-tests.ts @@ -50,9 +50,7 @@ export const databaseTests: any = functions.database .it('should have refs resources', (change, context) => expectEq( context.resource.name, - `projects/_/instances/${process.env.GCLOUD_PROJECT}/refs/dbTests/${ - context.params.testId - }/start` + `projects/_/instances/${process.env.GCLOUD_PROJECT}/refs/dbTests/${context.params.testId}/start` ) ) diff --git a/integration_test/functions/src/firestore-tests.ts b/integration_test/functions/src/firestore-tests.ts index de6581392..e5f8acdbf 100644 --- a/integration_test/functions/src/firestore-tests.ts +++ b/integration_test/functions/src/firestore-tests.ts @@ -22,9 +22,7 @@ export const firestoreTests: any = functions .it('should have well-formatted resource', (snap, context) => expectEq( context.resource.name, - `projects/${ - process.env.GCLOUD_PROJECT - }/databases/(default)/documents/tests/${context.params.documentId}` + `projects/${process.env.GCLOUD_PROJECT}/databases/(default)/documents/tests/${context.params.documentId}` ) ) diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index 679ba3a5a..967b3d376 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -52,9 +52,7 @@ function callScheduleTrigger(functionName: string, region: string) { { method: 'POST', host: 'cloudscheduler.googleapis.com', - path: `projects/${ - firebaseConfig.projectId - }/locations/us-central1/jobs/firebase-schedule-${functionName}-${region}:run`, + path: `projects/${firebaseConfig.projectId}/locations/us-central1/jobs/firebase-schedule-${functionName}-${region}:run`, headers: { 'Content-Type': 'application/json', }, @@ -199,9 +197,7 @@ export const integrationTests: any = functions resp .status(500) .send( - `FAIL - details at https://${ - process.env.GCLOUD_PROJECT - }.firebaseio.com/testRuns/${testId}` + `FAIL - details at https://${process.env.GCLOUD_PROJECT}.firebaseio.com/testRuns/${testId}` ); }); }); diff --git a/package.json b/package.json index de899ea52..35389179f 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,10 @@ "build:pack": "rm -rf lib && npm install && tsc -p tsconfig.release.json && npm pack", "build:release": "npm install --production && npm install typescript firebase-admin && tsc -p tsconfig.release.json", "build": "tsc -p tsconfig.release.json", - "format": "prettier --write '**/*.ts'", - "lint": "tslint --project tsconfig.json --config tslint.json", - "posttest": "npm run format", + "format": "prettier --check '**/*.ts'", + "format:fix": "prettier --write '**/*.ts'", + "lint": "tslint --config tslint.json --project tsconfig.json ", + "lint:fix": "tslint --config tslint.json --fix --project tsconfig.json", "test": "mocha -r ts-node/register ./spec/index.spec.ts" }, "dependencies": { @@ -58,6 +59,7 @@ "sinon": "^7.3.2", "ts-node": "^8.2.0", "tslint": "^5.17.0", + "tslint-config-prettier": "^1.18.0", "tslint-no-unused-expression-chai": "^0.1.4", "tslint-plugin-prettier": "^2.0.0", "typescript": "^3.5.1" diff --git a/spec/providers/auth.spec.ts b/spec/providers/auth.spec.ts index bee3435e0..4247272c5 100644 --- a/spec/providers/auth.spec.ts +++ b/spec/providers/auth.spec.ts @@ -23,10 +23,10 @@ import { expect } from 'chai'; import * as firebase from 'firebase-admin'; +import { Resolver } from 'dns'; import { CloudFunction, Event, EventContext } from '../../src/cloud-functions'; import * as functions from '../../src/index'; import * as auth from '../../src/providers/auth'; -import { Resolver } from 'dns'; describe('Auth Functions', () => { const event: Event = { diff --git a/src/apps.ts b/src/apps.ts index a3a5b3483..f1c30ebf6 100644 --- a/src/apps.ts +++ b/src/apps.ts @@ -20,8 +20,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import * as _ from 'lodash'; import * as firebase from 'firebase-admin'; +import * as _ from 'lodash'; import { firebaseConfig } from './config'; export function apps(): apps.Apps { @@ -65,7 +65,7 @@ export namespace apps { _appAlive(appName: string): boolean { try { - let app = firebase.app(appName); + const app = firebase.app(appName); return !_.get(app, 'isDeleted_'); } catch (e) { return false; @@ -83,7 +83,7 @@ export namespace apps { } retain() { - let increment = (n?: number) => { + const increment = (n?: number) => { return (n || 0) + 1; }; // Increment counter for admin because function might use event.data.ref @@ -91,7 +91,7 @@ export namespace apps { } release() { - let decrement = (n: number) => { + const decrement = (n: number) => { return n - 1; }; return delay(garbageCollectionInterval).then(() => { diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 1a5c6e214..881a7d275 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -127,8 +127,8 @@ export namespace Change { after: any, fieldMask: string ) { - let before = _.assign({}, after); - let masks = fieldMask.split(','); + const before = _.assign({}, after); + const masks = fieldMask.split(','); _.forEach(masks, (mask) => { const val = _.get(sparseBefore, mask); if (typeof val === 'undefined') { @@ -239,17 +239,17 @@ export function makeCloudFunction({ opts = {}, labels = {}, }: MakeCloudFunctionArgs): CloudFunction { - let cloudFunction: any = (data: any, context: any) => { + const cloudFunction: any = (data: any, context: any) => { if (legacyEventType && context.eventType === legacyEventType) { // v1beta1 event flow has different format for context, transform them to new format. context.eventType = provider + '.' + eventType; context.resource = { - service: service, + service, name: context.resource, }; } - let event: Event = { + const event: Event = { data, context, }; @@ -305,7 +305,7 @@ export function makeCloudFunction({ return {}; } - let trigger: any = _.assign(optsToTrigger(opts), { + const trigger: any = _.assign(optsToTrigger(opts), { eventTrigger: { resource: triggerResource(), eventType: legacyEventType || provider + '.' + eventType, @@ -335,15 +335,15 @@ function _makeParams( // In unit testing, `resource` may be unpopulated for a test event. return {}; } - let triggerResource = triggerResourceGetter(); - let wildcards = triggerResource.match(WILDCARD_REGEX); - let params: { [option: string]: any } = {}; + const triggerResource = triggerResourceGetter(); + const wildcards = triggerResource.match(WILDCARD_REGEX); + const params: { [option: string]: any } = {}; if (wildcards) { - let triggerResourceParts = _.split(triggerResource, '/'); - let eventResourceParts = _.split(context.resource.name, '/'); + const triggerResourceParts = _.split(triggerResource, '/'); + const eventResourceParts = _.split(context.resource.name, '/'); _.forEach(wildcards, (wildcard) => { - let wildcardNoBraces = wildcard.slice(1, -1); - let position = _.indexOf(triggerResourceParts, wildcard); + const wildcardNoBraces = wildcard.slice(1, -1); + const position = _.indexOf(triggerResourceParts, wildcard); params[wildcardNoBraces] = eventResourceParts[position]; }); } @@ -371,7 +371,7 @@ function _detectAuthType(event: Event) { } export function optsToTrigger(opts: DeploymentOptions) { - let trigger: any = {}; + const trigger: any = {}; if (opts.regions) { trigger.regions = opts.regions; } diff --git a/src/config.ts b/src/config.ts index b7bb05b93..1547b7679 100644 --- a/src/config.ts +++ b/src/config.ts @@ -32,7 +32,9 @@ export function config(): config.Config { export namespace config { // Config type is usable as a object (dot notation allowed), and firebase // property will also code complete. - export type Config = { [key: string]: any }; + export interface Config { + [key: string]: any; + } /** @internal */ export let singleton: config.Config; @@ -81,7 +83,7 @@ function init() { } try { - let path = + const path = process.env.CLOUD_RUNTIME_CONFIG || '../../../.runtimeconfig.json'; const parsed = require(path); delete parsed.firebase; diff --git a/src/encoder.ts b/src/encoder.ts index ddc7a4c55..c13d33808 100644 --- a/src/encoder.ts +++ b/src/encoder.ts @@ -24,8 +24,8 @@ export function dateToTimestampProto(timeString?: string) { if (typeof timeString === 'undefined') { return; } - let date = new Date(timeString); - let seconds = Math.floor(date.getTime() / 1000); + const date = new Date(timeString); + const seconds = Math.floor(date.getTime() / 1000); let nanos = 0; if (timeString.length > 20) { const nanoString = timeString.substring(20, timeString.length - 1); diff --git a/src/function-builder.ts b/src/function-builder.ts index 83e9a00b0..76c858b92 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -20,9 +20,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import * as _ from 'lodash'; import * as express from 'express'; +import * as _ from 'lodash'; +import { CloudFunction, EventContext, Schedule } from './cloud-functions'; import * as analytics from './providers/analytics'; import * as auth from './providers/auth'; import * as crashlytics from './providers/crashlytics'; @@ -32,7 +33,6 @@ import * as https from './providers/https'; import * as pubsub from './providers/pubsub'; import * as remoteConfig from './providers/remoteConfig'; import * as storage from './providers/storage'; -import { CloudFunction, EventContext, Schedule } from './cloud-functions'; /** * List of all regions supported by Cloud Functions. diff --git a/src/handler-builder.ts b/src/handler-builder.ts index 5c466fabc..115adcc38 100644 --- a/src/handler-builder.ts +++ b/src/handler-builder.ts @@ -23,6 +23,7 @@ import * as express from 'express'; import { apps } from './apps'; +import { CloudFunction, EventContext, HttpsFunction } from './cloud-functions'; import * as analytics from './providers/analytics'; import * as auth from './providers/auth'; import * as crashlytics from './providers/crashlytics'; @@ -32,7 +33,6 @@ import * as https from './providers/https'; import * as pubsub from './providers/pubsub'; import * as remoteConfig from './providers/remoteConfig'; import * as storage from './providers/storage'; -import { CloudFunction, EventContext, HttpsFunction } from './cloud-functions'; export class HandlerBuilder { constructor() {} diff --git a/src/index.ts b/src/index.ts index e82b05c1e..6cbe9e637 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,7 @@ import * as analytics from './providers/analytics'; import * as auth from './providers/auth'; import * as apps from './apps'; +import { handler } from './handler-builder'; import * as crashlytics from './providers/crashlytics'; import * as database from './providers/database'; import * as firestore from './providers/firestore'; @@ -32,10 +33,9 @@ import * as https from './providers/https'; import * as pubsub from './providers/pubsub'; import * as remoteConfig from './providers/remoteConfig'; import * as storage from './providers/storage'; -import { handler } from './handler-builder'; import { setup } from './setup'; -var app = apps.apps(); +const app = apps.apps(); export { analytics, diff --git a/src/providers/analytics.ts b/src/providers/analytics.ts index 3287264b3..4f8414240 100644 --- a/src/providers/analytics.ts +++ b/src/providers/analytics.ts @@ -23,10 +23,10 @@ import * as _ from 'lodash'; import { - makeCloudFunction, CloudFunction, Event, EventContext, + makeCloudFunction, } from '../cloud-functions'; import { DeploymentOptions } from '../function-builder'; @@ -141,7 +141,7 @@ export class AnalyticsEvent { this.params = {}; // In case of absent field, show empty (not absent) map. if (wireFormat.eventDim && wireFormat.eventDim.length > 0) { // If there's an eventDim, there'll always be exactly one. - let eventDim = wireFormat.eventDim[0]; + const eventDim = wireFormat.eventDim[0]; copyField(eventDim, this, 'name'); copyField(eventDim, this, 'params', (p) => _.mapValues(p, unwrapValue)); copyFieldTo(eventDim, this, 'valueInUsd', 'valueInUSD'); @@ -415,7 +415,7 @@ function copyField( } function copyFields(from: any, to: T, fields: K[]): void { - for (let field of fields) { + for (const field of fields) { copyField(from, to, field); } } @@ -450,7 +450,7 @@ function copyFields(from: any, to: T, fields: K[]): void { // method always returns a string, similarly to avoid loss of precision, unlike the less-conservative // 'unwrapValue' method just below. function unwrapValueAsString(wrapped: any): string { - let key: string = _.keys(wrapped)[0]; + const key: string = _.keys(wrapped)[0]; return _.toString(wrapped[key]); } // Ditto as the method above, but returning the values in the idiomatic JavaScript type (string for strings, @@ -468,8 +468,8 @@ function unwrapValueAsString(wrapped: any): string { // in precision for int64 fields, so use with care. const xValueNumberFields = ['intValue', 'floatValue', 'doubleValue']; function unwrapValue(wrapped: any): any { - let key: string = _.keys(wrapped)[0]; - let value: string = unwrapValueAsString(wrapped); + const key: string = _.keys(wrapped)[0]; + const value: string = unwrapValueAsString(wrapped); return _.includes(xValueNumberFields, key) ? _.toNumber(value) : value; } @@ -483,7 +483,7 @@ function copyTimestampToMillis( toName: K ) { if (from[fromName] !== undefined) { - to[toName] = _.round(from[fromName] / 1000); + to[toName] = _.round(from[fromName] / 1000) as any; } } @@ -497,6 +497,6 @@ function copyTimestampToString( toName: K ) { if (from[fromName] !== undefined) { - to[toName] = new Date(from[fromName] / 1000).toISOString(); + to[toName] = new Date(from[fromName] / 1000).toISOString() as any; } } diff --git a/src/providers/auth.ts b/src/providers/auth.ts index 95747ede5..2331c1cbd 100644 --- a/src/providers/auth.ts +++ b/src/providers/auth.ts @@ -20,14 +20,14 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import * as firebase from 'firebase-admin'; +import * as _ from 'lodash'; import { - makeCloudFunction, CloudFunction, - EventContext, Event, + EventContext, + makeCloudFunction, } from '../cloud-functions'; -import * as firebase from 'firebase-admin'; -import * as _ from 'lodash'; import { DeploymentOptions } from '../function-builder'; /** @internal */ @@ -120,7 +120,7 @@ export function userRecordConstructor( wireData: Object ): firebase.auth.UserRecord { // Falsey values from the wire format proto get lost when converted to JSON, this adds them back. - let falseyValues: any = { + const falseyValues: any = { email: null, emailVerified: false, displayName: null, @@ -133,9 +133,9 @@ export function userRecordConstructor( passwordHash: null, tokensValidAfterTime: null, }; - let record = _.assign({}, falseyValues, wireData); + const record = _.assign({}, falseyValues, wireData); - let meta = _.get(record, 'metadata'); + const meta = _.get(record, 'metadata'); if (meta) { _.set( record, diff --git a/src/providers/crashlytics.ts b/src/providers/crashlytics.ts index 2ad1db6b3..b335efeb8 100644 --- a/src/providers/crashlytics.ts +++ b/src/providers/crashlytics.ts @@ -21,9 +21,9 @@ // SOFTWARE. import { - makeCloudFunction, CloudFunction, EventContext, + makeCloudFunction, } from '../cloud-functions'; import { DeploymentOptions } from '../function-builder'; diff --git a/src/providers/database.ts b/src/providers/database.ts index 4b0aaf938..3890bc9c8 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -20,19 +20,19 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import * as firebase from 'firebase-admin'; import * as _ from 'lodash'; import { apps } from '../apps'; import { + Change, CloudFunction, - makeCloudFunction, Event, EventContext, - Change, + makeCloudFunction, } from '../cloud-functions'; -import { normalizePath, applyChange, pathParts, joinPath } from '../utils'; -import * as firebase from 'firebase-admin'; import { firebaseConfig } from '../config'; import { DeploymentOptions } from '../function-builder'; +import { applyChange, joinPath, normalizePath, pathParts } from '../utils'; /** @internal */ export const provider = 'google.firebase.database'; @@ -165,8 +165,8 @@ export class RefBuilder { context: EventContext ) => PromiseLike | any ): CloudFunction { - let dataConstructor = (raw: Event) => { - let [dbInstance, path] = resourceToInstanceAndPath( + const dataConstructor = (raw: Event) => { + const [dbInstance, path] = resourceToInstanceAndPath( raw.context.resource.name ); return new DataSnapshot( @@ -186,8 +186,8 @@ export class RefBuilder { context: EventContext ) => PromiseLike | any ): CloudFunction { - let dataConstructor = (raw: Event) => { - let [dbInstance, path] = resourceToInstanceAndPath( + const dataConstructor = (raw: Event) => { + const [dbInstance, path] = resourceToInstanceAndPath( raw.context.resource.name ); return new DataSnapshot(raw.data.data, path, this.apps.admin, dbInstance); @@ -207,7 +207,7 @@ export class RefBuilder { eventType, legacyEventType: `providers/${provider}/eventTypes/${eventType}`, triggerResource: this.triggerResource, - dataConstructor: dataConstructor, + dataConstructor, before: (event) => this.apps.retain(), after: (event) => this.apps.release(), opts: this.opts, @@ -215,24 +215,24 @@ export class RefBuilder { } private changeConstructor = (raw: Event): Change => { - let [dbInstance, path] = resourceToInstanceAndPath( + const [dbInstance, path] = resourceToInstanceAndPath( raw.context.resource.name ); - let before = new DataSnapshot( + const before = new DataSnapshot( raw.data.data, path, this.apps.admin, dbInstance ); - let after = new DataSnapshot( + const after = new DataSnapshot( applyChange(raw.data.data, raw.data.delta), path, this.apps.admin, dbInstance ); return { - before: before, - after: after, + before, + after, }; }; } @@ -240,21 +240,21 @@ export class RefBuilder { /* Utility function to extract database reference from resource string */ /** @internal */ export function resourceToInstanceAndPath(resource: string) { - let resourceRegex = `projects/([^/]+)/instances/([a-zA-Z0-9\-^/]+)/refs(/.+)?`; - let match = resource.match(new RegExp(resourceRegex)); + const resourceRegex = `projects/([^/]+)/instances/([a-zA-Z0-9\-^/]+)/refs(/.+)?`; + const match = resource.match(new RegExp(resourceRegex)); if (!match) { throw new Error( `Unexpected resource string for Firebase Realtime Database event: ${resource}. ` + 'Expected string in the format of "projects/_/instances/{firebaseioSubdomain}/refs/{ref=**}"' ); } - let [, project, dbInstanceName, path] = match; + const [, project, dbInstanceName, path] = match; if (project !== '_') { throw new Error( `Expect project to be '_' in a Firebase Realtime Database event` ); } - let dbInstance = 'https://' + dbInstanceName + '.firebaseio.com'; + const dbInstance = 'https://' + dbInstanceName + '.firebaseio.com'; return [dbInstance, path]; } @@ -301,14 +301,16 @@ export class DataSnapshot { } get key(): string { - let last = _.last(pathParts(this._fullPath())); + const last = _.last(pathParts(this._fullPath())); return !last || last === '' ? null : last; } val(): any { - let parts = pathParts(this._childPath); - let source = this._data; - let node = _.cloneDeep(parts.length ? _.get(source, parts, null) : source); + const parts = pathParts(this._childPath); + const source = this._data; + const node = _.cloneDeep( + parts.length ? _.get(source, parts, null) : source + ); return this._checkAndConvertToArray(node); } @@ -334,7 +336,7 @@ export class DataSnapshot { } forEach(action: (a: DataSnapshot) => boolean): boolean { - let val = this.val(); + const val = this.val(); if (_.isPlainObject(val)) { return _.some( val, @@ -349,12 +351,12 @@ export class DataSnapshot { } hasChildren(): boolean { - let val = this.val(); + const val = this.val(); return _.isPlainObject(val) && _.keys(val).length > 0; } numChildren(): number { - let val = this.val(); + const val = this.val(); return _.isPlainObject(val) ? Object.keys(val).length : 0; } @@ -374,15 +376,15 @@ export class DataSnapshot { if (typeof node !== 'object') { return node; } - let obj: any = {}; + const obj: any = {}; let numKeys = 0; let maxKey = 0; let allIntegerKeys = true; - for (let key in node) { + for (const key in node) { if (!node.hasOwnProperty(key)) { continue; } - let childNode = node[key]; + const childNode = node[key]; obj[key] = this._checkAndConvertToArray(childNode); numKeys++; const integerRegExp = /^(0|[1-9]\d*)$/; @@ -395,7 +397,7 @@ export class DataSnapshot { if (allIntegerKeys && maxKey < 2 * numKeys) { // convert to array. - let array: any = []; + const array: any = []; _.forOwn(obj, (val, key) => { array[key] = val; }); @@ -406,7 +408,12 @@ export class DataSnapshot { } private _dup(childPath?: string): DataSnapshot { - let dup = new DataSnapshot(this._data, undefined, this.app, this.instance); + const dup = new DataSnapshot( + this._data, + undefined, + this.app, + this.instance + ); [dup._path, dup._childPath] = [this._path, this._childPath]; if (childPath) { @@ -417,7 +424,7 @@ export class DataSnapshot { } private _fullPath(): string { - let out = (this._path || '') + '/' + (this._childPath || ''); + const out = (this._path || '') + '/' + (this._childPath || ''); return out; } } diff --git a/src/providers/firestore.ts b/src/providers/firestore.ts index 20dee281f..e8a354f5e 100644 --- a/src/providers/firestore.ts +++ b/src/providers/firestore.ts @@ -20,16 +20,16 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import { posix } from 'path'; -import * as _ from 'lodash'; import * as firebase from 'firebase-admin'; +import * as _ from 'lodash'; +import { posix } from 'path'; import { apps } from '../apps'; import { - makeCloudFunction, - CloudFunction, Change, + CloudFunction, Event, EventContext, + makeCloudFunction, } from '../cloud-functions'; import { dateToTimestampProto } from '../encoder'; import { DeploymentOptions } from '../function-builder'; @@ -108,7 +108,7 @@ export class NamespaceBuilder { if (!process.env.GCLOUD_PROJECT) { throw new Error('process.env.GCLOUD_PROJECT is not set.'); } - let database = posix.join( + const database = posix.join( 'projects', process.env.GCLOUD_PROJECT, 'databases', @@ -128,7 +128,7 @@ function _getValueProto(data: any, resource: string, valueFieldName: string) { // Firestore#snapshot_ takes resource string instead of proto for a non-existent snapshot return resource; } - let proto = { + const proto = { fields: _.get(data, [valueFieldName, 'fields'], {}), createTime: dateToTimestampProto( _.get(data, [valueFieldName, 'createTime']) @@ -146,12 +146,12 @@ export function snapshotConstructor(event: Event): DocumentSnapshot { if (!firestoreInstance) { firestoreInstance = firebase.firestore(apps().admin); } - let valueProto = _getValueProto( + const valueProto = _getValueProto( event.data, event.context.resource.name, 'value' ); - let readTime = dateToTimestampProto(_.get(event, 'data.value.readTime')); + const readTime = dateToTimestampProto(_.get(event, 'data.value.readTime')); return firestoreInstance.snapshot_(valueProto, readTime, 'json'); } @@ -161,12 +161,12 @@ export function beforeSnapshotConstructor(event: Event): DocumentSnapshot { if (!firestoreInstance) { firestoreInstance = firebase.firestore(apps().admin); } - let oldValueProto = _getValueProto( + const oldValueProto = _getValueProto( event.data, event.context.resource.name, 'oldValue' ); - let oldReadTime = dateToTimestampProto( + const oldReadTime = dateToTimestampProto( _.get(event, 'data.oldValue.readTime') ); return firestoreInstance.snapshot_(oldValueProto, oldReadTime, 'json'); diff --git a/src/providers/https.ts b/src/providers/https.ts index 315ce0768..06ab0e911 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -20,10 +20,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import * as cors from 'cors'; import * as express from 'express'; import * as firebase from 'firebase-admin'; import * as _ from 'lodash'; -import * as cors from 'cors'; import { apps } from '../apps'; import { HttpsFunction, optsToTrigger, Runnable } from '../cloud-functions'; import { DeploymentOptions } from '../function-builder'; @@ -62,7 +62,7 @@ export function _onRequestWithOpts( opts: DeploymentOptions ): HttpsFunction { // lets us add __trigger without altering handler: - let cloudFunction: any = (req: Request, res: express.Response) => { + const cloudFunction: any = (req: Request, res: express.Response) => { handler(req, res); }; cloudFunction.__trigger = _.assign(optsToTrigger(opts), { httpsTrigger: {} }); @@ -232,7 +232,7 @@ export class HttpsError extends Error { return 500; // This should never happen as long as the type system is doing its job. default: - throw 'Invalid error code: ' + this.code; + throw new Error('Invalid error code: ' + this.code); } } diff --git a/src/providers/pubsub.ts b/src/providers/pubsub.ts index 194ecda9c..9d92948ac 100644 --- a/src/providers/pubsub.ts +++ b/src/providers/pubsub.ts @@ -22,8 +22,8 @@ import { CloudFunction, - makeCloudFunction, EventContext, + makeCloudFunction, Schedule, ScheduleRetryConfig, } from '../cloud-functions'; @@ -91,7 +91,7 @@ export class ScheduleBuilder { contextOnlyHandler: handler, provider, service, - triggerResource: triggerResource, + triggerResource, eventType: 'topic.publish', opts: this._opts, labels: { 'deployment-scheduled': 'true' }, diff --git a/src/utils.ts b/src/utils.ts index d483f0697..0ebbb38c4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -52,7 +52,7 @@ export function applyChange(src: any, dest: any) { } export function pruneNulls(obj: any) { - for (let key in obj) { + for (const key in obj) { if (obj[key] === null) { delete obj[key]; } else if (_.isPlainObject(obj[key])) { @@ -69,7 +69,7 @@ export function valAt(source: any, path?: string) { return path ? null : source; } - let parts = pathParts(path); + const parts = pathParts(path); if (!parts.length) { return source; } @@ -77,7 +77,7 @@ export function valAt(source: any, path?: string) { let cur = source; let leaf; while (parts.length) { - let key = parts.shift(); + const key = parts.shift(); if (cur[key] === null || leaf) { return null; } else if (typeof cur[key] === 'object') { diff --git a/tslint.json b/tslint.json index f8f669f21..2efe809c3 100644 --- a/tslint.json +++ b/tslint.json @@ -1,6 +1,10 @@ { "defaultSeverity": "warning", - "extends": ["tslint:recommended", "tslint-no-unused-expression-chai"], + "extends": [ + "tslint:recommended", + "tslint-no-unused-expression-chai", + "tslint-config-prettier" + ], "rules": { "interface-name": false, "member-access": false, From 340bc770e86930598a02bf0b27932b752dfc9a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Wed, 3 Jul 2019 21:00:55 +0200 Subject: [PATCH 050/437] Update dependencies to latest versions (#492) --- package.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 35389179f..75686f91b 100644 --- a/package.json +++ b/package.json @@ -32,37 +32,37 @@ }, "dependencies": { "@types/cors": "^2.8.5", - "@types/express": "^4.11.1", + "@types/express": "^4.17.0", "@types/jsonwebtoken": "^8.3.2", - "@types/lodash": "^4.14.133", - "cors": "^2.8.4", + "@types/lodash": "^4.14.135", + "cors": "^2.8.5", "express": "^4.17.1", - "jsonwebtoken": "^8.3.2", - "lodash": "^4.6.1" + "jsonwebtoken": "^8.5.1", + "lodash": "^4.17.11" }, "devDependencies": { "@types/chai": "^4.1.7", "@types/chai-as-promised": "^7.1.0", "@types/mocha": "^5.2.7", "@types/mock-require": "^2.0.0", - "@types/nock": "^10.0.2", - "@types/node": "^8.10.49", - "@types/sinon": "^7.0.12", + "@types/nock": "^10.0.3", + "@types/node": "^8.10.50", + "@types/sinon": "^7.0.13", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", - "firebase-admin": "^8.0.0", - "istanbul": "^0.4.2", + "firebase-admin": "^8.2.0", + "istanbul": "^0.4.5", "mocha": "^6.1.4", "mock-require": "^3.0.3", "nock": "^10.0.6", - "prettier": "^1.17.1", + "prettier": "^1.18.2", "sinon": "^7.3.2", - "ts-node": "^8.2.0", - "tslint": "^5.17.0", + "ts-node": "^8.3.0", + "tslint": "^5.18.0", "tslint-config-prettier": "^1.18.0", "tslint-no-unused-expression-chai": "^0.1.4", - "tslint-plugin-prettier": "^2.0.0", - "typescript": "^3.5.1" + "tslint-plugin-prettier": "^2.0.1", + "typescript": "^3.5.2" }, "peerDependencies": { "firebase-admin": "^8.0.0" From b3566bf5f358d1bec71374654260f3300b9fa9d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Wed, 3 Jul 2019 23:14:28 +0200 Subject: [PATCH 051/437] Extract configuration to break dependency cycle (#494) * Extract configuration to break dependency cycle * Add comments to Schedule and ScheduleRetryConfig * Stricten regions definition * Fix tests broken due to strict typings * More strict types for regions - reverse order * Reformat --- spec/function-builder.spec.ts | 10 ++--- src/cloud-functions.ts | 16 +------- src/function-builder.ts | 59 +++++++----------------------- src/function-configuration.ts | 69 +++++++++++++++++++++++++++++++++++ src/index.ts | 9 +++-- src/providers/analytics.ts | 2 +- src/providers/auth.ts | 2 +- src/providers/crashlytics.ts | 2 +- src/providers/database.ts | 2 +- src/providers/firestore.ts | 2 +- src/providers/https.ts | 2 +- src/providers/pubsub.ts | 6 ++- src/providers/remoteConfig.ts | 2 +- src/providers/storage.ts | 2 +- 14 files changed, 106 insertions(+), 79 deletions(-) create mode 100644 src/function-configuration.ts diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index 04575e28b..b0c4d4c28 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -123,7 +123,7 @@ describe('FunctionBuilder', () => { expect(() => { functions .runWith({ timeoutSeconds: 90, memory: '256MB' }) - .region('unsupported'); + .region('unsupported' as any); }).to.throw(Error, 'region'); }); @@ -165,21 +165,21 @@ describe('FunctionBuilder', () => { it('should throw an error if user chooses an invalid region', () => { expect(() => { - return functions.region('unsupported'); + return functions.region('unsupported' as any); }).to.throw(Error, 'region'); expect(() => { - return functions.region('unsupported').runWith({ + return functions.region('unsupported' as any).runWith({ timeoutSeconds: 500, } as any); }).to.throw(Error, 'region'); expect(() => { - return functions.region('unsupported', 'us-east1'); + return functions.region('unsupported' as any, 'us-east1'); }).to.throw(Error, 'region'); expect(() => { - return functions.region('unsupported', 'us-east1').runWith({ + return functions.region('unsupported' as any, 'us-east1').runWith({ timeoutSeconds: 500, } as any); }).to.throw(Error, 'region'); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 881a7d275..f63992723 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -23,7 +23,7 @@ import { Request, Response } from 'express'; import * as _ from 'lodash'; import { apps } from './apps'; -import { DeploymentOptions } from './function-builder'; +import { DeploymentOptions, Schedule } from './function-configuration'; export { Request, Response }; const WILDCARD_REGEX = new RegExp('{[^/{}]*}', 'g'); @@ -168,20 +168,6 @@ export interface TriggerAnnotated { }; } -export interface ScheduleRetryConfig { - retryCount?: number; - maxRetryDuration?: string; - minBackoffDuration?: string; - maxBackoffDuration?: string; - maxDoublings?: number; -} - -export interface Schedule { - schedule: string; - timeZone?: string; - retryConfig?: ScheduleRetryConfig; -} - /** A Runnable has a `run` method which directly invokes the user-defined function - useful for unit testing. */ export interface Runnable { run: (data: T, context: any) => PromiseLike | any; diff --git a/src/function-builder.ts b/src/function-builder.ts index 76c858b92..28d9240c1 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -23,7 +23,14 @@ import * as express from 'express'; import * as _ from 'lodash'; -import { CloudFunction, EventContext, Schedule } from './cloud-functions'; +import { CloudFunction, EventContext } from './cloud-functions'; +import { + DeploymentOptions, + MAX_TIMEOUT_SECONDS, + RuntimeOptions, + SUPPORTED_REGIONS, + VALID_MEMORY_OPTIONS, +} from './function-configuration'; import * as analytics from './providers/analytics'; import * as auth from './providers/auth'; import * as crashlytics from './providers/crashlytics'; @@ -34,34 +41,6 @@ import * as pubsub from './providers/pubsub'; import * as remoteConfig from './providers/remoteConfig'; import * as storage from './providers/storage'; -/** - * List of all regions supported by Cloud Functions. - */ -const SUPPORTED_REGIONS = [ - 'us-central1', - 'us-east1', - 'us-east4', - 'europe-west1', - 'europe-west2', - 'asia-east2', - 'asia-northeast1', -]; - -/** - * List of available memory options supported by Cloud Functions. - */ -const VALID_MEMORY_OPTS = ['128MB', '256MB', '512MB', '1GB', '2GB']; - -// Adding this memory type here to error on compile for TS users. -// Unfortunately I have not found a way to merge this with VALID_MEMORY_OPS -// without it being super ugly. But here they are right next to each other at least. -type Memory = '128MB' | '256MB' | '512MB' | '1GB' | '2GB'; - -/** - * Cloud Functions max timeout value. - */ -const MAX_TIMEOUT_SECONDS = 540; - /** * Assert that the runtime options passed in are valid. * @param runtimeOptions object containing memory and timeout information. @@ -70,10 +49,10 @@ const MAX_TIMEOUT_SECONDS = 540; function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean { if ( runtimeOptions.memory && - !_.includes(VALID_MEMORY_OPTS, runtimeOptions.memory) + !_.includes(VALID_MEMORY_OPTIONS, runtimeOptions.memory) ) { throw new Error( - `The only valid memory allocation values are: ${VALID_MEMORY_OPTS.join( + `The only valid memory allocation values are: ${VALID_MEMORY_OPTIONS.join( ', ' )}` ); @@ -111,7 +90,9 @@ function assertRegionsAreValid(regions: string[]): boolean { * @param regions One of more region strings. * For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')` */ -export function region(...regions: string[]): FunctionBuilder { +export function region( + ...regions: Array +): FunctionBuilder { if (assertRegionsAreValid(regions)) { return new FunctionBuilder({ regions }); } @@ -130,18 +111,6 @@ export function runWith(runtimeOptions: RuntimeOptions): FunctionBuilder { } } -export interface RuntimeOptions { - timeoutSeconds?: number; - memory?: Memory; -} - -export interface DeploymentOptions { - regions?: string[]; - timeoutSeconds?: number; - memory?: Memory; - schedule?: Schedule; -} - export class FunctionBuilder { constructor(private options: DeploymentOptions) {} @@ -150,7 +119,7 @@ export class FunctionBuilder { * @param regions One or more region strings. * For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')` */ - region(...regions: string[]): FunctionBuilder { + region(...regions: Array): FunctionBuilder { if (assertRegionsAreValid(regions)) { this.options.regions = regions; return this; diff --git a/src/function-configuration.ts b/src/function-configuration.ts new file mode 100644 index 000000000..92ef2d3ff --- /dev/null +++ b/src/function-configuration.ts @@ -0,0 +1,69 @@ +/** + * List of all regions supported by Cloud Functions. + */ +export const SUPPORTED_REGIONS = [ + 'us-central1', + 'us-east1', + 'us-east4', + 'europe-west1', + 'europe-west2', + 'asia-east2', + 'asia-northeast1', +] as const; + +/** + * Cloud Functions min timeout value. + */ +export const MIN_TIMEOUT_SECONDS = 0; + +/** + * Cloud Functions max timeout value. + */ +export const MAX_TIMEOUT_SECONDS = 540; + +/** + * List of available memory options supported by Cloud Functions. + */ +export const VALID_MEMORY_OPTIONS = [ + '128MB', + '256MB', + '512MB', + '1GB', + '2GB', +] as const; + +/** + * Scheduler retry options. Applies only to scheduled functions. + */ +export interface ScheduleRetryConfig { + retryCount?: number; + maxRetryDuration?: string; + minBackoffDuration?: string; + maxBackoffDuration?: string; + maxDoublings?: number; +} + +/** + * Configuration options for scheduled functions. + */ +export interface Schedule { + schedule: string; + timeZone?: string; + retryConfig?: ScheduleRetryConfig; +} + +export interface RuntimeOptions { + /** + * Amount of memory to allocate to the function. + */ + memory?: typeof VALID_MEMORY_OPTIONS[number]; + /** + * Timeout for the function in seconds, possible values are 0 to 540. + */ + timeoutSeconds?: number; +} + +export interface DeploymentOptions extends RuntimeOptions { + regions?: string[]; + schedule?: Schedule; +} diff --git a/src/index.ts b/src/index.ts index 6cbe9e637..534080f56 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,9 +23,6 @@ // Providers: import * as analytics from './providers/analytics'; import * as auth from './providers/auth'; - -import * as apps from './apps'; -import { handler } from './handler-builder'; import * as crashlytics from './providers/crashlytics'; import * as database from './providers/database'; import * as firestore from './providers/firestore'; @@ -33,6 +30,9 @@ import * as https from './providers/https'; import * as pubsub from './providers/pubsub'; import * as remoteConfig from './providers/remoteConfig'; import * as storage from './providers/storage'; + +import * as apps from './apps'; +import { handler } from './handler-builder'; import { setup } from './setup'; const app = apps.apps(); @@ -52,8 +52,9 @@ export { }; // Exported root types: -export * from './config'; export * from './cloud-functions'; +export * from './config'; export * from './function-builder'; +export * from './function-configuration'; setup(); diff --git a/src/providers/analytics.ts b/src/providers/analytics.ts index 4f8414240..2778cd4b3 100644 --- a/src/providers/analytics.ts +++ b/src/providers/analytics.ts @@ -28,7 +28,7 @@ import { EventContext, makeCloudFunction, } from '../cloud-functions'; -import { DeploymentOptions } from '../function-builder'; +import { DeploymentOptions } from '../function-configuration'; /** @internal */ export const provider = 'google.analytics'; diff --git a/src/providers/auth.ts b/src/providers/auth.ts index 2331c1cbd..2ca4b4c9c 100644 --- a/src/providers/auth.ts +++ b/src/providers/auth.ts @@ -28,7 +28,7 @@ import { EventContext, makeCloudFunction, } from '../cloud-functions'; -import { DeploymentOptions } from '../function-builder'; +import { DeploymentOptions } from '../function-configuration'; /** @internal */ export const provider = 'google.firebase.auth'; diff --git a/src/providers/crashlytics.ts b/src/providers/crashlytics.ts index b335efeb8..eca79da68 100644 --- a/src/providers/crashlytics.ts +++ b/src/providers/crashlytics.ts @@ -25,7 +25,7 @@ import { EventContext, makeCloudFunction, } from '../cloud-functions'; -import { DeploymentOptions } from '../function-builder'; +import { DeploymentOptions } from '../function-configuration'; /** @internal */ export const provider = 'google.firebase.crashlytics'; diff --git a/src/providers/database.ts b/src/providers/database.ts index 3890bc9c8..3ebf2a5e5 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -31,7 +31,7 @@ import { makeCloudFunction, } from '../cloud-functions'; import { firebaseConfig } from '../config'; -import { DeploymentOptions } from '../function-builder'; +import { DeploymentOptions } from '../function-configuration'; import { applyChange, joinPath, normalizePath, pathParts } from '../utils'; /** @internal */ diff --git a/src/providers/firestore.ts b/src/providers/firestore.ts index e8a354f5e..d78706377 100644 --- a/src/providers/firestore.ts +++ b/src/providers/firestore.ts @@ -32,7 +32,7 @@ import { makeCloudFunction, } from '../cloud-functions'; import { dateToTimestampProto } from '../encoder'; -import { DeploymentOptions } from '../function-builder'; +import { DeploymentOptions } from '../function-configuration'; /** @internal */ export const provider = 'google.firestore'; diff --git a/src/providers/https.ts b/src/providers/https.ts index 06ab0e911..937c625b7 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -26,7 +26,7 @@ import * as firebase from 'firebase-admin'; import * as _ from 'lodash'; import { apps } from '../apps'; import { HttpsFunction, optsToTrigger, Runnable } from '../cloud-functions'; -import { DeploymentOptions } from '../function-builder'; +import { DeploymentOptions } from '../function-configuration'; /** * diff --git a/src/providers/pubsub.ts b/src/providers/pubsub.ts index 9d92948ac..6de173397 100644 --- a/src/providers/pubsub.ts +++ b/src/providers/pubsub.ts @@ -24,10 +24,12 @@ import { CloudFunction, EventContext, makeCloudFunction, +} from '../cloud-functions'; +import { + DeploymentOptions, Schedule, ScheduleRetryConfig, -} from '../cloud-functions'; -import { DeploymentOptions } from '../function-builder'; +} from '../function-configuration'; /** @internal */ export const provider = 'google.pubsub'; diff --git a/src/providers/remoteConfig.ts b/src/providers/remoteConfig.ts index 00fb8b877..60d5345d6 100644 --- a/src/providers/remoteConfig.ts +++ b/src/providers/remoteConfig.ts @@ -28,7 +28,7 @@ import { EventContext, makeCloudFunction, } from '../cloud-functions'; -import { DeploymentOptions } from '../function-builder'; +import { DeploymentOptions } from '../function-configuration'; /** @internal */ export const provider = 'google.firebase.remoteconfig'; diff --git a/src/providers/storage.ts b/src/providers/storage.ts index eb7821b99..436ffdce6 100644 --- a/src/providers/storage.ts +++ b/src/providers/storage.ts @@ -26,7 +26,7 @@ import { makeCloudFunction, } from '../cloud-functions'; import { firebaseConfig } from '../config'; -import { DeploymentOptions } from '../function-builder'; +import { DeploymentOptions } from '../function-configuration'; /** @internal */ export const provider = 'google.storage'; From 71dc8dbcccebe7f926cc46a90f053ce0e634d208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Wed, 3 Jul 2019 23:55:52 +0200 Subject: [PATCH 052/437] Avoid abbreviations to improve readability: opts -> options (#495) --- src/cloud-functions.ts | 24 +++++++++++------------ src/function-builder.ts | 35 +++++++++++++++++---------------- src/handler-builder.ts | 4 ++-- src/providers/analytics.ts | 12 ++++++------ src/providers/auth.ts | 10 +++++----- src/providers/crashlytics.ts | 10 +++++----- src/providers/database.ts | 24 +++++++++++------------ src/providers/firestore.ts | 37 +++++++++++++++++++---------------- src/providers/https.ts | 24 ++++++++++++----------- src/providers/pubsub.ts | 32 +++++++++++++++--------------- src/providers/remoteConfig.ts | 12 ++++++------ src/providers/storage.ts | 22 ++++++++++----------- 12 files changed, 126 insertions(+), 120 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index f63992723..2560bc824 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -202,7 +202,7 @@ export interface MakeCloudFunctionArgs { before?: (raw: Event) => void; after?: (raw: Event) => void; legacyEventType?: string; - opts?: { [key: string]: any }; + options?: { [key: string]: any }; labels?: { [key: string]: any }; } @@ -222,7 +222,7 @@ export function makeCloudFunction({ return; }, legacyEventType, - opts = {}, + options = {}, labels = {}, }: MakeCloudFunctionArgs): CloudFunction { const cloudFunction: any = (data: any, context: any) => { @@ -291,7 +291,7 @@ export function makeCloudFunction({ return {}; } - const trigger: any = _.assign(optsToTrigger(opts), { + const trigger: any = _.assign(optionsToTrigger(options), { eventTrigger: { resource: triggerResource(), eventType: legacyEventType || provider + '.' + eventType, @@ -356,15 +356,15 @@ function _detectAuthType(event: Event) { return 'UNAUTHENTICATED'; } -export function optsToTrigger(opts: DeploymentOptions) { +export function optionsToTrigger(options: DeploymentOptions) { const trigger: any = {}; - if (opts.regions) { - trigger.regions = opts.regions; + if (options.regions) { + trigger.regions = options.regions; } - if (opts.timeoutSeconds) { - trigger.timeout = opts.timeoutSeconds.toString() + 's'; + if (options.timeoutSeconds) { + trigger.timeout = options.timeoutSeconds.toString() + 's'; } - if (opts.memory) { + if (options.memory) { const memoryLookup = { '128MB': 128, '256MB': 256, @@ -372,10 +372,10 @@ export function optsToTrigger(opts: DeploymentOptions) { '1GB': 1024, '2GB': 2048, }; - trigger.availableMemoryMb = _.get(memoryLookup, opts.memory); + trigger.availableMemoryMb = _.get(memoryLookup, options.memory); } - if (opts.schedule) { - trigger.schedule = opts.schedule; + if (options.schedule) { + trigger.schedule = options.schedule; } return trigger; } diff --git a/src/function-builder.ts b/src/function-builder.ts index 28d9240c1..0db51a974 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -149,7 +149,7 @@ export class FunctionBuilder { */ onRequest: ( handler: (req: https.Request, resp: express.Response) => void - ) => https._onRequestWithOpts(handler, this.options), + ) => https._onRequestWithOptions(handler, this.options), /** * Declares a callable method for clients to call using a Firebase SDK. * @param handler A method that takes a data and context and returns a value. @@ -159,7 +159,7 @@ export class FunctionBuilder { data: any, context: https.CallableContext ) => any | Promise - ) => https._onCallWithOpts(handler, this.options), + ) => https._onCallWithOptions(handler, this.options), }; } @@ -171,7 +171,7 @@ export class FunctionBuilder { * @param instance The Realtime Database instance to use. */ instance: (instance: string) => - database._instanceWithOpts(instance, this.options), + database._instanceWithOptions(instance, this.options), /** * Select Firebase Realtime Database Reference to listen to. * @@ -195,7 +195,7 @@ export class FunctionBuilder { * about the user who triggered the Cloud Function. * @param ref Path of the database to listen to. */ - ref: (path: string) => database._refWithOpts(path, this.options), + ref: (path: string) => database._refWithOptions(path, this.options), }; } @@ -209,13 +209,13 @@ export class FunctionBuilder { * path is "/users/Ada". */ document: (path: string) => - firestore._documentWithOpts(path, this.options), + firestore._documentWithOptions(path, this.options), /** @internal */ namespace: (namespace: string) => - firestore._namespaceWithOpts(namespace, this.options), + firestore._namespaceWithOptions(namespace, this.options), /** @internal */ database: (database: string) => - firestore._databaseWithOpts(database, this.options), + firestore._databaseWithOptions(database, this.options), }; } @@ -225,7 +225,7 @@ export class FunctionBuilder { * Handle events related to Crashlytics issues. An issue in Crashlytics is an * aggregation of crashes which have a shared root cause. */ - issue: () => crashlytics._issueWithOpts(this.options), + issue: () => crashlytics._issueWithOptions(this.options), }; } @@ -236,7 +236,7 @@ export class FunctionBuilder { * @param analyticsEventType Name of the analytics event type. */ event: (analyticsEventType: string) => - analytics._eventWithOpts(analyticsEventType, this.options), + analytics._eventWithOptions(analyticsEventType, this.options), }; } @@ -254,9 +254,10 @@ export class FunctionBuilder { context: EventContext ) => PromiseLike | any ) => - remoteConfig._onUpdateWithOpts(handler, this.options) as CloudFunction< - remoteConfig.TemplateVersion - >, + remoteConfig._onUpdateWithOptions( + handler, + this.options + ) as CloudFunction, }; } @@ -269,12 +270,12 @@ export class FunctionBuilder { * @param bucket Name of the Google Cloud Storage bucket to listen to. */ bucket: (bucket?: string) => - storage._bucketWithOpts(this.options, bucket), + storage._bucketWithOptions(this.options, bucket), /** * Handle events related to Cloud Storage objects. */ - object: () => storage._objectWithOpts(this.options), + object: () => storage._objectWithOptions(this.options), }; } @@ -283,9 +284,9 @@ export class FunctionBuilder { /** Select Cloud Pub/Sub topic to listen to. * @param topic Name of Pub/Sub topic, must belong to the same project as the function. */ - topic: (topic: string) => pubsub._topicWithOpts(topic, this.options), + topic: (topic: string) => pubsub._topicWithOptions(topic, this.options), schedule: (schedule: string) => - pubsub._scheduleWithOpts(schedule, this.options), + pubsub._scheduleWithOptions(schedule, this.options), }; } @@ -294,7 +295,7 @@ export class FunctionBuilder { /** * Handle events related to Firebase authentication users. */ - user: () => auth._userWithOpts(this.options), + user: () => auth._userWithOptions(this.options), }; } } diff --git a/src/handler-builder.ts b/src/handler-builder.ts index 115adcc38..20e15986d 100644 --- a/src/handler-builder.ts +++ b/src/handler-builder.ts @@ -47,7 +47,7 @@ export class HandlerBuilder { onRequest: ( handler: (req: express.Request, resp: express.Response) => void ): HttpsFunction => { - const func = https._onRequestWithOpts(handler, {}); + const func = https._onRequestWithOptions(handler, {}); func.__trigger = {}; return func; }, @@ -61,7 +61,7 @@ export class HandlerBuilder { context: https.CallableContext ) => any | Promise ): HttpsFunction => { - const func = https._onCallWithOpts(handler, {}); + const func = https._onCallWithOptions(handler, {}); func.__trigger = {}; return func; }, diff --git a/src/providers/analytics.ts b/src/providers/analytics.ts index 2778cd4b3..3cd5612c5 100644 --- a/src/providers/analytics.ts +++ b/src/providers/analytics.ts @@ -40,13 +40,13 @@ export const service = 'app-measurement.com'; * @param analyticsEventType Name of the analytics event type. */ export function event(analyticsEventType: string) { - return _eventWithOpts(analyticsEventType, {}); + return _eventWithOptions(analyticsEventType, {}); } /** @internal */ -export function _eventWithOpts( +export function _eventWithOptions( analyticsEventType: string, - opts: DeploymentOptions + options: DeploymentOptions ) { return new AnalyticsEventBuilder(() => { if (!process.env.GCLOUD_PROJECT) { @@ -55,7 +55,7 @@ export function _eventWithOpts( return ( 'projects/' + process.env.GCLOUD_PROJECT + '/events/' + analyticsEventType ); - }, opts); + }, options); } /** @@ -67,7 +67,7 @@ export class AnalyticsEventBuilder { /** @internal */ constructor( private triggerResource: () => string, - private opts: DeploymentOptions + private options: DeploymentOptions ) {} /** @@ -97,7 +97,7 @@ export class AnalyticsEventBuilder { legacyEventType: `providers/google.firebase.analytics/eventTypes/event.log`, triggerResource: this.triggerResource, dataConstructor, - opts: this.opts, + options: this.options, }); } } diff --git a/src/providers/auth.ts b/src/providers/auth.ts index 2ca4b4c9c..e775116d2 100644 --- a/src/providers/auth.ts +++ b/src/providers/auth.ts @@ -39,17 +39,17 @@ export const service = 'firebaseauth.googleapis.com'; * Handle events related to Firebase authentication users. */ export function user() { - return _userWithOpts({}); + return _userWithOptions({}); } /** @internal */ -export function _userWithOpts(opts: DeploymentOptions) { +export function _userWithOptions(options: DeploymentOptions) { return new UserBuilder(() => { if (!process.env.GCLOUD_PROJECT) { throw new Error('process.env.GCLOUD_PROJECT is not set.'); } return 'projects/' + process.env.GCLOUD_PROJECT; - }, opts); + }, options); } export class UserRecordMetadata implements firebase.auth.UserMetadata { @@ -73,7 +73,7 @@ export class UserBuilder { /** @internal */ constructor( private triggerResource: () => string, - private opts?: DeploymentOptions + private options?: DeploymentOptions ) {} /** Respond to the creation of a Firebase Auth user. */ @@ -105,7 +105,7 @@ export class UserBuilder { triggerResource: this.triggerResource, dataConstructor: UserBuilder.dataConstructor, legacyEventType: `providers/firebase.auth/eventTypes/${eventType}`, - opts: this.opts, + options: this.options, }); } } diff --git a/src/providers/crashlytics.ts b/src/providers/crashlytics.ts index eca79da68..751e868bd 100644 --- a/src/providers/crashlytics.ts +++ b/src/providers/crashlytics.ts @@ -37,17 +37,17 @@ export const service = 'fabric.io'; * aggregation of crashes which have a shared root cause. */ export function issue() { - return _issueWithOpts({}); + return _issueWithOptions({}); } /** @internal */ -export function _issueWithOpts(opts: DeploymentOptions) { +export function _issueWithOptions(options: DeploymentOptions) { return new IssueBuilder(() => { if (!process.env.GCLOUD_PROJECT) { throw new Error('process.env.GCLOUD_PROJECT is not set.'); } return 'projects/' + process.env.GCLOUD_PROJECT; - }, opts); + }, options); } /** Builder used to create Cloud Functions for Crashlytics issue events. */ @@ -55,7 +55,7 @@ export class IssueBuilder { /** @internal */ constructor( private triggerResource: () => string, - private opts: DeploymentOptions + private options: DeploymentOptions ) {} /** @internal */ @@ -95,7 +95,7 @@ export class IssueBuilder { service, legacyEventType: `providers/firebase.crashlytics/eventTypes/${eventType}`, triggerResource: this.triggerResource, - opts: this.opts, + options: this.options, }); } } diff --git a/src/providers/database.ts b/src/providers/database.ts index 3ebf2a5e5..4fb28a2bd 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -48,7 +48,7 @@ const databaseURLRegex = new RegExp('https://([^.]+).firebaseio.com'); * @param instance The Realtime Database instance to use. */ export function instance(instance: string) { - return _instanceWithOpts(instance, {}); + return _instanceWithOptions(instance, {}); } /** @@ -75,35 +75,35 @@ export function instance(instance: string) { * @param ref Path of the database to listen to. */ export function ref(path: string) { - return _refWithOpts(path, {}); + return _refWithOptions(path, {}); } /** @internal */ -export function _instanceWithOpts( +export function _instanceWithOptions( instance: string, - opts: DeploymentOptions + options: DeploymentOptions ): InstanceBuilder { - return new InstanceBuilder(instance, opts); + return new InstanceBuilder(instance, options); } export class InstanceBuilder { /* @internal */ - constructor(private instance: string, private opts: DeploymentOptions) {} + constructor(private instance: string, private options: DeploymentOptions) {} ref(path: string): RefBuilder { const normalized = normalizePath(path); return new RefBuilder( apps(), () => `projects/_/instances/${this.instance}/refs/${normalized}`, - this.opts + this.options ); } } /** @internal */ -export function _refWithOpts( +export function _refWithOptions( path: string, - opts: DeploymentOptions + options: DeploymentOptions ): RefBuilder { const resourceGetter = () => { const normalized = normalizePath(path); @@ -126,7 +126,7 @@ export function _refWithOpts( return `projects/_/instances/${subdomain}/refs/${normalized}`; }; - return new RefBuilder(apps(), resourceGetter, opts); + return new RefBuilder(apps(), resourceGetter, options); } /** Builder used to create Cloud Functions for Firebase Realtime Database References. */ @@ -135,7 +135,7 @@ export class RefBuilder { constructor( private apps: apps.Apps, private triggerResource: () => string, - private opts: DeploymentOptions + private options: DeploymentOptions ) {} /** Respond to any write that affects a ref. */ @@ -210,7 +210,7 @@ export class RefBuilder { dataConstructor, before: (event) => this.apps.retain(), after: (event) => this.apps.release(), - opts: this.opts, + options: this.options, }); } diff --git a/src/providers/firestore.ts b/src/providers/firestore.ts index d78706377..923763225 100644 --- a/src/providers/firestore.ts +++ b/src/providers/firestore.ts @@ -51,47 +51,50 @@ export type DocumentSnapshot = firebase.firestore.DocumentSnapshot; * path is "/users/Ada". */ export function document(path: string) { - return _documentWithOpts(path, {}); + return _documentWithOptions(path, {}); } /** @internal */ // Multiple namespaces are not yet supported by Firestore. export function namespace(namespace: string) { - return _namespaceWithOpts(namespace, {}); + return _namespaceWithOptions(namespace, {}); } /** @internal */ // Multiple databases are not yet supported by Firestore. export function database(database: string) { - return _databaseWithOpts(database, {}); + return _databaseWithOptions(database, {}); } /** @internal */ -export function _databaseWithOpts( +export function _databaseWithOptions( database: string = defaultDatabase, - opts: DeploymentOptions + options: DeploymentOptions ) { - return new DatabaseBuilder(database, opts); + return new DatabaseBuilder(database, options); } /** @internal */ -export function _namespaceWithOpts(namespace: string, opts: DeploymentOptions) { - return _databaseWithOpts(defaultDatabase, opts).namespace(namespace); +export function _namespaceWithOptions( + namespace: string, + options: DeploymentOptions +) { + return _databaseWithOptions(defaultDatabase, options).namespace(namespace); } /** @internal */ -export function _documentWithOpts(path: string, opts: DeploymentOptions) { - return _databaseWithOpts(defaultDatabase, opts).document(path); +export function _documentWithOptions(path: string, options: DeploymentOptions) { + return _databaseWithOptions(defaultDatabase, options).document(path); } export class DatabaseBuilder { /** @internal */ - constructor(private database: string, private opts: DeploymentOptions) {} + constructor(private database: string, private options: DeploymentOptions) {} namespace(namespace: string) { - return new NamespaceBuilder(this.database, this.opts, namespace); + return new NamespaceBuilder(this.database, this.options, namespace); } document(path: string) { - return new NamespaceBuilder(this.database, this.opts).document(path); + return new NamespaceBuilder(this.database, this.options).document(path); } } @@ -99,7 +102,7 @@ export class NamespaceBuilder { /** @internal */ constructor( private database: string, - private opts: DeploymentOptions, + private options: DeploymentOptions, private namespace?: string ) {} @@ -119,7 +122,7 @@ export class NamespaceBuilder { this.namespace ? `documents@${this.namespace}` : 'documents', path ); - }, this.opts); + }, this.options); } } @@ -183,7 +186,7 @@ export class DocumentBuilder { /** @internal */ constructor( private triggerResource: () => string, - private opts: DeploymentOptions + private options: DeploymentOptions ) { // TODO what validation do we want to do here? } @@ -245,7 +248,7 @@ export class DocumentBuilder { triggerResource: this.triggerResource, legacyEventType: `providers/cloud.firestore/eventTypes/${eventType}`, dataConstructor, - opts: this.opts, + options: this.options, }); } } diff --git a/src/providers/https.ts b/src/providers/https.ts index 937c625b7..0a82de012 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -25,7 +25,7 @@ import * as express from 'express'; import * as firebase from 'firebase-admin'; import * as _ from 'lodash'; import { apps } from '../apps'; -import { HttpsFunction, optsToTrigger, Runnable } from '../cloud-functions'; +import { HttpsFunction, optionsToTrigger, Runnable } from '../cloud-functions'; import { DeploymentOptions } from '../function-configuration'; /** @@ -42,8 +42,8 @@ export interface Request extends express.Request { */ export function onRequest( handler: (req: Request, resp: express.Response) => void -) { - return _onRequestWithOpts(handler, {}); +): HttpsFunction { + return _onRequestWithOptions(handler, {}); } /** @@ -53,20 +53,22 @@ export function onRequest( export function onCall( handler: (data: any, context: CallableContext) => any | Promise ): HttpsFunction & Runnable { - return _onCallWithOpts(handler, {}); + return _onCallWithOptions(handler, {}); } /** @internal */ -export function _onRequestWithOpts( +export function _onRequestWithOptions( handler: (req: Request, resp: express.Response) => void, - opts: DeploymentOptions + options: DeploymentOptions ): HttpsFunction { // lets us add __trigger without altering handler: const cloudFunction: any = (req: Request, res: express.Response) => { handler(req, res); }; - cloudFunction.__trigger = _.assign(optsToTrigger(opts), { httpsTrigger: {} }); - // TODO parse the opts + cloudFunction.__trigger = _.assign(optionsToTrigger(options), { + httpsTrigger: {}, + }); + // TODO parse the options return cloudFunction; } @@ -414,9 +416,9 @@ export function decode(data: any): any { const corsHandler = cors({ origin: true, methods: 'POST' }); /** @internal */ -export function _onCallWithOpts( +export function _onCallWithOptions( handler: (data: any, context: CallableContext) => any | Promise, - opts: DeploymentOptions + options: DeploymentOptions ): HttpsFunction & Runnable { const func = async (req: Request, res: express.Response) => { try { @@ -483,7 +485,7 @@ export function _onCallWithOpts( return corsHandler(req, res, () => func(req, res)); }; - corsFunc.__trigger = _.assign(optsToTrigger(opts), { + corsFunc.__trigger = _.assign(optionsToTrigger(options), { httpsTrigger: {}, labels: { 'deployment-callable': 'true' }, }); diff --git a/src/providers/pubsub.ts b/src/providers/pubsub.ts index 6de173397..bf6f7cce3 100644 --- a/src/providers/pubsub.ts +++ b/src/providers/pubsub.ts @@ -40,13 +40,13 @@ export const service = 'pubsub.googleapis.com'; * @param topic Name of Pub/Sub topic, must belong to the same project as the function. */ export function topic(topic: string) { - return _topicWithOpts(topic, {}); + return _topicWithOptions(topic, {}); } /** @internal */ -export function _topicWithOpts( +export function _topicWithOptions( topic: string, - opts: DeploymentOptions + options: DeploymentOptions ): TopicBuilder { if (topic.indexOf('/') !== -1) { throw new Error('Topic name may not have a /'); @@ -57,28 +57,28 @@ export function _topicWithOpts( throw new Error('process.env.GCLOUD_PROJECT is not set.'); } return `projects/${process.env.GCLOUD_PROJECT}/topics/${topic}`; - }, opts); + }, options); } export function schedule(schedule: string): ScheduleBuilder { - return _scheduleWithOpts(schedule, {}); + return _scheduleWithOptions(schedule, {}); } export class ScheduleBuilder { - private _opts: DeploymentOptions; + private _options: DeploymentOptions; /** @internal */ - constructor(private schedule: Schedule, private opts: DeploymentOptions) { - this._opts = { schedule, ...opts }; + constructor(private schedule: Schedule, private options: DeploymentOptions) { + this._options = { schedule, ...options }; } retryConfig(config: ScheduleRetryConfig): ScheduleBuilder { - this._opts.schedule.retryConfig = config; + this._options.schedule.retryConfig = config; return this; } timeZone(timeZone: string): ScheduleBuilder { - this._opts.schedule.timeZone = timeZone; + this._options.schedule.timeZone = timeZone; return this; } @@ -95,7 +95,7 @@ export class ScheduleBuilder { service, triggerResource, eventType: 'topic.publish', - opts: this._opts, + options: this._options, labels: { 'deployment-scheduled': 'true' }, }); return cloudFunction; @@ -103,11 +103,11 @@ export class ScheduleBuilder { } /** @internal */ -export function _scheduleWithOpts( +export function _scheduleWithOptions( schedule: string, - opts: DeploymentOptions + options: DeploymentOptions ): ScheduleBuilder { - return new ScheduleBuilder({ schedule }, opts); + return new ScheduleBuilder({ schedule }, options); } /** Builder used to create Cloud Functions for Google Pub/Sub topics. */ @@ -115,7 +115,7 @@ export class TopicBuilder { /** @internal */ constructor( private triggerResource: () => string, - private opts: DeploymentOptions + private options: DeploymentOptions ) {} /** Handle a Pub/Sub message that was published to a Cloud Pub/Sub topic */ @@ -129,7 +129,7 @@ export class TopicBuilder { triggerResource: this.triggerResource, eventType: 'topic.publish', dataConstructor: (raw) => new Message(raw.data), - opts: this.opts, + options: this.options, }); } } diff --git a/src/providers/remoteConfig.ts b/src/providers/remoteConfig.ts index 60d5345d6..91903a58f 100644 --- a/src/providers/remoteConfig.ts +++ b/src/providers/remoteConfig.ts @@ -46,16 +46,16 @@ export function onUpdate( context: EventContext ) => PromiseLike | any ): CloudFunction { - return _onUpdateWithOpts(handler, {}); + return _onUpdateWithOptions(handler, {}); } /** @internal */ -export function _onUpdateWithOpts( +export function _onUpdateWithOptions( handler: ( version: TemplateVersion, context: EventContext ) => PromiseLike | any, - opts: DeploymentOptions + options: DeploymentOptions ): CloudFunction { const triggerResource = () => { if (!process.env.GCLOUD_PROJECT) { @@ -63,7 +63,7 @@ export function _onUpdateWithOpts( } return `projects/${process.env.GCLOUD_PROJECT}`; }; - return new UpdateBuilder(triggerResource, opts).onUpdate(handler); + return new UpdateBuilder(triggerResource, options).onUpdate(handler); } /** Builder used to create Cloud Functions for Remote Config. */ @@ -71,7 +71,7 @@ export class UpdateBuilder { /** @internal */ constructor( private triggerResource: () => string, - private opts: DeploymentOptions + private options: DeploymentOptions ) {} /** @@ -92,7 +92,7 @@ export class UpdateBuilder { service, triggerResource: this.triggerResource, eventType: 'update', - opts: this.opts, + options: this.options, }); } } diff --git a/src/providers/storage.ts b/src/providers/storage.ts index 436ffdce6..2fc705776 100644 --- a/src/providers/storage.ts +++ b/src/providers/storage.ts @@ -40,19 +40,19 @@ export const service = 'storage.googleapis.com'; * @param bucket Name of the Google Cloud Storage bucket to listen to. */ export function bucket(bucket?: string) { - return _bucketWithOpts({}, bucket); + return _bucketWithOptions({}, bucket); } /** * Handle events related to Cloud Storage objects. */ export function object() { - return _objectWithOpts({}); + return _objectWithOptions({}); } /** @internal */ -export function _bucketWithOpts( - opts: DeploymentOptions, +export function _bucketWithOptions( + options: DeploymentOptions, bucket?: string ): BucketBuilder { const resourceGetter = () => { @@ -68,24 +68,24 @@ export function _bucketWithOpts( } return `projects/_/buckets/${bucket}`; }; - return new BucketBuilder(resourceGetter, opts); + return new BucketBuilder(resourceGetter, options); } /** @internal */ -export function _objectWithOpts(opts: DeploymentOptions): ObjectBuilder { - return _bucketWithOpts(opts).object(); +export function _objectWithOptions(options: DeploymentOptions): ObjectBuilder { + return _bucketWithOptions(options).object(); } export class BucketBuilder { /** @internal */ constructor( private triggerResource: () => string, - private opts: DeploymentOptions + private options: DeploymentOptions ) {} /** Handle events for objects in this bucket. */ object() { - return new ObjectBuilder(this.triggerResource, this.opts); + return new ObjectBuilder(this.triggerResource, this.options); } } @@ -93,7 +93,7 @@ export class ObjectBuilder { /** @internal */ constructor( private triggerResource: () => string, - private opts: DeploymentOptions + private options: DeploymentOptions ) {} /** @internal */ @@ -157,7 +157,7 @@ export class ObjectBuilder { service, eventType, triggerResource: this.triggerResource, - opts: this.opts, + options: this.options, }); } } From bff5bf7c7473ce050087953ac7dbb462002a0e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Thu, 4 Jul 2019 00:11:41 +0200 Subject: [PATCH 053/437] Update TypeScript in integration tests (#498) --- integration_test/package.node10.json | 4 ++-- integration_test/package.node8.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/integration_test/package.node10.json b/integration_test/package.node10.json index b90ff8272..f45bab456 100644 --- a/integration_test/package.node10.json +++ b/integration_test/package.node10.json @@ -14,10 +14,10 @@ }, "main": "lib/index.js", "devDependencies": { - "typescript": "~3.1.0" + "typescript": "~3.5.0" }, "engines": { "node": "10" }, "private": true -} \ No newline at end of file +} diff --git a/integration_test/package.node8.json b/integration_test/package.node8.json index 31a46b8aa..777c11a89 100644 --- a/integration_test/package.node8.json +++ b/integration_test/package.node8.json @@ -14,10 +14,10 @@ }, "main": "lib/index.js", "devDependencies": { - "typescript": "~3.1.0" + "typescript": "~3.5.0" }, "engines": { "node": "8" }, "private": true -} \ No newline at end of file +} From 8e308fe85398637987b06a13a209df0068d00fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Thu, 4 Jul 2019 00:28:01 +0200 Subject: [PATCH 054/437] Reformat comments (#496) * Avoid abbreviations to improve readability: opts -> options * Reformat comments to be more consistent and readable --- src/cloud-functions.ts | 182 +++++++++++++++++++++++++--------------- src/function-builder.ts | 93 ++++++++++++-------- src/providers/https.ts | 5 +- 3 files changed, 170 insertions(+), 110 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 2560bc824..baec346f4 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -28,7 +28,8 @@ export { Request, Response }; const WILDCARD_REGEX = new RegExp('{[^/{}]*}', 'g'); -/** Wire format for an event +/** + * Wire format for an event. * @internal */ export interface Event { @@ -41,56 +42,78 @@ export interface Event { data: any; } -/** The context in which an event occurred. +/** + * The context in which an event occurred. + * * An EventContext describes: * - The time an event occurred. * - A unique identifier of the event. * - The resource on which the event occurred, if applicable. - * - Authorization of the request that triggered the event, if applicable and available. + * - Authorization of the request that triggered the event, if applicable and + * available. */ export interface EventContext { - /** ID of the event */ + /** + * Firebase auth variable for the user whose action triggered the function. + * Field will be null for unauthenticated users, and will not exist for admin + * users. Only available for database functions. + */ + auth?: { + token: object; + uid: string; + }; + /** + * Type of authentication for the triggering action. Only available for + * database functions. + */ + authType?: 'ADMIN' | 'USER' | 'UNAUTHENTICATED'; + /** + * ID of the event + */ eventId: string; - /** Timestamp for when the event occured (ISO string) */ - timestamp: string; - /** Type of event */ + /** + * Type of event + */ eventType: string; - /** Resource that triggered the event */ - resource: Resource; - /** Key-value pairs that represent the values of wildcards in a database reference - * Cannot be accessed while inside the handler namespace. + /** + * Key-value pairs that represent the values of wildcards in a database + * reference. Cannot be accessed while inside the handler namespace. */ params: { [option: string]: any }; - /** Type of authentication for the triggering action, valid value are: 'ADMIN', 'USER', - * 'UNAUTHENTICATED'. Only available for database functions. + /** + * Resource that triggered the event */ - authType?: 'ADMIN' | 'USER' | 'UNAUTHENTICATED'; - /** Firebase auth variable for the user whose action triggered the function. Field will be - * null for unauthenticated users, and will not exist for admin users. Only available - * for database functions. + resource: Resource; + /** + * Timestamp for when the event ocurred (ISO 8601 string) */ - auth?: { - uid: string; - token: object; - }; + timestamp: string; } -/** Change describes a change of state - "before" represents the state prior - * to the event, "after" represents the state after the event. +/** + * Change describes a change of state - "before" represents the state prior to + * the event, "after" represents the state after the event. */ export class Change { constructor(public before: T, public after: T) {} } -/** ChangeJson is the JSON format used to construct a Change object. */ +/** + * ChangeJson is the JSON format used to construct a Change object. + */ export interface ChangeJson { - /** Key-value pairs representing state of data before the change. - * If `fieldMask` is set, then only fields that changed are present in `before`. + /** + * Key-value pairs representing state of data after the change. */ - before?: any; - /** Key-value pairs representing state of data after the change. */ after?: any; - /** Comma-separated string that represents names of field that changed. */ + /** + * Key-value pairs representing state of data before the change. If + * `fieldMask` is set, then only fields that changed are present in `before`. + */ + before?: any; + /** + * Comma-separated string that represents names of field that changed. + */ fieldMask?: string; } @@ -99,13 +122,17 @@ export namespace Change { return x as T; } - /** Factory method for creating a Change from a `before` object and an `after` object. */ + /** + * Factory method for creating a Change from a `before` object and an `after` + * object. + */ export function fromObjects(before: T, after: T) { return new Change(before, after); } - /** Factory method for creating a Change from a JSON and an optional customizer function to be - * applied to both the `before` and the `after` fields. + /** + * Factory method for creating a Change from a JSON and an optional customizer + * function to be applied to both the `before` and the `after` fields. */ export function fromJSON( json: ChangeJson, @@ -121,7 +148,9 @@ export namespace Change { ); } - /** @internal */ + /** + * @internal + */ export function applyFieldMask( sparseBefore: any, after: any, @@ -141,8 +170,10 @@ export namespace Change { } } -/** Resource is a standard format for defining a resource (google.rpc.context.AttributeContext.Resource). - * In Cloud Functions, it is the resource that triggered the function - such as a storage bucket. +/** + * Resource is a standard format for defining a resource + * (google.rpc.context.AttributeContext.Resource). In Cloud Functions, it is the + * resource that triggered the function - such as a storage bucket. */ export interface Resource { service: string; @@ -151,83 +182,96 @@ export interface Resource { labels?: { [tag: string]: string }; } -/** TriggerAnnotated is used internally by the firebase CLI to understand what type of Cloud Function to deploy. */ +/** + * TriggerAnnotated is used internally by the firebase CLI to understand what + * type of Cloud Function to deploy. + */ export interface TriggerAnnotated { __trigger: { - httpsTrigger?: {}; + availableMemoryMb?: number; eventTrigger?: { eventType: string; resource: string; service: string; }; + httpsTrigger?: {}; labels?: { [key: string]: string }; regions?: string[]; - timeout?: string; - availableMemoryMb?: number; schedule?: Schedule; + timeout?: string; }; } -/** A Runnable has a `run` method which directly invokes the user-defined function - useful for unit testing. */ +/** + * A Runnable has a `run` method which directly invokes the user-defined + * function - useful for unit testing. + */ export interface Runnable { run: (data: T, context: any) => PromiseLike | any; } /** - * An HttpsFunction is both an object that exports its trigger definitions at __trigger and - * can be called as a function that takes an express.js Request and Response object. + * An HttpsFunction is both an object that exports its trigger definitions at + * __trigger and can be called as a function that takes an express.js Request + * and Response object. */ export type HttpsFunction = TriggerAnnotated & ((req: Request, resp: Response) => void); /** - * A CloudFunction is both an object that exports its trigger definitions at __trigger and - * can be called as a function using the raw JS API for Google Cloud Functions. + * A CloudFunction is both an object that exports its trigger definitions at + * __trigger and can be called as a function using the raw JS API for Google + * Cloud Functions. */ export type CloudFunction = Runnable & TriggerAnnotated & ((input: any, context?: any) => PromiseLike | any); -/** @internal */ +/** + * @internal + */ export interface MakeCloudFunctionArgs { - // TODO should remove `provider` and require a fully qualified `eventType` - // once all providers have migrated to new format. - provider: string; - eventType: string; - triggerResource: () => string; - service: string; + after?: (raw: Event) => void; + before?: (raw: Event) => void; + contextOnlyHandler?: (context: EventContext) => PromiseLike | any; dataConstructor?: (raw: Event) => EventData; + eventType: string; handler?: (data: EventData, context: EventContext) => PromiseLike | any; - contextOnlyHandler?: (context: EventContext) => PromiseLike | any; - before?: (raw: Event) => void; - after?: (raw: Event) => void; + labels?: { [key: string]: any }; legacyEventType?: string; options?: { [key: string]: any }; - labels?: { [key: string]: any }; + /* + * TODO: should remove `provider` and require a fully qualified `eventType` + * once all providers have migrated to new format. + */ + provider: string; + service: string; + triggerResource: () => string; } -/** @internal */ +/** + * @internal + */ export function makeCloudFunction({ - provider, - eventType, - triggerResource, - service, + after = () => {}, + before = () => {}, + contextOnlyHandler, dataConstructor = (raw: Event) => raw.data, + eventType, handler, - contextOnlyHandler, - before = () => { - return; - }, - after = () => { - return; - }, + labels = {}, legacyEventType, options = {}, - labels = {}, + provider, + service, + triggerResource, }: MakeCloudFunctionArgs): CloudFunction { const cloudFunction: any = (data: any, context: any) => { if (legacyEventType && context.eventType === legacyEventType) { - // v1beta1 event flow has different format for context, transform them to new format. + /* + * v1beta1 event flow has different format for context, transform them to + * new format. + */ context.eventType = provider + '.' + eventType; context.resource = { service, diff --git a/src/function-builder.ts b/src/function-builder.ts index 0db51a974..bdf7f296f 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -88,7 +88,10 @@ function assertRegionsAreValid(regions: string[]): boolean { /** * Configure the regions that the function is deployed to. * @param regions One of more region strings. - * For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')` + * @example + * functions.region('us-east1') + * @example + * functions.region('us-east1', 'us-central1') */ export function region( ...regions: Array @@ -100,10 +103,11 @@ export function region( /** * Configure runtime options for the function. - * @param runtimeOptions Object with 2 optional fields: - * 1. `timeoutSeconds`: timeout for the function in seconds, possible values are 0 to 540 - * 2. `memory`: amount of memory to allocate to the function, - * possible values are: '128MB', '256MB', '512MB', '1GB', and '2GB'. + * @param runtimeOptions Object with three optional fields: + * 1. memory: amount of memory to allocate to the function, possible values + * are: '128MB', '256MB', '512MB', '1GB', and '2GB'. + * 2. timeoutSeconds: timeout for the function in seconds, possible values are + * 0 to 540. */ export function runWith(runtimeOptions: RuntimeOptions): FunctionBuilder { if (assertRuntimeOptionsValid(runtimeOptions)) { @@ -117,7 +121,10 @@ export class FunctionBuilder { /** * Configure the regions that the function is deployed to. * @param regions One or more region strings. - * For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')` + * @example + * functions.region('us-east1') + * @example + * functions.region('us-east1', 'us-central1') */ region(...regions: Array): FunctionBuilder { if (assertRegionsAreValid(regions)) { @@ -128,10 +135,11 @@ export class FunctionBuilder { /** * Configure runtime options for the function. - * @param runtimeOptions Object with 2 optional fields: - * 1. timeoutSeconds: timeout for the function in seconds, possible values are 0 to 540 - * 2. memory: amount of memory to allocate to the function, possible values are: - * '128MB', '256MB', '512MB', '1GB', and '2GB'. + * @param runtimeOptions Object with three optional fields: + * 1. memory: amount of memory to allocate to the function, possible values + * are: '128MB', '256MB', '512MB', '1GB', and '2GB'. + * 2. timeoutSeconds: timeout for the function in seconds, possible values are + * 0 to 540. */ runWith(runtimeOptions: RuntimeOptions): FunctionBuilder { if (assertRuntimeOptionsValid(runtimeOptions)) { @@ -166,33 +174,36 @@ export class FunctionBuilder { get database() { return { /** - * Selects a database instance that will trigger the function. - * If omitted, will pick the default database for your project. + * Selects a database instance that will trigger the function. If omitted, + * will pick the default database for your project. * @param instance The Realtime Database instance to use. */ instance: (instance: string) => database._instanceWithOptions(instance, this.options), + /** * Select Firebase Realtime Database Reference to listen to. * - * This method behaves very similarly to the method of the same name in the - * client and Admin Firebase SDKs. Any change to the Database that affects the - * data at or below the provided `path` will fire an event in Cloud Functions. + * This method behaves very similarly to the method of the same name in + * the client and Admin Firebase SDKs. Any change to the Database that + * affects the data at or below the provided `path` will fire an event in + * Cloud Functions. * * There are three important differences between listening to a Realtime - * Database event in Cloud Functions and using the Realtime Database in the - * client and Admin SDKs: - * 1. Cloud Functions allows wildcards in the `path` name. Any `path` component - * in curly brackets (`{}`) is a wildcard that matches all strings. The value - * that matched a certain invocation of a Cloud Function is returned as part - * of the `context.params` object. For example, `ref("messages/{messageId}")` - * matches changes at `/messages/message1` or `/messages/message2`, resulting - * in `context.params.messageId` being set to `"message1"` or `"message2"`, - * respectively. - * 2. Cloud Functions do not fire an event for data that already existed before - * the Cloud Function was deployed. - * 3. Cloud Function events have access to more information, including information - * about the user who triggered the Cloud Function. + * Database event in Cloud Functions and using the Realtime Database in + * the client and Admin SDKs: + * 1. Cloud Functions allows wildcards in the `path` name. Any `path` + * component in curly brackets (`{}`) is a wildcard that matches all + * strings. The value that matched a certain invocation of a Cloud + * Function is returned as part of the `context.params` object. For + * example, `ref("messages/{messageId}")` matches changes at + * `/messages/message1` or `/messages/message2`, resulting in + * `context.params.messageId` being set to `"message1"` or + * `"message2"`, respectively. + * 2. Cloud Functions do not fire an event for data that already existed + * before the Cloud Function was deployed. + * 3. Cloud Function events have access to more information, including + * information about the user who triggered the Cloud Function. * @param ref Path of the database to listen to. */ ref: (path: string) => database._refWithOptions(path, this.options), @@ -210,10 +221,16 @@ export class FunctionBuilder { */ document: (path: string) => firestore._documentWithOptions(path, this.options), - /** @internal */ + + /** + * @internal + */ namespace: (namespace: string) => firestore._namespaceWithOptions(namespace, this.options), - /** @internal */ + + /** + * @internal + */ database: (database: string) => firestore._databaseWithOptions(database, this.options), }; @@ -222,8 +239,8 @@ export class FunctionBuilder { get crashlytics() { return { /** - * Handle events related to Crashlytics issues. An issue in Crashlytics is an - * aggregation of crashes which have a shared root cause. + * Handle events related to Crashlytics issues. An issue in Crashlytics is + * an aggregation of crashes which have a shared root cause. */ issue: () => crashlytics._issueWithOptions(this.options), }; @@ -264,9 +281,9 @@ export class FunctionBuilder { get storage() { return { /** - * The optional bucket function allows you to choose which buckets' events to handle. - * This step can be bypassed by calling object() directly, which will use the default - * Cloud Storage for Firebase bucket. + * The optional bucket function allows you to choose which buckets' events + * to handle. This step can be bypassed by calling object() directly, + * which will use the default Cloud Storage for Firebase bucket. * @param bucket Name of the Google Cloud Storage bucket to listen to. */ bucket: (bucket?: string) => @@ -281,8 +298,10 @@ export class FunctionBuilder { get pubsub() { return { - /** Select Cloud Pub/Sub topic to listen to. - * @param topic Name of Pub/Sub topic, must belong to the same project as the function. + /** + * Select Cloud Pub/Sub topic to listen to. + * @param topic Name of Pub/Sub topic, must belong to the same project as + * the function. */ topic: (topic: string) => pubsub._topicWithOptions(topic, this.options), schedule: (schedule: string) => diff --git a/src/providers/https.ts b/src/providers/https.ts index 0a82de012..8c25ba82a 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -28,13 +28,10 @@ import { apps } from '../apps'; import { HttpsFunction, optionsToTrigger, Runnable } from '../cloud-functions'; import { DeploymentOptions } from '../function-configuration'; -/** - * - * - */ export interface Request extends express.Request { rawBody: Buffer; } + /** * Handle HTTP requests. * @param handler A function that takes a request and response object, From e3cff3a2b23711c8a9f66e4b9f435278c48de61f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Thu, 4 Jul 2019 01:46:20 +0200 Subject: [PATCH 055/437] Support running integration tests in directories with special characters (#499) * Support running integration tests in directories with special characters * Remove unnecessary spaces --- integration_test/run_tests.sh | 54 ++++++++++++++++------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index ec5de6b74..911fc1dd4 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -4,7 +4,7 @@ set -e function usage { - echo "Usage: $0 []" + echo "Usage: ${0} []" exit 1 } @@ -15,16 +15,12 @@ function usage { # ./integration_test/run_tests.sh chenky-test-proj # Example usage (from root dir) with token: # ./integration_test/run_tests.sh chenky-test-proj $TOKEN -if [[ $1 == "" ]]; then +if [[ "${1}" == "" ]]; then usage fi -TOKEN="" -if [[ $2 != "" ]]; then - TOKEN=$2 -fi - -PROJECT_ID=$1 +PROJECT_ID="${1}" +TOKEN="${2}" # Directory where this script lives. DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" @@ -35,35 +31,35 @@ function announce { function build_sdk { announce "Building SDK..." - cd $DIR/.. + cd "${DIR}/.." rm -f firebase-functions-*.tgz npm run build:pack mv firebase-functions-*.tgz integration_test/functions/firebase-functions.tgz } function pick_node8 { - cd $DIR + cd "${DIR}" cp package.node8.json functions/package.json } function pick_node10 { - cd $DIR + cd "${DIR}" cp package.node10.json functions/package.json } function install_deps { announce "Installing dependencies..." - cd $DIR/functions + cd "${DIR}/functions" rm -rf node_modules/firebase-functions npm install } function delete_all_functions { announce "Deleting all functions in project..." - cd $DIR + cd "${DIR}" # Try to delete, if there are errors it is because the project is already empty, - # in that case do nothing. - if [[ $TOKEN == "" ]]; then + # in that case do nothing. + if [[ "${TOKEN}" == "" ]]; then firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID || : & else firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID --token=$TOKEN || : & @@ -73,40 +69,40 @@ function delete_all_functions { } function deploy { - cd $DIR + cd "${DIR}" ./functions/node_modules/.bin/tsc -p functions/ # Deploy functions, and security rules for database and Firestore. If the deploy fails, retry twice - if [[ $TOKEN == "" ]]; then - for i in 1 2 3; do firebase deploy --project=$PROJECT_ID --only functions,database,firestore && break; done + if [[ "${TOKEN}" == "" ]]; then + for i in 1 2 3; do firebase deploy --project="${PROJECT_ID}" --only functions,database,firestore && break; done else - for i in 1 2 3; do firebase deploy --project=$PROJECT_ID --token=$TOKEN --only functions,database,firestore && break; done + for i in 1 2 3; do firebase deploy --project="${PROJECT_ID}" --token="${TOKEN}" --only functions,database,firestore && break; done fi } function run_tests { - announce "Running the integration tests..." + announce "Running integration tests..." # Construct the URL for the test function. This may change in the future, # causing this script to start failing, but currently we don't have a very # reliable way of determining the URL dynamically. TEST_DOMAIN="cloudfunctions.net" - if [[ $FIREBASE_FUNCTIONS_URL == "https://preprod-cloudfunctions.sandbox.googleapis.com" ]]; then + if [[ "${FIREBASE_FUNCTIONS_URL}" == "https://preprod-cloudfunctions.sandbox.googleapis.com" ]]; then TEST_DOMAIN="txcloud.net" fi - TEST_URL="https://us-central1-$PROJECT_ID.$TEST_DOMAIN/integrationTests" - echo $TEST_URL + TEST_URL="https://us-central1-${PROJECT_ID}.${TEST_DOMAIN}/integrationTests" + echo "${TEST_URL}" - curl --fail $TEST_URL + curl --fail "${TEST_URL}" } function cleanup { announce "Performing cleanup..." delete_all_functions - rm $DIR/functions/firebase-functions.tgz - rm $DIR/functions/package.json - rm -f $DIR/functions/firebase-debug.log - rm -rf $DIR/functions/lib - rm -rf $DIR/functions/node_modules + rm "${DIR}/functions/firebase-functions.tgz" + rm "${DIR}/functions/package.json" + rm -f "${DIR}/functions/firebase-debug.log" + rm -rf "${DIR}/functions/lib" + rm -rf "${DIR}/functions/node_modules" } # Setup From d6a6a6b90e32ac9028ffa8ad16cfe086e3c9c28b Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Mon, 8 Jul 2019 22:36:01 +0000 Subject: [PATCH 056/437] [firebase-release] Updated SDK for Cloud Functions to 3.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 75686f91b..969f64585 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.0.2", + "version": "3.1.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From fa950f74ff21085e53c623c4156f3792449d7646 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Mon, 8 Jul 2019 22:36:19 +0000 Subject: [PATCH 057/437] [firebase-release] Removed change log and reset repo after 3.1.0 release --- changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 1385ce18b..e69de29bb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1 +0,0 @@ -feature - Adds region support for us-east4. \ No newline at end of file From e7c9c6a97b0a5ca09af92e2d8e6801f09f51aaaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Wed, 10 Jul 2019 01:23:05 +0200 Subject: [PATCH 058/437] Remove unused file (#502) --- spec/index.spec.ts | 1 - spec/testing.spec.ts | 32 -------------------------------- src/testing.ts | 29 ----------------------------- testing/README.md | 12 ------------ testing/package.json | 9 --------- tsconfig.release.json | 2 +- 6 files changed, 1 insertion(+), 84 deletions(-) delete mode 100644 spec/testing.spec.ts delete mode 100644 src/testing.ts delete mode 100644 testing/README.md delete mode 100644 testing/package.json diff --git a/spec/index.spec.ts b/spec/index.spec.ts index 6d56878da..64ffdf62e 100644 --- a/spec/index.spec.ts +++ b/spec/index.spec.ts @@ -43,5 +43,4 @@ import './providers/pubsub.spec'; import './providers/remoteConfig.spec'; import './providers/storage.spec'; import './setup.spec'; -import './testing.spec'; import './utils.spec'; diff --git a/spec/testing.spec.ts b/spec/testing.spec.ts deleted file mode 100644 index e4bc73d55..000000000 --- a/spec/testing.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2017 Firebase -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import { expect } from 'chai'; - -import * as testing from '../src/testing'; - -// TODO(rjh): As actual testing methods become available, replace this with actual tests. -describe('testing', () => { - it('should be accessible through the entrypoint', () => { - expect(testing.whereAreTheBugs()).to.not.equal('Earth'); - }); -}); diff --git a/src/testing.ts b/src/testing.ts deleted file mode 100644 index b1b09e8e8..000000000 --- a/src/testing.ts +++ /dev/null @@ -1,29 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2017 Firebase -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -// This file is an entry point into the testing-only functionality of the -// Firebase Functions SDK. - -// TODO(rjh): provide actual testing functionality. -export function whereAreTheBugs(): string { - return 'Klendathu'; -} diff --git a/testing/README.md b/testing/README.md deleted file mode 100644 index aea2fe460..000000000 --- a/testing/README.md +++ /dev/null @@ -1,12 +0,0 @@ -### Cloud Functions for Firebase testing utilities - -This module is the access point for testing-only utilities for Cloud Functions -for Firebase. We discourage relying on code in this module for your production -application, but encourage using the utilities found here to write effective -unit tests. - -This testing module can be accessed with the following import: - -`import * as testing from 'firebase-functions/testing';` - -TODO(rjh): document testing utilities included here. diff --git a/testing/package.json b/testing/package.json deleted file mode 100644 index d9080a89c..000000000 --- a/testing/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "firebase-functions-testing", - "version": "0.4.1", - "description": "Node helpers for Firebase Functions unit testing. See ../package.json for more information", - "main": "../lib/testing.js", - "author": "Firebase Team", - "license": "MIT", - "typings": "../lib/testing.d.ts" -} diff --git a/tsconfig.release.json b/tsconfig.release.json index 58d0d8e8f..2ec3fe56b 100644 --- a/tsconfig.release.json +++ b/tsconfig.release.json @@ -9,5 +9,5 @@ "target": "es2017", "typeRoots": ["node_modules/@types"] }, - "files": ["src/index.ts", "src/testing.ts"] + "files": ["src/index.ts"] } From 8150b3437c5f6eb2fcd7afea179e09bcc0dada29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Wed, 10 Jul 2019 02:10:06 +0200 Subject: [PATCH 059/437] Simplify TypeScript configuration (#504) --- tsconfig.json | 8 ++------ tsconfig.release.json | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index b99abf115..0992f6d5b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,8 @@ { "compilerOptions": { - "lib": ["es2017"], - "module": "commonjs", - "noImplicitAny": false, - "outDir": ".tmp", "resolveJsonModule": true, "sourceMap": true, - "target": "es2017" }, - "include": ["src/**/*.ts", "spec/**/*.ts"] + "extends": "./tsconfig.release.json", + "include": ["./src/**/*.ts", "./spec/**/*.ts"] } diff --git a/tsconfig.release.json b/tsconfig.release.json index 2ec3fe56b..2ae872506 100644 --- a/tsconfig.release.json +++ b/tsconfig.release.json @@ -7,7 +7,7 @@ "outDir": "lib", "stripInternal": true, "target": "es2017", - "typeRoots": ["node_modules/@types"] + "typeRoots": ["./node_modules/@types"] }, - "files": ["src/index.ts"] + "files": ["./src/index.ts"] } From fae19c9255db55ac3330adceafa440a09f067f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Wed, 10 Jul 2019 02:38:37 +0200 Subject: [PATCH 060/437] Remove redundant fields from ScheduleBuilder (#505) --- src/providers/pubsub.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/providers/pubsub.ts b/src/providers/pubsub.ts index bf6f7cce3..837594b3d 100644 --- a/src/providers/pubsub.ts +++ b/src/providers/pubsub.ts @@ -27,7 +27,6 @@ import { } from '../cloud-functions'; import { DeploymentOptions, - Schedule, ScheduleRetryConfig, } from '../function-configuration'; @@ -65,20 +64,16 @@ export function schedule(schedule: string): ScheduleBuilder { } export class ScheduleBuilder { - private _options: DeploymentOptions; - /** @internal */ - constructor(private schedule: Schedule, private options: DeploymentOptions) { - this._options = { schedule, ...options }; - } + constructor(private options: DeploymentOptions) {} retryConfig(config: ScheduleRetryConfig): ScheduleBuilder { - this._options.schedule.retryConfig = config; + this.options.schedule.retryConfig = config; return this; } timeZone(timeZone: string): ScheduleBuilder { - this._options.schedule.timeZone = timeZone; + this.options.schedule.timeZone = timeZone; return this; } @@ -95,7 +90,7 @@ export class ScheduleBuilder { service, triggerResource, eventType: 'topic.publish', - options: this._options, + options: this.options, labels: { 'deployment-scheduled': 'true' }, }); return cloudFunction; @@ -107,7 +102,7 @@ export function _scheduleWithOptions( schedule: string, options: DeploymentOptions ): ScheduleBuilder { - return new ScheduleBuilder({ schedule }, options); + return new ScheduleBuilder({ ...options, schedule: { schedule } }); } /** Builder used to create Cloud Functions for Google Pub/Sub topics. */ From 9b1a0fd902645756022435ee7d730a124574be8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Wed, 10 Jul 2019 02:51:48 +0200 Subject: [PATCH 061/437] Enable and apply `noUnusedLocals` compiler option (#506) * Simplify TypeScript configuration * Remove redundant fields from ScheduleBuilder * Enable and apply compiler option --- spec/apps.spec.ts | 3 --- spec/providers/auth.spec.ts | 7 ------- spec/providers/remoteConfig.spec.ts | 19 ------------------- src/cloud-functions.ts | 1 - src/providers/remoteConfig.ts | 1 - tsconfig.release.json | 1 + 6 files changed, 1 insertion(+), 31 deletions(-) diff --git a/spec/apps.spec.ts b/spec/apps.spec.ts index 209a20b72..9fff98c71 100644 --- a/spec/apps.spec.ts +++ b/spec/apps.spec.ts @@ -29,12 +29,9 @@ import * as sinon from 'sinon'; describe('apps', () => { let apps: appsNamespace.Apps; - let claims; beforeEach(() => { apps = new appsNamespace.Apps(); - // mock claims intentionally contains dots, square brackets, and nested paths - claims = { token: { firebase: { identities: { 'google.com': ['111'] } } } }; }); afterEach(() => { diff --git a/spec/providers/auth.spec.ts b/spec/providers/auth.spec.ts index 4247272c5..769bc03bf 100644 --- a/spec/providers/auth.spec.ts +++ b/spec/providers/auth.spec.ts @@ -23,7 +23,6 @@ import { expect } from 'chai'; import * as firebase from 'firebase-admin'; -import { Resolver } from 'dns'; import { CloudFunction, Event, EventContext } from '../../src/cloud-functions'; import * as functions from '../../src/index'; import * as auth from '../../src/providers/auth'; @@ -102,15 +101,9 @@ describe('Auth Functions', () => { }); describe('#_dataConstructor', () => { - let cloudFunctionCreate: CloudFunction; let cloudFunctionDelete: CloudFunction; before(() => { - cloudFunctionCreate = auth - .user() - .onCreate( - (data: firebase.auth.UserRecord, context: EventContext) => data - ); cloudFunctionDelete = auth .user() .onDelete( diff --git a/spec/providers/remoteConfig.spec.ts b/spec/providers/remoteConfig.spec.ts index d6e553cf4..ef6d68572 100644 --- a/spec/providers/remoteConfig.spec.ts +++ b/spec/providers/remoteConfig.spec.ts @@ -46,25 +46,6 @@ describe('RemoteConfig Functions', () => { }; } - function makeEvent(data: any, context: { [key: string]: any }): Event { - context = context || {}; - return { - data, - context: _.merge( - { - eventId: '123', - timestamp: '2018-07-03T00:49:04.264Z', - eventType: 'google.firebase.remoteconfig.update', - resource: { - name: 'projects/project1', - service: 'service', - }, - }, - context - ), - }; - } - describe('#onUpdate', () => { function expectedTrigger(): TriggerAnnotated { return { diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index baec346f4..e74a23645 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -22,7 +22,6 @@ import { Request, Response } from 'express'; import * as _ from 'lodash'; -import { apps } from './apps'; import { DeploymentOptions, Schedule } from './function-configuration'; export { Request, Response }; diff --git a/src/providers/remoteConfig.ts b/src/providers/remoteConfig.ts index 91903a58f..a43ed9164 100644 --- a/src/providers/remoteConfig.ts +++ b/src/providers/remoteConfig.ts @@ -24,7 +24,6 @@ import * as _ from 'lodash'; import { CloudFunction, - Event, EventContext, makeCloudFunction, } from '../cloud-functions'; diff --git a/tsconfig.release.json b/tsconfig.release.json index 2ae872506..e93d5d4b6 100644 --- a/tsconfig.release.json +++ b/tsconfig.release.json @@ -4,6 +4,7 @@ "lib": ["es2017"], "module": "commonjs", "noImplicitAny": false, + "noUnusedLocals": true, "outDir": "lib", "stripInternal": true, "target": "es2017", From c78a7e4cc1d62ee6b544ad787fcea38ffbd03d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Wed, 10 Jul 2019 02:57:33 +0200 Subject: [PATCH 062/437] Specify files included in the package using opt-in strategy (#503) * Specify included files to minimise package size * Single line for files --- .npmignore | 17 ----------------- package.json | 1 + 2 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 .npmignore diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 4472a5eff..000000000 --- a/.npmignore +++ /dev/null @@ -1,17 +0,0 @@ -.tmp -coverage -.vscode -.idea -tsconfig.* -tslint.* -.travis.yml -.github - -# Don't include the raw typescript -src -spec -integration_test -# TODO(rjh) add back once testing isn't just a joke -testing -lib/testing.* -*.tgz diff --git a/package.json b/package.json index 969f64585..83f22009d 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ }, "license": "MIT", "author": "Firebase Team", + "files": ["lib"], "main": "lib/index.js", "types": "lib/index.d.ts", "scripts": { From 72a0824a3122c9e2c93b222e3358e264646eba9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Wed, 10 Jul 2019 03:02:40 +0200 Subject: [PATCH 063/437] Specify a type of DeploymentOptions.regions more precisely (#507) --- src/function-configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 92ef2d3ff..ec1695ccd 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -64,6 +64,6 @@ export interface RuntimeOptions { } export interface DeploymentOptions extends RuntimeOptions { - regions?: string[]; + regions?: Array; schedule?: Schedule; } From 2e642b7348982946eb7d7c25a8e604713888b86b Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Tue, 9 Jul 2019 18:17:48 -0700 Subject: [PATCH 064/437] Change @internal => @hidden (#511) * @internal => @hidden * single line @hidden * add * to some @hiddens --- src/apps.ts | 6 +++--- src/cloud-functions.ts | 14 ++++---------- src/config.ts | 4 ++-- src/function-builder.ts | 8 ++------ src/handler-builder.ts | 4 ++-- src/providers/analytics.ts | 16 ++++++++-------- src/providers/auth.ts | 8 ++++---- src/providers/crashlytics.ts | 10 +++++----- src/providers/database.ts | 14 +++++++------- src/providers/firestore.ts | 26 +++++++++++++------------- src/providers/https.ts | 14 +++++++------- src/providers/pubsub.ts | 12 ++++++------ src/providers/remoteConfig.ts | 8 ++++---- src/providers/storage.ts | 14 +++++++------- 14 files changed, 74 insertions(+), 84 deletions(-) diff --git a/src/apps.ts b/src/apps.ts index f1c30ebf6..43c8d705d 100644 --- a/src/apps.ts +++ b/src/apps.ts @@ -32,10 +32,10 @@ export function apps(): apps.Apps { } export namespace apps { - /** @internal */ + /** @hidden */ export const garbageCollectionInterval = 2 * 60 * 1000; - /** @internal */ + /** @hidden */ export function delay(delay: number) { return new Promise((resolve) => { setTimeout(resolve, delay); @@ -51,7 +51,7 @@ export namespace apps { variable?: any; } - /** @internal */ + /** @hidden */ export interface RefCounter { [appName: string]: number; } diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index e74a23645..686e9e31e 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -29,7 +29,7 @@ const WILDCARD_REGEX = new RegExp('{[^/{}]*}', 'g'); /** * Wire format for an event. - * @internal + * @hidden */ export interface Event { context: { @@ -147,9 +147,7 @@ export namespace Change { ); } - /** - * @internal - */ + /** @hidden */ export function applyFieldMask( sparseBefore: any, after: any, @@ -226,9 +224,7 @@ export type CloudFunction = Runnable & TriggerAnnotated & ((input: any, context?: any) => PromiseLike | any); -/** - * @internal - */ +/** @hidden */ export interface MakeCloudFunctionArgs { after?: (raw: Event) => void; before?: (raw: Event) => void; @@ -248,9 +244,7 @@ export interface MakeCloudFunctionArgs { triggerResource: () => string; } -/** - * @internal - */ +/** @hidden */ export function makeCloudFunction({ after = () => {}, before = () => {}, diff --git a/src/config.ts b/src/config.ts index 1547b7679..9823b3746 100644 --- a/src/config.ts +++ b/src/config.ts @@ -36,11 +36,11 @@ export namespace config { [key: string]: any; } - /** @internal */ + /** @hidden */ export let singleton: config.Config; } -/* @internal */ +/** @hidden */ export function firebaseConfig(): firebase.AppOptions | null { const env = process.env.FIREBASE_CONFIG; if (env) { diff --git a/src/function-builder.ts b/src/function-builder.ts index bdf7f296f..d39dbfbfc 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -222,15 +222,11 @@ export class FunctionBuilder { document: (path: string) => firestore._documentWithOptions(path, this.options), - /** - * @internal - */ + /** @hidden */ namespace: (namespace: string) => firestore._namespaceWithOptions(namespace, this.options), - /** - * @internal - */ + /** @hidden */ database: (database: string) => firestore._databaseWithOptions(database, this.options), }; diff --git a/src/handler-builder.ts b/src/handler-builder.ts index 20e15986d..d0e5ac9a7 100644 --- a/src/handler-builder.ts +++ b/src/handler-builder.ts @@ -119,11 +119,11 @@ export class HandlerBuilder { get document() { return new firestore.DocumentBuilder(() => null, {}); }, - /** @internal */ + /** @hidden */ get namespace() { return new firestore.DocumentBuilder(() => null, {}); }, - /** @internal */ + /** @hidden */ get database() { return new firestore.DocumentBuilder(() => null, {}); }, diff --git a/src/providers/analytics.ts b/src/providers/analytics.ts index 3cd5612c5..457307e46 100644 --- a/src/providers/analytics.ts +++ b/src/providers/analytics.ts @@ -30,9 +30,9 @@ import { } from '../cloud-functions'; import { DeploymentOptions } from '../function-configuration'; -/** @internal */ +/** @hidden */ export const provider = 'google.analytics'; -/** @internal */ +/** @hidden */ export const service = 'app-measurement.com'; /** @@ -43,7 +43,7 @@ export function event(analyticsEventType: string) { return _eventWithOptions(analyticsEventType, {}); } -/** @internal */ +/** @hidden */ export function _eventWithOptions( analyticsEventType: string, options: DeploymentOptions @@ -64,7 +64,7 @@ export function _eventWithOptions( * Access via [`functions.analytics.event()`](functions.analytics#event). */ export class AnalyticsEventBuilder { - /** @internal */ + /** @hidden */ constructor( private triggerResource: () => string, private options: DeploymentOptions @@ -136,7 +136,7 @@ export class AnalyticsEvent { /** User-related dimensions. */ user?: UserDimensions; - /** @internal */ + /** @hidden */ constructor(wireFormat: any) { this.params = {}; // In case of absent field, show empty (not absent) map. if (wireFormat.eventDim && wireFormat.eventDim.length > 0) { @@ -200,7 +200,7 @@ export class UserDimensions { /** Information regarding the bundle in which these events were uploaded. */ bundleInfo: ExportBundleInfo; - /** @internal */ + /** @hidden */ constructor(wireFormat: any) { // These are interfaces or primitives, no transformation needed. copyFields(wireFormat, this, [ @@ -242,7 +242,7 @@ export class UserPropertyValue { /** UTC client time when the user property was last set. */ setTime: string; - /** @internal */ + /** @hidden */ constructor(wireFormat: any) { copyField(wireFormat, this, 'value', unwrapValueAsString); copyTimestampToString(wireFormat, this, 'setTimestampUsec', 'setTime'); @@ -381,7 +381,7 @@ export class ExportBundleInfo { /** Timestamp offset (in milliseconds) between collection time and upload time. */ serverTimestampOffset: number; - /** @internal */ + /** @hidden */ constructor(wireFormat: any) { copyField(wireFormat, this, 'bundleSequenceId'); copyTimestampToMillis( diff --git a/src/providers/auth.ts b/src/providers/auth.ts index e775116d2..722410340 100644 --- a/src/providers/auth.ts +++ b/src/providers/auth.ts @@ -30,9 +30,9 @@ import { } from '../cloud-functions'; import { DeploymentOptions } from '../function-configuration'; -/** @internal */ +/** @hidden */ export const provider = 'google.firebase.auth'; -/** @internal */ +/** @hidden */ export const service = 'firebaseauth.googleapis.com'; /** @@ -42,7 +42,7 @@ export function user() { return _userWithOptions({}); } -/** @internal */ +/** @hidden */ export function _userWithOptions(options: DeploymentOptions) { return new UserBuilder(() => { if (!process.env.GCLOUD_PROJECT) { @@ -70,7 +70,7 @@ export class UserBuilder { return userRecordConstructor(raw.data); } - /** @internal */ + /** @hidden */ constructor( private triggerResource: () => string, private options?: DeploymentOptions diff --git a/src/providers/crashlytics.ts b/src/providers/crashlytics.ts index 751e868bd..89701d42f 100644 --- a/src/providers/crashlytics.ts +++ b/src/providers/crashlytics.ts @@ -27,9 +27,9 @@ import { } from '../cloud-functions'; import { DeploymentOptions } from '../function-configuration'; -/** @internal */ +/** @hidden */ export const provider = 'google.firebase.crashlytics'; -/** @internal */ +/** @hidden */ export const service = 'fabric.io'; /** @@ -40,7 +40,7 @@ export function issue() { return _issueWithOptions({}); } -/** @internal */ +/** @hidden */ export function _issueWithOptions(options: DeploymentOptions) { return new IssueBuilder(() => { if (!process.env.GCLOUD_PROJECT) { @@ -52,13 +52,13 @@ export function _issueWithOptions(options: DeploymentOptions) { /** Builder used to create Cloud Functions for Crashlytics issue events. */ export class IssueBuilder { - /** @internal */ + /** @hidden */ constructor( private triggerResource: () => string, private options: DeploymentOptions ) {} - /** @internal */ + /** @hidden */ onNewDetected(handler: any): Error { throw new Error('"onNewDetected" is now deprecated, please use "onNew"'); } diff --git a/src/providers/database.ts b/src/providers/database.ts index 4fb28a2bd..7740a4373 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -34,9 +34,9 @@ import { firebaseConfig } from '../config'; import { DeploymentOptions } from '../function-configuration'; import { applyChange, joinPath, normalizePath, pathParts } from '../utils'; -/** @internal */ +/** @hidden */ export const provider = 'google.firebase.database'; -/** @internal */ +/** @hidden */ export const service = 'firebaseio.com'; // NOTE(inlined): Should we relax this a bit to allow staging or alternate implementations of our API? @@ -78,7 +78,7 @@ export function ref(path: string) { return _refWithOptions(path, {}); } -/** @internal */ +/** @hidden */ export function _instanceWithOptions( instance: string, options: DeploymentOptions @@ -87,7 +87,7 @@ export function _instanceWithOptions( } export class InstanceBuilder { - /* @internal */ + /** @hidden */ constructor(private instance: string, private options: DeploymentOptions) {} ref(path: string): RefBuilder { @@ -100,7 +100,7 @@ export class InstanceBuilder { } } -/** @internal */ +/** @hidden */ export function _refWithOptions( path: string, options: DeploymentOptions @@ -131,7 +131,7 @@ export function _refWithOptions( /** Builder used to create Cloud Functions for Firebase Realtime Database References. */ export class RefBuilder { - /** @internal */ + /** @hidden */ constructor( private apps: apps.Apps, private triggerResource: () => string, @@ -238,7 +238,7 @@ export class RefBuilder { } /* Utility function to extract database reference from resource string */ -/** @internal */ +/** @hidden */ export function resourceToInstanceAndPath(resource: string) { const resourceRegex = `projects/([^/]+)/instances/([a-zA-Z0-9\-^/]+)/refs(/.+)?`; const match = resource.match(new RegExp(resourceRegex)); diff --git a/src/providers/firestore.ts b/src/providers/firestore.ts index 923763225..291d7d7f9 100644 --- a/src/providers/firestore.ts +++ b/src/providers/firestore.ts @@ -34,11 +34,11 @@ import { import { dateToTimestampProto } from '../encoder'; import { DeploymentOptions } from '../function-configuration'; -/** @internal */ +/** @hidden */ export const provider = 'google.firestore'; -/** @internal */ +/** @hidden */ export const service = 'firestore.googleapis.com'; -/** @internal */ +/** @hidden */ export const defaultDatabase = '(default)'; let firestoreInstance: any; export type DocumentSnapshot = firebase.firestore.DocumentSnapshot; @@ -53,18 +53,18 @@ export type DocumentSnapshot = firebase.firestore.DocumentSnapshot; export function document(path: string) { return _documentWithOptions(path, {}); } -/** @internal */ +/** @hidden */ // Multiple namespaces are not yet supported by Firestore. export function namespace(namespace: string) { return _namespaceWithOptions(namespace, {}); } -/** @internal */ +/** @hidden */ // Multiple databases are not yet supported by Firestore. export function database(database: string) { return _databaseWithOptions(database, {}); } -/** @internal */ +/** @hidden */ export function _databaseWithOptions( database: string = defaultDatabase, options: DeploymentOptions @@ -72,7 +72,7 @@ export function _databaseWithOptions( return new DatabaseBuilder(database, options); } -/** @internal */ +/** @hidden */ export function _namespaceWithOptions( namespace: string, options: DeploymentOptions @@ -80,13 +80,13 @@ export function _namespaceWithOptions( return _databaseWithOptions(defaultDatabase, options).namespace(namespace); } -/** @internal */ +/** @hidden */ export function _documentWithOptions(path: string, options: DeploymentOptions) { return _databaseWithOptions(defaultDatabase, options).document(path); } export class DatabaseBuilder { - /** @internal */ + /** @hidden */ constructor(private database: string, private options: DeploymentOptions) {} namespace(namespace: string) { @@ -99,7 +99,7 @@ export class DatabaseBuilder { } export class NamespaceBuilder { - /** @internal */ + /** @hidden */ constructor( private database: string, private options: DeploymentOptions, @@ -144,7 +144,7 @@ function _getValueProto(data: any, resource: string, valueFieldName: string) { return proto; } -/** @internal */ +/** @hidden */ export function snapshotConstructor(event: Event): DocumentSnapshot { if (!firestoreInstance) { firestoreInstance = firebase.firestore(apps().admin); @@ -158,7 +158,7 @@ export function snapshotConstructor(event: Event): DocumentSnapshot { return firestoreInstance.snapshot_(valueProto, readTime, 'json'); } -/** @internal */ +/** @hidden */ // TODO remove this function when wire format changes to new format export function beforeSnapshotConstructor(event: Event): DocumentSnapshot { if (!firestoreInstance) { @@ -183,7 +183,7 @@ function changeConstructor(raw: Event) { } export class DocumentBuilder { - /** @internal */ + /** @hidden */ constructor( private triggerResource: () => string, private options: DeploymentOptions diff --git a/src/providers/https.ts b/src/providers/https.ts index 8c25ba82a..e6c8a63f4 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -53,7 +53,7 @@ export function onCall( return _onCallWithOptions(handler, {}); } -/** @internal */ +/** @hidden */ export function _onRequestWithOptions( handler: (req: Request, resp: express.Response) => void, options: DeploymentOptions @@ -182,7 +182,7 @@ export class HttpsError extends Error { } /** - * @internal + * @hidden * A string representation of the Google error code for this error for HTTP. */ get status() { @@ -190,7 +190,7 @@ export class HttpsError extends Error { } /** - * @internal + * @hidden * Returns the canonical http status code for the given error. */ get httpStatus(): number { @@ -235,7 +235,7 @@ export class HttpsError extends Error { } } - /** @internal */ + /** @hidden */ public toJSON() { const json: any = { status: this.status, @@ -334,7 +334,7 @@ const UNSIGNED_LONG_TYPE = 'type.googleapis.com/google.protobuf.UInt64Value'; * Encodes arbitrary data in our special format for JSON. * This is exposed only for testing. */ -/** @internal */ +/** @hidden */ export function encode(data: any): any { if (_.isNull(data) || _.isUndefined(data)) { return null; @@ -372,7 +372,7 @@ export function encode(data: any): any { * Decodes our special format for JSON into native types. * This is exposed only for testing. */ -/** @internal */ +/** @hidden */ export function decode(data: any): any { if (data === null) { return data; @@ -412,7 +412,7 @@ export function decode(data: any): any { const corsHandler = cors({ origin: true, methods: 'POST' }); -/** @internal */ +/** @hidden */ export function _onCallWithOptions( handler: (data: any, context: CallableContext) => any | Promise, options: DeploymentOptions diff --git a/src/providers/pubsub.ts b/src/providers/pubsub.ts index 837594b3d..265c39deb 100644 --- a/src/providers/pubsub.ts +++ b/src/providers/pubsub.ts @@ -30,9 +30,9 @@ import { ScheduleRetryConfig, } from '../function-configuration'; -/** @internal */ +/** @hidden */ export const provider = 'google.pubsub'; -/** @internal */ +/** @hidden */ export const service = 'pubsub.googleapis.com'; /** Select Cloud Pub/Sub topic to listen to. @@ -42,7 +42,7 @@ export function topic(topic: string) { return _topicWithOptions(topic, {}); } -/** @internal */ +/** @hidden */ export function _topicWithOptions( topic: string, options: DeploymentOptions @@ -64,7 +64,7 @@ export function schedule(schedule: string): ScheduleBuilder { } export class ScheduleBuilder { - /** @internal */ + /** @hidden */ constructor(private options: DeploymentOptions) {} retryConfig(config: ScheduleRetryConfig): ScheduleBuilder { @@ -97,7 +97,7 @@ export class ScheduleBuilder { } } -/** @internal */ +/** @hidden */ export function _scheduleWithOptions( schedule: string, options: DeploymentOptions @@ -107,7 +107,7 @@ export function _scheduleWithOptions( /** Builder used to create Cloud Functions for Google Pub/Sub topics. */ export class TopicBuilder { - /** @internal */ + /** @hidden */ constructor( private triggerResource: () => string, private options: DeploymentOptions diff --git a/src/providers/remoteConfig.ts b/src/providers/remoteConfig.ts index a43ed9164..cd32e5423 100644 --- a/src/providers/remoteConfig.ts +++ b/src/providers/remoteConfig.ts @@ -29,9 +29,9 @@ import { } from '../cloud-functions'; import { DeploymentOptions } from '../function-configuration'; -/** @internal */ +/** @hidden */ export const provider = 'google.firebase.remoteconfig'; -/** @internal */ +/** @hidden */ export const service = 'firebaseremoteconfig.googleapis.com'; /** @@ -48,7 +48,7 @@ export function onUpdate( return _onUpdateWithOptions(handler, {}); } -/** @internal */ +/** @hidden */ export function _onUpdateWithOptions( handler: ( version: TemplateVersion, @@ -67,7 +67,7 @@ export function _onUpdateWithOptions( /** Builder used to create Cloud Functions for Remote Config. */ export class UpdateBuilder { - /** @internal */ + /** @hidden */ constructor( private triggerResource: () => string, private options: DeploymentOptions diff --git a/src/providers/storage.ts b/src/providers/storage.ts index 2fc705776..93d6b0ed0 100644 --- a/src/providers/storage.ts +++ b/src/providers/storage.ts @@ -28,9 +28,9 @@ import { import { firebaseConfig } from '../config'; import { DeploymentOptions } from '../function-configuration'; -/** @internal */ +/** @hidden */ export const provider = 'google.storage'; -/** @internal */ +/** @hidden */ export const service = 'storage.googleapis.com'; /** @@ -50,7 +50,7 @@ export function object() { return _objectWithOptions({}); } -/** @internal */ +/** @hidden */ export function _bucketWithOptions( options: DeploymentOptions, bucket?: string @@ -71,13 +71,13 @@ export function _bucketWithOptions( return new BucketBuilder(resourceGetter, options); } -/** @internal */ +/** @hidden */ export function _objectWithOptions(options: DeploymentOptions): ObjectBuilder { return _bucketWithOptions(options).object(); } export class BucketBuilder { - /** @internal */ + /** @hidden */ constructor( private triggerResource: () => string, private options: DeploymentOptions @@ -90,13 +90,13 @@ export class BucketBuilder { } export class ObjectBuilder { - /** @internal */ + /** @hidden */ constructor( private triggerResource: () => string, private options: DeploymentOptions ) {} - /** @internal */ + /** @hidden */ onChange(handler: any): Error { throw new Error( '"onChange" is now deprecated, please use "onArchive", "onDelete", ' + From 6b0d23480fa4a4bb80ba3fe47017578849e727a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Wed, 10 Jul 2019 04:05:20 +0200 Subject: [PATCH 065/437] Move unexported dependencies on @types to devDependencies (#510) * Move unexported dependencies on @types to devDependencies * fix ordering in package.json * remove old change with files --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 83f22009d..fd83fbb0b 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,7 @@ "test": "mocha -r ts-node/register ./spec/index.spec.ts" }, "dependencies": { - "@types/cors": "^2.8.5", "@types/express": "^4.17.0", - "@types/jsonwebtoken": "^8.3.2", - "@types/lodash": "^4.14.135", "cors": "^2.8.5", "express": "^4.17.1", "jsonwebtoken": "^8.5.1", @@ -44,6 +41,9 @@ "devDependencies": { "@types/chai": "^4.1.7", "@types/chai-as-promised": "^7.1.0", + "@types/cors": "^2.8.5", + "@types/jsonwebtoken": "^8.3.2", + "@types/lodash": "^4.14.135", "@types/mocha": "^5.2.7", "@types/mock-require": "^2.0.0", "@types/nock": "^10.0.3", From 3f1cdd839d1630eeed3ba8fd563375c680e437da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Wed, 10 Jul 2019 08:33:00 +0200 Subject: [PATCH 066/437] Run Prettier for all supported file formats (#509) * Run Prettier for all supported file formats * Reformat all files * formatter fixes --- .github/CONTRIBUTING.md | 2 +- .github/ISSUE_TEMPLATE/---feature-request.md | 6 ++++-- .github/ISSUE_TEMPLATE/---report-a-bug.md | 11 +++++------ .github/PULL_REQUEST_TEMPLATE.md | 7 +++---- .prettierignore | 3 +++ integration_test/README.md | 6 +++--- integration_test/functions/tsconfig.json | 8 ++------ package.json | 4 ++-- tsconfig.json | 2 +- 9 files changed, 24 insertions(+), 25 deletions(-) create mode 100644 .prettierignore diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6ec97c9ef..b918352d7 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -21,4 +21,4 @@ All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help] for more information on using pull requests. -[GitHub Help]: https://help.github.com/articles/about-pull-requests/ \ No newline at end of file +[github help]: https://help.github.com/articles/about-pull-requests/ diff --git a/.github/ISSUE_TEMPLATE/---feature-request.md b/.github/ISSUE_TEMPLATE/---feature-request.md index d47fbda18..93f7d6de1 100644 --- a/.github/ISSUE_TEMPLATE/---feature-request.md +++ b/.github/ISSUE_TEMPLATE/---feature-request.md @@ -1,12 +1,14 @@ --- name: "\U0001F4A1 Feature Request" -about: Have a feature you'd like to see in the functions SDK? Request it through our +about: + Have a feature you'd like to see in the functions SDK? Request it through our support channel. title: '' labels: '' assignees: '' --- - diff --git a/.github/ISSUE_TEMPLATE/---report-a-bug.md b/.github/ISSUE_TEMPLATE/---report-a-bug.md index 4484df5fa..694c4aedf 100644 --- a/.github/ISSUE_TEMPLATE/---report-a-bug.md +++ b/.github/ISSUE_TEMPLATE/---report-a-bug.md @@ -1,12 +1,12 @@ --- -name: "⚠️ Report a Bug" +name: '⚠️ Report a Bug' about: Think you found a bug in the SDK? Report it here. title: '' labels: '' assignees: '' - --- - @@ -21,11 +21,13 @@ template_path=.github/ISSUE_TEMPLATE/---report-a-bug.md be fixed in the latest versions. --> **node:** + **firebase-functions:** **firebase-tools:** + **firebase-admin:** @@ -34,17 +36,14 @@ be fixed in the latest versions. --> - ### [REQUIRED] Steps to reproduce - ### [REQUIRED] Expected behavior - ### [REQUIRED] Actual behavior - ### Description \ No newline at end of file + diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..de457f940 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +lib +package.json +spec/fixtures/credential/unparsable.key.json diff --git a/integration_test/README.md b/integration_test/README.md index d0a1f3ef9..c6a0ca32d 100644 --- a/integration_test/README.md +++ b/integration_test/README.md @@ -1,13 +1,13 @@ -How to Use ---------- +## How to Use -***ATTENTION***: Running this test will wipe the contents of the Firebase project(s) you run it against. Make sure you use disposable Firebase project(s)! +**_ATTENTION_**: Running this test will wipe the contents of the Firebase project(s) you run it against. Make sure you use disposable Firebase project(s)! Run the integration test as follows: ```bash ./run_tests.sh [] ``` + If just one project_id is provided, the both the node6 and node8 tests will be run on that project, in series. If two project_ids are provided, the node6 tests will be run on the first project and the node8 tests will be run on the second one, in parallel. The tests run fully automatically, and will print the result on standard out. The integration test for HTTPS is that it properly kicks off other integration tests and returns a result. From there the other integration test suites will write their results back to the database, where you can check the detailed results if you'd like. diff --git a/integration_test/functions/tsconfig.json b/integration_test/functions/tsconfig.json index a8e9362db..a5bd57033 100644 --- a/integration_test/functions/tsconfig.json +++ b/integration_test/functions/tsconfig.json @@ -6,11 +6,7 @@ "noImplicitAny": false, "outDir": "lib", "declaration": true, - "typeRoots": [ - "node_modules/@types" - ] + "typeRoots": ["node_modules/@types"] }, - "files": [ - "src/index.ts" - ] + "files": ["src/index.ts"] } diff --git a/package.json b/package.json index fd83fbb0b..916c4159f 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "build:pack": "rm -rf lib && npm install && tsc -p tsconfig.release.json && npm pack", "build:release": "npm install --production && npm install typescript firebase-admin && tsc -p tsconfig.release.json", "build": "tsc -p tsconfig.release.json", - "format": "prettier --check '**/*.ts'", - "format:fix": "prettier --write '**/*.ts'", + "format": "prettier --check '**/*.{json,md,ts,yml,yaml}'", + "format:fix": "prettier --write '**/*.{json,md,ts,yml,yaml}'", "lint": "tslint --config tslint.json --project tsconfig.json ", "lint:fix": "tslint --config tslint.json --fix --project tsconfig.json", "test": "mocha -r ts-node/register ./spec/index.spec.ts" diff --git a/tsconfig.json b/tsconfig.json index 0992f6d5b..2f7cc7e0d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "resolveJsonModule": true, - "sourceMap": true, + "sourceMap": true }, "extends": "./tsconfig.release.json", "include": ["./src/**/*.ts", "./spec/**/*.ts"] From 59adaa80fbb197d86f3f6bed9c577ab42b5107bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Thu, 11 Jul 2019 01:59:49 +0200 Subject: [PATCH 067/437] Remove unused valAt utility (#513) --- spec/utils.spec.ts | 23 +---------------------- src/utils.ts | 31 ------------------------------- 2 files changed, 1 insertion(+), 53 deletions(-) diff --git a/spec/utils.spec.ts b/spec/utils.spec.ts index 57634dd72..2ea64fb7e 100644 --- a/spec/utils.spec.ts +++ b/spec/utils.spec.ts @@ -21,7 +21,7 @@ // SOFTWARE. import { expect } from 'chai'; -import { applyChange, normalizePath, pathParts, valAt } from '../src/utils'; +import { applyChange, normalizePath, pathParts } from '../src/utils'; describe('utils', () => { describe('.normalizePath(path: string)', () => { @@ -42,27 +42,6 @@ describe('utils', () => { }); }); - describe('.valAt(source: any, path?: string): any', () => { - it('should be null if null along any point in the path', () => { - expect(valAt(null)).to.be.null; - expect(valAt(null, '/foo')).to.be.null; - expect(valAt({ a: { b: null } }, '/a/b/c')).to.be.null; - }); - - it('should be null if accessing a path past a leaf value', () => { - expect(valAt({ a: 2 }, '/a/b')).to.be.null; - }); - - it('should be the leaf value if one is present', () => { - expect(valAt({ a: { b: 23 } }, '/a/b')).to.eq(23); - expect(valAt({ a: { b: 23 } }, '/a')).to.deep.equal({ b: 23 }); - }); - - it('should be undefined if in unexplored territory', () => { - expect(valAt({ a: 23 }, '/b')).to.be.undefined; - }); - }); - describe('.applyChange(from: any, to: any): any', () => { it('should return the to value for non-object values of from and to', () => { expect(applyChange({ a: 'b' }, null)).to.eq(null); diff --git a/src/utils.ts b/src/utils.ts index 0ebbb38c4..ffec72a2d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -61,34 +61,3 @@ export function pruneNulls(obj: any) { } return obj; } - -export function valAt(source: any, path?: string) { - if (source === null) { - return null; - } else if (typeof source !== 'object') { - return path ? null : source; - } - - const parts = pathParts(path); - if (!parts.length) { - return source; - } - - let cur = source; - let leaf; - while (parts.length) { - const key = parts.shift(); - if (cur[key] === null || leaf) { - return null; - } else if (typeof cur[key] === 'object') { - if (parts.length) { - cur = cur[key]; - } else { - return cur[key]; - } - } else { - leaf = cur[key]; - } - } - return leaf; -} From f3af1759f0bba3c13a0fed9e14682aad76580dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Thu, 11 Jul 2019 02:05:28 +0200 Subject: [PATCH 068/437] Include all TypeScript files in development configuration (#512) * Include all TypeScript files in development configuration * Autofix TSLint issues * Reformat --- integration_test/functions/src/auth-tests.ts | 8 ++++---- integration_test/functions/src/database-tests.ts | 4 ++-- integration_test/functions/src/firestore-tests.ts | 4 ++-- integration_test/functions/src/https-tests.ts | 2 +- integration_test/functions/src/index.ts | 13 +++++-------- integration_test/functions/src/pubsub-tests.ts | 6 +++--- .../functions/src/remoteConfig-tests.ts | 2 +- integration_test/functions/src/storage-tests.ts | 3 +-- integration_test/functions/src/testing.ts | 12 +++++++----- tsconfig.json | 2 +- 10 files changed, 27 insertions(+), 29 deletions(-) diff --git a/integration_test/functions/src/auth-tests.ts b/integration_test/functions/src/auth-tests.ts index aaef21e6a..2c1e1d4e6 100644 --- a/integration_test/functions/src/auth-tests.ts +++ b/integration_test/functions/src/auth-tests.ts @@ -1,10 +1,10 @@ -import * as functions from 'firebase-functions'; -import { TestSuite, expectEq } from './testing'; import * as admin from 'firebase-admin'; +import * as functions from 'firebase-functions'; +import { expectEq, TestSuite } from './testing'; import UserMetadata = admin.auth.UserRecord; export const createUserTests: any = functions.auth.user().onCreate((u, c) => { - let testId: string = u.displayName; + const testId: string = u.displayName; console.log(`testId is ${testId}`); return new TestSuite('auth user onCreate') @@ -38,7 +38,7 @@ export const createUserTests: any = functions.auth.user().onCreate((u, c) => { }); export const deleteUserTests: any = functions.auth.user().onDelete((u, c) => { - let testId: string = u.displayName; + const testId: string = u.displayName; console.log(`testId is ${testId}`); return new TestSuite('auth user onDelete') diff --git a/integration_test/functions/src/database-tests.ts b/integration_test/functions/src/database-tests.ts index 215e6bd3b..de3030d3a 100644 --- a/integration_test/functions/src/database-tests.ts +++ b/integration_test/functions/src/database-tests.ts @@ -1,6 +1,6 @@ -import * as functions from 'firebase-functions'; -import { TestSuite, expectEq, expectMatches } from './testing'; import * as admin from 'firebase-admin'; +import * as functions from 'firebase-functions'; +import { expectEq, expectMatches, TestSuite } from './testing'; import DataSnapshot = admin.database.DataSnapshot; const testIdFieldName = 'testId'; diff --git a/integration_test/functions/src/firestore-tests.ts b/integration_test/functions/src/firestore-tests.ts index e5f8acdbf..49d1ae919 100644 --- a/integration_test/functions/src/firestore-tests.ts +++ b/integration_test/functions/src/firestore-tests.ts @@ -1,6 +1,6 @@ -import * as functions from 'firebase-functions'; -import { TestSuite, expectEq, expectDeepEq } from './testing'; import * as admin from 'firebase-admin'; +import * as functions from 'firebase-functions'; +import { expectDeepEq, expectEq, TestSuite } from './testing'; import DocumentSnapshot = admin.firestore.DocumentSnapshot; const testIdFieldName = 'documentId'; diff --git a/integration_test/functions/src/https-tests.ts b/integration_test/functions/src/https-tests.ts index 7a0889ef6..55c7df983 100644 --- a/integration_test/functions/src/https-tests.ts +++ b/integration_test/functions/src/https-tests.ts @@ -1,6 +1,6 @@ import * as functions from 'firebase-functions'; import * as _ from 'lodash'; -import { TestSuite, expectEq } from './testing'; +import { expectEq, TestSuite } from './testing'; export const callableTests: any = functions.https.onCall((d) => { return new TestSuite('https onCall') diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index 967b3d376..05f9e2035 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -1,11 +1,8 @@ -import * as functions from 'firebase-functions'; -import * as https from 'https'; -import * as admin from 'firebase-admin'; import { Request, Response } from 'express'; +import * as admin from 'firebase-admin'; +import * as functions from 'firebase-functions'; import * as fs from 'fs'; - -import * as PubSub from '@google-cloud/pubsub'; -const pubsub = PubSub(); +import * as https from 'https'; export * from './pubsub-tests'; export * from './database-tests'; @@ -82,7 +79,7 @@ export const integrationTests: any = functions .split('.') .slice(1) .join('.'); - let pubsub: any = require('@google-cloud/pubsub')(); + const pubsub: any = require('@google-cloud/pubsub')(); const testId = admin .database() .ref() @@ -155,7 +152,7 @@ export const integrationTests: any = functions .then(() => { // On test completion, check that all tests pass and reply "PASS", or provide further details. console.log('Waiting for all tests to report they pass...'); - let ref = admin.database().ref(`testRuns/${testId}`); + const ref = admin.database().ref(`testRuns/${testId}`); return new Promise((resolve, reject) => { let testsExecuted = 0; ref.on('child_added', (snapshot) => { diff --git a/integration_test/functions/src/pubsub-tests.ts b/integration_test/functions/src/pubsub-tests.ts index ebac609dc..3c1b1ecc7 100644 --- a/integration_test/functions/src/pubsub-tests.ts +++ b/integration_test/functions/src/pubsub-tests.ts @@ -1,6 +1,6 @@ -import * as functions from 'firebase-functions'; import * as admin from 'firebase-admin'; -import { TestSuite, expectEq, evaluate, success } from './testing'; +import * as functions from 'firebase-functions'; +import { evaluate, expectEq, success, TestSuite } from './testing'; import PubsubMessage = functions.pubsub.Message; // TODO(inlined) use multiple queues to run inline. @@ -64,7 +64,7 @@ export const schedule: any = functions.pubsub // For the test, the job is triggered by the jobs:run api .onRun((context) => { let testId; - let db = admin.database(); + const db = admin.database(); return new Promise(async (resolve, reject) => { await db .ref('testRuns') diff --git a/integration_test/functions/src/remoteConfig-tests.ts b/integration_test/functions/src/remoteConfig-tests.ts index 2b824fbbd..3474e1fc2 100644 --- a/integration_test/functions/src/remoteConfig-tests.ts +++ b/integration_test/functions/src/remoteConfig-tests.ts @@ -1,5 +1,5 @@ import * as functions from 'firebase-functions'; -import { TestSuite, expectEq } from './testing'; +import { expectEq, TestSuite } from './testing'; import TemplateVersion = functions.remoteConfig.TemplateVersion; export const remoteConfigTests: any = functions.remoteConfig.onUpdate( diff --git a/integration_test/functions/src/storage-tests.ts b/integration_test/functions/src/storage-tests.ts index 7ced40fc9..43cdef5e2 100644 --- a/integration_test/functions/src/storage-tests.ts +++ b/integration_test/functions/src/storage-tests.ts @@ -1,7 +1,6 @@ import * as functions from 'firebase-functions'; -import { TestSuite, expectEq, expectDeepEq } from './testing'; +import { expectEq, TestSuite } from './testing'; import ObjectMetadata = functions.storage.ObjectMetadata; -const testIdFieldName = 'documentId'; export const storageTests: any = functions .runWith({ diff --git a/integration_test/functions/src/testing.ts b/integration_test/functions/src/testing.ts index d3782a981..43d3e69d5 100644 --- a/integration_test/functions/src/testing.ts +++ b/integration_test/functions/src/testing.ts @@ -1,9 +1,11 @@ import * as firebase from 'firebase-admin'; -import * as _ from 'lodash'; import { EventContext } from 'firebase-functions'; +import * as _ from 'lodash'; export type TestCase = (data: T, context?: EventContext) => any; -export type TestCaseMap = { [key: string]: TestCase }; +export interface TestCaseMap { + [key: string]: TestCase; +} export class TestSuite { private name: string; @@ -20,8 +22,8 @@ export class TestSuite { } run(testId: string, data: T, context?: EventContext): Promise { - let running: Array> = []; - for (let testName in this.tests) { + const running: Array> = []; + for (const testName in this.tests) { if (!this.tests.hasOwnProperty(testName)) { continue; } @@ -36,7 +38,7 @@ export class TestSuite { }, (error) => { console.error(`Failed: ${testName}`, error); - return { name: testName, passed: 0, error: error }; + return { name: testName, passed: 0, error }; } ); running.push(run); diff --git a/tsconfig.json b/tsconfig.json index 2f7cc7e0d..6538f1f07 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,5 +4,5 @@ "sourceMap": true }, "extends": "./tsconfig.release.json", - "include": ["./src/**/*.ts", "./spec/**/*.ts"] + "include": ["**/*.ts"] } From 5363a48ccebcb9340923dbf09f882f1bc4a48288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Fri, 12 Jul 2019 00:19:54 +0200 Subject: [PATCH 069/437] Define Codeowners (#516) * Define Codeowners * Add @kevinajian to Codeowners --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..49f7349e0 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @kevinajian @thechenky From a6ef584f9e7f296cb2d1aec9fdb7b5a4c31010ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Fri, 12 Jul 2019 01:16:34 +0200 Subject: [PATCH 070/437] Extract and document path manipulation utilities (#514) --- spec/index.spec.ts | 1 + spec/utilities/path.spec.ts | 24 ++++++++++++++++++++++++ spec/utils.spec.ts | 20 +------------------- src/providers/database.ts | 3 ++- src/utilities/path.ts | 35 +++++++++++++++++++++++++++++++++++ src/utils.ts | 20 -------------------- 6 files changed, 63 insertions(+), 40 deletions(-) create mode 100644 spec/utilities/path.spec.ts create mode 100644 src/utilities/path.ts diff --git a/spec/index.spec.ts b/spec/index.spec.ts index 64ffdf62e..0eb95045d 100644 --- a/spec/index.spec.ts +++ b/spec/index.spec.ts @@ -43,4 +43,5 @@ import './providers/pubsub.spec'; import './providers/remoteConfig.spec'; import './providers/storage.spec'; import './setup.spec'; +import './utilities/path.spec'; import './utils.spec'; diff --git a/spec/utilities/path.spec.ts b/spec/utilities/path.spec.ts new file mode 100644 index 000000000..c0a5dc318 --- /dev/null +++ b/spec/utilities/path.spec.ts @@ -0,0 +1,24 @@ +import { expect } from 'chai'; +import { normalizePath, pathParts } from '../../src/utilities/path'; + +describe('utilities', () => { + describe('path', () => { + describe('#normalizePath', () => { + it('should strip leading and trailing slash', () => { + expect(normalizePath('/my/path/is/{rad}/')).to.eq('my/path/is/{rad}'); + }); + }); + + describe('#pathParts', () => { + it('should turn a path into an array of strings', () => { + expect(pathParts('/foo/bar/baz')).to.deep.equal(['foo', 'bar', 'baz']); + }); + + it('should turn a root path, empty string, or null path into an empty array', () => { + expect(pathParts('')).to.deep.equal([]); + expect(pathParts(null)).to.deep.equal([]); + expect(pathParts('/')).to.deep.equal([]); + }); + }); + }); +}); diff --git a/spec/utils.spec.ts b/spec/utils.spec.ts index 2ea64fb7e..51a8478fa 100644 --- a/spec/utils.spec.ts +++ b/spec/utils.spec.ts @@ -21,27 +21,9 @@ // SOFTWARE. import { expect } from 'chai'; -import { applyChange, normalizePath, pathParts } from '../src/utils'; +import { applyChange } from '../src/utils'; describe('utils', () => { - describe('.normalizePath(path: string)', () => { - it('should strip leading and trailing slash', () => { - expect(normalizePath('/my/path/is/{rad}/')).to.eq('my/path/is/{rad}'); - }); - }); - - describe('.pathParts(path: string): string[]', () => { - it('should turn a path into an array of strings', () => { - expect(pathParts('/foo/bar/baz')).to.deep.equal(['foo', 'bar', 'baz']); - }); - - it('should turn a root path, empty string, or null path into an empty array', () => { - expect(pathParts('')).to.deep.equal([]); - expect(pathParts(null)).to.deep.equal([]); - expect(pathParts('/')).to.deep.equal([]); - }); - }); - describe('.applyChange(from: any, to: any): any', () => { it('should return the to value for non-object values of from and to', () => { expect(applyChange({ a: 'b' }, null)).to.eq(null); diff --git a/src/providers/database.ts b/src/providers/database.ts index 7740a4373..ec6969909 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -32,7 +32,8 @@ import { } from '../cloud-functions'; import { firebaseConfig } from '../config'; import { DeploymentOptions } from '../function-configuration'; -import { applyChange, joinPath, normalizePath, pathParts } from '../utils'; +import { joinPath, normalizePath, pathParts } from '../utilities/path'; +import { applyChange } from '../utils'; /** @hidden */ export const provider = 'google.firebase.database'; diff --git a/src/utilities/path.ts b/src/utilities/path.ts new file mode 100644 index 000000000..40ab8f047 --- /dev/null +++ b/src/utilities/path.ts @@ -0,0 +1,35 @@ +/** + * Removes leading and trailing slashes from a path. + * + * @param path A path to normalize, in POSIX format. + */ +export function normalizePath(path: string): string { + if (!path) { + return ''; + } + return path.replace(/^\//, '').replace(/\/$/, ''); +} + +/** + * Normalizes a given path and splits it into an array of segments. + * + * @param path A path to split, in POSIX format. + */ +export function pathParts(path: string): string[] { + if (!path || path === '' || path === '/') { + return []; + } + return normalizePath(path).split('/'); +} + +/** + * Normalizes given paths and joins these together using a POSIX separator. + * + * @param base A first path segment, in POSIX format. + * @param child A second path segment, in POSIX format. + */ +export function joinPath(base: string, child: string) { + return pathParts(base) + .concat(pathParts(child)) + .join('/'); +} diff --git a/src/utils.ts b/src/utils.ts index ffec72a2d..b52c1e54f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -22,26 +22,6 @@ import * as _ from 'lodash'; -export function normalizePath(path: string): string { - if (!path) { - return ''; - } - return path.replace(/^\//, '').replace(/\/$/, ''); -} - -export function pathParts(path: string): string[] { - if (!path || path === '' || path === '/') { - return []; - } - return normalizePath(path).split('/'); -} - -export function joinPath(base: string, child: string) { - return pathParts(base) - .concat(pathParts(child)) - .join('/'); -} - export function applyChange(src: any, dest: any) { // if not mergeable, don't merge if (!_.isPlainObject(dest) || !_.isPlainObject(src)) { From 03155440dea92fe6ffd166ebea9c022f5ee0c13d Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Thu, 11 Jul 2019 16:39:30 -0700 Subject: [PATCH 071/437] Add automatic TypeDoc generation to Functions SDK (#491) * add typedoc generation, somewhat working * fix from admin => functions in a comment * add npm run apidocs command to generate apidocs * modify to use modules instead of file mode * change toc to match modules mode of typedoc * add UserInfo to be generated, modify toc.yaml to correct links * add missing sections to toc.yaml * fix merge conflict * fix erroneous warnings about missing and unlisted files * remove underscores * edit toc.yaml to fix the links * remove underscores in links inside the html files * unhide Event * hide Event * add docgen/html to gitignore * formatting * remove Event from toc, add proper toc file checking * clean up docgen script --- .gitignore | 1 + docgen/content-sources/HOME.md | 3 + docgen/content-sources/toc.yaml | 114 +++++ docgen/generate-docs.js | 339 ++++++++++++++ docgen/theme/assets/css/firebase.css | 40 ++ docgen/theme/assets/css/main.css | 552 +++++++++++++++++++++++ docgen/theme/assets/images/lockup.png | Bin 0 -> 2646 bytes docgen/theme/layouts/default.hbs | 33 ++ docgen/theme/partials/breadcrumb.hbs | 11 + docgen/theme/partials/comment.hbs | 22 + docgen/theme/partials/header.hbs | 23 + docgen/theme/partials/member.sources.hbs | 15 + docgen/theme/partials/navigation.hbs | 22 + docgen/theme/templates/reflection.hbs | 72 +++ docgen/tsconfig.json | 3 + docgen/typedoc.js | 27 ++ package.json | 10 +- src/providers/auth.ts | 5 + 18 files changed, 1290 insertions(+), 2 deletions(-) create mode 100644 docgen/content-sources/HOME.md create mode 100644 docgen/content-sources/toc.yaml create mode 100644 docgen/generate-docs.js create mode 100644 docgen/theme/assets/css/firebase.css create mode 100644 docgen/theme/assets/css/main.css create mode 100644 docgen/theme/assets/images/lockup.png create mode 100644 docgen/theme/layouts/default.hbs create mode 100644 docgen/theme/partials/breadcrumb.hbs create mode 100644 docgen/theme/partials/comment.hbs create mode 100644 docgen/theme/partials/header.hbs create mode 100644 docgen/theme/partials/member.sources.hbs create mode 100644 docgen/theme/partials/navigation.hbs create mode 100644 docgen/theme/templates/reflection.hbs create mode 100644 docgen/tsconfig.json create mode 100644 docgen/typedoc.js diff --git a/.gitignore b/.gitignore index d345a2f7a..ba3cdeec5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .tmp .vscode/ coverage +docgen/html firebase-functions-*.tgz integration_test/.firebaserc integration_test/*.log diff --git a/docgen/content-sources/HOME.md b/docgen/content-sources/HOME.md new file mode 100644 index 000000000..c89df0a57 --- /dev/null +++ b/docgen/content-sources/HOME.md @@ -0,0 +1,3 @@ +# Firebase Functions SDK Reference + +Functions SDK!!! diff --git a/docgen/content-sources/toc.yaml b/docgen/content-sources/toc.yaml new file mode 100644 index 000000000..c2766e430 --- /dev/null +++ b/docgen/content-sources/toc.yaml @@ -0,0 +1,114 @@ +toc: + - title: 'functions' + path: /docs/reference/functions/cloud_functions_.html + section: + - title: 'CloudFunction' + path: /docs/reference/functions/cloud_functions_.html#cloudfunction + - title: 'HttpsFunction' + path: /docs/reference/functions/cloud_functions_.html#httpsfunction + - title: 'EventContext' + path: /docs/reference/functions/cloud_functions_.eventcontext.html + - title: 'FunctionBuilder' + path: /docs/reference/functions/function_builder_.functionbuilder.html + - title: 'Change' + path: /docs/reference/functions/cloud_functions_.change.html + + - title: 'functions.config' + path: /docs/reference/functions/config_.html + section: + - title: 'Config' + path: /docs/reference/functions/config_.config.html + + - title: 'functions.analytics' + path: /docs/reference/functions/providers_analytics_.html + section: + - title: 'AnalyticsEvent' + path: /docs/reference/functions/providers_analytics_.analyticsevent.html + - title: 'AnalyticsEventBuilder' + path: /docs/reference/functions/providers_analytics_.analyticseventbuilder.html + - title: 'AppInfo' + path: /docs/reference/functions/providers_analytics_.appinfo.html + - title: 'DeviceInfo' + path: /docs/reference/functions/providers_analytics_.deviceinfo.html + - title: 'ExportBundleInfo' + path: /docs/reference/functions/providers_analytics_.exportbundleinfo.html + - title: 'GeoInfo' + path: /docs/reference/functions/providers_analytics_.geoinfo.html + - title: 'UserDimensions' + path: /docs/reference/functions/providers_analytics_.userdimensions.html + - title: 'UserPropertyValue' + path: /docs/reference/functions/providers_analytics_.userpropertyvalue.html + + - title: 'functions.auth' + path: /docs/reference/functions/providers_auth_.html + section: + - title: 'UserBuilder' + path: /docs/reference/functions/providers_auth_.userbuilder.html + - title: 'UserInfo' + path: /docs/reference/functions/providers_auth_.html#userinfo + - title: 'UserRecordMetadata' + path: /docs/reference/functions/providers_auth_.userrecordmetadata.html + - title: 'UserRecord' + path: /docs/reference/functions/providers_auth_.html#userrecord + + - title: 'functions.crashlytics' + path: /docs/reference/functions/providers_crashlytics_.html + section: + - title: 'Issue' + path: /docs/reference/functions/providers_crashlytics_.issue.html + - title: 'IssueBuilder' + path: /docs/reference/functions/providers_crashlytics_.issuebuilder.html + - title: 'AppInfo' + path: /docs/reference/functions/providers_crashlytics_.appinfo.html + - title: 'VelocityAlert' + path: /docs/reference/functions/providers_crashlytics_.velocityalert.html + + - title: 'functions.firestore' + path: /docs/reference/functions/providers_firestore_.html + section: + - title: 'DocumentBuilder' + path: /docs/reference/functions/providers_firestore_.documentbuilder.html + - title: 'DocumentSnapshot' + path: /docs/reference/functions/providers_firestore_.html#documentsnapshot + + - title: 'functions.database' + path: /docs/reference/functions/providers_database_.html + section: + - title: 'DataSnapshot' + path: /docs/reference/functions/providers_database_.datasnapshot.html + - title: 'RefBuilder' + path: /docs/reference/functions/providers_database_.refbuilder.html + - title: 'InstanceBuilder' + path: /docs/reference/functions/providers_database_.instancebuilder.html + + - title: 'functions.https' + path: /docs/reference/functions/providers_https_.html + section: + - title: 'HttpsError' + path: /docs/reference/functions/providers_https_.httpserror.html + + - title: 'functions.pubsub' + path: /docs/reference/functions/providers_pubsub_.html + section: + - title: 'Message' + path: /docs/reference/functions/providers_pubsub_.message.html + - title: 'TopicBuilder' + path: /docs/reference/functions/providers_pubsub_.topicbuilder.html + + - title: 'functions.remoteconfig' + path: /docs/reference/functions/providers_remoteconfig_.html + section: + - title: 'RemoteConfigUser' + path: /docs/reference/functions/providers_remoteconfig_.remoteconfiguser.html + - title: 'TemplateVersion' + path: /docs/reference/functions/providers_remoteconfig_.templateversion.html + + - title: 'functions.storage' + path: /docs/reference/functions/providers_storage_.html + section: + - title: 'BucketBuilder' + path: /docs/reference/functions/providers_storage_.bucketbuilder.html + - title: 'ObjectBuilder' + path: /docs/reference/functions/providers_storage_.objectbuilder.html + - title: 'ObjectMetadata' + path: /docs/reference/functions/providers_storage_.objectmetadata.html diff --git a/docgen/generate-docs.js b/docgen/generate-docs.js new file mode 100644 index 000000000..299250e64 --- /dev/null +++ b/docgen/generate-docs.js @@ -0,0 +1,339 @@ +/** + * @license + * Copyright 2019 Google Inc. + * + * 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 { exec } = require('child-process-promise'); +const fs = require('mz/fs'); +const path = require('path'); +const yargs = require('yargs'); +const yaml = require('js-yaml'); +const _ = require('lodash'); + +const repoPath = path.resolve(`${__dirname}/..`); + +// Command-line options. +const { source: sourceFile } = yargs + .option('source', { + default: `${repoPath}/src`, + describe: 'Typescript source file(s)', + type: 'string' + }) + .version(false) + .help().argv; + +const docPath = path.resolve(`${__dirname}/html`); +const contentPath = path.resolve(`${__dirname}/content-sources`); +const tempHomePath = path.resolve(`${contentPath}/HOME_TEMP.md`); +const devsitePath = `/docs/reference/functions/`; + +/** + * Strips path prefix and returns only filename. + * @param {string} path + */ +function stripPath(path) { + const parts = path.split('/'); + return parts[parts.length - 1]; +} + +/** + * Runs Typedoc command. + * + * Additional config options come from ./typedoc.js + */ +function runTypedoc() { + const command = `${repoPath}/node_modules/.bin/typedoc ${sourceFile} \ + --out ${docPath} \ + --readme ${tempHomePath} \ + --options ${__dirname}/typedoc.js \ + --theme ${__dirname}/theme`; + + console.log('Running command:\n', command); + return exec(command); +} + +/** + * Moves files from subdir to root. + * @param {string} subdir Subdir to move files out of. + */ +function moveFilesToRoot(subdir) { + return exec(`mv ${docPath}/${subdir}/* ${docPath}`) + .then(() => { + exec(`rmdir ${docPath}/${subdir}`); + }) + .catch(e => console.error(e)); +} + +/** + * Renames files to remove the leading underscores. + * We need to do this because devsite hides these files. + * Example: + * _cloud_functions_.resource.html => cloud_functions_.resource.html + */ +function renameFiles() { + return fs.readdir(docPath).then(files => { + console.log(files); + files.forEach(file => { + let newFileName = file; + if (_.startsWith(file, "_") && _.endsWith(file, "html")) { + newFileName = _.trimStart(file, "_"); + fs.rename(`${docPath}/${file}`, `${docPath}/${newFileName}`, (err) => { + if (err) console.log(err) + }); + } + }) + }) +} + +/** + * Reformat links to match flat structure. + * @param {string} file File to fix links in. + */ +function fixLinks(file) { + return fs.readFile(file, 'utf8').then(data => { + const flattenedLinks = data + .replace(/\.\.\//g, '') + .replace(/(modules|interfaces|classes)\//g, '') + .replace(/\"_/g, '"'); + let caseFixedLinks = flattenedLinks; + for (const lower in lowerToUpperLookup) { + const re = new RegExp(lower, 'g'); + caseFixedLinks = caseFixedLinks.replace(re, lowerToUpperLookup[lower]); + } + return fs.writeFile(file, caseFixedLinks); + }); +} + +let tocText = ''; + +/** + * Generates temporary markdown file that will be sourced by Typedoc to + * create index.html. + * + * @param {string} tocRaw + * @param {string} homeRaw + */ +function generateTempHomeMdFile(tocRaw, homeRaw) { + const { toc } = yaml.safeLoad(tocRaw); + let tocPageLines = [homeRaw, '# API Reference']; + toc.forEach(group => { + tocPageLines.push(`\n## [${group.title}](${stripPath(group.path)})`); + const section = group.section || []; + section.forEach(item => { + tocPageLines.push(`- [${item.title}](${stripPath(item.path)})`); + }); + }); + return fs.writeFile(tempHomePath, tocPageLines.join('\n')); +} + +/** + * Mapping between lowercase file name and correctly cased name. + * Used to update links when filenames are capitalized. + */ +const lowerToUpperLookup = {}; + +/** + * Checks to see if any files listed in toc.yaml were not generated. + * If files exist, fixes filename case to match toc.yaml version. + */ +function checkForMissingFilesAndFixFilenameCase() { + // Get filenames from toc.yaml. + const filenames = tocText + .split('\n') + .filter(line => line.includes('path:')) + .map(line => line.split(devsitePath)[1]); + // Logs warning to console if a file from TOC is not found. + // console.log(filenames); + const fileCheckPromises = filenames.map(filename => { + // Warns if file does not exist, fixes filename case if it does. + // Preferred filename for devsite should be capitalized and taken from + // toc.yaml. + const tocFilePath = `${docPath}/${filename}`; + // Generated filename from Typedoc will be lowercase. + const generatedFilePath = `${docPath}/${filename.toLowerCase()}`; + return fs.exists(generatedFilePath).then(exists => { + if (exists) { + // Store in a lookup table for link fixing. + lowerToUpperLookup[ + `${filename.toLowerCase()}` + ] = `${filename}`; + return fs.rename(generatedFilePath, tocFilePath); + } else { + console.warn( + `Missing file: ${filename} requested ` + + `in toc.yaml but not found in ${docPath}` + ); + } + }); + }); + return Promise.all(fileCheckPromises).then(() => filenames); +} + +/** + * Gets a list of html files in generated dir and checks if any are not + * found in toc.yaml. + * Option to remove the file if not found (used for node docs). + * + * @param {Array} filenamesFromToc Filenames pulled from toc.yaml + * @param {boolean} shouldRemove Should just remove the file + */ +function checkForUnlistedFiles(filenamesFromToc, shouldRemove) { + return fs.readdir(docPath).then(files => { + const htmlFiles = files + .filter(filename => filename.slice(-4) === 'html'); + const removePromises = []; + htmlFiles.forEach(filename => { + if ( + !filenamesFromToc.includes(filename) && + filename !== 'index' && + filename !== 'globals' + ) { + if (shouldRemove) { + console.log( + `REMOVING ${docPath}/${filename} - not listed in toc.yaml.` + ); + removePromises.push(fs.unlink(`${docPath}/${filename}`)); + } else { + // This is just a warning, it doesn't need to finish before + // the process continues. + console.warn( + `Unlisted file: ${filename} generated ` + + `but not listed in toc.yaml.` + ); + } + } + }); + if (shouldRemove) { + return Promise.all(removePromises).then(() => + htmlFiles.filter(filename => filenamesFromToc.includes(filename)) + ); + } else { + return htmlFiles; + } + }); +} + +/** + * Writes a _toc_autogenerated.yaml as a record of all files that were + * autogenerated. Helpful to tech writers. + * + * @param {Array} htmlFiles List of html files found in generated dir. + */ +function writeGeneratedFileList(htmlFiles) { + const fileList = htmlFiles.map(filename => { + return { + title: filename, + path: `${devsitePath}${filename}` + }; + }); + const generatedTocYAML = yaml.safeDump({ toc: fileList }); + return fs + .writeFile(`${docPath}/_toc_autogenerated.yaml`, generatedTocYAML) + .then(() => htmlFiles); +} + +/** + * Fix all links in generated files to other generated files to point to top + * level of generated docs dir. + * + * @param {Array} htmlFiles List of html files found in generated dir. + */ +function fixAllLinks(htmlFiles) { + const writePromises = []; + htmlFiles.forEach(file => { + // Update links in each html file to match flattened file structure. + writePromises.push(fixLinks(`${docPath}/${file}`)); + }); + return Promise.all(writePromises); +} + +/** + * Main document generation process. + * + * Steps for generating documentation: + * 1) Create temporary md file as source of homepage. + * 2) Run Typedoc, sourcing index.d.ts for API content and temporary md file + * for index.html content. + * 3) Write table of contents file. + * 4) Flatten file structure by moving all items up to root dir and fixing + * links as needed. + * 5) Check for mismatches between TOC list and generated file list. + */ +Promise.all([ + fs.readFile(`${contentPath}/toc.yaml`, 'utf8'), + fs.readFile(`${contentPath}/HOME.md`, 'utf8') +]) + // Read TOC and homepage text and assemble a homepage markdown file. + // This file will be sourced by Typedoc to generate index.html. + .then(([tocRaw, homeRaw]) => { + tocText = tocRaw; + return generateTempHomeMdFile(tocRaw, homeRaw); + }) + // Run main Typedoc process (uses index.d.ts and generated temp file above). + .then(runTypedoc) + .then(output => { + // Typedoc output. + console.log(output.stdout); + // Clean up temp home markdown file. (Nothing needs to wait for this.) + fs.unlink(tempHomePath); + // Devsite doesn't like css.map files. + return fs.unlink(`${docPath}/assets/css/main.css.map`); + }) + // Write out TOC file. Do this after Typedoc step to prevent Typedoc + // erroring when it finds an unexpected file in the target dir. + .then(() => fs.writeFile(`${docPath}/_toc.yaml`, tocText)) + // Flatten file structure. These categories don't matter to us and it makes + // it easier to manage the docs directory. + .then(() => { + return Promise.all([ + moveFilesToRoot('classes'), + moveFilesToRoot('modules'), + moveFilesToRoot('interfaces'), + ]); + }) + // Rename files to remove the underscores since devsite hides those. + .then(renameFiles) + // Check for files listed in TOC that are missing and warn if so. + // Not blocking. + .then(checkForMissingFilesAndFixFilenameCase) + // Check for files that exist but aren't listed in the TOC and warn. + // (If API is node, actually remove the file.) + // Removal is blocking, warnings aren't. + .then(filenamesFromToc => + checkForUnlistedFiles(filenamesFromToc, false) + ) + // Write a _toc_autogenerated.yaml to record what files were created. + .then(htmlFiles => writeGeneratedFileList(htmlFiles)) + // Correct the links in all the generated html files now that files have + // all been moved to top level. + // .then(removeUnderscores) + .then(fixAllLinks) + .then(() => { + fs.readFile(`${docPath}/index.html`, 'utf8').then(data => { + // String to include devsite local variables. + const localVariablesIncludeString = `{% include "docs/web/_local_variables.html" %}\n`; + return fs.writeFile( + `${docPath}/index.html`, + localVariablesIncludeString + data + ); + }); + }) + .catch(e => { + if (e.stdout) { + console.error(e.stdout); + } else { + console.error(e); + } + }); diff --git a/docgen/theme/assets/css/firebase.css b/docgen/theme/assets/css/firebase.css new file mode 100644 index 000000000..86c592820 --- /dev/null +++ b/docgen/theme/assets/css/firebase.css @@ -0,0 +1,40 @@ +.firebase-docs .project-name { + color: #333; + display: inline-block; + font-size: 20px; + font-weight: normal; + margin-left: 130px; +} + +.firebase-docs aside.tsd-sources { + padding: 8px; +} + +.firebase-docs .tsd-panel li { + margin: 0; +} + +.firebase-docs aside.tsd-sources:before { + content: unset; +} + +.firebase-docs dl.tsd-comment-tags dt.tag-example { + float: none; + text-transform: capitalize; + color: #000; + font-size: 1.1em; + padding: 5px; + border: none; +} + +.firebase-docs dl.tsd-comment-tags dd.tag-body-example { + padding-left: 0; +} + +.firebase-docs .tsd-breadcrumb .breadcrumb-name a { + color: #039be5; +} + +.firebase-docs .tsd-breadcrumb .model-name { + color: #333; +} \ No newline at end of file diff --git a/docgen/theme/assets/css/main.css b/docgen/theme/assets/css/main.css new file mode 100644 index 000000000..12f3d05d9 --- /dev/null +++ b/docgen/theme/assets/css/main.css @@ -0,0 +1,552 @@ +/*! normalize.css v1.1.3 | MIT License | git.io/normalize */ +/* ========================================================================== HTML5 display definitions ========================================================================== */ +/** Correct `block` display not defined in IE 6/7/8/9 and Firefox 3. */ +article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { display: block; } + +/** Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. */ +audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } + +/** Prevent modern browsers from displaying `audio` without controls. Remove excess height in iOS 5 devices. */ +audio:not([controls]) { display: none; height: 0; } + +/** Address styling not present in IE 7/8/9, Firefox 3, and Safari 4. Known issue: no IE 6 support. */ +[hidden] { display: none; } + +/* ========================================================================== Base ========================================================================== */ +/** 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using `em` units. 2. Prevent iOS text size adjust after orientation change, without disabling user zoom. */ +html { font-size: 100%; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ font-family: sans-serif; } + +/** Address `font-family` inconsistency between `textarea` and other form elements. */ +button, input, select, textarea { font-family: sans-serif; } + +/** Address margins handled incorrectly in IE 6/7. */ +body { margin: 0; } + +/* ========================================================================== Links ========================================================================== */ +/** Address `outline` inconsistency between Chrome and other browsers. */ +a:focus { outline: thin dotted; } +a:active, a:hover { outline: 0; } + +/** Improve readability when focused and also mouse hovered in all browsers. */ +/* ========================================================================== Typography ========================================================================== */ +/** Address font sizes and margins set differently in IE 6/7. Address font sizes within `section` and `article` in Firefox 4+, Safari 5, and Chrome. */ +h1 { font-size: 2em; margin: 0.67em 0; } + +h2 { font-size: 1.5em; margin: 0.83em 0; } + +h3 { font-size: 1.17em; margin: 1em 0; } + +h4, .tsd-index-panel h3 { font-size: 1em; margin: 1.33em 0; } + +h5 { font-size: 0.83em; margin: 1.67em 0; } + +h6 { font-size: 0.67em; margin: 2.33em 0; } + +/** Address styling not present in IE 7/8/9, Safari 5, and Chrome. */ +abbr[title] { border-bottom: 1px dotted; } + +/** Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. */ +b, strong { font-weight: bold; } + +blockquote { margin: 1em 40px; } + +/** Address styling not present in Safari 5 and Chrome. */ +dfn { font-style: italic; } + +/** Address differences between Firefox and other browsers. Known issue: no IE 6/7 normalization. */ +hr { box-sizing: content-box; height: 0; } + +/** Address styling not present in IE 6/7/8/9. */ +mark { background: #ff0; color: #000; } + +/** Address margins set differently in IE 6/7. */ +p, pre { margin: 1em 0; } + +/** Improve readability of pre-formatted text in all browsers. */ +pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } + +/** Address CSS quotes not supported in IE 6/7. */ +q { quotes: none; } +q:before, q:after { content: ""; content: none; } + +/** Address `quotes` property not supported in Safari 4. */ +/** Address inconsistent and variable font size in all browsers. */ +small { font-size: 80%; } + +/** Prevent `sub` and `sup` affecting `line-height` in all browsers. */ +sub { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } + +sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; top: -0.5em; } + +sub { bottom: -0.25em; } + +/* ========================================================================== Lists ========================================================================== */ +dd { margin: 0 0 0 40px; } + +/** Address paddings set differently in IE 6/7. */ +menu, ol, ul { padding: 0 0 0 40px; } + +/** Correct list images handled incorrectly in IE 7. */ +nav ul, nav ol { list-style: none; list-style-image: none; } + +/* ========================================================================== Embedded content ========================================================================== */ +/** 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3. 2. Improve image quality when scaled in IE 7. */ +img { border: 0; /* 1 */ -ms-interpolation-mode: bicubic; } + +/* 2 */ +/** Correct overflow displayed oddly in IE 9. */ +svg:not(:root) { overflow: hidden; } + +/* ========================================================================== Figures ========================================================================== */ +/** Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11. */ +figure, form { margin: 0; } + +/* ========================================================================== Forms ========================================================================== */ +/** Correct margin displayed oddly in IE 6/7. */ +/** Define consistent border, margin, and padding. */ +fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } + +/** 1. Correct color not being inherited in IE 6/7/8/9. 2. Correct text not wrapping in Firefox 3. 3. Correct alignment displayed oddly in IE 6/7. */ +legend { border: 0; /* 1 */ padding: 0; white-space: normal; /* 2 */ *margin-left: -7px; } + +/* 3 */ +/** 1. Correct font size not being inherited in all browsers. 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5, and Chrome. 3. Improve appearance and consistency in all browsers. */ +button, input, select, textarea { font-size: 100%; /* 1 */ margin: 0; /* 2 */ vertical-align: baseline; /* 3 */ *vertical-align: middle; } + +/* 3 */ +/** Address Firefox 3+ setting `line-height` on `input` using `!important` in the UA stylesheet. */ +button, input { line-height: normal; } + +/** Address inconsistent `text-transform` inheritance for `button` and `select`. All other form control elements do not inherit `text-transform` values. Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+. Correct `select` style inheritance in Firefox 4+ and Opera. */ +button, select { text-transform: none; } + +/** 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. 2. Correct inability to style clickable `input` types in iOS. 3. Improve usability and consistency of cursor style between image-type `input` and others. 4. Remove inner spacing in IE 7 without affecting normal text inputs. Known issue: inner spacing remains in IE 6. */ +button, html input[type="button"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ *overflow: visible; } + +/* 4 */ +input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ *overflow: visible; } + +/* 4 */ +/** Re-set default cursor for disabled elements. */ +button[disabled], html input[disabled] { cursor: default; } + +/** 1. Address box sizing set to content-box in IE 8/9. 2. Remove excess padding in IE 8/9. 3. Remove excess padding in IE 7. Known issue: excess padding remains in IE 6. */ +input { /* 3 */ } +input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ *height: 13px; /* 3 */ *width: 13px; } +input[type="search"] { -webkit-appearance: textfield; /* 1 */ /* 2 */ box-sizing: content-box; } +input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } + +/** 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome (include `-moz` to future-proof). */ +/** Remove inner padding and search cancel button in Safari 5 and Chrome on OS X. */ +/** Remove inner padding and border in Firefox 3+. */ +button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } + +/** 1. Remove default vertical scrollbar in IE 6/7/8/9. 2. Improve readability and alignment in all browsers. */ +textarea { overflow: auto; /* 1 */ vertical-align: top; } + +/* 2 */ +/* ========================================================================== Tables ========================================================================== */ +/** Remove most spacing between table cells. */ +table { border-collapse: collapse; border-spacing: 0; } + +.hljs { display: inline-block; padding: 0.5em; background: white; color: #37474f; } + +.hljs-comment, +.hljs-annotation, +.hljs-template_comment, +.diff .hljs-header, +.hljs-chunk, +.apache .hljs-cbracket { color: #d81b60; } + +.hljs-keyword, +.hljs-id, +.hljs-built_in, +.css .smalltalk .hljs-class, +.hljs-winutils, +.bash .hljs-variable, +.tex .hljs-command, +.hljs-request, +.hljs-status, +.hljs-meta, +.nginx .hljs-title { color: #3b78e7; } + +.xml .hljs-tag { color: #3b78e7; } +.xml .hljs-tag .hljs-value { color: #3b78e7; } + +.hljs-string, +.hljs-title, +.hljs-parent, +.hljs-tag .hljs-value, +.hljs-rules .hljs-value { color: #0d904f; } + +.devsite-dark-code .hljs { display: inline-block; padding: 0.5em; background: white; color: #eceff1; } + +.devsite-dark-code .hljs-comment, +.devsite-dark-code .hljs-annotation, +.devsite-dark-code .hljs-template_comment, +.devsite-dark-code .diff .hljs-header, +.devsite-dark-code .hljs-chunk { color: #f06292; } + +.devsite-dark-code .hljs-keyword, +.devsite-dark-code .hljs-id, +.devsite-dark-code .hljs-built_in, +.devsite-dark-code .hljs-winutils, +.devsite-dark-code .hljs-request, +.devsite-dark-code .hljs-status, +.devsite-dark-code .hljs-meta { color: #4dd0e1; } + +.devsite-dark-code .hljs-string, +.devsite-dark-code .hljs-title, +.devsite-dark-code .hljs-parent, +.devsite-dark-code .hljs-tag .hljs-value, +.devsite-dark-code .hljs-rules .hljs-value { color: #9ccc65; } + +.col > :first-child, .col-1 > :first-child, .col-2 > :first-child, .col-3 > :first-child, .col-4 > :first-child, .col-5 > :first-child, .col-6 > :first-child, .col-7 > :first-child, .col-8 > :first-child, .col-9 > :first-child, .col-10 > :first-child, .col-11 > :first-child, .tsd-panel > :first-child, ul.tsd-descriptions > li > :first-child, .col > :first-child > :first-child, .col-1 > :first-child > :first-child, .col-2 > :first-child > :first-child, .col-3 > :first-child > :first-child, .col-4 > :first-child > :first-child, .col-5 > :first-child > :first-child, .col-6 > :first-child > :first-child, .col-7 > :first-child > :first-child, .col-8 > :first-child > :first-child, .col-9 > :first-child > :first-child, .col-10 > :first-child > :first-child, .col-11 > :first-child > :first-child, .tsd-panel > :first-child > :first-child, ul.tsd-descriptions > li > :first-child > :first-child, .col > :first-child > :first-child > :first-child, .col-1 > :first-child > :first-child > :first-child, .col-2 > :first-child > :first-child > :first-child, .col-3 > :first-child > :first-child > :first-child, .col-4 > :first-child > :first-child > :first-child, .col-5 > :first-child > :first-child > :first-child, .col-6 > :first-child > :first-child > :first-child, .col-7 > :first-child > :first-child > :first-child, .col-8 > :first-child > :first-child > :first-child, .col-9 > :first-child > :first-child > :first-child, .col-10 > :first-child > :first-child > :first-child, .col-11 > :first-child > :first-child > :first-child, .tsd-panel > :first-child > :first-child > :first-child, ul.tsd-descriptions > li > :first-child > :first-child > :first-child { margin-top: 0; } +.col > :last-child, .col-1 > :last-child, .col-2 > :last-child, .col-3 > :last-child, .col-4 > :last-child, .col-5 > :last-child, .col-6 > :last-child, .col-7 > :last-child, .col-8 > :last-child, .col-9 > :last-child, .col-10 > :last-child, .col-11 > :last-child, .tsd-panel > :last-child, ul.tsd-descriptions > li > :last-child, .col > :last-child > :last-child, .col-1 > :last-child > :last-child, .col-2 > :last-child > :last-child, .col-3 > :last-child > :last-child, .col-4 > :last-child > :last-child, .col-5 > :last-child > :last-child, .col-6 > :last-child > :last-child, .col-7 > :last-child > :last-child, .col-8 > :last-child > :last-child, .col-9 > :last-child > :last-child, .col-10 > :last-child > :last-child, .col-11 > :last-child > :last-child, .tsd-panel > :last-child > :last-child, ul.tsd-descriptions > li > :last-child > :last-child, .col > :last-child > :last-child > :last-child, .col-1 > :last-child > :last-child > :last-child, .col-2 > :last-child > :last-child > :last-child, .col-3 > :last-child > :last-child > :last-child, .col-4 > :last-child > :last-child > :last-child, .col-5 > :last-child > :last-child > :last-child, .col-6 > :last-child > :last-child > :last-child, .col-7 > :last-child > :last-child > :last-child, .col-8 > :last-child > :last-child > :last-child, .col-9 > :last-child > :last-child > :last-child, .col-10 > :last-child > :last-child > :last-child, .col-11 > :last-child > :last-child > :last-child, .tsd-panel > :last-child > :last-child > :last-child, ul.tsd-descriptions > li > :last-child > :last-child > :last-child { margin-bottom: 0; } + +.container { max-width: 1200px; margin: 0 auto; padding: 0 40px; } +@media (max-width: 640px) { .container { padding: 0 20px; } } + +.container-main { padding-bottom: 200px; } + +.row { position: relative; margin: 0 -10px; } +.row:after { visibility: hidden; display: block; content: ""; clear: both; height: 0; } + +.col, .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11 { box-sizing: border-box; float: left; padding: 0 10px; } + +.col-1 { width: 8.33333%; } + +.offset-1 { margin-left: 8.33333%; } + +.col-2 { width: 16.66667%; } + +.offset-2 { margin-left: 16.66667%; } + +.col-3 { width: 25%; } + +.offset-3 { margin-left: 25%; } + +.col-4 { width: 33.33333%; } + +.offset-4 { margin-left: 33.33333%; } + +.col-5 { width: 41.66667%; } + +.offset-5 { margin-left: 41.66667%; } + +.col-6 { width: 50%; } + +.offset-6 { margin-left: 50%; } + +.col-7 { width: 58.33333%; } + +.offset-7 { margin-left: 58.33333%; } + +.col-8 { width: 66.66667%; } + +.offset-8 { margin-left: 66.66667%; } + +.col-9 { width: 75%; } + +.offset-9 { margin-left: 75%; } + +.col-10 { width: 83.33333%; } + +.offset-10 { margin-left: 83.33333%; } + +.col-11 { width: 91.66667%; } + +.offset-11 { margin-left: 91.66667%; } + +.tsd-kind-icon { display: block; position: relative; padding-left: 20px; text-indent: -20px; } + +.no-transition { transition: none !important; } + +@-webkit-keyframes fade-in { from { opacity: 0; } + to { opacity: 1; } } + +@keyframes fade-in { from { opacity: 0; } + to { opacity: 1; } } +@-webkit-keyframes fade-out { from { opacity: 1; visibility: visible; } + to { opacity: 0; } } +@keyframes fade-out { from { opacity: 1; visibility: visible; } + to { opacity: 0; } } +@-webkit-keyframes fade-in-delayed { 0% { opacity: 0; } + 33% { opacity: 0; } + 100% { opacity: 1; } } +@keyframes fade-in-delayed { 0% { opacity: 0; } + 33% { opacity: 0; } + 100% { opacity: 1; } } +@-webkit-keyframes fade-out-delayed { 0% { opacity: 1; visibility: visible; } + 66% { opacity: 0; } + 100% { opacity: 0; } } +@keyframes fade-out-delayed { 0% { opacity: 1; visibility: visible; } + 66% { opacity: 0; } + 100% { opacity: 0; } } +@-webkit-keyframes shift-to-left { from { -webkit-transform: translate(0, 0); transform: translate(0, 0); } + to { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } } +@keyframes shift-to-left { from { -webkit-transform: translate(0, 0); transform: translate(0, 0); } + to { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } } +@-webkit-keyframes unshift-to-left { from { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } + to { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } +@keyframes unshift-to-left { from { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } + to { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } +@-webkit-keyframes pop-in-from-right { from { -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } + to { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } +@keyframes pop-in-from-right { from { -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } + to { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } +@-webkit-keyframes pop-out-to-right { from { -webkit-transform: translate(0, 0); transform: translate(0, 0); visibility: visible; } + to { -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } } +@keyframes pop-out-to-right { from { -webkit-transform: translate(0, 0); transform: translate(0, 0); visibility: visible; } + to { -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } } + +a { color: #4da6ff; text-decoration: none; } +a:hover { text-decoration: underline; } + +pre { padding: 10px; } +pre code { padding: 0; font-size: 100%; background-color: transparent; } + +.tsd-typography ul { list-style: square; padding: 0 0 0 20px; margin: 0; } +.tsd-typography h4, .tsd-typography .tsd-index-panel h3, .tsd-index-panel .tsd-typography h3, .tsd-typography h5, .tsd-typography h6 { font-size: 1em; margin: 0; } +.tsd-typography h5, .tsd-typography h6 { font-weight: normal; } +.tsd-typography p, .tsd-typography ul, .tsd-typography ol { margin: 1em 0; } + +@media (min-width: 901px) and (max-width: 1024px) { html.default .col-content { width: 72%; } + html.default .col-menu { width: 28%; } + html.default .tsd-navigation { padding-left: 10px; } } +@media (max-width: 900px) { html.default .col-content { float: none; width: 100%; } + html.default .col-menu { position: fixed !important; overflow: auto; -webkit-overflow-scrolling: touch; overflow-scrolling: touch; z-index: 1024; top: 0 !important; bottom: 0 !important; left: auto !important; right: 0 !important; width: 100%; padding: 20px 20px 0 0; max-width: 450px; visibility: hidden; background-color: #fff; -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } + html.default .col-menu > *:last-child { padding-bottom: 20px; } + html.default .overlay { content: ""; display: block; position: fixed; z-index: 1023; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.75); visibility: hidden; } + html.default.to-has-menu .overlay { -webkit-animation: fade-in 0.4s; animation: fade-in 0.4s; } + html.default.to-has-menu header, html.default.to-has-menu footer, html.default.to-has-menu .col-content { -webkit-animation: shift-to-left 0.4s; animation: shift-to-left 0.4s; } + html.default.to-has-menu .col-menu { -webkit-animation: pop-in-from-right 0.4s; animation: pop-in-from-right 0.4s; } + html.default.from-has-menu .overlay { -webkit-animation: fade-out 0.4s; animation: fade-out 0.4s; } + html.default.from-has-menu header, html.default.from-has-menu footer, html.default.from-has-menu .col-content { -webkit-animation: unshift-to-left 0.4s; animation: unshift-to-left 0.4s; } + html.default.from-has-menu .col-menu { -webkit-animation: pop-out-to-right 0.4s; animation: pop-out-to-right 0.4s; } + html.default.has-menu body { overflow: hidden; } + html.default.has-menu .overlay { visibility: visible; } + html.default.has-menu header, html.default.has-menu footer, html.default.has-menu .col-content { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } + html.default.has-menu .col-menu { visibility: visible; -webkit-transform: translate(0, 0); transform: translate(0, 0); } } + +.tsd-page-title { padding: 0; margin: 0; background: #fff; } +.tsd-page-title h1 { font-weight: normal; margin: 0; } + +.tsd-breadcrumb { margin: 0; padding: 0; color: #808080; } +.tsd-breadcrumb a { color: #808080; text-decoration: none; } +.tsd-breadcrumb a:hover { text-decoration: underline; } +.tsd-breadcrumb li { display: inline; margin-right: -0.25em; } + +html.minimal .container { margin: 0; } +html.minimal .container-main { padding-top: 50px; padding-bottom: 0; } +html.minimal .content-wrap { padding-left: 300px; } +html.minimal .tsd-navigation { position: fixed !important; overflow: auto; -webkit-overflow-scrolling: touch; overflow-scrolling: touch; box-sizing: border-box; z-index: 1; left: 0; top: 40px; bottom: 0; width: 300px; padding: 20px; margin: 0; } +html.minimal .tsd-member .tsd-member { margin-left: 0; } +html.minimal .tsd-page-toolbar { position: fixed; z-index: 2; } +html.minimal #tsd-filter .tsd-filter-group { right: 0; -webkit-transform: none; transform: none; } +html.minimal footer { background-color: transparent; } +html.minimal footer .container { padding: 0; } +html.minimal .tsd-generator { padding: 0; } +@media (max-width: 900px) { html.minimal .tsd-navigation { display: none; } + html.minimal .content-wrap { padding-left: 0; } } + +dl.tsd-comment-tags { overflow: hidden; } +dl.tsd-comment-tags dt { clear: both; float: left; padding: 1px 5px; margin: 0 10px 0 0; border-radius: 4px; border: 1px solid #808080; color: #808080; font-size: 0.8em; font-weight: normal; } +dl.tsd-comment-tags dd { margin: 0 0 10px 0; } +dl.tsd-comment-tags p { margin: 0; } + +.tsd-panel.tsd-comment .lead { font-size: 1.1em; line-height: 1.333em; margin-bottom: 2em; } +.tsd-panel.tsd-comment .lead:last-child { margin-bottom: 0; } + +.toggle-protected .tsd-is-private { display: none; } + +.toggle-public .tsd-is-private, .toggle-public .tsd-is-protected, .toggle-public .tsd-is-private-protected { display: none; } + +.toggle-inherited .tsd-is-inherited { display: none; } + +.toggle-only-exported .tsd-is-not-exported { display: none; } + +.toggle-externals .tsd-is-external { display: none; } + +#tsd-filter { position: relative; display: inline-block; height: 40px; vertical-align: bottom; } +.no-filter #tsd-filter { display: none; } +#tsd-filter .tsd-filter-group { display: inline-block; height: 40px; vertical-align: bottom; white-space: nowrap; } +#tsd-filter input { display: none; } +@media (max-width: 900px) { #tsd-filter .tsd-filter-group { display: block; position: absolute; top: 40px; right: 20px; height: auto; background-color: #fff; visibility: hidden; -webkit-transform: translate(50%, 0); transform: translate(50%, 0); box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); } + .has-options #tsd-filter .tsd-filter-group { visibility: visible; } + .to-has-options #tsd-filter .tsd-filter-group { -webkit-animation: fade-in 0.2s; animation: fade-in 0.2s; } + .from-has-options #tsd-filter .tsd-filter-group { -webkit-animation: fade-out 0.2s; animation: fade-out 0.2s; } + #tsd-filter label, #tsd-filter .tsd-select { display: block; padding-right: 20px; } } + +footer { border-top: 1px solid #eee; background-color: #fff; } +footer.with-border-bottom { border-bottom: 1px solid #eee; } +footer .tsd-legend-group { font-size: 0; } +footer .tsd-legend { display: inline-block; width: 25%; padding: 0; font-size: 16px; list-style: none; line-height: 1.333em; vertical-align: top; } +@media (max-width: 900px) { footer .tsd-legend { width: 50%; } } + +.tsd-hierarchy { list-style: square; padding: 0 0 0 20px; margin: 0; } +.tsd-hierarchy .target { font-weight: bold; } + +.tsd-index-panel .tsd-index-content { margin-bottom: -30px !important; } +.tsd-index-panel .tsd-index-section { margin-bottom: 30px !important; } +.tsd-index-panel h3 { margin: 0 -20px 10px -20px; padding: 0 20px 10px 20px; border-bottom: 1px solid #eee; } +.tsd-index-panel ul.tsd-index-list { -webkit-column-count: 3; -moz-column-count: 3; -ms-column-count: 3; -o-column-count: 3; column-count: 3; -webkit-column-gap: 20px; -moz-column-gap: 20px; -ms-column-gap: 20px; -o-column-gap: 20px; column-gap: 20px; padding: 0; list-style: none; line-height: 1.333em; } +@media (max-width: 900px) { .tsd-index-panel ul.tsd-index-list { -webkit-column-count: 1; -moz-column-count: 1; -ms-column-count: 1; -o-column-count: 1; column-count: 1; } } +@media (min-width: 901px) and (max-width: 1024px) { .tsd-index-panel ul.tsd-index-list { -webkit-column-count: 2; -moz-column-count: 2; -ms-column-count: 2; -o-column-count: 2; column-count: 2; } } +.tsd-index-panel ul.tsd-index-list li { -webkit-column-break-inside: avoid; -moz-column-break-inside: avoid; -ms-column-break-inside: avoid; -o-column-break-inside: avoid; column-break-inside: avoid; -webkit-page-break-inside: avoid; -moz-page-break-inside: avoid; -ms-page-break-inside: avoid; -o-page-break-inside: avoid; page-break-inside: avoid; } + +.tsd-flag { display: inline-block; padding: 1px 5px; border-radius: 4px; color: #fff; background-color: #808080; text-indent: 0; font-size: 14px; font-weight: normal; line-height: 1.5em; } + +.tsd-anchor { position: absolute; top: -100px; } + +.tsd-member { position: relative; } +.tsd-member .tsd-anchor + h3 { margin-top: 0; margin-bottom: 0; border-bottom: none; } + +.tsd-navigation { padding: 0 0 0 40px; } +.tsd-navigation a { display: block; padding-top: 2px; padding-bottom: 2px; border-left: 2px solid transparent; color: #222; text-decoration: none; transition: border-left-color 0.1s; } +.tsd-navigation a:hover { text-decoration: underline; } +.tsd-navigation ul { margin: 0; padding: 0; list-style: none; } +.tsd-navigation li { padding: 0; } + +.tsd-navigation.primary { padding-bottom: 40px; } +.tsd-navigation.primary a { display: block; padding-top: 6px; padding-bottom: 6px; } +.tsd-navigation.primary ul li a { padding-left: 5px; } +.tsd-navigation.primary ul li li a { padding-left: 25px; } +.tsd-navigation.primary ul li li li a { padding-left: 45px; } +.tsd-navigation.primary ul li li li li a { padding-left: 65px; } +.tsd-navigation.primary ul li li li li li a { padding-left: 85px; } +.tsd-navigation.primary ul li li li li li li a { padding-left: 105px; } +.tsd-navigation.primary > ul { border-bottom: 1px solid #eee; } +.tsd-navigation.primary li { border-top: 1px solid #eee; } +.tsd-navigation.primary li.current > a { font-weight: bold; } +.tsd-navigation.primary li.label span { display: block; padding: 20px 0 6px 5px; color: #808080; } +.tsd-navigation.primary li.globals + li > span, .tsd-navigation.primary li.globals + li > a { padding-top: 20px; } + +.tsd-navigation.secondary ul { transition: opacity 0.2s; } +.tsd-navigation.secondary ul li a { padding-left: 25px; } +.tsd-navigation.secondary ul li li a { padding-left: 45px; } +.tsd-navigation.secondary ul li li li a { padding-left: 65px; } +.tsd-navigation.secondary ul li li li li a { padding-left: 85px; } +.tsd-navigation.secondary ul li li li li li a { padding-left: 105px; } +.tsd-navigation.secondary ul li li li li li li a { padding-left: 125px; } +.tsd-navigation.secondary ul.current a { border-left-color: #eee; } +.tsd-navigation.secondary li.focus > a, .tsd-navigation.secondary ul.current li.focus > a { border-left-color: #000; } +.tsd-navigation.secondary li.current { margin-top: 20px; margin-bottom: 20px; border-left-color: #eee; } +.tsd-navigation.secondary li.current > a { font-weight: bold; } + +@media (min-width: 901px) { .menu-sticky-wrap { position: static; } + .no-csspositionsticky .menu-sticky-wrap.sticky { position: fixed; } + .no-csspositionsticky .menu-sticky-wrap.sticky-current { position: fixed; } + .no-csspositionsticky .menu-sticky-wrap.sticky-current ul.before-current, .no-csspositionsticky .menu-sticky-wrap.sticky-current ul.after-current { opacity: 0; } + .no-csspositionsticky .menu-sticky-wrap.sticky-bottom { position: absolute; top: auto !important; left: auto !important; bottom: 0; right: 0; } + .csspositionsticky .menu-sticky-wrap.sticky { position: -webkit-sticky; position: sticky; } + .csspositionsticky .menu-sticky-wrap.sticky-current { position: -webkit-sticky; position: sticky; } } + +.tsd-panel { margin: 20px 0; padding: 20px; background-color: #fff; } +.tsd-panel:empty { display: none; } +.tsd-panel > h1, .tsd-panel > h2, .tsd-panel > h3 { margin: 1.5em -20px 10px -20px; padding: 0 20px 10px 20px; border-bottom: 1px solid #ebebeb; } +.tsd-panel > h1.tsd-before-signature, .tsd-panel > h2.tsd-before-signature, .tsd-panel > h3.tsd-before-signature { margin-bottom: 0; border-bottom: 0; } +.tsd-panel table { display: block; width: 100%; overflow: auto; margin-top: 10px; word-break: normal; word-break: keep-all; } +.tsd-panel table th { font-weight: bold; } +.tsd-panel table th, .tsd-panel table td { padding: 6px 13px; border: 1px solid #ddd; } +.tsd-panel table tr { background-color: #fff; border-top: 1px solid #ccc; } +.tsd-panel table tr:nth-child(2n) { background-color: #f8f8f8; } + +.tsd-panel-group { margin: 60px 0; } +.tsd-panel-group > h1, .tsd-panel-group > h2, .tsd-panel-group > h3 { padding-left: 20px; padding-right: 20px; } + +#tsd-search { transition: background-color 0.2s; } +#tsd-search .title { position: relative; z-index: 2; } +#tsd-search .field { position: absolute; left: 0; top: 0; right: 40px; height: 40px; } +#tsd-search .field input { box-sizing: border-box; position: relative; top: -50px; z-index: 1; width: 100%; padding: 0 10px; opacity: 0; outline: 0; border: 0; background: transparent; color: #222; } +#tsd-search .field label { position: absolute; overflow: hidden; right: -40px; } +#tsd-search .field input, #tsd-search .title { transition: opacity 0.2s; } +#tsd-search .results { position: absolute; visibility: hidden; top: 40px; width: 100%; margin: 0; padding: 0; list-style: none; box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); } +#tsd-search .results li { padding: 0 10px; background-color: #fdfdfd; } +#tsd-search .results li:nth-child(even) { background-color: #fff; } +#tsd-search .results li.state { display: none; } +#tsd-search .results li.current, #tsd-search .results li:hover { background-color: #eee; } +#tsd-search .results a { display: block; } +#tsd-search .results a:before { top: 10px; } +#tsd-search .results span.parent { color: #808080; font-weight: normal; } +#tsd-search.has-focus { background-color: #eee; } +#tsd-search.has-focus .field input { top: 0; opacity: 1; } +#tsd-search.has-focus .title { z-index: 0; opacity: 0; } +#tsd-search.has-focus .results { visibility: visible; } +#tsd-search.loading .results li.state.loading { display: block; } +#tsd-search.failure .results li.state.failure { display: block; } + +.tsd-signature { margin: 0 0 1em 0; padding: 10px; border: 1px solid #eee; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 14px; } +.tsd-signature.tsd-kind-icon { padding-left: 30px; } +.tsd-panel > .tsd-signature { margin-left: -20px; margin-right: -20px; border-width: 1px 0; } +.tsd-panel > .tsd-signature.tsd-kind-icon { padding-left: 40px; } + +.tsd-signature-symbol { color: #808080; font-weight: normal; } + +.tsd-signature-type { font-style: italic; font-weight: normal; } + +.tsd-signatures { padding: 0; margin: 0 0 1em 0; border: 1px solid #eee; } +.tsd-signatures .tsd-signature { margin: 0; border-width: 1px 0 0 0; transition: background-color 0.1s; } +.tsd-signatures .tsd-signature:first-child { border-top-width: 0; } +.tsd-signatures .tsd-signature.current { background-color: #eee; } +.tsd-signatures.active > .tsd-signature { cursor: pointer; } +.tsd-panel > .tsd-signatures { margin-left: -20px; margin-right: -20px; border-width: 1px 0; } +.tsd-panel > .tsd-signatures .tsd-signature.tsd-kind-icon { padding-left: 40px; } +.tsd-panel > a.anchor + .tsd-signatures { border-top-width: 0; margin-top: -20px; } + +ul.tsd-descriptions { position: relative; overflow: hidden; transition: height 0.3s; padding: 0; list-style: none; } +ul.tsd-descriptions.active > .tsd-description { display: none; } +ul.tsd-descriptions.active > .tsd-description.current { display: block; } +ul.tsd-descriptions.active > .tsd-description.fade-in { -webkit-animation: fade-in-delayed 0.3s; animation: fade-in-delayed 0.3s; } +ul.tsd-descriptions.active > .tsd-description.fade-out { -webkit-animation: fade-out-delayed 0.3s; animation: fade-out-delayed 0.3s; position: absolute; display: block; top: 0; left: 0; right: 0; opacity: 0; visibility: hidden; } +ul.tsd-descriptions h4, ul.tsd-descriptions .tsd-index-panel h3, .tsd-index-panel ul.tsd-descriptions h3 { font-size: 16px; margin: 1em 0 0.5em 0; } + +ul.tsd-parameters, ul.tsd-type-parameters { list-style: square; margin: 0; padding-left: 20px; } +ul.tsd-parameters > li.tsd-parameter-siganture, ul.tsd-type-parameters > li.tsd-parameter-siganture { list-style: none; margin-left: -20px; } +ul.tsd-parameters h5, ul.tsd-type-parameters h5 { font-size: 16px; margin: 1em 0 0.5em 0; } +ul.tsd-parameters .tsd-comment, ul.tsd-type-parameters .tsd-comment { margin-top: -0.5em; } + +.tsd-sources { font-size: 14px; color: #808080; } +.tsd-sources a { color: #808080; text-decoration: underline; } +.tsd-sources ul, .tsd-sources p { margin: 0 !important; } +.tsd-sources ul { list-style: none; padding: 0; } + +.tsd-page-toolbar { position: absolute; z-index: 1; top: 0; left: 0; width: 100%; height: 40px; color: #333; background: #fff; border-bottom: 1px solid #eee; } +.tsd-page-toolbar a { color: #333; text-decoration: none; } +.tsd-page-toolbar a.title { font-weight: bold; } +.tsd-page-toolbar a.title:hover { text-decoration: underline; } +.tsd-page-toolbar .table-wrap { display: table; width: 100%; height: 40px; } +.tsd-page-toolbar .table-cell { display: table-cell; position: relative; white-space: nowrap; line-height: 40px; } +.tsd-page-toolbar .table-cell:first-child { width: 100%; } + +.tsd-widget:before, .tsd-select .tsd-select-label:before, .tsd-select .tsd-select-list li:before { content: ""; display: inline-block; width: 40px; height: 40px; margin: 0 -8px 0 0; background-image: url(../images/widgets.png); background-repeat: no-repeat; text-indent: -1024px; vertical-align: bottom; } +@media (-webkit-min-device-pixel-ratio: 1.5), (min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { .tsd-widget:before, .tsd-select .tsd-select-label:before, .tsd-select .tsd-select-list li:before { background-image: url(../images/widgets@2x.png); background-size: 320px 40px; } } + +.tsd-widget { display: inline-block; overflow: hidden; opacity: 0.6; height: 40px; transition: opacity 0.1s, background-color 0.2s; vertical-align: bottom; cursor: pointer; } +.tsd-widget:hover { opacity: 0.8; } +.tsd-widget.active { opacity: 1; background-color: #eee; } +.tsd-widget.no-caption { width: 40px; } +.tsd-widget.no-caption:before { margin: 0; } +.tsd-widget.search:before { background-position: 0 0; } +.tsd-widget.menu:before { background-position: -40px 0; } +.tsd-widget.options:before { background-position: -80px 0; } +.tsd-widget.options, .tsd-widget.menu { display: none; } +@media (max-width: 900px) { .tsd-widget.options, .tsd-widget.menu { display: inline-block; } } +input[type=checkbox] + .tsd-widget:before { background-position: -120px 0; } +input[type=checkbox]:checked + .tsd-widget:before { background-position: -160px 0; } + +.tsd-select { position: relative; display: inline-block; height: 40px; transition: opacity 0.1s, background-color 0.2s; vertical-align: bottom; cursor: pointer; } +.tsd-select .tsd-select-label { opacity: 0.6; transition: opacity 0.2s; } +.tsd-select .tsd-select-label:before { background-position: -240px 0; } +.tsd-select.active .tsd-select-label { opacity: 0.8; } +.tsd-select.active .tsd-select-list { visibility: visible; opacity: 1; transition-delay: 0s; } +.tsd-select .tsd-select-list { position: absolute; visibility: hidden; top: 40px; left: 0; margin: 0; padding: 0; opacity: 0; list-style: none; box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); transition: visibility 0s 0.2s, opacity 0.2s; } +.tsd-select .tsd-select-list li { padding: 0 20px 0 0; background-color: #fdfdfd; } +.tsd-select .tsd-select-list li:before { background-position: 40px 0; } +.tsd-select .tsd-select-list li:nth-child(even) { background-color: #fff; } +.tsd-select .tsd-select-list li:hover { background-color: #eee; } +.tsd-select .tsd-select-list li.selected:before { background-position: -200px 0; } +@media (max-width: 900px) { .tsd-select .tsd-select-list { top: 0; left: auto; right: 100%; margin-right: -5px; } + .tsd-select .tsd-select-label:before { background-position: -280px 0; } } + +img { max-width: 100%; } diff --git a/docgen/theme/assets/images/lockup.png b/docgen/theme/assets/images/lockup.png new file mode 100644 index 0000000000000000000000000000000000000000..f4cdcf24a42787815f45a0fbd6b2211b69cf87a0 GIT binary patch literal 2646 zcmV-c3aRypP)qsBRaHO8(yazJJDcK~r7o->-O8-CmCY zr#`z4j)yXEeD5C+QGXbQp&O3{6EN^efRRlACm;I?A{d6LFLdL<9qEFg1D^mmd|wt3 z48znHx^b-gd_m5Ej{^+djaf9@j|hfg>IdC8+I~|`$@3orIJi9vC-!?0!7xmHsN8V! zv28Hov*eTv-U-;2So2OZ3=0|#S#nA~dXus49GuwaMTGk>3{$z`=tGHBaz6JF{69*I z)m=oa;&>=lL@*3f8!9&(+L0=gv&e@5(p@9==0-nJs{ngs~{)x zp#rjiWM$D%LcX>j=QGVWUw`b;1R|7N`%VaHLxhP)foe!;#45B0#QKRaeSRFSMqw#s z_*1l;&$dcr7ER}m?)K(!%3GAvBHfXLW4pYFAi4U@6rA4$aQRDFM3{(Mf)edf7Sv9# zpdncdqUDr$AwRqk7RG|5btEGoq;p#flcV-Ai!(uUA- zzV&HY!Zg%^);U;E4oG)otI3(i%sBV^f}BN~VDfkPXa8x&ENaOqkr{OE>chA}1mC5O%rG5Hw&F!B+JzqA`xGNCb%!$EN$Q4uA33 z(XwMkkzV8ugbeq`90Lw)O;B>?pTZ3J>xyzB4Fx8CiK70k$%&j6*CPFhFbOR|^)rx< zc#V&^K&j;#57H~!91unBaM1rGBI6Du=HN401reI(5O?si3RvT*q>t*eyJKz%nyw_L zWZ_@c<(x`xfQcVA!0d@foXUyx09@RYK!iz{CMfY|<)V1XE)-fA^|9`-+5adKsPSj$ zpMyFxMO^5!OJj}!!)=*zIj!uekn;x(Fn{RbTavRVXodw<3aa{>K7v?LoMvi}L|Se1 zKaM0_xhVC|!A~5w*M)I$am+DbWaDZ%pDtuiLQbTZIkn`JTzE8#2$L{LP(s#T#E%4! zNZExRq^4;Vgt(*11sf&%Pm?Sj&sRbKiPGm{WqzCI;4eFV7)jbZc59V>1j>#ZM3U-p zae2()c&H64xR#tnmYs7u0NE@2~W%HNyu5NX>BV>XJDvF6>Ps!W0Z$W>G}|`LzoKHKvGmvqrMY z^fGL7y)sT@qgEp0QST30!W%H=FtRCEdOs&-Pq#8B&G|>pBf=C66!Z?e$993Ry8o_> z164$EiqGM3%Xbq6k=8XEa~S;OikwT2qnJM>mQvi3)4)oufr5&4FR1z)J^FI01*LaB zDGAqo%;DIskh}*}$|-LdoBNYXODWZOv-{?r+^gT%h6n?=odgZn0AF1MwZcPAL#>@l zzA^$xgcOvHfJ0GZbscjQv|q|8SxU+5Ni3zfDQ6KB<0~v^%?0%!`cdC0Zotv@J@rNe zpUw9LkX{?Y_SS98VRUoC%ARCF$bC5_mp-3BgaNEqL4BG_2z0n#B#JAZ+Z1%Lxn942 z>g?KDmobOYjVt!IC`W|`CC-3Ky=FaZ+<)wr1MaG2TRNRXJSxwKKin!aj0mX!ML>Z6x! zP%w4)6n%=hHs&yJ2Q2-su9=gd3qSWEi7k(u7e9;m}+`GAdCKv3g8 zOdlb?-5U_1Hm3FQnvXdg35FK_)3ElL(?SXwVnH~LV?Ou4&Xf_E(?YM3%T-)@3Z*Zn>X9eU8T|*y%r$VZYzD1yw$T zjd*IJ$Bi+sc-;4fd%QQ&J*nB#TOj}D+R2%Jy$fbecEI(4O)12Sh}yywFPb0{EfXn* z^wAr_)j>SYBU+M4;>4zNonu;$#nkV&zsm6=8XFJDF?TW^D5}3Lm-c7oGF$2{=j`bY z$PI7K;6>ENA~B4`>OUOn4&4~@XBS`Uab3>&bDeNwwEg_`fz2UAFbwlS=*FS0{)xlg zx#ib-YA)x}%bhTBusw^P)35F%!?2)~q~7a8or%e#-IMt@^yOTBwF{<>cI5Eb3B;@J zAj7brN>I|a+-PTN=2Vw+IjsVa{GOR&F&RBrP@2IG{C?Vrg`{rVa?YLYEEa$^xAB0x z3B!U~qJCxKP$w3W2r-ex7r6lBIt&Y{5H&G*q$9WdswL*q%UxUmVvK+Vl?=9fk;KHo zj*Ns0K<>k^pbW#XpbW#XpbW!;G7Q6lG7JmKFpLSj52E7_O`Jf{)c^nh07*qoM6N<$ Eg8U-`K>z>% literal 0 HcmV?d00001 diff --git a/docgen/theme/layouts/default.hbs b/docgen/theme/layouts/default.hbs new file mode 100644 index 000000000..72111fb4e --- /dev/null +++ b/docgen/theme/layouts/default.hbs @@ -0,0 +1,33 @@ + + + + + + + + + + {{#ifCond model.name '==' project.name}}{{project.name}}{{else}}{{model.name}} | {{project.name}}{{/ifCond}} + + + + + + + +{{> header}} + +
+
+
+ {{{contents}}} +
+
+
+ +
+ +{{> analytics}} + + + diff --git a/docgen/theme/partials/breadcrumb.hbs b/docgen/theme/partials/breadcrumb.hbs new file mode 100644 index 000000000..db115163f --- /dev/null +++ b/docgen/theme/partials/breadcrumb.hbs @@ -0,0 +1,11 @@ + +{{#if parent}} + {{#with parent}}{{> breadcrumb}}{{/with}} + +{{/if}} \ No newline at end of file diff --git a/docgen/theme/partials/comment.hbs b/docgen/theme/partials/comment.hbs new file mode 100644 index 000000000..f92e54301 --- /dev/null +++ b/docgen/theme/partials/comment.hbs @@ -0,0 +1,22 @@ +{{#with comment}} + {{#if hasVisibleComponent}} +
+ {{#if shortText}} +
+ {{#markdown}}{{{shortText}}}{{/markdown}} +
+ {{/if}} + {{#if text}} + {{#markdown}}{{{text}}}{{/markdown}} + {{/if}} + {{#if tags}} +
+ {{#each tags}} +
{{tagName}}
+
{{#markdown}}{{{text}}}{{/markdown}}
+ {{/each}} +
+ {{/if}} +
+ {{/if}} +{{/with}} \ No newline at end of file diff --git a/docgen/theme/partials/header.hbs b/docgen/theme/partials/header.hbs new file mode 100644 index 000000000..4aee65a6b --- /dev/null +++ b/docgen/theme/partials/header.hbs @@ -0,0 +1,23 @@ +
+
+
+

+ {{#ifCond model.name '==' project.name}} + {{else}} +
    + {{#with model.parent}}{{> breadcrumb}}{{/with}} +
  • {{model.name}}
  • + {{#if model.typeParameters}} + < + {{#each model.typeParameters}} + {{#if @index}}, {{/if}} + {{name}} + {{/each}} + > + {{/if}} +
+ {{/ifCond}} +

+
+
+
\ No newline at end of file diff --git a/docgen/theme/partials/member.sources.hbs b/docgen/theme/partials/member.sources.hbs new file mode 100644 index 000000000..5a0e186f0 --- /dev/null +++ b/docgen/theme/partials/member.sources.hbs @@ -0,0 +1,15 @@ +{{#if implementationOf}} + +{{/if}} +{{#if inheritedFrom}} + +{{/if}} +{{#if overwrites}} + +{{/if}} \ No newline at end of file diff --git a/docgen/theme/partials/navigation.hbs b/docgen/theme/partials/navigation.hbs new file mode 100644 index 000000000..54704739a --- /dev/null +++ b/docgen/theme/partials/navigation.hbs @@ -0,0 +1,22 @@ +{{#if isVisible}} + {{#if isLabel}} +
  • + {{{wbr title}}} +
  • + {{else}} + {{#unless isGlobals}} +
  • + {{{wbr title}}} + {{#if isInPath}} + {{#if children}} +
      + {{#each children}} + {{> navigation}} + {{/each}} +
    + {{/if}} + {{/if}} +
  • + {{/unless}} + {{/if}} +{{/if}} diff --git a/docgen/theme/templates/reflection.hbs b/docgen/theme/templates/reflection.hbs new file mode 100644 index 000000000..53cd2879a --- /dev/null +++ b/docgen/theme/templates/reflection.hbs @@ -0,0 +1,72 @@ +{{#with model}} + {{#if hasComment}} +
    + {{> comment}} +
    + {{/if}} +{{/with}} + +{{#if model.typeParameters}} +
    +

    Type parameters

    + {{#with model}}{{> typeParameters}}{{/with}} +
    +{{/if}} + +{{#if model.implementedTypes}} +
    +

    Implements

    +
      + {{#each model.implementedTypes}} +
    • {{> type}}
    • + {{/each}} +
    +
    +{{/if}} + +{{#if model.implementedBy}} +
    +

    Implemented by

    +
      + {{#each model.implementedBy}} +
    • {{> type}}
    • + {{/each}} +
    +
    +{{/if}} + +{{#if model.signatures}} +
    +

    Callable

    + {{#with model}}{{> member.signatures}}{{/with}} +
    +{{/if}} + +{{#if model.indexSignature}} +
    +

    Indexable

    +
    {{#compact}} + [ + {{#each model.indexSignature.parameters}} + {{name}}: {{#with type}}{{>type}}{{/with}} + {{/each}} + ]:  + {{#with model.indexSignature.type}}{{>type}}{{/with}} + {{/compact}}
    + + {{#with model.indexSignature}} + {{> comment}} + {{/with}} + + {{#if model.indexSignature.type.declaration}} + {{#with model.indexSignature.type.declaration}} + {{> parameter}} + {{/with}} + {{/if}} +
    +{{/if}} + +{{#with model}} + {{> index}} + {{> members}} +{{/with}} \ No newline at end of file diff --git a/docgen/tsconfig.json b/docgen/tsconfig.json new file mode 100644 index 000000000..3c43903cf --- /dev/null +++ b/docgen/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.json" +} diff --git a/docgen/typedoc.js b/docgen/typedoc.js new file mode 100644 index 000000000..58156ff59 --- /dev/null +++ b/docgen/typedoc.js @@ -0,0 +1,27 @@ +/** + * @license + * Copyright 2019 Google Inc. + * + * 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 options = { + // includeDeclarations: true, + excludeExternals: true, + ignoreCompilerErrors: true, + name: 'Firebase Functions SDK', + mode: 'modules', + hideGenerator: true +}; + +module.exports = options; diff --git a/package.json b/package.json index 916c4159f..10f0c33d1 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "format:fix": "prettier --write '**/*.{json,md,ts,yml,yaml}'", "lint": "tslint --config tslint.json --project tsconfig.json ", "lint:fix": "tslint --config tslint.json --fix --project tsconfig.json", - "test": "mocha -r ts-node/register ./spec/index.spec.ts" + "test": "mocha -r ts-node/register ./spec/index.spec.ts", + "apidocs": "node docgen/generate-docs.js" }, "dependencies": { "@types/express": "^4.17.0", @@ -51,10 +52,13 @@ "@types/sinon": "^7.0.13", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", + "child-process-promise": "^2.2.1", "firebase-admin": "^8.2.0", "istanbul": "^0.4.5", + "js-yaml": "^3.13.1", "mocha": "^6.1.4", "mock-require": "^3.0.3", + "mz": "^2.7.0", "nock": "^10.0.6", "prettier": "^1.18.2", "sinon": "^7.3.2", @@ -63,7 +67,9 @@ "tslint-config-prettier": "^1.18.0", "tslint-no-unused-expression-chai": "^0.1.4", "tslint-plugin-prettier": "^2.0.1", - "typescript": "^3.5.2" + "typedoc": "^0.14.2", + "typescript": "^3.5.2", + "yargs": "^13.2.4" }, "peerDependencies": { "firebase-admin": "^8.0.0" diff --git a/src/providers/auth.ts b/src/providers/auth.ts index 722410340..7274399b2 100644 --- a/src/providers/auth.ts +++ b/src/providers/auth.ts @@ -116,6 +116,11 @@ export class UserBuilder { */ export type UserRecord = firebase.auth.UserRecord; +/** + * UserInfo that is part of the UserRecord + */ +export type UserInfo = firebase.auth.UserInfo; + export function userRecordConstructor( wireData: Object ): firebase.auth.UserRecord { From 273c5ff776450742c4a794f0227456cbd7c64cee Mon Sep 17 00:00:00 2001 From: Erik Macik <42585523+esmacik@users.noreply.github.com> Date: Thu, 11 Jul 2019 18:24:44 -0700 Subject: [PATCH 072/437] Add code comments for storage events (#517) * Add code comments for storage events * Fix some comment formatting issues --- src/providers/storage.ts | 179 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 169 insertions(+), 10 deletions(-) diff --git a/src/providers/storage.ts b/src/providers/storage.ts index 93d6b0ed0..3ba98ea72 100644 --- a/src/providers/storage.ts +++ b/src/providers/storage.ts @@ -34,17 +34,22 @@ export const provider = 'google.storage'; export const service = 'storage.googleapis.com'; /** - * The optional bucket function allows you to choose which buckets' events to handle. - * This step can be bypassed by calling object() directly, which will use the default - * Cloud Storage for Firebase bucket. - * @param bucket Name of the Google Cloud Storage bucket to listen to. + * Registers a Cloud Function scoped to a specific storage bucket. + * + * @param bucket Name of the bucket to which this Cloud Function is + * scoped. + * + * @return Storage bucket builder interface. */ export function bucket(bucket?: string) { return _bucketWithOptions({}, bucket); } /** - * Handle events related to Cloud Storage objects. + * Registers a Cloud Function scoped to the default storage bucket for the + * project. + * + * @return Storage object builder interface. */ export function object() { return _objectWithOptions({}); @@ -76,6 +81,11 @@ export function _objectWithOptions(options: DeploymentOptions): ObjectBuilder { return _bucketWithOptions(options).object(); } +/** + * The Google Cloud Storage bucket builder interface. + * + * Access via [`functions.storage.bucket()`](functions.storage#.bucket). + */ export class BucketBuilder { /** @hidden */ constructor( @@ -83,12 +93,22 @@ export class BucketBuilder { private options: DeploymentOptions ) {} - /** Handle events for objects in this bucket. */ + /** + * Event handler which fires every time a Google Cloud Storage change occurs. + * + * @return Storage object builder interface scoped to the specified storage + * bucket. + */ object() { return new ObjectBuilder(this.triggerResource, this.options); } } +/** + * The Google Cloud Storage object builder interface. + * + * Access via [`functions.storage.object()`](functions.storage#.object). + */ export class ObjectBuilder { /** @hidden */ constructor( @@ -104,7 +124,17 @@ export class ObjectBuilder { ); } - /** Respond to archiving of an object, this is only for buckets that enabled object versioning. */ + /** + * Event handler sent only when a bucket has enabled object versioning. + * This event indicates that the live version of an object has become an + * archived version, either because it was archived or because it was + * overwritten by the upload of an object of the same name. + * + * @param handler Event handler which is run every time a Google Cloud Storage + * archival occurs. + * + * @return A Cloud Function which you can export and deploy. + */ onArchive( handler: ( object: ObjectMetadata, @@ -114,7 +144,20 @@ export class ObjectBuilder { return this.onOperation(handler, 'object.archive'); } - /** Respond to the deletion of an object (not to archiving, if object versioning is enabled). */ + /** + * Event handler which fires every time a Google Cloud Storage deletion occurs. + * + * Sent when an object has been permanently deleted. This includes objects + * that are overwritten or are deleted as part of the bucket's lifecycle + * configuration. For buckets with object versioning enabled, this is not + * sent when an object is archived, even if archival occurs + * via the `storage.objects.delete` method. + * + * @param handler Event handler which is run every time a Google Cloud Storage + * deletion occurs. + * + * @return A Cloud Function which you can export and deploy. + */ onDelete( handler: ( object: ObjectMetadata, @@ -124,7 +167,19 @@ export class ObjectBuilder { return this.onOperation(handler, 'object.delete'); } - /** Respond to the successful creation of an object. */ + /** + * Event handler which fires every time a Google Cloud Storage object + * creation occurs. + * + * Sent when a new object (or a new generation of an existing object) + * is successfully created in the bucket. This includes copying or rewriting + * an existing object. A failed upload does not trigger this event. + * + * @param handler Event handler which is run every time a Google Cloud Storage + * object creation occurs. + * + * @return A Cloud Function which you can export and deploy. + */ onFinalize( handler: ( object: ObjectMetadata, @@ -134,7 +189,15 @@ export class ObjectBuilder { return this.onOperation(handler, 'object.finalize'); } - /** Respond to metadata updates of existing objects. */ + /** + * Event handler which fires every time the metadata of an existing object + * changes. + * + * @param handler Event handler which is run every time a Google Cloud Storage + * metadata update occurs. + * + * @return A Cloud Function which you can export and deploy. + */ onMetadataUpdate( handler: ( object: ObjectMetadata, @@ -144,6 +207,7 @@ export class ObjectBuilder { return this.onOperation(handler, 'object.metadataUpdate'); } + /** @hidden */ private onOperation( handler: ( object: ObjectMetadata, @@ -162,30 +226,101 @@ export class ObjectBuilder { } } +/** Interface representing a Google Google Cloud Storage object metadata object. */ export interface ObjectMetadata { + /** The kind of the object, which is always `storage#object`. */ kind: string; + + /** + * The ID of the object, including the bucket name, object name, and + * generation number. + */ id: string; + + /** Storage bucket that contains the object. */ bucket: string; + + /** Storage class of the object. */ storageClass: string; + + /** + * The value of the `Content-Length` header, used to determine the length of + * the object data in bytes. + */ size: string; + + /** The creation time of the object in RFC 3339 format. */ timeCreated: string; + + /** + * The modification time of the object metadata in RFC 3339 format. + */ updated: string; + + /** Link to access the object, assuming you have sufficient permissions. */ selfLink?: string; + + /**The object's name. */ name?: string; + + /** + * Generation version number that changes each time the object is + * overwritten. + */ generation?: string; + + /** The object's content type, also known as the MIME type. */ contentType?: string; + + /** + * Meta-generation version number that changes each time the object's metadata + * is updated. + */ metageneration?: string; + + /** + * The deletion time of the object in RFC 3339 format. Returned + * only if this version of the object has been deleted. + */ timeDeleted?: string; + timeStorageClassUpdated?: string; + + /** + * MD5 hash for the object. All Google Cloud Storage objects + * have a CRC32C hash or MD5 hash. + */ md5Hash?: string; + + /** Media download link. */ mediaLink?: string; + + /** + * Content-Encoding to indicate that an object is compressed + * (for example, with gzip compression) while maintaining its Content-Type. + */ contentEncoding?: string; + + /** + * The value of the `Content-Disposition` header, used to specify presentation + * information about the data being transmitted. + */ contentDisposition?: string; + + /** ISO 639-1 language code of the content. */ contentLanguage?: string; + + /** + * The value of the `Cache-Control` header, used to determine whether Internet + * caches are allowed to cache public data for an object. + */ cacheControl?: string; + + /** User-provided metadata. */ metadata?: { [key: string]: string; }; + acl?: [ { kind?: string; @@ -206,13 +341,37 @@ export interface ObjectMetadata { etag?: string; } ]; + owner?: { entity?: string; entityId?: string; }; + + /** + * The object's CRC32C hash. All Google Cloud Storage objects + * have a CRC32C hash or MD5 hash. + */ crc32c?: string; + + /** + * Specifies the number of originally uploaded objects from which + * a composite object was created. + */ componentCount?: string; + etag?: string; + + /** + * Customer-supplied encryption key. + * + * This object contains the following properties: + * * `encryptionAlgorithm` (`string|undefined`): The encryption algorithm that + * was used. Always contains the value `AES256`. + * * `keySha256` (`string|undefined`): An RFC 4648 base64-encoded string of the + * SHA256 hash of your encryption key. You can use this SHA256 hash to + * uniquely identify the AES-256 encryption key required to decrypt the + * object, which you must store securely. + */ customerEncryption?: { encryptionAlgorithm?: string; keySha256?: string; From 1f6499c87a596393dd721fa36a1397cfac81e454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Fri, 12 Jul 2019 18:36:10 +0200 Subject: [PATCH 073/437] Introduce Mocha configuration file (#515) * Introduce mocha configuration file * Reformat * Remove an empty file --- .mocharc.yaml | 10 ++++++++++ mocha/setup.ts | 7 +++++++ package.json | 8 +++++--- spec/index.spec.ts | 47 ---------------------------------------------- 4 files changed, 22 insertions(+), 50 deletions(-) create mode 100644 .mocharc.yaml create mode 100644 mocha/setup.ts delete mode 100644 spec/index.spec.ts diff --git a/.mocharc.yaml b/.mocharc.yaml new file mode 100644 index 000000000..932144124 --- /dev/null +++ b/.mocharc.yaml @@ -0,0 +1,10 @@ +exit: true +extension: + - ts +file: + - mocha/setup.ts +package: ./package.json +reporter: spec +require: + - 'ts-node/register' +spec: spec/**/*.spec.ts diff --git a/mocha/setup.ts b/mocha/setup.ts new file mode 100644 index 000000000..ba4d08621 --- /dev/null +++ b/mocha/setup.ts @@ -0,0 +1,7 @@ +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as nock from 'nock'; + +chai.use(chaiAsPromised); + +nock.disableNetConnect(); diff --git a/package.json b/package.json index 10f0c33d1..76ea87fb5 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,13 @@ }, "license": "MIT", "author": "Firebase Team", - "files": ["lib"], + "files": [ + "lib" + ], "main": "lib/index.js", "types": "lib/index.d.ts", "scripts": { + "apidocs": "node docgen/generate-docs.js", "build:pack": "rm -rf lib && npm install && tsc -p tsconfig.release.json && npm pack", "build:release": "npm install --production && npm install typescript firebase-admin && tsc -p tsconfig.release.json", "build": "tsc -p tsconfig.release.json", @@ -29,8 +32,7 @@ "format:fix": "prettier --write '**/*.{json,md,ts,yml,yaml}'", "lint": "tslint --config tslint.json --project tsconfig.json ", "lint:fix": "tslint --config tslint.json --fix --project tsconfig.json", - "test": "mocha -r ts-node/register ./spec/index.spec.ts", - "apidocs": "node docgen/generate-docs.js" + "test": "mocha" }, "dependencies": { "@types/express": "^4.17.0", diff --git a/spec/index.spec.ts b/spec/index.spec.ts deleted file mode 100644 index 0eb95045d..000000000 --- a/spec/index.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2017 Firebase -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import * as chai from 'chai'; -import * as chaiAsPromised from 'chai-as-promised'; -chai.use(chaiAsPromised); - -import * as nock from 'nock'; -nock.disableNetConnect(); - -import 'mocha'; - -import './apps.spec'; -import './cloud-functions.spec'; -import './config.spec'; -import './function-builder.spec'; -import './providers/analytics.spec'; -import './providers/auth.spec'; -import './providers/crashlytics.spec'; -import './providers/database.spec'; -import './providers/firestore.spec'; -import './providers/https.spec'; -import './providers/pubsub.spec'; -import './providers/remoteConfig.spec'; -import './providers/storage.spec'; -import './setup.spec'; -import './utilities/path.spec'; -import './utils.spec'; From 87e75d717d5acf32d893e867078c531542d30c69 Mon Sep 17 00:00:00 2001 From: egilmorez Date: Fri, 12 Jul 2019 11:59:01 -0700 Subject: [PATCH 074/437] Adding comments for EventContext and main CloudFunctions methods. (#519) * Adding comments for EventContext and main CloudFunctions methods. * Fixing spacing and alignment issues found by thechenky. * Committing edits from npm run format:fix. --- src/cloud-functions.ts | 100 +++++++++++++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 18 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 686e9e31e..b35ba6ee1 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -25,11 +25,13 @@ import * as _ from 'lodash'; import { DeploymentOptions, Schedule } from './function-configuration'; export { Request, Response }; +/** @hidden */ const WILDCARD_REGEX = new RegExp('{[^/{}]*}', 'g'); /** - * Wire format for an event. * @hidden + * + * Wire format for an event. */ export interface Event { context: { @@ -53,38 +55,89 @@ export interface Event { */ export interface EventContext { /** - * Firebase auth variable for the user whose action triggered the function. - * Field will be null for unauthenticated users, and will not exist for admin - * users. Only available for database functions. + * Authentication information for the user that triggered the function. + * This object contains `uid` and `token` properties for authenticated users. + * For more detail including token keys, see the + * [security rules reference](/docs/firestore/reference/security/#properties). + * + * This field is only populated for Realtime Database triggers and Callable + * functions. For an unauthenticated user, this field is null. For Firebase + * admin users and event types that do not provide user information, this field + * does not exist. */ auth?: { token: object; uid: string; }; + /** - * Type of authentication for the triggering action. Only available for - * database functions. + * The level of permissions for a user. Valid values are: + * + * * `ADMIN` Developer user or user authenticated via a service account. + * * `USER` Known user. + * * `UNAUTHENTICATED` Unauthenticated action + * * `null` For event types that do not provide user information (all except + * Realtime Database). */ authType?: 'ADMIN' | 'USER' | 'UNAUTHENTICATED'; + /** - * ID of the event + * The event’s unique identifier. */ eventId: string; + /** - * Type of event + * Type of event. Valid values are: + * + * * `providers/google.firebase.analytics/eventTypes/event.log` + * * `providers/firebase.auth/eventTypes/user.create` + * * `providers/firebase.auth/eventTypes/user.delete` + * * `providers/firebase.crashlytics/eventTypes/issue.new` + * * `providers/firebase.crashlytics/eventTypes/issue.regressed` + * * `providers/firebase.crashlytics/eventTypes/issue.velocityAlert` + * * `providers/google.firebase.database/eventTypes/ref.write` + * * `providers/google.firebase.database/eventTypes/ref.create` + * * `providers/google.firebase.database/eventTypes/ref.update` + * * `providers/google.firebase.database/eventTypes/ref.delete` + * * `providers/cloud.firestore/eventTypes/document.write` + * * `providers/cloud.firestore/eventTypes/document.create` + * * `providers/cloud.firestore/eventTypes/document.update` + * * `providers/cloud.firestore/eventTypes/document.delete` + * * `google.pubsub.topic.publish` + * * `google.storage.object.finalize` + * * `google.storage.object.archive` + * * `google.storage.object.delete` + * * `google.storage.object.metadataUpdate` + * * `google.firebase.remoteconfig.update` */ eventType: string; + /** - * Key-value pairs that represent the values of wildcards in a database - * reference. Cannot be accessed while inside the handler namespace. + * An object containing the values of the wildcards in the `path` parameter + * provided to the [`ref()`](functions.database#.ref) method for a Realtime + * Database trigger. Cannot be accessed while inside the handler namespace. */ params: { [option: string]: any }; + /** - * Resource that triggered the event + * The resource that emitted the event. Valid values are: + * + * * Analytics — `projects//events/` + * * Realtime Database — + `projects/_/instances//refs/` + * * Storage — + `projects/_/buckets//objects/#` + * * Authentication — `projects/` + * * Pub/Sub — `projects//topics/` + * + * Because Realtime Database instances and Cloud Storage buckets are globally + * unique and not tied to the project, their resources start with `projects/_`. + * Underscore is not a valid project name. */ resource: Resource; /** - * Timestamp for when the event ocurred (ISO 8601 string) + * Timestamp for the event as an + * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) string. */ timestamp: string; } @@ -180,6 +233,7 @@ export interface Resource { } /** + * @hidden * TriggerAnnotated is used internally by the firebase CLI to understand what * type of Cloud Function to deploy. */ @@ -208,17 +262,23 @@ export interface Runnable { } /** - * An HttpsFunction is both an object that exports its trigger definitions at - * __trigger and can be called as a function that takes an express.js Request - * and Response object. + * The Cloud Function type for HTTPS triggers. This should be exported from your + * JavaScript file to define a Cloud Function. + * + * This type is a special JavaScript function which takes Express + * [`Request`](https://expressjs.com/en/api.html#req) and + * [`Response`](https://expressjs.com/en/api.html#res) objects as its only + * arguments. */ export type HttpsFunction = TriggerAnnotated & ((req: Request, resp: Response) => void); /** - * A CloudFunction is both an object that exports its trigger definitions at - * __trigger and can be called as a function using the raw JS API for Google - * Cloud Functions. + * The Cloud Function type for all non-HTTPS triggers. This should be exported + * from your JavaScript file to define a Cloud Function. + * + * This type is a special JavaScript function which takes a templated + * `Event` object as its only argument. */ export type CloudFunction = Runnable & TriggerAnnotated & @@ -346,6 +406,7 @@ export function makeCloudFunction({ return cloudFunction; } +/** @hidden */ function _makeParams( context: EventContext, triggerResourceGetter: () => string @@ -373,6 +434,7 @@ function _makeParams( return params; } +/** @hidden */ function _makeAuth(event: Event, authType: string) { if (authType === 'UNAUTHENTICATED') { return null; @@ -383,6 +445,7 @@ function _makeAuth(event: Event, authType: string) { }; } +/** @hidden */ function _detectAuthType(event: Event) { if (_.get(event, 'context.auth.admin')) { return 'ADMIN'; @@ -393,6 +456,7 @@ function _detectAuthType(event: Event) { return 'UNAUTHENTICATED'; } +/** @hidden */ export function optionsToTrigger(options: DeploymentOptions) { const trigger: any = {}; if (options.regions) { From f7df0c7be3246094657a5ce8c907bd2eb7d49ebb Mon Sep 17 00:00:00 2001 From: egilmorez Date: Mon, 15 Jul 2019 16:02:47 -0700 Subject: [PATCH 075/437] Adding comments to try and populate information for Change and ChangeJson (#525) * Adding comments to try and populate information for Change and ChangeJson. * Remembered to run npm run format:fix! --- docgen/content-sources/toc.yaml | 2 ++ src/cloud-functions.ts | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docgen/content-sources/toc.yaml b/docgen/content-sources/toc.yaml index c2766e430..9ba761c15 100644 --- a/docgen/content-sources/toc.yaml +++ b/docgen/content-sources/toc.yaml @@ -12,6 +12,8 @@ toc: path: /docs/reference/functions/function_builder_.functionbuilder.html - title: 'Change' path: /docs/reference/functions/cloud_functions_.change.html + - title: 'ChangeJson' + path: /docs/reference/functions/cloud_functions_.changejson.html - title: 'functions.config' path: /docs/reference/functions/config_.html diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index b35ba6ee1..e8d099dca 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -143,15 +143,19 @@ export interface EventContext { } /** - * Change describes a change of state - "before" represents the state prior to - * the event, "after" represents the state after the event. + * The Functions interface for events that change state, such as + * Realtime Database or Cloud Firestore `onWrite` and `onUpdate`. + * + * For more information about the format used to construct `Change` objects, see + * [`cloud-functions.ChangeJson`](/docs/reference/functions/cloud_functions_.changejson). + * */ export class Change { constructor(public before: T, public after: T) {} } /** - * ChangeJson is the JSON format used to construct a Change object. + * `ChangeJson` is the JSON format used to construct a Change object. */ export interface ChangeJson { /** @@ -164,17 +168,20 @@ export interface ChangeJson { */ before?: any; /** - * Comma-separated string that represents names of field that changed. + * @hidden + * Comma-separated string that represents names of fields that changed. */ fieldMask?: string; } export namespace Change { + /** @hidden */ function reinterpretCast(x: any) { return x as T; } /** + * @hidden * Factory method for creating a Change from a `before` object and an `after` * object. */ @@ -183,6 +190,7 @@ export namespace Change { } /** + * @hidden * Factory method for creating a Change from a JSON and an optional customizer * function to be applied to both the `before` and the `after` fields. */ From 36916b873fc25fc94f4fc425ffceaf138d0a4b54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Tue, 16 Jul 2019 01:13:11 +0200 Subject: [PATCH 076/437] Fix exhaustiveness of error code switch in http status conversion (#520) * Fix exhaustiveness of error code switch in http status conversion * Add a dot at the end of an error message * Add a newline before parameter documentation --- src/providers/https.ts | 3 ++- src/utilities/assertions.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/utilities/assertions.ts diff --git a/src/providers/https.ts b/src/providers/https.ts index e6c8a63f4..3c3999123 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -27,6 +27,7 @@ import * as _ from 'lodash'; import { apps } from '../apps'; import { HttpsFunction, optionsToTrigger, Runnable } from '../cloud-functions'; import { DeploymentOptions } from '../function-configuration'; +import { assertNever } from '../utilities/assertions'; export interface Request extends express.Request { rawBody: Buffer; @@ -231,7 +232,7 @@ export class HttpsError extends Error { return 500; // This should never happen as long as the type system is doing its job. default: - throw new Error('Invalid error code: ' + this.code); + assertNever(this.code); } } diff --git a/src/utilities/assertions.ts b/src/utilities/assertions.ts new file mode 100644 index 000000000..49ff5e36d --- /dev/null +++ b/src/utilities/assertions.ts @@ -0,0 +1,16 @@ +/** + * @file Provides common assertion helpers which can be used to improve + * strictness of both type checking and runtime. + */ + +/** + * Checks that the given value is of type `never` — the type that’s left after + * all other cases have been removed. + * + * @param x A value of type `never`. + */ +export function assertNever(x: never): never { + throw new Error( + `Unhandled discriminated union member: ${JSON.stringify(x)}.` + ); +} From a2e58e7d1b366a44fb3b895b6cdb5355892e1cae Mon Sep 17 00:00:00 2001 From: Erik Macik <42585523+esmacik@users.noreply.github.com> Date: Tue, 16 Jul 2019 09:48:29 -0700 Subject: [PATCH 077/437] Adding code comments to remoteConfig.ts source (#524) * Adding code comments to remoteConfig.ts source * Update src/providers/remoteConfig.ts Co-Authored-By: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> * Update src/providers/remoteConfig.ts Co-Authored-By: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> * Update src/providers/remoteConfig.ts Co-Authored-By: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> * Update src/providers/remoteConfig.ts Co-Authored-By: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> * Update src/providers/remoteConfig.ts Co-Authored-By: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> * Update src/providers/remoteConfig.ts Co-Authored-By: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> * Revised wording for TemplateVersion.rollBackSource * Adding only npm run format:fix, everything else is the same as my previous commit * Fixed some small typos found by Diana, 'if' and 'Config' --- src/providers/remoteConfig.ts | 48 +++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/providers/remoteConfig.ts b/src/providers/remoteConfig.ts index cd32e5423..f15716c17 100644 --- a/src/providers/remoteConfig.ts +++ b/src/providers/remoteConfig.ts @@ -35,9 +35,13 @@ export const provider = 'google.firebase.remoteconfig'; export const service = 'firebaseremoteconfig.googleapis.com'; /** - * Handle all updates (including rollbacks) that affect a Remote Config project. - * @param handler A function that takes the updated Remote Config template - * version metadata as an argument. + * Registers a function that triggers on Firebase Remote Config template + * update events. + * + * @param handler A function that takes the updated Remote Config + * template version metadata as an argument. + * + * @return A Cloud Function that you can export and deploy. */ export function onUpdate( handler: ( @@ -97,8 +101,8 @@ export class UpdateBuilder { } /** - * Interface representing a Remote Config template version metadata object that - * was emitted when the project was updated. + * An interface representing a Remote Config template version metadata object + * emitted when a project is updated. */ export interface TemplateVersion { /** The version number of the updated Remote Config template. */ @@ -107,27 +111,49 @@ export interface TemplateVersion { /** When the template was updated in format (ISO8601 timestamp). */ updateTime: string; - /** Metadata about the account that performed the update. */ + /** + * Metadata about the account that performed the update, of + * type [`RemoteConfigUser`](/docs/reference/remote-config/rest/v1/Version#remoteconfiguser). + */ updateUser: RemoteConfigUser; - /** A description associated with the particular Remote Config template. */ + /** A description associated with this Remote Config template version. */ description: string; - /** The origin of the caller. */ + /** + * The origin of the caller - either the Firebase console or the Remote Config + * REST API. See [`RemoteConfigUpdateOrigin`](/docs/reference/remote-config/rest/v1/Version#remoteconfigupdateorigin) + * for valid values. + */ updateOrigin: string; - /** The type of update action that was performed. */ + /** + * The type of update action that was performed, whether forced, + * incremental, or a rollback operation. See + * [`RemoteConfigUpdateType`](/docs/reference/remote-config/rest/v1/Version#remoteconfigupdatetype) + * for valid values. + */ updateType: string; /** - * The version number of the Remote Config template that was rolled back to, - * if the update was a rollback. + * The version number of the Remote Config template that this update rolled back to. + * Only applies if this update was a rollback. */ rollbackSource?: number; } +/** + * An interface representing metadata for a Remote Config account + * that performed the update. Contains the same fields as + * [`RemoteConfigUser`](/docs/reference/remote-config/rest/v1/Version#remoteconfiguser). + */ export interface RemoteConfigUser { + /** Name of the Remote Config account that performed the update. */ name?: string; + + /** Email address of the Remote Config account that performed the update. */ email: string; + + /** Image URL of the Remote Config account that performed the update. */ imageUrl?: string; } From ef4e0db481df4989c47fdcb30abac8d0b465f018 Mon Sep 17 00:00:00 2001 From: Jeroen Claassens Date: Wed, 17 Jul 2019 10:52:00 +0200 Subject: [PATCH 078/437] Resolve security vulnerability CVE-2019-10744 (#526) * Resolve security vulnerability CVE-2019-10744 Signed-off-by: Jeroen Claassens * Resolve prettier formatting * Resolve requested review by @thechenky Signed-off-by: Jeroen Claassens --- changelog.txt | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index e69de29bb..ef03d65d5 100644 --- a/changelog.txt +++ b/changelog.txt @@ -0,0 +1 @@ +fixed - Upgrade lodash dependency to resolve security vulnerability CVE-2019-10744 \ No newline at end of file diff --git a/package.json b/package.json index 76ea87fb5..de58f1199 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "cors": "^2.8.5", "express": "^4.17.1", "jsonwebtoken": "^8.5.1", - "lodash": "^4.17.11" + "lodash": "^4.17.14" }, "devDependencies": { "@types/chai": "^4.1.7", From 9c4f6807a827ad3bdbe89674ce881c29f9ea7846 Mon Sep 17 00:00:00 2001 From: Michael Zoech Date: Wed, 17 Jul 2019 02:38:53 -0700 Subject: [PATCH 079/437] Test Lab Functions (#229) * Firebase Test Lab Provider * Minor code cleanups * Update event type and provider namespace to new format * Rename testlab to testLab * Add testMatrix handler function to HandlerBuilder * Resolve PR comments * Resolve PR comments * Add changelog message for new Test Lab trigger * Resolve minor pr comments * Promisify makeRequest and reuse in existing http request helpers * Fix testLab import in handler-builder.ts * Necessary fixes after merging master --- changelog.txt | 3 +- integration_test/functions/src/index.ts | 36 +-- integration_test/functions/src/test-utils.ts | 38 +++ .../functions/src/testLab-tests.ts | 26 ++ .../functions/src/testLab-utils.ts | 117 +++++++ integration_test/run_tests.sh | 4 +- spec/index.spec.ts | 1 + spec/providers/testLab.spec.ts | 255 +++++++++++++++ src/function-builder.ts | 10 + src/handler-builder.ts | 10 + src/index.ts | 2 + src/providers/testLab.ts | 297 ++++++++++++++++++ 12 files changed, 775 insertions(+), 24 deletions(-) create mode 100644 integration_test/functions/src/test-utils.ts create mode 100644 integration_test/functions/src/testLab-tests.ts create mode 100644 integration_test/functions/src/testLab-utils.ts create mode 100644 spec/providers/testLab.spec.ts create mode 100644 src/providers/testLab.ts diff --git a/changelog.txt b/changelog.txt index 1385ce18b..d05db1837 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1 +1,2 @@ -feature - Adds region support for us-east4. \ No newline at end of file +feature - Adds support for Test Lab triggered functions with `functions.testLab`. +feature - Adds region support for us-east4. diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index 967b3d376..e8fa396e9 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -14,36 +14,29 @@ export * from './firestore-tests'; export * from './https-tests'; export * from './remoteConfig-tests'; export * from './storage-tests'; +export * from './testLab-tests'; const numTests = Object.keys(exports).length; // Assumption: every exported function is its own test. +import * as utils from './test-utils'; +import * as testLab from './testLab-utils'; + import 'firebase-functions'; // temporary shim until process.env.FIREBASE_CONFIG available natively in GCF(BUG 63586213) const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG); admin.initializeApp(); // TODO(klimt): Get rid of this once the JS client SDK supports callable triggers. function callHttpsTrigger(name: string, data: any, baseUrl) { - return new Promise((resolve, reject) => { - const request = https.request( - { - method: 'POST', - host: 'us-central1-' + firebaseConfig.projectId + '.' + baseUrl, - path: '/' + name, - headers: { - 'Content-Type': 'application/json', - }, + return utils.makeRequest( + { + method: 'POST', + host: 'us-central1-' + firebaseConfig.projectId + '.' + baseUrl, + path: '/' + name, + headers: { + 'Content-Type': 'application/json', }, - (response) => { - let body = ''; - response.on('data', (chunk) => { - body += chunk; - }); - response.on('end', () => resolve(body)); - } - ); - request.on('error', reject); - request.write(JSON.stringify({ data })); - request.end(); - }); + }, + JSON.stringify({ data }) + ); } function callScheduleTrigger(functionName: string, region: string) { @@ -149,6 +142,7 @@ export const integrationTests: any = functions .storage() .bucket() .upload('/tmp/' + testId + '.txt'), + testLab.startTestRun(firebaseConfig.projectId, testId), // Invoke the schedule for our scheduled function to fire callScheduleTrigger('schedule', 'us-central1'), ]) diff --git a/integration_test/functions/src/test-utils.ts b/integration_test/functions/src/test-utils.ts new file mode 100644 index 000000000..eda799a67 --- /dev/null +++ b/integration_test/functions/src/test-utils.ts @@ -0,0 +1,38 @@ +import * as https from 'https'; + +/** + * Makes an http request asynchronously and returns the response data. + * + * This function wraps the callback-based `http.request()` function with a + * Promise. The returned Promise will be rejected in case the request fails with an + * error, or the response code is not in the 200-299 range. + * + * @param options Request options for the request. + * @param body Optional body to send as part of the request. + * @returns Promise returning the response data as string. + */ +export function makeRequest( + options: https.RequestOptions, + body?: string +): Promise { + return new Promise((resolve, reject) => { + const request = https.request(options, (response) => { + let body = ''; + response.on('data', (chunk) => { + body += chunk; + }); + response.on('end', () => { + if (response.statusCode < 200 || response.statusCode > 299) { + reject(body); + return; + } + resolve(body); + }); + }); + if (body) { + request.write(body); + } + request.on('error', reject); + request.end(); + }); +} diff --git a/integration_test/functions/src/testLab-tests.ts b/integration_test/functions/src/testLab-tests.ts new file mode 100644 index 000000000..a6350c57d --- /dev/null +++ b/integration_test/functions/src/testLab-tests.ts @@ -0,0 +1,26 @@ +import * as functions from 'firebase-functions'; +import * as _ from 'lodash'; +import { TestSuite, expectEq } from './testing'; +import TestMatrix = functions.testLab.TestMatrix; + +export const testLabTests: any = functions + .runWith({ + timeoutSeconds: 540, + }) + .testLab.testMatrix() + .onComplete((matrix, context) => { + return new TestSuite('test matrix complete') + .it('should have eventId', (snap, context) => context.eventId) + + .it('should have timestamp', (snap, context) => context.timestamp) + + .it('should have right eventType', (_, context) => + expectEq(context.eventType, 'google.testing.testMatrix.complete') + ) + + .it("should be in state 'INVALID'", (matrix, _) => + expectEq(matrix.state, 'INVALID') + ) + + .run(_.get(matrix, 'clientInfo.details.testId'), matrix, context); + }); diff --git a/integration_test/functions/src/testLab-utils.ts b/integration_test/functions/src/testLab-utils.ts new file mode 100644 index 000000000..2f457e70d --- /dev/null +++ b/integration_test/functions/src/testLab-utils.ts @@ -0,0 +1,117 @@ +import * as http from 'http'; +import * as https from 'https'; +import * as admin from 'firebase-admin'; +import * as _ from 'lodash'; +import * as utils from './test-utils'; + +interface AndroidDevice { + androidModelId: string; + androidVersionId: string; + locale: string; + orientation: string; +} + +const TESTING_API_SERVICE_NAME = 'testing.googleapis.com'; + +/** + * Creates a new TestMatrix in Test Lab which is expected to be rejected as + * invalid. + * + * @param projectId Project for which the test run will be created + * @param testId Test id which will be encoded in client info details + */ +export async function startTestRun(projectId: string, testId: string) { + const accessToken = await admin.credential + .applicationDefault() + .getAccessToken(); + const device = await fetchDefaultDevice(accessToken); + return await createTestMatrix(accessToken, projectId, testId, device); +} + +async function fetchDefaultDevice( + accessToken: admin.GoogleOAuthAccessToken +): Promise { + const response = await utils.makeRequest( + requestOptions(accessToken, 'GET', '/v1/testEnvironmentCatalog/ANDROID') + ); + const data = JSON.parse(response); + const models = _.get(data, 'androidDeviceCatalog.models', []); + const defaultModels = models.filter( + (m) => + m.tags !== undefined && + m.tags.indexOf('default') > -1 && + m.supportedVersionIds !== undefined && + m.supportedVersionIds.length > 0 + ); + + if (defaultModels.length === 0) { + throw new Error('No default device found'); + } + + const model = defaultModels[0]; + const versions = model.supportedVersionIds; + + return { + androidModelId: model.id, + androidVersionId: versions[versions.length - 1], + locale: 'en', + orientation: 'portrait', + }; +} + +function createTestMatrix( + accessToken: admin.GoogleOAuthAccessToken, + projectId: string, + testId: string, + device: AndroidDevice +): Promise { + const options = requestOptions( + accessToken, + 'POST', + '/v1/projects/' + projectId + '/testMatrices' + ); + const body = { + projectId: projectId, + testSpecification: { + androidRoboTest: { + appApk: { + gcsPath: 'gs://path/to/non-existing-app.apk', + }, + }, + }, + environmentMatrix: { + androidDeviceList: { + androidDevices: [device], + }, + }, + resultStorage: { + googleCloudStorage: { + gcsPath: 'gs://' + admin.storage().bucket().name, + }, + }, + clientInfo: { + name: 'CloudFunctionsSDKIntegrationTest', + clientInfoDetails: { + key: 'testId', + value: testId, + }, + }, + }; + return utils.makeRequest(options, JSON.stringify(body)); +} + +function requestOptions( + accessToken: admin.GoogleOAuthAccessToken, + method: string, + path: string +): https.RequestOptions { + return { + method: method, + hostname: TESTING_API_SERVICE_NAME, + path: path, + headers: { + Authorization: 'Bearer ' + accessToken.access_token, + 'Content-Type': 'application/json', + }, + }; +} diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index 911fc1dd4..d7017af52 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -60,9 +60,9 @@ function delete_all_functions { # Try to delete, if there are errors it is because the project is already empty, # in that case do nothing. if [[ "${TOKEN}" == "" ]]; then - firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID || : & + firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests testLabTests --force --project=$PROJECT_ID || : & else - firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests --force --project=$PROJECT_ID --token=$TOKEN || : & + firebase functions:delete callableTests createUserTests databaseTests deleteUserTests firestoreTests integrationTests pubsubTests remoteConfigTests testLabTests --force --project=$PROJECT_ID --token=$TOKEN || : & fi wait announce "Project emptied." diff --git a/spec/index.spec.ts b/spec/index.spec.ts index 6d56878da..e438e6a97 100644 --- a/spec/index.spec.ts +++ b/spec/index.spec.ts @@ -42,6 +42,7 @@ import './providers/https.spec'; import './providers/pubsub.spec'; import './providers/remoteConfig.spec'; import './providers/storage.spec'; +import './providers/testLab.spec'; import './setup.spec'; import './testing.spec'; import './utils.spec'; diff --git a/spec/providers/testLab.spec.ts b/spec/providers/testLab.spec.ts new file mode 100644 index 000000000..bf74a6fd1 --- /dev/null +++ b/spec/providers/testLab.spec.ts @@ -0,0 +1,255 @@ +// The MIT License (MIT) +// +// Copyright (c) 2019 Firebase +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import { expect } from 'chai'; + +import * as testLab from '../../src/providers/testLab'; + +describe('Test Lab Functions', () => { + describe('#onComplete', () => { + describe('with process.env.GCLOUD_PROJECT set', () => { + before(() => { + process.env.GCLOUD_PROJECT = 'project1'; + }); + + after(() => { + delete process.env.GCLOUD_PROJECT; + }); + + it('should return a TriggerDefinition with appropriate values', () => { + const func = testLab.testMatrix().onComplete(() => null); + expect(func.__trigger).to.deep.equal({ + eventTrigger: { + service: 'testing.googleapis.com', + eventType: 'google.testing.testMatrix.complete', + resource: 'projects/project1/testMatrices/{matrix}', + }, + }); + }); + + it('should parse TestMatrix in "INVALID" state', () => { + const event = { + data: { + clientInfo: { + name: 'test', + }, + invalidMatrixDetails: 'INVALID_INPUT_APK', + resultStorage: { + googleCloudStorage: { + gcsPath: 'gs://test.appspot.com', + }, + }, + state: 'INVALID', + testMatrixId: 'matrix-375mfeu9mnw8t', + timestamp: '2019-04-15T17:43:32.538Z', + }, + context: { + resource: {}, + }, + }; + const expected = { + testMatrixId: 'matrix-375mfeu9mnw8t', + state: 'INVALID', + createTime: '2019-04-15T17:43:32.538Z', + outcomeSummary: undefined, + invalidMatrixDetails: 'INVALID_INPUT_APK', + resultStorage: { + gcsPath: 'gs://test.appspot.com', + resultsUrl: undefined, + toolResultsHistoryId: undefined, + toolResultsExecutionId: undefined, + }, + clientInfo: { + name: 'test', + details: {}, + }, + }; + const func = testLab.testMatrix().onComplete((matrix) => matrix); + return expect(func(event.data, event.context)).to.eventually.deep.equal( + expected + ); + }); + + it('should parse TestMatrix in "FINISHED" state', () => { + const event = { + data: { + clientInfo: { + name: 'test', + }, + outcomeSummary: 'FAILURE', + resultStorage: { + googleCloudStorage: { + gcsPath: 'gs://test.appspot.com', + }, + toolResultsExecution: { + executionId: '6352915701487950333', + historyId: 'bh.9b6f4dac24d3049', + projectId: 'test', + }, + toolResultsHistory: { + historyId: 'bh.9b6f4dac24d3049', + projectId: 'test', + }, + resultsUrl: 'https://path/to/results', + }, + state: 'FINISHED', + testMatrixId: 'matrix-tsgjk8pnvxhya', + timestamp: '2019-04-15T18:03:11.115Z', + }, + context: { + resource: {}, + }, + }; + const expected = { + testMatrixId: 'matrix-tsgjk8pnvxhya', + state: 'FINISHED', + createTime: '2019-04-15T18:03:11.115Z', + outcomeSummary: 'FAILURE', + invalidMatrixDetails: undefined, + resultStorage: { + gcsPath: 'gs://test.appspot.com', + toolResultsHistoryId: 'bh.9b6f4dac24d3049', + toolResultsExecutionId: '6352915701487950333', + resultsUrl: 'https://path/to/results', + }, + clientInfo: { + name: 'test', + details: {}, + }, + }; + const func = testLab.testMatrix().onComplete((matrix) => matrix); + return expect(func(event.data, event.context)).to.eventually.deep.equal( + expected + ); + }); + }); + + describe('process.env.GCLOUD_PROJECT not set', () => { + it('should not throw if trigger is not accessed', () => { + expect(() => testLab.testMatrix().onComplete(() => null)).to.not.throw( + Error + ); + }); + + it('should throw when trigger is accessed', () => { + expect( + () => testLab.testMatrix().onComplete(() => null).__trigger + ).to.throw(Error); + }); + }); + }); + + describe('TestMatrix', () => { + describe('constructor', () => { + it('should populate basic fields', () => { + const expected = { + testMatrixId: 'id1', + createTime: '2019-02-08T18:50:32.178Z', + state: 'FINISHED', + outcomeSummary: 'SUCCESS', + invalidMatrixDetails: 'DETAILS_UNAVAILABLE', + resultStorage: new testLab.ResultStorage(), + clientInfo: new testLab.ClientInfo(), + }; + const actual = new testLab.TestMatrix({ + testMatrixId: 'id1', + timestamp: '2019-02-08T18:50:32.178Z', + state: 'FINISHED', + outcomeSummary: 'SUCCESS', + invalidMatrixDetails: 'DETAILS_UNAVAILABLE', + }); + expect(actual).to.deep.equal(expected); + }); + }); + }); + + describe('ClientInfo', () => { + describe('constructor', () => { + it('should populate basic fields', () => { + const expected = { + name: 'client', + details: {}, + }; + const actual = new testLab.ClientInfo({ + name: 'client', + }); + expect(actual).to.deep.equal(expected); + }); + + it('should populate key/value details', () => { + const expected = { + name: 'client', + details: { + k0: 'v0', + k1: '', + }, + }; + const actual = new testLab.ClientInfo({ + name: 'client', + clientInfoDetails: [ + { + key: 'k0', + value: 'v0', + }, + { + key: 'k1', + }, + ], + }); + expect(actual).to.deep.equal(expected); + }); + }); + }); + + describe('ResultStorage', () => { + describe('constructor', () => { + it('should populate basic fields', () => { + const expected = { + gcsPath: 'path', + toolResultsHistoryId: 'h1', + toolResultsExecutionId: 'e2', + resultsUrl: 'http://example.com/', + }; + const actual = new testLab.ResultStorage({ + googleCloudStorage: { + gcsPath: 'path', + }, + toolResultsHistory: { + projectId: 'p1', + historyId: 'h1', + }, + toolResultsExecution: { + projectId: 'p2', + historyId: 'h2', + executionId: 'e2', + }, + resultsUrl: 'http://example.com/', + }); + expect(actual).to.deep.equal(expected); + }); + + it('should not throw on unset fields', () => { + expect(() => new testLab.ResultStorage({})).to.not.throw(); + }); + }); + }); +}); diff --git a/src/function-builder.ts b/src/function-builder.ts index bdf7f296f..3d24d6a56 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -40,6 +40,7 @@ import * as https from './providers/https'; import * as pubsub from './providers/pubsub'; import * as remoteConfig from './providers/remoteConfig'; import * as storage from './providers/storage'; +import * as testLab from './providers/testLab'; /** * Assert that the runtime options passed in are valid. @@ -317,4 +318,13 @@ export class FunctionBuilder { user: () => auth._userWithOptions(this.options), }; } + + get testLab() { + return { + /** + * Handle events related to Test Lab test matrices. + */ + testMatrix: () => testLab._testMatrixWithOpts(this.options), + }; + } } diff --git a/src/handler-builder.ts b/src/handler-builder.ts index 20e15986d..94a55bf71 100644 --- a/src/handler-builder.ts +++ b/src/handler-builder.ts @@ -33,6 +33,7 @@ import * as https from './providers/https'; import * as pubsub from './providers/pubsub'; import * as remoteConfig from './providers/remoteConfig'; import * as storage from './providers/storage'; +import * as testLab from './providers/testLab'; export class HandlerBuilder { constructor() {} @@ -213,6 +214,15 @@ export class HandlerBuilder { }, }; } + + get testLab() { + /** Handle events related to Test Lab test matrices. */ + return { + get testMatrix() { + return new testLab.TestMatrixBuilder(() => null, {}); + }, + }; + } } export let handler = new HandlerBuilder(); diff --git a/src/index.ts b/src/index.ts index 534080f56..d4951c512 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,6 +30,7 @@ import * as https from './providers/https'; import * as pubsub from './providers/pubsub'; import * as remoteConfig from './providers/remoteConfig'; import * as storage from './providers/storage'; +import * as testLab from './providers/testLab'; import * as apps from './apps'; import { handler } from './handler-builder'; @@ -49,6 +50,7 @@ export { pubsub, remoteConfig, storage, + testLab, }; // Exported root types: diff --git a/src/providers/testLab.ts b/src/providers/testLab.ts new file mode 100644 index 000000000..86398c5c9 --- /dev/null +++ b/src/providers/testLab.ts @@ -0,0 +1,297 @@ +// The MIT License (MIT) +// +// Copyright (c) 2019 Firebase +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the 'Software'), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import * as _ from 'lodash'; + +import { + CloudFunction, + Event, + EventContext, + makeCloudFunction, +} from '../cloud-functions'; +import { DeploymentOptions } from '../function-configuration'; + +/** @internal */ +export const PROVIDER = 'google.testing'; +/** @internal */ +export const SERVICE = 'testing.googleapis.com'; +/** @internal */ +export const TEST_MATRIX_COMPLETE_EVENT_TYPE = 'testMatrix.complete'; + +/** Handle events related to Test Lab test matrices. */ +export function testMatrix() { + return _testMatrixWithOpts({}); +} + +/** @internal */ +export function _testMatrixWithOpts(opts: DeploymentOptions) { + return new TestMatrixBuilder(() => { + if (!process.env.GCLOUD_PROJECT) { + throw new Error('process.env.GCLOUD_PROJECT is not set.'); + } + return 'projects/' + process.env.GCLOUD_PROJECT + '/testMatrices/{matrix}'; + }, opts); +} + +/** Builder used to create Cloud Functions for Test Lab test matrices events. */ +export class TestMatrixBuilder { + /** @internal */ + constructor( + private triggerResource: () => string, + private options: DeploymentOptions + ) {} + + /** Handle a TestMatrix that reached a final test state. */ + onComplete( + handler: ( + testMatrix: TestMatrix, + context: EventContext + ) => PromiseLike | any + ): CloudFunction { + const dataConstructor = (raw: Event) => { + return new TestMatrix(raw.data); + }; + return makeCloudFunction({ + provider: PROVIDER, + eventType: TEST_MATRIX_COMPLETE_EVENT_TYPE, + triggerResource: this.triggerResource, + service: SERVICE, + dataConstructor, + handler, + options: this.options, + }); + } +} + +/** TestMatrix captures details about a test run. */ +export class TestMatrix { + /** Unique id set by the service. */ + testMatrixId: string; + + /** When this test matrix was initially created (ISO8601 timestamp). */ + createTime: string; + + /** Indicates the current progress of the test matrix */ + state: TestState; + + /** + * The overall outcome of the test matrix run. Only set when the test matrix + * state is FINISHED. + */ + outcomeSummary?: OutcomeSummary; + + /** For 'INVALID' matrices only, describes why the matrix is invalid. */ + invalidMatrixDetails?: InvalidMatrixDetails; + + /** Where the results for the matrix are located. */ + resultStorage: ResultStorage; + + /** Information about the client which invoked the test. */ + clientInfo: ClientInfo; + + /** @internal */ + constructor(data: any) { + this.testMatrixId = data.testMatrixId; + this.createTime = data.timestamp; + this.state = data.state; + this.outcomeSummary = data.outcomeSummary; + this.invalidMatrixDetails = data.invalidMatrixDetails; + this.resultStorage = new ResultStorage(data.resultStorage); + this.clientInfo = new ClientInfo(data.clientInfo); + } +} + +/** Information about the client which invoked the test. */ +export class ClientInfo { + /** Client name, e.g. 'gcloud'. */ + name: string; + + /** Map of detailed information about the client which invoked the test. */ + details: { [key: string]: string }; + + /** @internal */ + constructor(data?: any) { + this.name = _.get(data, 'name', ''); + this.details = {}; + _.forEach(_.get(data, 'clientInfoDetails'), (detail: any) => { + this.details[detail.key] = detail.value || ''; + }); + } +} + +/** Locations where the test results are stored. */ +export class ResultStorage { + /** A storage location within Google Cloud Storage (GCS) for the test artifacts. */ + gcsPath?: string; + + /** Id of the ToolResults History containing these results. */ + toolResultsHistoryId?: string; + + /** + * Id of the ToolResults execution that the detailed TestMatrix results are + * written to. + */ + toolResultsExecutionId?: string; + + /** URL to test results in Firebase Console. */ + resultsUrl?: string; + + /** @internal */ + constructor(data?: any) { + this.gcsPath = _.get(data, 'googleCloudStorage.gcsPath'); + this.toolResultsHistoryId = _.get(data, 'toolResultsHistory.historyId'); + this.toolResultsExecutionId = _.get( + data, + 'toolResultsExecution.executionId' + ); + this.resultsUrl = _.get(data, 'resultsUrl'); + } +} + +/** + * The detailed reason that a Matrix was deemed INVALID. + * + * Possible values: + * - 'DETAILS_UNAVAILABLE': The matrix is INVALID, but there are no further + * details available. + * - 'MALFORMED_APK': The input app APK could not be parsed. + * - 'MALFORMED_TEST_APK': The input test APK could not be parsed. + * - 'NO_MANIFEST': The AndroidManifest.xml could not be found. + * - 'NO_PACKAGE_NAME': The APK manifest does not declare a package name. + * - 'INVALID_PACKAGE_NAME': The APK application ID is invalid. + * - 'TEST_SAME_AS_APP': The test package and app package are the same. + * - 'NO_INSTRUMENTATION': The test apk does not declare an instrumentation. + * - 'NO_SIGNATURE': The input app apk does not have a signature. + * - 'INSTRUMENTATION_ORCHESTRATOR_INCOMPATIBLE': The test runner class + * specified by user or in the test APK's manifest file is not compatible with + * Android Test Orchestrator. + * - 'NO_TEST_RUNNER_CLASS': The test APK does not contain the test runner class + * specified by user or in the manifest file. + * - 'NO_LAUNCHER_ACTIVITY': A main launcher activity could not be found. + * - 'FORBIDDEN_PERMISSIONS': The app declares one or more permissions that are + * not allowed. + * - 'INVALID_ROBO_DIRECTIVES': There is a conflict in the provided + * robo_directives. + * - 'INVALID_RESOURCE_NAME': There is at least one invalid resource name in the + * provided robo directives. + * - 'INVALID_DIRECTIVE_ACTION': Invalid definition of action in the robo + * directives, e.g. a click or ignore action includes an input text field. + * - 'TEST_LOOP_INTENT_FILTER_NOT_FOUND': There is no test loop intent filter, + * or the one that is given is not formatted correctly. + * - 'SCENARIO_LABEL_NOT_DECLARED': The request contains a scenario label that + * was not declared in the manifest. + * - 'SCENARIO_LABEL_MALFORMED': There was an error when parsing a label value. + * - 'SCENARIO_NOT_DECLARED': The request contains a scenario number that was + * not declared in the manifest. + * - 'DEVICE_ADMIN_RECEIVER': Device administrator applications are not allowed. + * - 'MALFORMED_XC_TEST_ZIP': The zipped XCTest was malformed. The zip did not ] + * contain a single .xctestrun file and the contents of the + * DerivedData/Build/Products directory. + * - 'BUILT_FOR_IOS_SIMULATOR': The zipped XCTest was built for the iOS + * simulator rather than for a physical device. + * - 'NO_TESTS_IN_XC_TEST_ZIP': The .xctestrun file did not specify any test + * targets. + * - 'USE_DESTINATION_ARTIFACTS': One or more of the test targets defined in the + * .xctestrun file specifies "UseDestinationArtifacts", which is disallowed. + * - 'TEST_NON_APP_HOSTED': XC tests which run on physical devices must have + * "IsAppHostedTestBundle" == "true" in the xctestrun file. + * - 'PLIST_CANNOT_BE_PARSED': An Info.plist file in the XCTest zip could not be + * parsed. + * - 'NO_CODE_APK': APK contains no code. + * - 'INVALID_INPUT_APK': Either the provided input APK path was malformed, the + * APK file does not exist, or the user does not have permission to access the + * APK file. + * - 'INVALID_APK_PREVIEW_SDK': APK is built for a preview SDK which is + * unsupported. + */ +export type InvalidMatrixDetails = + | 'DETAILS_UNAVAILABLE' + | 'MALFORMED_APK' + | 'MALFORMED_TEST_APK' + | 'NO_MANIFEST' + | 'NO_PACKAGE_NAME' + | 'INVALID_PACKAGE_NAME' + | 'TEST_SAME_AS_APP' + | 'NO_INSTRUMENTATION' + | 'NO_SIGNATURE' + | 'INSTRUMENTATION_ORCHESTRATOR_INCOMPATIBLE' + | 'NO_TEST_RUNNER_CLASS' + | 'NO_LAUNCHER_ACTIVITY' + | 'FORBIDDEN_PERMISSIONS' + | 'INVALID_ROBO_DIRECTIVES' + | 'INVALID_RESOURCE_NAME' + | 'INVALID_DIRECTIVE_ACTION' + | 'TEST_LOOP_INTENT_FILTER_NOT_FOUND' + | 'SCENARIO_LABEL_NOT_DECLARED' + | 'SCENARIO_LABEL_MALFORMED' + | 'SCENARIO_NOT_DECLARED' + | 'DEVICE_ADMIN_RECEIVER' + | 'MALFORMED_XC_TEST_ZIP' + | 'BUILT_FOR_IOS_SIMULATOR' + | 'NO_TESTS_IN_XC_TEST_ZIP' + | 'USE_DESTINATION_ARTIFACTS' + | 'TEST_NOT_APP_HOSTED' + | 'PLIST_CANNOT_BE_PARSED' + | 'NO_CODE_APK' + | 'INVALID_INPUT_APK' + | 'INVALID_APK_PREVIEW_SDK'; + +/** + * The state (i.e. progress) of a TestMatrix. + * + * Possible values: + * - 'VALIDATING': The matrix is being validated. + * - 'PENDING': The matrix is waiting for resources to become available. + * - 'FINISHED': The matrix has terminated normally. This means that the matrix + * level processing completed normally, but individual executions may be in an + * ERROR state. + * - 'ERROR': The matrix has stopped because it encountered an infrastructure + * failure. + * - 'INVALID': The matrix was not run because the provided inputs are not + * valid. E.g. the input file is not of the expected type, or is + * malformed/corrupt. + */ +export type TestState = + | 'VALIDATING' + | 'PENDING' + | 'FINISHED' + | 'ERROR' + | 'INVALID'; + +/** + * Outcome summary for a finished TestMatrix. + * + * Possible values: + * - 'SUCCESS': The test matrix run was successful, for instance: + * - All the test cases passed. + * - Robo did not detect a crash of the application under test. + * - 'FAILURE': The test run failed, for instance: + * - One or more test cases failed. + * - A test timed out. + * - The application under test crashed. + * - 'INCONCLUSIVE': Something unexpected happened. The run should still be + * considered unsuccessful but this is likely a transient problem and + * re-running the test might be successful. + * - 'SKIPPED': All tests were skipped, for instance: + * - All device configurations were incompatible. + */ +export type OutcomeSummary = 'SUCCESS' | 'FAILURE' | 'INCONCLUSIVE' | 'SKIPPED'; From de59422b9a09743e3c346def80e2cf2ccdf77a99 Mon Sep 17 00:00:00 2001 From: Erik Macik <42585523+esmacik@users.noreply.github.com> Date: Wed, 17 Jul 2019 12:33:30 -0700 Subject: [PATCH 080/437] Em pubsub (#527) * Adding code comments to pupsub.ts * Adding code comments to pubsub.ts source file * Removing line breaks between @param and @return tags for TopicBuilder.onPublish() and topic() --- src/providers/pubsub.ts | 46 ++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/providers/pubsub.ts b/src/providers/pubsub.ts index 265c39deb..d1a687819 100644 --- a/src/providers/pubsub.ts +++ b/src/providers/pubsub.ts @@ -35,8 +35,12 @@ export const provider = 'google.pubsub'; /** @hidden */ export const service = 'pubsub.googleapis.com'; -/** Select Cloud Pub/Sub topic to listen to. - * @param topic Name of Pub/Sub topic, must belong to the same project as the function. +/** + * Registers a Cloud Function triggered when a Google Cloud Pub/Sub message + * is sent to a specified topic. + * + * @param topic The Pub/Sub topic to watch for message events. + * @return Pub/Sub topic builder interface. */ export function topic(topic: string) { return _topicWithOptions(topic, {}); @@ -105,7 +109,11 @@ export function _scheduleWithOptions( return new ScheduleBuilder({ ...options, schedule: { schedule } }); } -/** Builder used to create Cloud Functions for Google Pub/Sub topics. */ +/** + * The Google Cloud Pub/Sub topic builder. + * + * Access via [`functions.pubsub.topic()`](functions.pubsub#.topic). + */ export class TopicBuilder { /** @hidden */ constructor( @@ -113,7 +121,14 @@ export class TopicBuilder { private options: DeploymentOptions ) {} - /** Handle a Pub/Sub message that was published to a Cloud Pub/Sub topic */ + /** + * Event handler that fires every time a Cloud Pub/Sub message is + * published. + * + * @param handler Event handler that runs every time a Cloud Pub/Sub message + * is published. + * @return A Cloud Function that you can export and deploy. + */ onPublish( handler: (message: Message, context: EventContext) => PromiseLike | any ): CloudFunction { @@ -130,15 +145,22 @@ export class TopicBuilder { } /** - * A Pub/Sub message. + * Interface representing a Google Cloud Pub/Sub message. * - * This class has an additional .json helper which will correctly deserialize any - * message that was a JSON object when published with the JS SDK. .json will throw - * if the message is not a base64 encoded JSON string. + * @param data Payload of a Pub/Sub message. */ export class Message { + /** + * The data payload of this message object as a base64-encoded string. + */ readonly data: string; + + /** + * User-defined attributes published with the message, if any. + */ readonly attributes: { [key: string]: string }; + + /** @hidden */ private _json: any; constructor(data: any) { @@ -149,6 +171,9 @@ export class Message { ]; } + /** + * The JSON data payload of this message object, if any. + */ get json(): any { if (typeof this._json === 'undefined') { this._json = JSON.parse(new Buffer(this.data, 'base64').toString('utf8')); @@ -157,6 +182,11 @@ export class Message { return this._json; } + /** + * Returns a JSON-serializable representation of this object. + * + * @return A JSON-serializable representation of this object. + */ toJSON(): any { return { data: this.data, From 3491a6d4e57432b5747acddd47e1a6a1109f2b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Thu, 18 Jul 2019 20:42:36 +0200 Subject: [PATCH 081/437] Increase type strictness of HttpsError class (#521) * Increase type strictness of HttpsError * Unhide toJSON method on HttpsErrors * Remove unused import * Hide httpErrorCode and toJSON on HttpsError class --- src/providers/https.ts | 166 +++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 88 deletions(-) diff --git a/src/providers/https.ts b/src/providers/https.ts index 3c3999123..d941f67c8 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -27,7 +27,6 @@ import * as _ from 'lodash'; import { apps } from '../apps'; import { HttpsFunction, optionsToTrigger, Runnable } from '../cloud-functions'; import { DeploymentOptions } from '../function-configuration'; -import { assertNever } from '../utilities/assertions'; export interface Request extends express.Request { rawBody: Buffer; @@ -128,33 +127,65 @@ export type FunctionsErrorCode = | 'data-loss' | 'unauthenticated'; +export type CanonicalErrorCodeName = + | 'OK' + | 'CANCELLED' + | 'UNKNOWN' + | 'INVALID_ARGUMENT' + | 'DEADLINE_EXCEEDED' + | 'NOT_FOUND' + | 'ALREADY_EXISTS' + | 'PERMISSION_DENIED' + | 'UNAUTHENTICATED' + | 'RESOURCE_EXHAUSTED' + | 'FAILED_PRECONDITION' + | 'ABORTED' + | 'OUT_OF_RANGE' + | 'UNIMPLEMENTED' + | 'INTERNAL' + | 'UNAVAILABLE' + | 'DATA_LOSS'; + +interface HttpErrorCode { + canonicalName: CanonicalErrorCodeName; + status: number; +} + /** - * Standard error codes for different ways a request can fail, as defined by: + * Standard error codes and HTTP statuses for different ways a request can fail, + * as defined by: * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto * * This map is used primarily to convert from a client error code string to - * to the HTTP format error code string, and make sure it's in the supported set. + * to the HTTP format error code string and status, and make sure it's in the + * supported set. */ -const errorCodeMap: { [name: string]: string } = { - ok: 'OK', - cancelled: 'CANCELLED', - unknown: 'UNKNOWN', - 'invalid-argument': 'INVALID_ARGUMENT', - 'deadline-exceeded': 'DEADLINE_EXCEEDED', - 'not-found': 'NOT_FOUND', - 'already-exists': 'ALREADY_EXISTS', - 'permission-denied': 'PERMISSION_DENIED', - unauthenticated: 'UNAUTHENTICATED', - 'resource-exhausted': 'RESOURCE_EXHAUSTED', - 'failed-precondition': 'FAILED_PRECONDITION', - aborted: 'ABORTED', - 'out-of-range': 'OUT_OF_RANGE', - unimplemented: 'UNIMPLEMENTED', - internal: 'INTERNAL', - unavailable: 'UNAVAILABLE', - 'data-loss': 'DATA_LOSS', +const errorCodeMap: { [name in FunctionsErrorCode]: HttpErrorCode } = { + ok: { canonicalName: 'OK', status: 200 }, + cancelled: { canonicalName: 'CANCELLED', status: 499 }, + unknown: { canonicalName: 'UNKNOWN', status: 500 }, + 'invalid-argument': { canonicalName: 'INVALID_ARGUMENT', status: 400 }, + 'deadline-exceeded': { canonicalName: 'DEADLINE_EXCEEDED', status: 504 }, + 'not-found': { canonicalName: 'NOT_FOUND', status: 404 }, + 'already-exists': { canonicalName: 'ALREADY_EXISTS', status: 409 }, + 'permission-denied': { canonicalName: 'PERMISSION_DENIED', status: 403 }, + unauthenticated: { canonicalName: 'UNAUTHENTICATED', status: 401 }, + 'resource-exhausted': { canonicalName: 'RESOURCE_EXHAUSTED', status: 429 }, + 'failed-precondition': { canonicalName: 'FAILED_PRECONDITION', status: 400 }, + aborted: { canonicalName: 'ABORTED', status: 409 }, + 'out-of-range': { canonicalName: 'OUT_OF_RANGE', status: 400 }, + unimplemented: { canonicalName: 'UNIMPLEMENTED', status: 501 }, + internal: { canonicalName: 'INTERNAL', status: 500 }, + unavailable: { canonicalName: 'UNAVAILABLE', status: 503 }, + 'data-loss': { canonicalName: 'DATA_LOSS', status: 500 }, }; +interface HttpErrorWireFormat { + details?: unknown; + message: string; + status: CanonicalErrorCodeName; +} + /** * An explicit error that can be thrown from a handler to send an error to the * client that called the function. @@ -164,88 +195,46 @@ export class HttpsError extends Error { * A standard error code that will be returned to the client. This also * determines the HTTP status code of the response, as defined in code.proto. */ - readonly code: FunctionsErrorCode; + public readonly code: FunctionsErrorCode; /** * Extra data to be converted to JSON and included in the error response. */ - readonly details?: unknown; + public readonly details: unknown; + + /** + * A wire format representation of a provided error code. + * + * @hidden + */ + public readonly httpErrorCode: HttpErrorCode; constructor(code: FunctionsErrorCode, message: string, details?: unknown) { super(message); - if (!errorCodeMap[code]) { - throw new Error('Unknown error status: ' + code); + // A sanity check for non-TypeScript consumers. + if (code in errorCodeMap === false) { + throw new Error(`Unknown error code: ${code}.`); } this.code = code; this.details = details; - } - - /** - * @hidden - * A string representation of the Google error code for this error for HTTP. - */ - get status() { - return errorCodeMap[this.code]; - } - - /** - * @hidden - * Returns the canonical http status code for the given error. - */ - get httpStatus(): number { - switch (this.code) { - case 'ok': - return 200; - case 'cancelled': - return 499; - case 'unknown': - return 500; - case 'invalid-argument': - return 400; - case 'deadline-exceeded': - return 504; - case 'not-found': - return 404; - case 'already-exists': - return 409; - case 'permission-denied': - return 403; - case 'unauthenticated': - return 401; - case 'resource-exhausted': - return 429; - case 'failed-precondition': - return 400; - case 'aborted': - return 409; - case 'out-of-range': - return 400; - case 'unimplemented': - return 501; - case 'internal': - return 500; - case 'unavailable': - return 503; - case 'data-loss': - return 500; - // This should never happen as long as the type system is doing its job. - default: - assertNever(this.code); - } + this.httpErrorCode = errorCodeMap[code]; } /** @hidden */ - public toJSON() { - const json: any = { - status: this.status, - message: this.message, + public toJSON(): HttpErrorWireFormat { + const { + details, + httpErrorCode: { canonicalName: status }, + message, + } = this; + + return { + ...(details === undefined ? {} : { details }), + message, + status, }; - if (!_.isUndefined(this.details)) { - json.details = this.details; - } - return json; } } @@ -472,8 +461,9 @@ export function _onCallWithOptions( error = new HttpsError('internal', 'INTERNAL'); } - const status = error.httpStatus; + const { status } = error.httpErrorCode; const body = { error: error.toJSON() }; + res.status(status).send(body); } }; From cc663231245256c444e54211911c25fbda0db3d3 Mon Sep 17 00:00:00 2001 From: Erik Macik <42585523+esmacik@users.noreply.github.com> Date: Thu, 18 Jul 2019 15:24:05 -0700 Subject: [PATCH 082/437] Adding code comments and @hidden tags for src/providers/database.ts (#531) * Adding code comments to src/providers/database.ts file * Adding an @hidden tag to properties and methods of database.DataSnapshot with an underscore before them --- src/providers/database.ts | 212 ++++++++++++++++++++++++++++++++++---- 1 file changed, 191 insertions(+), 21 deletions(-) diff --git a/src/providers/database.ts b/src/providers/database.ts index ec6969909..8a2f9453a 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -44,16 +44,26 @@ export const service = 'firebaseio.com'; const databaseURLRegex = new RegExp('https://([^.]+).firebaseio.com'); /** - * Selects a database instance that will trigger the function. - * If omitted, will pick the default database for your project. - * @param instance The Realtime Database instance to use. + * Registers a function that triggers on events from a specific + * Firebase Realtime Database instance. + * + * Use this method together with `ref` to specify the instance on which to + * watch for database events. For example: `firebase.database.instance('my-app-db-2').ref('/foo/bar')` + * + * Note that `functions.database.ref` used without `instance` watches the + * *default* instance for events. + * + * @param instance The instance name of the database instance + * to watch for write events. + * @return Firebase Realtime Database instance builder interface. */ export function instance(instance: string) { return _instanceWithOptions(instance, {}); } /** - * Select Firebase Realtime Database Reference to listen to. + * Registers a function that triggers on Firebase Realtime Database write + * events. * * This method behaves very similarly to the method of the same name in the * client and Admin Firebase SDKs. Any change to the Database that affects the @@ -65,15 +75,19 @@ export function instance(instance: string) { * 1. Cloud Functions allows wildcards in the `path` name. Any `path` component * in curly brackets (`{}`) is a wildcard that matches all strings. The value * that matched a certain invocation of a Cloud Function is returned as part - * of the `context.params` object. For example, `ref("messages/{messageId}")` - * matches changes at `/messages/message1` or `/messages/message2`, resulting - * in `context.params.messageId` being set to `"message1"` or `"message2"`, + * of the [`event.params`](functions.EventContext#params) object. For + * example, `ref("messages/{messageId}")` matches changes at + * `/messages/message1` or `/messages/message2`, resulting in + * `event.params.messageId` being set to `"message1"` or `"message2"`, * respectively. * 2. Cloud Functions do not fire an event for data that already existed before * the Cloud Function was deployed. - * 3. Cloud Function events have access to more information, including information - * about the user who triggered the Cloud Function. - * @param ref Path of the database to listen to. + * 3. Cloud Function events have access to more information, including a + * snapshot of the previous event data and information about the user who + * triggered the Cloud Function. + * + * @param path The path within the Database to watch for write events. + * @return Firebase Realtime Database builder interface. */ export function ref(path: string) { return _refWithOptions(path, {}); @@ -87,10 +101,18 @@ export function _instanceWithOptions( return new InstanceBuilder(instance, options); } +/** + * The Firebase Realtime Database instance builder interface. + * + * Access via [`functions.database.instance()`](functions.database#.instance). + */ export class InstanceBuilder { /** @hidden */ constructor(private instance: string, private options: DeploymentOptions) {} + /** + * @return Firebase Realtime Database reference builder interface. + */ ref(path: string): RefBuilder { const normalized = normalizePath(path); return new RefBuilder( @@ -130,7 +152,11 @@ export function _refWithOptions( return new RefBuilder(apps(), resourceGetter, options); } -/** Builder used to create Cloud Functions for Firebase Realtime Database References. */ +/** + * The Firebase Realtime Database reference builder interface. + * + * Access via [`functions.database.ref()`](functions.database#.ref). + */ export class RefBuilder { /** @hidden */ constructor( @@ -139,7 +165,14 @@ export class RefBuilder { private options: DeploymentOptions ) {} - /** Respond to any write that affects a ref. */ + /** + * Event handler that fires every time a Firebase Realtime Database write + * of any kind (creation, update, or delete) occurs. + * + * @param handler Event handler that runs every time a Firebase Realtime Database + * write occurs. + * @return A Cloud Function that you can export and deploy. + */ onWrite( handler: ( change: Change, @@ -149,7 +182,15 @@ export class RefBuilder { return this.onOperation(handler, 'ref.write', this.changeConstructor); } - /** Respond to update on a ref. */ + /** + * Event handler that fires every time data is updated in + * Firebase Realtime Database. + * + * @param handler Event handler which is run every time a Firebase Realtime Database + * write occurs. + * @return A Cloud + * Function which you can export and deploy. + */ onUpdate( handler: ( change: Change, @@ -159,7 +200,14 @@ export class RefBuilder { return this.onOperation(handler, 'ref.update', this.changeConstructor); } - /** Respond to new data on a ref. */ + /** + * Event handler that fires every time new data is created in + * Firebase Realtime Database. + * + * @param handler Event handler that runs every time new data is created in + * Firebase Realtime Database. + * @return A Cloud Function that you can export and deploy. + */ onCreate( handler: ( snapshot: DataSnapshot, @@ -180,7 +228,14 @@ export class RefBuilder { return this.onOperation(handler, 'ref.create', dataConstructor); } - /** Respond to all data being deleted from a ref. */ + /** + * Event handler that fires every time data is deleted from + * Firebase Realtime Database. + * + * @param handler Event handler that runs every time data is deleted from + * Firebase Realtime Database. + * @return A Cloud Function that you can export and deploy. + */ onDelete( handler: ( snapshot: DataSnapshot, @@ -259,11 +314,22 @@ export function resourceToInstanceAndPath(resource: string) { return [dbInstance, path]; } +/** + * Interface representing a Firebase Realtime Database data snapshot. + */ export class DataSnapshot { public instance: string; + + /** @hidden */ private _ref: firebase.database.Reference; + + /** @hidden */ private _path: string; + + /** @hidden */ private _data: any; + + /** @hidden */ private _childPath: string; constructor( @@ -286,7 +352,11 @@ export class DataSnapshot { this._data = data; } - /** Ref returns a reference to the database with full admin access. */ + /** + * Returns a [`Reference`](/docs/reference/admin/node/admin.database.Reference) + * to the Database location where the triggering write occurred. Has + * full read and write access. + */ get ref(): firebase.database.Reference { if (!this.app) { // may be unpopulated in user's unit tests @@ -301,11 +371,30 @@ export class DataSnapshot { return this._ref; } + /** + * The key (last part of the path) of the location of this `DataSnapshot`. + * + * The last token in a Database location is considered its key. For example, + * "ada" is the key for the `/users/ada/` node. Accessing the key on any + * `DataSnapshot` will return the key for the location that generated it. + * However, accessing the key on the root URL of a Database will return `null`. + */ get key(): string { const last = _.last(pathParts(this._fullPath())); return !last || last === '' ? null : last; } + /** + * Extracts a JavaScript value from a `DataSnapshot`. + * + * Depending on the data in a `DataSnapshot`, the `val()` method may return a + * scalar type (string, number, or boolean), an array, or an object. It may also + * return `null`, indicating that the `DataSnapshot` is empty (contains no + * data). + * + * @return The DataSnapshot's contents as a JavaScript value (Object, + * Array, string, number, boolean, or `null`). + */ val(): any { const parts = pathParts(this._childPath); const source = this._data; @@ -315,20 +404,52 @@ export class DataSnapshot { return this._checkAndConvertToArray(node); } - // TODO(inlined): figure out what to do here + /** + * Exports the entire contents of the `DataSnapshot` as a JavaScript object. + * + * The `exportVal()` method is similar to `val()`, except priority information + * is included (if available), making it suitable for backing up your data. + * + * @return The contents of the `DataSnapshot` as a JavaScript value + * (Object, Array, string, number, boolean, or `null`). + */ exportVal(): any { return this.val(); } - // TODO(inlined): figure out what to do here + /** + * Gets the priority value of the data in this `DataSnapshot`. + * + * As an alternative to using priority, applications can order collections by + * ordinary properties. See [Sorting and filtering + * data](/docs/database/web/lists-of-data#sorting_and_filtering_data). + * + * @return The priority value of the data. + */ getPriority(): string | number | null { return 0; } + /** + * Returns `true` if this `DataSnapshot` contains any data. It is slightly more + * efficient than using `snapshot.val() !== null`. + * + * @return `true` if this `DataSnapshot` contains any data; otherwise, `false`. + */ exists(): boolean { return !_.isNull(this.val()); } + /** + * Gets a `DataSnapshot` for the location at the specified relative path. + * + * The relative path can either be a simple child name (for example, "ada") or + * a deeper slash-separated path (for example, "ada/name/first"). + * + * @param path A relative path from this location to the desired child + * location. + * @return The specified child location. + */ child(childPath: string): DataSnapshot { if (!childPath) { return this; @@ -336,6 +457,25 @@ export class DataSnapshot { return this._dup(childPath); } + /** + * Enumerates the `DataSnapshot`s of the children items. + * + * Because of the way JavaScript objects work, the ordering of data in the + * JavaScript object returned by `val()` is not guaranteed to match the ordering + * on the server nor the ordering of `child_added` events. That is where + * `forEach()` comes in handy. It guarantees the children of a `DataSnapshot` + * will be iterated in their query order. + * + * If no explicit `orderBy*()` method is used, results are returned + * ordered by key (unless priorities are used, in which case, results are + * returned by priority). + * + * @param action A function that will be called for each child `DataSnapshot`. + * The callback can return `true` to cancel further enumeration. + * + * @return `true` if enumeration was canceled due to your callback + * returning `true`. + */ forEach(action: (a: DataSnapshot) => boolean): boolean { const val = this.val(); if (_.isPlainObject(val)) { @@ -347,29 +487,57 @@ export class DataSnapshot { return false; } + /** + * Returns `true` if the specified child path has (non-`null`) data. + * + * @param path A relative path to the location of a potential child. + * @return `true` if data exists at the specified child path; otherwise, + * `false`. + */ hasChild(childPath: string): boolean { return this.child(childPath).exists(); } + /** + * Returns whether or not the `DataSnapshot` has any non-`null` child + * properties. + * + * You can use `hasChildren()` to determine if a `DataSnapshot` has any + * children. If it does, you can enumerate them using `forEach()`. If it + * doesn't, then either this snapshot contains a primitive value (which can be + * retrieved with `val()`) or it is empty (in which case, `val()` will return + * `null`). + * + * @return `true` if this snapshot has any children; else `false`. + */ hasChildren(): boolean { const val = this.val(); return _.isPlainObject(val) && _.keys(val).length > 0; } + /** + * Returns the number of child properties of this `DataSnapshot`. + * + * @return Number of child properties of this `DataSnapshot`. + */ numChildren(): number { const val = this.val(); return _.isPlainObject(val) ? Object.keys(val).length : 0; } /** - * Prints the value of the snapshot; use '.previous.toJSON()' and '.current.toJSON()' to explicitly see - * the previous and current values of the snapshot. + * Returns a JSON-serializable representation of this object. + * + * @return A JSON-serializable representation of this object. */ toJSON(): Object { return this.val(); } - /* Recursive function to check if keys are numeric & convert node object to array if they are */ + /** Recursive function to check if keys are numeric & convert node object to array if they are + * + * @hidden + */ private _checkAndConvertToArray(node: any): any { if (node === null || typeof node === 'undefined') { return null; @@ -408,6 +576,7 @@ export class DataSnapshot { return obj; } + /** @hidden */ private _dup(childPath?: string): DataSnapshot { const dup = new DataSnapshot( this._data, @@ -424,6 +593,7 @@ export class DataSnapshot { return dup; } + /** @hidden */ private _fullPath(): string { const out = (this._path || '') + '/' + (this._childPath || ''); return out; From 4e043af87459ee5d1ebd627ddc2c1db4f5012ff6 Mon Sep 17 00:00:00 2001 From: Erik Macik <42585523+esmacik@users.noreply.github.com> Date: Mon, 22 Jul 2019 22:03:41 -0700 Subject: [PATCH 083/437] Adding code comments to src/providers/crashlytics.ts (#534) * Adding code comments to src/providers/crashlytics.ts from production docs * Fixing some small documentation format nits found by Diana for VelocityAlert and Issue.resolvedTime --- src/providers/crashlytics.ts | 87 +++++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/src/providers/crashlytics.ts b/src/providers/crashlytics.ts index 89701d42f..e7a6fe882 100644 --- a/src/providers/crashlytics.ts +++ b/src/providers/crashlytics.ts @@ -33,8 +33,9 @@ export const provider = 'google.firebase.crashlytics'; export const service = 'fabric.io'; /** - * Handle events related to Crashlytics issues. An issue in Crashlytics is an - * aggregation of crashes which have a shared root cause. + * Registers a Cloud Function to handle Crashlytics issue events. + * + * @returns Crashlytics issue event builder interface. */ export function issue() { return _issueWithOptions({}); @@ -50,7 +51,7 @@ export function _issueWithOptions(options: DeploymentOptions) { }, options); } -/** Builder used to create Cloud Functions for Crashlytics issue events. */ +/** The Firebase Crashlytics issue builder interface. */ export class IssueBuilder { /** @hidden */ constructor( @@ -63,21 +64,46 @@ export class IssueBuilder { throw new Error('"onNewDetected" is now deprecated, please use "onNew"'); } - /** Handle Crashlytics New Issue events. */ + /** + * Event handler that fires every time a new issue occurs in a project. + * + * @param handler Event handler that fires every time a new issue event occurs. + * @example + * ```javascript + * exports.postOnNewIssue = functions.crashlytics.issue().onNew(event => { + * const { data } = event; + * issueId = data.issueId; + * issueTitle = data.issueTitle; + * const slackMessage = ` There's a new issue (${issueId}) ` + + * `in your app - ${issueTitle}`; + * return notifySlack(slackMessage).then(() => { + * console.log(`Posted new issue ${issueId} successfully to Slack`); + * }); + * }); + * ``` + */ onNew( handler: (issue: Issue, context: EventContext) => PromiseLike | any ): CloudFunction { return this.onEvent(handler, 'issue.new'); } - /** Handle Crashlytics Regressed Issue events. */ + /** + * Event handler that fires every time a regressed issue reoccurs in a project. + * + * @param handler Event handler that fires every time a regressed issue event occurs. + */ onRegressed( handler: (issue: Issue, context: EventContext) => PromiseLike | any ): CloudFunction { return this.onEvent(handler, 'issue.regressed'); } - /** Handle Crashlytics Velocity Alert events. */ + /** + * Event handler that fires every time a velocity alert occurs in a project. + * + * @param handler handler that fires every time a velocity alert issue event occurs. + */ onVelocityAlert( handler: (issue: Issue, context: EventContext) => PromiseLike | any ): CloudFunction { @@ -101,52 +127,71 @@ export class IssueBuilder { } /** - * Interface representing a Crashlytics issue event that was logged for a specific issue. + * Interface representing a Firebase Crashlytics event that was logged for a specific issue. */ export interface Issue { - /** Fabric Issue ID. */ + /** Crashlytics-provided issue ID. */ issueId: string; - /** Issue title. */ + /** Crashlytics-provided issue title. */ issueTitle: string; - /** App information. */ + /** AppInfo interface describing the App. */ appInfo: AppInfo; - /** When the issue was created (ISO8601 time stamp). */ + /** + * UTC when the issue occurred in ISO8601 standard representation. + * + * Example: 1970-01-17T10:52:15.661-08:00 + */ createTime: string; - /** When the issue was resolved, if the issue has been resolved (ISO8601 time stamp). */ + /** + * UTC When the issue was closed in ISO8601 standard representation. + * + * Example: 1970-01-17T10:52:15.661-08:00 + */ resolvedTime?: string; - /** Contains details about the velocity alert, if this event was triggered by a velocity alert. */ + /** Information about the velocity alert, like number of crashes and percentage of users affected by the issue. */ velocityAlert?: VelocityAlert; } +/** Interface representing Firebase Crashlytics VelocityAlert data. */ export interface VelocityAlert { - /** The percentage of sessions which have been impacted by this issue. Example: .04 */ + /** + * The percentage of sessions which have been impacted by this issue. + * + * Example: .04 + */ crashPercentage: number; /** The number of crashes that this issue has caused. */ crashes: number; } -/** - * Interface representing the application where this issue occurred. - */ +/** Interface representing Firebase Crashlytics AppInfo data. */ export interface AppInfo { - /** The app's name. Example: "My Awesome App". */ + /** + * The app's name. + * + * Example: "My Awesome App". + */ appName: string; - /** The app's platform. Examples: "android", "ios". */ + /** The app's platform. + * + * Examples: "android", "ios". + */ appPlatform: string; /** Unique application identifier within an app store, either the Android package name or the iOS bundle id. */ appId: string; /** - * The latest app version which is affected by the issue. - * Examples: "1.0", "4.3.1.1.213361", "2.3 (1824253)", "v1.8b22p6". + * The app's version name. + * + * Examples: "1.0", "4.3.1.1.213361", "2.3 (1824253)", "v1.8b22p6". */ latestAppVersion: string; } From 6e97877304441cc914302bfdff228a7f086a9262 Mon Sep 17 00:00:00 2001 From: Erik Macik <42585523+esmacik@users.noreply.github.com> Date: Mon, 22 Jul 2019 22:07:18 -0700 Subject: [PATCH 084/437] Adding code comments to analytics.ts (#535) * Adding code comments to src/providers/analytics.ts * Tweaking some things requested by Eric, 'Function' instead of 'Cloud Function', and 'ID' instead of 'id' --- src/providers/analytics.ts | 113 ++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 40 deletions(-) diff --git a/src/providers/analytics.ts b/src/providers/analytics.ts index 457307e46..43578f8b6 100644 --- a/src/providers/analytics.ts +++ b/src/providers/analytics.ts @@ -36,8 +36,12 @@ export const provider = 'google.analytics'; export const service = 'app-measurement.com'; /** - * Select analytics events to listen to for events. - * @param analyticsEventType Name of the analytics event type. + * Registers a function to handle analytics events. + * + * @param analyticsEventType Name of the analytics event type to which + * this Cloud Function is scoped. + * + * @return Analytics event builder interface. */ export function event(analyticsEventType: string) { return _eventWithOptions(analyticsEventType, {}); @@ -61,7 +65,7 @@ export function _eventWithOptions( /** * The Firebase Analytics event builder interface. * - * Access via [`functions.analytics.event()`](functions.analytics#event). + * Access via [`functions.analytics.event()`](functions.analytics#.event). */ export class AnalyticsEventBuilder { /** @hidden */ @@ -73,12 +77,10 @@ export class AnalyticsEventBuilder { /** * Event handler that fires every time a Firebase Analytics event occurs. * - * @param {!function(!functions.Event)} - * handler Event handler that fires every time a Firebase Analytics event + * @param handler Event handler that fires every time a Firebase Analytics event * occurs. * - * @return {!functions.CloudFunction} A - * Cloud Function you can export. + * @return A function that you can export and deploy. */ onLog( handler: ( @@ -102,13 +104,11 @@ export class AnalyticsEventBuilder { } } -/** - * Interface representing a Firebase Analytics event that was logged for a specific user. - */ +/** Interface representing a Firebase Analytics event that was logged for a specific user. */ export class AnalyticsEvent { /** - * The date on which the event.was logged. - * (`YYYYMMDD` format in the registered timezone of your app). + * The date on which the event.was logged. + * (`YYYYMMDD` format in the registered timezone of your app). */ reportingDate: string; @@ -232,11 +232,9 @@ export class UserDimensions { } } -/** - * Predefined or custom properties stored on the client side. - */ +/** Predefined or custom properties stored on the client side. */ export class UserPropertyValue { - /** Last set value of a user property. */ + /** The last set value of a user property. */ value: string; /** UTC client time when the user property was last set. */ @@ -250,47 +248,55 @@ export class UserPropertyValue { } /** - * Interface representing the device that triggered these Firebase Analytics events. + * Interface representing the device that triggered these + * Firebase Analytics events. */ export interface DeviceInfo { /** * Device category. + * * Examples: "tablet" or "mobile". */ deviceCategory?: string; /** * Device brand name. + * * Examples: "Samsung", "HTC" */ mobileBrandName?: string; /** * Device model name in human-readable format. + * * Example: "iPhone 7" */ mobileModelName?: string; /** * Device marketing name. + * * Example: "Galaxy S4 Mini" */ mobileMarketingName?: string; /** * Device model, as read from the OS. + * * Example: "iPhone9,1" */ deviceModel?: string; /** * Device OS version when data capture ended. + * * Example: "4.4.2" */ platformVersion?: string; /** * Vendor specific device identifier. This is IDFV on iOS. Not used for Android. + * * Example: '599F9C00-92DC-4B5C-9464-7971F01F8370' */ deviceId?: string; @@ -313,69 +319,86 @@ export interface DeviceInfo { /** * The time zone of the device when data was uploaded, as seconds skew from UTC. - * Use this to calculate the device's local time for [`event.timestamp`](functions.Event#timestamp)`. + * Use this to calculate the device's local time for [`event.timestamp`](functions.Event#timestamp). */ deviceTimeZoneOffsetSeconds: number; /** * The device's Limit Ad Tracking setting. * When `true`, you cannot use `resettableDeviceId` for remarketing, demographics or influencing ads serving - * behaviour. However, you can use resettableDeviceId for conversion tracking and campaign attribution. + * behaviour. However, you can use `resettableDeviceId` for conversion tracking and campaign attribution. */ limitedAdTracking: boolean; } -/** - * Interface representing the geographic origin of the events. - */ +/** Interface representing the geographic origin of the events. */ export interface GeoInfo { - /** The geographic continent. Example: "Americas". */ + /** + * The geographic continent. + * + * Example: "South America". + */ continent?: string; - /** The geographic country. Example: "Brazil". */ + /** + * The geographic country. + * + * Example: "Brazil". + */ country?: string; - /** The geographic region. Example: "State of Sao Paulo". */ + /** + * The geographic region. + * + * Example: "State of Sao Paulo". + */ region?: string; - /** The geographic city. Example: "Sao Paulo". */ + /** + * The geographic city. + * + * Example: "Sao Paulo". + */ city?: string; } -/** - * Interface representing the application that triggered these events. - */ +/** Interface representing the application that triggered these events. */ export interface AppInfo { /** - * The app's version name. - * Examples: "1.0", "4.3.1.1.213361", "2.3 (1824253)", "v1.8b22p6". + * The app's version name. + * + * Examples: "1.0", "4.3.1.1.213361", "2.3 (1824253)", "v1.8b22p6". */ appVersion?: string; /** - * Unique id for this instance of the app. - * Example: "71683BF9FA3B4B0D9535A1F05188BAF3". + * Unique ID for this instance of the app. + * + * Example: "71683BF9FA3B4B0D9535A1F05188BAF3". */ appInstanceId: string; /** - * The identifier of the store that installed the app. - * Examples: "com.sec.android.app.samsungapps", "com.amazon.venezia", "com.nokia.nstore". + * The identifier of the store that installed the app. + * + * Examples: "com.sec.android.app.samsungapps", "com.amazon.venezia", "com.nokia.nstore". */ appStore?: string; - /** The app platform. Examples: "ANDROID", "IOS". */ + /** + * The app platform. + * + * Examples: "ANDROID", "IOS". + */ appPlatform: string; /** Unique application identifier within an app store. */ appId?: string; } -/** - * Interface representing the bundle in which these events were uploaded. - */ +/** Interface representing the bundle these events were uploaded to. */ export class ExportBundleInfo { - /** Monotonically increasing index for each bundle set by the Analytics SDK. */ + /** Monotonically increasing index for each bundle set by the Analytics SDK. */ bundleSequenceId: number; /** Timestamp offset (in milliseconds) between collection time and upload time. */ @@ -393,6 +416,7 @@ export class ExportBundleInfo { } } +/** @hidden */ function copyFieldTo( from: any, to: T, @@ -405,6 +429,7 @@ function copyFieldTo( } } +/** @hidden */ function copyField( from: any, to: T, @@ -414,6 +439,7 @@ function copyField( copyFieldTo(from, to, field as string, field, transform); } +/** @hidden */ function copyFields(from: any, to: T, fields: K[]): void { for (const field of fields) { copyField(from, to, field); @@ -449,10 +475,12 @@ function copyFields(from: any, to: T, fields: K[]): void { // is due to the encoding library, which renders int64 values as strings to avoid loss of precision. This // method always returns a string, similarly to avoid loss of precision, unlike the less-conservative // 'unwrapValue' method just below. +/** @hidden */ function unwrapValueAsString(wrapped: any): string { const key: string = _.keys(wrapped)[0]; return _.toString(wrapped[key]); } + // Ditto as the method above, but returning the values in the idiomatic JavaScript type (string for strings, // number for numbers): // { @@ -466,7 +494,10 @@ function unwrapValueAsString(wrapped: any): string { // purposes can be divided into 'number' versus 'string'. This method will render all the numbers as // JavaScript's 'number' type, since we prefer using idiomatic types. Note that this may lead to loss // in precision for int64 fields, so use with care. +/** @hidden */ const xValueNumberFields = ['intValue', 'floatValue', 'doubleValue']; + +/** @hidden */ function unwrapValue(wrapped: any): any { const key: string = _.keys(wrapped)[0]; const value: string = unwrapValueAsString(wrapped); @@ -476,6 +507,7 @@ function unwrapValue(wrapped: any): any { // The JSON payload delivers timestamp fields as strings of timestamps denoted in microseconds. // The JavaScript convention is to use numbers denoted in milliseconds. This method // makes it easy to convert a field of one type into the other. +/** @hidden */ function copyTimestampToMillis( from: any, to: T, @@ -490,6 +522,7 @@ function copyTimestampToMillis( // The JSON payload delivers timestamp fields as strings of timestamps denoted in microseconds. // In our SDK, we'd like to present timestamp as ISO-format strings. This method makes it easy // to convert a field of one type into the other. +/** @hidden */ function copyTimestampToString( from: any, to: T, From e5466959ea4f5a0729d6a0a241e65301f2ec29d7 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Wed, 24 Jul 2019 13:35:00 -0700 Subject: [PATCH 085/437] add timestamp to the .tgz bundle name to avoid GCF caching old versions of it (#539) --- integration_test/run_tests.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index d7017af52..765e3b0d7 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -20,6 +20,7 @@ if [[ "${1}" == "" ]]; then fi PROJECT_ID="${1}" +TIMESTAMP=$(date +%s) TOKEN="${2}" # Directory where this script lives. @@ -34,17 +35,19 @@ function build_sdk { cd "${DIR}/.." rm -f firebase-functions-*.tgz npm run build:pack - mv firebase-functions-*.tgz integration_test/functions/firebase-functions.tgz + mv firebase-functions-*.tgz "integration_test/functions/firebase-functions-${TIMESTAMP}.tgz" } function pick_node8 { cd "${DIR}" cp package.node8.json functions/package.json + sed -i '' "s/firebase-functions.tgz/firebase-functions-${TIMESTAMP}.tgz/g" functions/package.json } function pick_node10 { cd "${DIR}" cp package.node10.json functions/package.json + sed -i '' "s/firebase-functions.tgz/firebase-functions-${TIMESTAMP}.tgz/g" functions/package.json } function install_deps { @@ -98,7 +101,7 @@ function run_tests { function cleanup { announce "Performing cleanup..." delete_all_functions - rm "${DIR}/functions/firebase-functions.tgz" + rm "${DIR}/functions/firebase-functions-*.tgz" rm "${DIR}/functions/package.json" rm -f "${DIR}/functions/firebase-debug.log" rm -rf "${DIR}/functions/lib" From 8b84bf6f3f4bc66c03aa415d0cfead6ecc962f45 Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Wed, 24 Jul 2019 20:39:20 +0000 Subject: [PATCH 086/437] [firebase-release] Updated SDK for Cloud Functions to 3.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index de58f1199..b50c25e99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.1.0", + "version": "3.2.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From a4266b7da1991eb14bd5afecbb31e709af36ed3c Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Wed, 24 Jul 2019 20:39:33 +0000 Subject: [PATCH 087/437] [firebase-release] Removed change log and reset repo after 3.2.0 release --- changelog.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index 2dc41f3fa..e69de29bb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,2 +0,0 @@ -feature - Adds support for Test Lab triggered functions with `functions.testLab`. -fixed - Upgrade lodash dependency to resolve security vulnerability CVE-2019-10744. \ No newline at end of file From e32e8cc92993ed47fc665bbacf735e38d87cc7f4 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Wed, 24 Jul 2019 17:34:26 -0700 Subject: [PATCH 088/437] Update CODEOWNERS --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 49f7349e0..bb94688b2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @kevinajian @thechenky +* @thechenky From 381c733c0b0f1df7fe7bb4811c0eb1a191b00b27 Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Thu, 1 Aug 2019 15:16:48 -0700 Subject: [PATCH 089/437] Fix sed command that breaks on linux (#545) * fix sed command that breaks on linux * fixing rm error --- integration_test/run_tests.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index 765e3b0d7..903936b13 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -41,13 +41,19 @@ function build_sdk { function pick_node8 { cd "${DIR}" cp package.node8.json functions/package.json - sed -i '' "s/firebase-functions.tgz/firebase-functions-${TIMESTAMP}.tgz/g" functions/package.json + # we have to do the -e flag here so that it work both on linux and mac os, but that creates an extra + # backup file called package.json-e that we should clean up afterwards. + sed -i -e "s/firebase-functions.tgz/firebase-functions-${TIMESTAMP}.tgz/g" functions/package.json + rm -f functions/package.json-e } function pick_node10 { cd "${DIR}" cp package.node10.json functions/package.json - sed -i '' "s/firebase-functions.tgz/firebase-functions-${TIMESTAMP}.tgz/g" functions/package.json + # we have to do the -e flag here so that it work both on linux and mac os, but that creates an extra + # backup file called package.json-e that we should clean up afterwards. + sed -i -e "s/firebase-functions.tgz/firebase-functions-${TIMESTAMP}.tgz/g" functions/package.json + rm -f functions/package.json-e } function install_deps { @@ -101,7 +107,7 @@ function run_tests { function cleanup { announce "Performing cleanup..." delete_all_functions - rm "${DIR}/functions/firebase-functions-*.tgz" + rm "${DIR}/functions/firebase-functions-${TIMESTAMP}.tgz" rm "${DIR}/functions/package.json" rm -f "${DIR}/functions/firebase-debug.log" rm -rf "${DIR}/functions/lib" From 8057d116915ec293da0b67e07c09d8a1c3dacead Mon Sep 17 00:00:00 2001 From: Diana Tkachenko <31747099+thechenky@users.noreply.github.com> Date: Mon, 7 Oct 2019 20:03:29 +0200 Subject: [PATCH 090/437] Remove chenky from codeowners --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bb94688b2..8b1378917 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @thechenky + From a001a8537dd9704d1d1c089191ce2ab514d1e3fb Mon Sep 17 00:00:00 2001 From: Sam Stern Date: Mon, 7 Oct 2019 13:56:45 -0700 Subject: [PATCH 091/437] Add emulator app override function (#565) --- changelog.txt | 1 + src/apps.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/changelog.txt b/changelog.txt index e69de29bb..c3be06fd4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -0,0 +1 @@ +feature - Add a helper function for the Firebase Emulator suite. \ No newline at end of file diff --git a/src/apps.ts b/src/apps.ts index 43c8d705d..163cd4795 100644 --- a/src/apps.ts +++ b/src/apps.ts @@ -58,6 +58,7 @@ export namespace apps { export class Apps { private _refCounter: RefCounter; + private _emulatedAdminApp?: firebase.app.App; constructor() { this._refCounter = {}; @@ -105,12 +106,25 @@ export namespace apps { } get admin(): firebase.app.App { + if (this._emulatedAdminApp) { + return this._emulatedAdminApp; + } + if (this._appAlive('__admin__')) { return firebase.app('__admin__'); } return firebase.initializeApp(this.firebaseArgs, '__admin__'); } + /** + * This function allows the Firebase Emulator Suite to override the FirebaseApp instance + * used by the Firebase Functions SDK. Developers should never call this function for + * other purposes. + */ + setEmulatedAdminApp(app: firebase.app.App) { + this._emulatedAdminApp = app; + } + private get firebaseArgs() { return _.assign({}, firebaseConfig(), { credential: firebase.credential.applicationDefault(), From bf5f3b5073b2c70286265c19eeb320b3a5bd3f0c Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Thu, 10 Oct 2019 21:58:16 +0000 Subject: [PATCH 092/437] [firebase-release] Updated SDK for Cloud Functions to 3.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b50c25e99..70edbc1ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.2.0", + "version": "3.3.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From 3018b60c05c349ceee62b5790898a3d7468f3abf Mon Sep 17 00:00:00 2001 From: Firebase Operations Date: Thu, 10 Oct 2019 21:58:27 +0000 Subject: [PATCH 093/437] [firebase-release] Removed change log and reset repo after 3.3.0 release --- changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index c3be06fd4..e69de29bb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1 +0,0 @@ -feature - Add a helper function for the Firebase Emulator suite. \ No newline at end of file From c5d3a7bf986e39af22d5f74e6c4e99fd432baa79 Mon Sep 17 00:00:00 2001 From: Erik Macik <42585523+esmacik@users.noreply.github.com> Date: Thu, 24 Oct 2019 14:55:21 -0600 Subject: [PATCH 094/437] Linking to external types - docgen (#549) * First version of addTypeAliasLinks that will scan TypeDoc HTML output and insert links to external library documentation. * Updated link for UserRecord and UserInfo * Resolving a few of Hiranya's comments. Removing unecessary debugging console log, JSDOM is now imported with one line, and Map of types and links is now created from a JSON file, Still need to resolve a couple of Hiranya's comments. * Ran npm run format:fix * Resolving more of Hiranya's comments. Loading JSON file uses 'require' rather than 'read file'. JSON file is now simpler. Script only replaces fully qualified names rather than aliases in document. * Adding comment as requested by thechenky. * Updating package.json to include JSDom module required by doc generation script. --- docgen/generate-docs.js | 34 ++++++++++++++++++++++++++++++++++ docgen/type-aliases.json | 5 +++++ package.json | 1 + 3 files changed, 40 insertions(+) create mode 100644 docgen/type-aliases.json diff --git a/docgen/generate-docs.js b/docgen/generate-docs.js index 299250e64..01275ae2d 100644 --- a/docgen/generate-docs.js +++ b/docgen/generate-docs.js @@ -39,6 +39,10 @@ const contentPath = path.resolve(`${__dirname}/content-sources`); const tempHomePath = path.resolve(`${contentPath}/HOME_TEMP.md`); const devsitePath = `/docs/reference/functions/`; +const { JSDOM } = require("jsdom"); + +const typeMap = require('./type-aliases.json'); + /** * Strips path prefix and returns only filename. * @param {string} path @@ -103,6 +107,7 @@ function renameFiles() { */ function fixLinks(file) { return fs.readFile(file, 'utf8').then(data => { + data = addTypeAliasLinks(data); const flattenedLinks = data .replace(/\.\.\//g, '') .replace(/(modules|interfaces|classes)\//g, '') @@ -116,6 +121,35 @@ function fixLinks(file) { }); } +/** + * Adds links to external documentation for type aliases that + * reference an external library. + * + * @param data File data to add external library links to. + */ +function addTypeAliasLinks(data) { + const htmlDom = new JSDOM(data); + /** + * Select .tsd-signature-type because all potential external + * links will have this identifier. + */ + const fileTags = htmlDom.window.document.querySelectorAll(".tsd-signature-type"); + fileTags.forEach(tag => { + const mapping = typeMap[tag.textContent]; + if (mapping) { + console.log('Adding link to '+tag.textContent+" documentation."); + + // Add the corresponding document link to this type + const linkChild = htmlDom.window.document.createElement('a'); + linkChild.setAttribute('href', mapping); + linkChild.textContent = tag.textContent; + tag.textContent = null; + tag.appendChild(linkChild); + } + }); + return htmlDom.serialize(); +} + let tocText = ''; /** diff --git a/docgen/type-aliases.json b/docgen/type-aliases.json new file mode 100644 index 000000000..240c50269 --- /dev/null +++ b/docgen/type-aliases.json @@ -0,0 +1,5 @@ +{ + "firebase.firestore.DocumentSnapshot": "https://googleapis.dev/nodejs/firestore/latest/DocumentSnapshot.html", + "firebase.auth.UserRecord": "https://firebase.google.com/docs/reference/admin/node/admin.auth.UserRecord.html", + "firebase.auth.UserInfo": "https://firebase.google.com/docs/reference/admin/node/admin.auth.UserInfo.html" +} diff --git a/package.json b/package.json index 70edbc1ab..b9d7e31fc 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "firebase-admin": "^8.2.0", "istanbul": "^0.4.5", "js-yaml": "^3.13.1", + "jsdom": "^15.2.0", "mocha": "^6.1.4", "mock-require": "^3.0.3", "mz": "^2.7.0", From fd04eec120637477b776f4a755716d33c26ba23e Mon Sep 17 00:00:00 2001 From: egilmorez Date: Thu, 31 Oct 2019 11:28:54 -0700 Subject: [PATCH 095/437] I persist in trying to fix links. (#577) --- src/cloud-functions.ts | 2 +- src/providers/analytics.ts | 5 +++-- src/providers/database.ts | 4 ++-- src/providers/pubsub.ts | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index e8d099dca..1309aba61 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -114,7 +114,7 @@ export interface EventContext { /** * An object containing the values of the wildcards in the `path` parameter - * provided to the [`ref()`](functions.database#.ref) method for a Realtime + * provided to the [`ref()`](providers_database_.html#ref) method for a Realtime * Database trigger. Cannot be accessed while inside the handler namespace. */ params: { [option: string]: any }; diff --git a/src/providers/analytics.ts b/src/providers/analytics.ts index 43578f8b6..2d57d6f5e 100644 --- a/src/providers/analytics.ts +++ b/src/providers/analytics.ts @@ -184,7 +184,7 @@ export class UserDimensions { * A map of user properties set with the * [`setUserProperty`](https://firebase.google.com/docs/analytics/android/properties) API. * - * All values are [`UserPropertyValue`](functions.analytics.UserPropertyValue) objects. + * All values are [`UserPropertyValue`](providers_analytics_.userpropertyvalue) objects. */ userProperties: { [key: string]: UserPropertyValue }; @@ -319,7 +319,8 @@ export interface DeviceInfo { /** * The time zone of the device when data was uploaded, as seconds skew from UTC. - * Use this to calculate the device's local time for [`event.timestamp`](functions.Event#timestamp). + * Use this to calculate the device's local time for + * [`EventContext.timestamp`](cloud_functions_eventcontext.html#timestamp). */ deviceTimeZoneOffsetSeconds: number; diff --git a/src/providers/database.ts b/src/providers/database.ts index 8a2f9453a..e648be20e 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -75,7 +75,7 @@ export function instance(instance: string) { * 1. Cloud Functions allows wildcards in the `path` name. Any `path` component * in curly brackets (`{}`) is a wildcard that matches all strings. The value * that matched a certain invocation of a Cloud Function is returned as part - * of the [`event.params`](functions.EventContext#params) object. For + * of the [`EventContext.params`](cloud_functions_eventcontext.html#params object. For * example, `ref("messages/{messageId}")` matches changes at * `/messages/message1` or `/messages/message2`, resulting in * `event.params.messageId` being set to `"message1"` or `"message2"`, @@ -104,7 +104,7 @@ export function _instanceWithOptions( /** * The Firebase Realtime Database instance builder interface. * - * Access via [`functions.database.instance()`](functions.database#.instance). + * Access via [`database.instance()`](providers_database_.html#instance). */ export class InstanceBuilder { /** @hidden */ diff --git a/src/providers/pubsub.ts b/src/providers/pubsub.ts index d1a687819..aee9e6c17 100644 --- a/src/providers/pubsub.ts +++ b/src/providers/pubsub.ts @@ -112,7 +112,7 @@ export function _scheduleWithOptions( /** * The Google Cloud Pub/Sub topic builder. * - * Access via [`functions.pubsub.topic()`](functions.pubsub#.topic). + * Access via [`functions.pubsub.topic()`](providers_pubsub_.html#topic). */ export class TopicBuilder { /** @hidden */ From dd6cde1a6d4121e0a3a7bdef8a29a89c4b030f3e Mon Sep 17 00:00:00 2001 From: Kevin Jian Date: Fri, 8 Nov 2019 14:06:45 -0800 Subject: [PATCH 096/437] Update typescript in integration tests (#581) --- integration_test/package.node10.json | 2 +- integration_test/package.node8.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration_test/package.node10.json b/integration_test/package.node10.json index f45bab456..cf5eeeb25 100644 --- a/integration_test/package.node10.json +++ b/integration_test/package.node10.json @@ -14,7 +14,7 @@ }, "main": "lib/index.js", "devDependencies": { - "typescript": "~3.5.0" + "typescript": "~3.6.0" }, "engines": { "node": "10" diff --git a/integration_test/package.node8.json b/integration_test/package.node8.json index 777c11a89..8803f2fdc 100644 --- a/integration_test/package.node8.json +++ b/integration_test/package.node8.json @@ -14,7 +14,7 @@ }, "main": "lib/index.js", "devDependencies": { - "typescript": "~3.5.0" + "typescript": "~3.6.0" }, "engines": { "node": "8" From edcb35dd042cf350d50dfb618d60d0a5686e06fd Mon Sep 17 00:00:00 2001 From: egilmorez Date: Tue, 19 Nov 2019 10:01:41 -0800 Subject: [PATCH 097/437] Porting in minimal content for functions.config() reference (#582) * I persist in trying to fix links. * Adding comments for functions.config() along with a change that somehow evaded my last update to storage.ts. * Adding new entry for new config.Config.html file. * Removing circular link per review. --- docgen/content-sources/toc.yaml | 2 ++ src/config.ts | 13 +++++++++++-- src/providers/storage.ts | 4 ++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docgen/content-sources/toc.yaml b/docgen/content-sources/toc.yaml index 9ba761c15..378af2eef 100644 --- a/docgen/content-sources/toc.yaml +++ b/docgen/content-sources/toc.yaml @@ -20,6 +20,8 @@ toc: section: - title: 'Config' path: /docs/reference/functions/config_.config.html + - title: 'config.Config' + path: /docs/reference/functions/config_.config.config.html - title: 'functions.analytics' path: /docs/reference/functions/providers_analytics_.html diff --git a/src/config.ts b/src/config.ts index 9823b3746..8cd4d3dd1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -29,9 +29,18 @@ export function config(): config.Config { return config.singleton; } +/** + * Store and retrieve project configuration data such as third-party API + * keys or other settings. You can set configuration values using the + * Firebase CLI as described in + * [Environment Configuration](/docs/functions/config-env). + */ export namespace config { - // Config type is usable as a object (dot notation allowed), and firebase - // property will also code complete. + /** + * The Functions configuration interface. + * + * Access via `functions.config()`. + */ export interface Config { [key: string]: any; } diff --git a/src/providers/storage.ts b/src/providers/storage.ts index 3ba98ea72..33b2a64eb 100644 --- a/src/providers/storage.ts +++ b/src/providers/storage.ts @@ -84,7 +84,7 @@ export function _objectWithOptions(options: DeploymentOptions): ObjectBuilder { /** * The Google Cloud Storage bucket builder interface. * - * Access via [`functions.storage.bucket()`](functions.storage#.bucket). + * Access via [`functions.storage.bucket()`](providers_storage_.html#bucket). */ export class BucketBuilder { /** @hidden */ @@ -107,7 +107,7 @@ export class BucketBuilder { /** * The Google Cloud Storage object builder interface. * - * Access via [`functions.storage.object()`](functions.storage#.object). + * Access via [`functions.storage.object()`](providers_storage_.html#object). */ export class ObjectBuilder { /** @hidden */ From 8039e0286eebb054275dae6763e50e76c2e05d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natan=20S=C4=85gol?= Date: Wed, 27 Nov 2019 22:46:27 +0100 Subject: [PATCH 098/437] Allow specifying failure policies (#482) * Define an interface for FailurePolicy * Extract functions config to avoid dependency cycles, assign failure policy to triggers * Add a changelog entry * Update dependencies, minor version bump * Reformat * Fix a typo: allows -> allow * Avoid abbreviations to improve readability * Rename remaining Opts to Options * Add tests for specifying failure policy * Reformat * Change format of an entry in the changelog * Revert version bump * Extract configuration to break dependency cycle * Add comments to Schedule and ScheduleRetryConfig * Stricten regions definition * Fix tests broken due to strict typings * More strict types for regions - reverse order * Reformat * Remove unused import * Conform with npm formatting * Reintroduce unused variable to minimize diff size * Import lodash using _ exclusively * Kepp @hidden tags in one line if no other JSDoc is present * Fix a typo - sentence ending with a comma * Wrap JSDoc as specified in Google JavaScript Style Guide * Move memory lookup table to functions-configuration and stricten type definition * Move default failure policy to functions-configuration * Separate standarization of options from construction of the options object * Rephrase failure policy description * Rephrase description of memory and timeoutSeconds options * Simplify tests for invalid failure policies * Align public description of failure policies with a documentation of its interface * Add a missing dot in the Changelog * Minor stylistic changes to the documentation * Make a test case more understandable * Reformat --- changelog.txt | 1 + spec/function-builder.spec.ts | 42 +++++++++- spec/providers/auth.spec.ts | 4 +- src/cloud-functions.ts | 73 ++++++++++------- src/function-builder.ts | 148 +++++++++++++++++++++++----------- src/function-configuration.ts | 26 ++++++ 6 files changed, 212 insertions(+), 82 deletions(-) diff --git a/changelog.txt b/changelog.txt index e69de29bb..404ffb503 100644 --- a/changelog.txt +++ b/changelog.txt @@ -0,0 +1 @@ +feature - Allow specifying retry policies for event triggered functions. diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index b0c4d4c28..1ba12c7fa 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -79,14 +79,29 @@ describe('FunctionBuilder', () => { it('should allow valid runtime options to be set', () => { const fn = functions .runWith({ - timeoutSeconds: 90, + failurePolicy: { retry: {} }, memory: '256MB', + timeoutSeconds: 90, }) .auth.user() .onCreate((user) => user); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.failurePolicy).to.deep.equal({ retry: {} }); + }); + + it("should apply a default failure policy if it's aliased with `true`", () => { + const fn = functions + .runWith({ + failurePolicy: true, + memory: '256MB', + timeoutSeconds: 90, + }) + .auth.user() + .onCreate((user) => user); + + expect(fn.__trigger.failurePolicy).to.deep.equal({ retry: {} }); }); it('should allow both supported region and valid runtime options to be set', () => { @@ -132,7 +147,26 @@ describe('FunctionBuilder', () => { functions .region('asia-northeast1') .runWith({ timeoutSeconds: 600, memory: '256MB' }); - }).to.throw(Error, 'TimeoutSeconds'); + }).to.throw(Error, 'RuntimeOptions.timeoutSeconds'); + }); + + it('should throw an error if user chooses a failurePolicy which is neither an object nor a boolean', () => { + expect(() => + functions.runWith({ + failurePolicy: (1234 as unknown) as functions.RuntimeOptions['failurePolicy'], + }) + ).to.throw( + Error, + 'RuntimeOptions.failurePolicy must be a boolean or an object' + ); + }); + + it('should throw an error if user chooses a failurePolicy.retry which is not an object', () => { + expect(() => + functions.runWith({ + failurePolicy: { retry: (1234 as unknown) as object }, + }) + ).to.throw(Error, 'RuntimeOptions.failurePolicy.retry'); }); it('should throw an error if user chooses an invalid memory allocation', () => { @@ -154,13 +188,13 @@ describe('FunctionBuilder', () => { return functions.runWith({ timeoutSeconds: 1000000, } as any); - }).to.throw(Error, 'TimeoutSeconds'); + }).to.throw(Error, 'RuntimeOptions.timeoutSeconds'); expect(() => { return functions.region('asia-east2').runWith({ timeoutSeconds: 1000000, } as any); - }).to.throw(Error, 'TimeoutSeconds'); + }).to.throw(Error, 'RuntimeOptions.timeoutSeconds'); }); it('should throw an error if user chooses an invalid region', () => { diff --git a/spec/providers/auth.spec.ts b/spec/providers/auth.spec.ts index 769bc03bf..bb2ab7761 100644 --- a/spec/providers/auth.spec.ts +++ b/spec/providers/auth.spec.ts @@ -197,9 +197,7 @@ describe('Auth Functions', () => { }); describe('#onDelete', () => { - const cloudFunctionDelete: CloudFunction< - firebase.auth.UserRecord - > = functions.handler.auth.user.onDelete( + const cloudFunctionDelete: CloudFunction = functions.handler.auth.user.onDelete( (data: firebase.auth.UserRecord) => data ); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 1309aba61..b18c965d7 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -22,7 +22,13 @@ import { Request, Response } from 'express'; import * as _ from 'lodash'; -import { DeploymentOptions, Schedule } from './function-configuration'; +import { + DEFAULT_FAILURE_POLICY, + DeploymentOptions, + FailurePolicy, + MEMORY_LOOKUP, + Schedule, +} from './function-configuration'; export { Request, Response }; /** @hidden */ @@ -202,6 +208,7 @@ export namespace Change { if (json.fieldMask) { before = applyFieldMask(before, json.after, json.fieldMask); } + return Change.fromObjects( customizer(before || {}), customizer(json.after || {}) @@ -216,7 +223,8 @@ export namespace Change { ) { const before = _.assign({}, after); const masks = fieldMask.split(','); - _.forEach(masks, (mask) => { + + masks.forEach((mask) => { const val = _.get(sparseBefore, mask); if (typeof val === 'undefined') { _.unset(before, mask); @@ -224,6 +232,7 @@ export namespace Change { _.set(before, mask, val); } }); + return before; } } @@ -253,6 +262,7 @@ export interface TriggerAnnotated { resource: string; service: string; }; + failurePolicy?: FailurePolicy; httpsTrigger?: {}; labels?: { [key: string]: string }; regions?: string[]; @@ -312,6 +322,40 @@ export interface MakeCloudFunctionArgs { triggerResource: () => string; } +/** @hidden */ +export function optionsToTrigger({ + failurePolicy: failurePolicyOrAlias, + memory, + regions, + schedule, + timeoutSeconds, +}: DeploymentOptions): TriggerAnnotated['__trigger'] { + /* + * FailurePolicy can be aliased with a boolean value in the public API. + * Convert aliases `true` and `false` to a standardized interface. + */ + const failurePolicy: FailurePolicy | undefined = + failurePolicyOrAlias === false + ? undefined + : failurePolicyOrAlias === true + ? DEFAULT_FAILURE_POLICY + : failurePolicyOrAlias; + + const availableMemoryMb: number | undefined = + memory === undefined ? undefined : MEMORY_LOOKUP[memory]; + + const timeout: string | undefined = + timeoutSeconds === undefined ? undefined : `${timeoutSeconds}s`; + + return { + ...(failurePolicy === undefined ? {} : { failurePolicy }), + ...(availableMemoryMb === undefined ? {} : { availableMemoryMb }), + ...(regions === undefined ? {} : { regions }), + ...(schedule === undefined ? {} : { schedule }), + ...(timeout === undefined ? {} : { timeout }), + }; +} + /** @hidden */ export function makeCloudFunction({ after = () => {}, @@ -463,28 +507,3 @@ function _detectAuthType(event: Event) { } return 'UNAUTHENTICATED'; } - -/** @hidden */ -export function optionsToTrigger(options: DeploymentOptions) { - const trigger: any = {}; - if (options.regions) { - trigger.regions = options.regions; - } - if (options.timeoutSeconds) { - trigger.timeout = options.timeoutSeconds.toString() + 's'; - } - if (options.memory) { - const memoryLookup = { - '128MB': 128, - '256MB': 256, - '512MB': 512, - '1GB': 1024, - '2GB': 2048, - }; - trigger.availableMemoryMb = _.get(memoryLookup, options.memory); - } - if (options.schedule) { - trigger.schedule = options.schedule; - } - return trigger; -} diff --git a/src/function-builder.ts b/src/function-builder.ts index 580e0498b..13fd30990 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -27,6 +27,7 @@ import { CloudFunction, EventContext } from './cloud-functions'; import { DeploymentOptions, MAX_TIMEOUT_SECONDS, + MIN_TIMEOUT_SECONDS, RuntimeOptions, SUPPORTED_REGIONS, VALID_MEMORY_OPTIONS, @@ -45,28 +46,62 @@ import * as testLab from './providers/testLab'; /** * Assert that the runtime options passed in are valid. * @param runtimeOptions object containing memory and timeout information. - * @throws { Error } Memory and TimeoutSeconds values must be valid. + * @throws { Error } FailurePolicy, Memory and TimeoutSeconds values must be + * valid. */ -function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean { - if ( - runtimeOptions.memory && - !_.includes(VALID_MEMORY_OPTIONS, runtimeOptions.memory) - ) { - throw new Error( - `The only valid memory allocation values are: ${VALID_MEMORY_OPTIONS.join( - ', ' - )}` - ); +function assertRuntimeOptionsValidity(runtimeOptions: RuntimeOptions): void { + if (_.isObjectLike(runtimeOptions) === false) { + throw new Error('RuntimeOptions must be an object.'); } - if ( - runtimeOptions.timeoutSeconds > MAX_TIMEOUT_SECONDS || - runtimeOptions.timeoutSeconds < 0 - ) { - throw new Error( - `TimeoutSeconds must be between 0 and ${MAX_TIMEOUT_SECONDS}` - ); + + const { failurePolicy, memory, timeoutSeconds } = runtimeOptions; + + if (failurePolicy !== undefined) { + if ( + _.isBoolean(failurePolicy) === false && + _.isObjectLike(failurePolicy) === false + ) { + throw new Error( + `RuntimeOptions.failurePolicy must be a boolean or an object.` + ); + } + + if (typeof failurePolicy === 'object') { + if ( + _.isObjectLike(failurePolicy.retry) === false || + _.isEmpty(failurePolicy.retry) === false + ) { + throw new Error( + 'RuntimeOptions.failurePolicy.retry must be an empty object.' + ); + } + } + } + + if (memory !== undefined) { + if (_.includes(VALID_MEMORY_OPTIONS, memory) === false) { + throw new Error( + `RuntimeOptions.memory must be one of: ${VALID_MEMORY_OPTIONS.join( + ', ' + )}.` + ); + } + } + + if (timeoutSeconds !== undefined) { + if (typeof timeoutSeconds !== 'number') { + throw new Error('RuntimeOptions.timeoutSeconds must be a number.'); + } + + if ( + timeoutSeconds < MIN_TIMEOUT_SECONDS || + timeoutSeconds > MAX_TIMEOUT_SECONDS + ) { + throw new Error( + `RuntimeOptions.timeoutSeconds must be between ${MIN_TIMEOUT_SECONDS} and ${MAX_TIMEOUT_SECONDS}.` + ); + } } - return true; } /** @@ -74,16 +109,16 @@ function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean { * @param regions list of regions. * @throws { Error } Regions must be in list of supported regions. */ -function assertRegionsAreValid(regions: string[]): boolean { - if (!regions.length) { - throw new Error('You must specify at least one region'); +function assertRegionsValidity(regions: string[]): void { + if (regions.length === 0) { + throw new Error('You must specify at least one region.'); } - if (_.difference(regions, SUPPORTED_REGIONS).length) { + + if (_.difference(regions, SUPPORTED_REGIONS).length !== 0) { throw new Error( - `The only valid regions are: ${SUPPORTED_REGIONS.join(', ')}` + `The only valid regions are: ${SUPPORTED_REGIONS.join(', ')}.` ); } - return true; } /** @@ -97,23 +132,27 @@ function assertRegionsAreValid(regions: string[]): boolean { export function region( ...regions: Array ): FunctionBuilder { - if (assertRegionsAreValid(regions)) { - return new FunctionBuilder({ regions }); - } + assertRegionsValidity(regions); + + return new FunctionBuilder({ regions }); } /** * Configure runtime options for the function. * @param runtimeOptions Object with three optional fields: - * 1. memory: amount of memory to allocate to the function, possible values - * are: '128MB', '256MB', '512MB', '1GB', and '2GB'. - * 2. timeoutSeconds: timeout for the function in seconds, possible values are - * 0 to 540. + * 1. failurePolicy: failure policy of the function, with boolean `true` being + * equivalent to providing an empty retry object. + * 2. memory: amount of memory to allocate to the function, with possible + * values being '128MB', '256MB', '512MB', '1GB', and '2GB'. + * 3. timeoutSeconds: timeout for the function in seconds, with possible + * values being 0 to 540. + * + * Value must not be null. */ export function runWith(runtimeOptions: RuntimeOptions): FunctionBuilder { - if (assertRuntimeOptionsValid(runtimeOptions)) { - return new FunctionBuilder(runtimeOptions); - } + assertRuntimeOptionsValidity(runtimeOptions); + + return new FunctionBuilder(runtimeOptions); } export class FunctionBuilder { @@ -128,28 +167,40 @@ export class FunctionBuilder { * functions.region('us-east1', 'us-central1') */ region(...regions: Array): FunctionBuilder { - if (assertRegionsAreValid(regions)) { - this.options.regions = regions; - return this; - } + assertRegionsValidity(regions); + + this.options.regions = regions; + + return this; } /** * Configure runtime options for the function. * @param runtimeOptions Object with three optional fields: - * 1. memory: amount of memory to allocate to the function, possible values - * are: '128MB', '256MB', '512MB', '1GB', and '2GB'. - * 2. timeoutSeconds: timeout for the function in seconds, possible values are - * 0 to 540. + * 1. failurePolicy: failure policy of the function, with boolean `true` being + * equivalent to providing an empty retry object. + * 2. memory: amount of memory to allocate to the function, with possible + * values being '128MB', '256MB', '512MB', '1GB', and '2GB'. + * 3. timeoutSeconds: timeout for the function in seconds, with possible + * values being 0 to 540. + * + * Value must not be null. */ runWith(runtimeOptions: RuntimeOptions): FunctionBuilder { - if (assertRuntimeOptionsValid(runtimeOptions)) { - this.options = _.assign(this.options, runtimeOptions); - return this; - } + assertRuntimeOptionsValidity(runtimeOptions); + + this.options = _.assign(this.options, runtimeOptions); + + return this; } get https() { + if (this.options.failurePolicy !== undefined) { + console.warn( + 'RuntimeOptions.failurePolicy is not supported in https functions.' + ); + } + return { /** * Handle HTTP requests. @@ -161,7 +212,8 @@ export class FunctionBuilder { ) => https._onRequestWithOptions(handler, this.options), /** * Declares a callable method for clients to call using a Firebase SDK. - * @param handler A method that takes a data and context and returns a value. + * @param handler A method that takes a data and context and returns + * a value. */ onCall: ( handler: ( diff --git a/src/function-configuration.ts b/src/function-configuration.ts index ec1695ccd..f3883155b 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -32,6 +32,19 @@ export const VALID_MEMORY_OPTIONS = [ '2GB', ] as const; +/** + * A mapping of memory options to its representation in the Cloud Functions API. + */ +export const MEMORY_LOOKUP: { + [Name in typeof VALID_MEMORY_OPTIONS[number]]: number; +} = { + '128MB': 128, + '256MB': 256, + '512MB': 512, + '1GB': 1024, + '2GB': 2048, +}; + /** * Scheduler retry options. Applies only to scheduled functions. */ @@ -52,7 +65,20 @@ export interface Schedule { retryConfig?: ScheduleRetryConfig; } +export interface FailurePolicy { + retry: {}; +} + +export const DEFAULT_FAILURE_POLICY: FailurePolicy = { + retry: {}, +}; + export interface RuntimeOptions { + /** + * Failure policy of the function, with boolean `true` being equivalent to + * providing an empty retry object. + */ + failurePolicy?: FailurePolicy | boolean; /** * Amount of memory to allocate to the function. */ From 9df27f44c0c96b3dc83e2e30ee93ff45809e8374 Mon Sep 17 00:00:00 2001 From: Tina Liang Date: Mon, 13 Jan 2020 11:52:31 -0800 Subject: [PATCH 099/437] adding scheduled function to handler builder (#230) * adding scheduled function to handler builder --- spec/providers/pubsub.spec.ts | 106 +++++++++++++++++++++++----------- src/handler-builder.ts | 6 ++ src/providers/pubsub.ts | 99 ++++++++++++++++--------------- 3 files changed, 131 insertions(+), 80 deletions(-) diff --git a/spec/providers/pubsub.spec.ts b/spec/providers/pubsub.spec.ts index 53776c357..f64cd2119 100644 --- a/spec/providers/pubsub.spec.ts +++ b/spec/providers/pubsub.spec.ts @@ -311,45 +311,83 @@ describe('Pubsub Functions', () => { describe('handler namespace', () => { describe('#onPublish', () => { - it('should return an empty trigger', () => { - const result = functions.handler.pubsub.topic.onPublish(() => null); - expect(result.__trigger).to.deep.equal({}); - }); + describe('#topic', () => { + it('should return an empty trigger', () => { + const result = functions.handler.pubsub.topic.onPublish(() => null); + expect(result.__trigger).to.deep.equal({}); + }); - it('should properly handle a new-style event', () => { - const raw = new Buffer('{"hello":"world"}', 'utf8').toString('base64'); - const event = { - data: { - data: raw, - attributes: { - foo: 'bar', + it('should properly handle a new-style event', () => { + const raw = new Buffer('{"hello":"world"}', 'utf8').toString( + 'base64' + ); + const event = { + data: { + data: raw, + attributes: { + foo: 'bar', + }, }, - }, - context: { - eventId: '70172329041928', - timestamp: '2018-04-09T07:56:12.975Z', - eventType: 'google.pubsub.topic.publish', - resource: { - service: 'pubsub.googleapis.com', - name: 'projects/project1/topics/toppy', + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.pubsub.topic.publish', + resource: { + service: 'pubsub.googleapis.com', + name: 'projects/project1/topics/toppy', + }, }, - }, - }; - - const result = functions.handler.pubsub.topic.onPublish((data) => { - return { - raw: data.data, - json: data.json, - attributes: data.attributes, }; - }); - return expect( - result(event.data, event.context) - ).to.eventually.deep.equal({ - raw, - json: { hello: 'world' }, - attributes: { foo: 'bar' }, + const result = functions.handler.pubsub.topic.onPublish((data) => { + return { + raw: data.data, + json: data.json, + attributes: data.attributes, + }; + }); + + return expect( + result(event.data, event.context) + ).to.eventually.deep.equal({ + raw, + json: { hello: 'world' }, + attributes: { foo: 'bar' }, + }); + }); + }); + describe('#schedule', () => { + it('should return an empty trigger', () => { + const result = functions.handler.pubsub.schedule.onRun(() => null); + expect(result.__trigger).to.deep.equal({}); + }); + it('should return a handler with a proper event context', () => { + const raw = new Buffer('{"hello":"world"}', 'utf8').toString( + 'base64' + ); + const event = { + data: { + data: raw, + attributes: { + foo: 'bar', + }, + }, + context: { + eventId: '70172329041928', + timestamp: '2018-04-09T07:56:12.975Z', + eventType: 'google.pubsub.topic.publish', + resource: { + service: 'pubsub.googleapis.com', + name: 'projects/project1/topics/toppy', + }, + }, + }; + const result = functions.handler.pubsub.schedule.onRun( + (context) => context.eventId + ); + return expect(result(event.data, event.context)).to.eventually.equal( + '70172329041928' + ); }); }); }); diff --git a/src/handler-builder.ts b/src/handler-builder.ts index 22b2268aa..5d7c122d7 100644 --- a/src/handler-builder.ts +++ b/src/handler-builder.ts @@ -201,6 +201,12 @@ export class HandlerBuilder { get topic() { return new pubsub.TopicBuilder(() => null, {}); }, + /** + * Handle periodic events triggered by Cloud Scheduler. + */ + get schedule() { + return new pubsub.ScheduleBuilder(() => null, {}); + }, }; } diff --git a/src/providers/pubsub.ts b/src/providers/pubsub.ts index aee9e6c17..0f12f669c 100644 --- a/src/providers/pubsub.ts +++ b/src/providers/pubsub.ts @@ -63,52 +63,6 @@ export function _topicWithOptions( }, options); } -export function schedule(schedule: string): ScheduleBuilder { - return _scheduleWithOptions(schedule, {}); -} - -export class ScheduleBuilder { - /** @hidden */ - constructor(private options: DeploymentOptions) {} - - retryConfig(config: ScheduleRetryConfig): ScheduleBuilder { - this.options.schedule.retryConfig = config; - return this; - } - - timeZone(timeZone: string): ScheduleBuilder { - this.options.schedule.timeZone = timeZone; - return this; - } - - onRun(handler: (context: EventContext) => PromiseLike | any) { - const triggerResource = () => { - if (!process.env.GCLOUD_PROJECT) { - throw new Error('process.env.GCLOUD_PROJECT is not set.'); - } - return `projects/${process.env.GCLOUD_PROJECT}/topics`; // The CLI will append the correct topic name based on region and function name - }; - const cloudFunction = makeCloudFunction({ - contextOnlyHandler: handler, - provider, - service, - triggerResource, - eventType: 'topic.publish', - options: this.options, - labels: { 'deployment-scheduled': 'true' }, - }); - return cloudFunction; - } -} - -/** @hidden */ -export function _scheduleWithOptions( - schedule: string, - options: DeploymentOptions -): ScheduleBuilder { - return new ScheduleBuilder({ ...options, schedule: { schedule } }); -} - /** * The Google Cloud Pub/Sub topic builder. * @@ -144,6 +98,59 @@ export class TopicBuilder { } } +export function schedule(schedule: string): ScheduleBuilder { + return _scheduleWithOptions(schedule, {}); +} + +/** @hidden */ +export function _scheduleWithOptions( + schedule: string, + options: DeploymentOptions +): ScheduleBuilder { + const triggerResource = () => { + if (!process.env.GCLOUD_PROJECT) { + throw new Error('process.env.GCLOUD_PROJECT is not set.'); + } + // The CLI will append the correct topic name based on region and function name + return `projects/${process.env.GCLOUD_PROJECT}/topics`; + }; + return new ScheduleBuilder(triggerResource, { + ...options, + schedule: { schedule }, + }); +} + +export class ScheduleBuilder { + /** @hidden */ + constructor( + private triggerResource: () => string, + private options: DeploymentOptions + ) {} + + retryConfig(config: ScheduleRetryConfig): ScheduleBuilder { + this.options.schedule.retryConfig = config; + return this; + } + + timeZone(timeZone: string): ScheduleBuilder { + this.options.schedule.timeZone = timeZone; + return this; + } + + onRun(handler: (context: EventContext) => PromiseLike | any) { + const cloudFunction = makeCloudFunction({ + contextOnlyHandler: handler, + provider, + service, + triggerResource: this.triggerResource, + eventType: 'topic.publish', + options: this.options, + labels: { 'deployment-scheduled': 'true' }, + }); + return cloudFunction; + } +} + /** * Interface representing a Google Cloud Pub/Sub message. * From ae902790ecc53ee6344726c08af4255adc0de783 Mon Sep 17 00:00:00 2001 From: Kevin Jian Date: Wed, 5 Feb 2020 12:50:06 -0800 Subject: [PATCH 100/437] Update type to fix tests (#620) --- spec/fixtures/mockrequest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/fixtures/mockrequest.ts b/spec/fixtures/mockrequest.ts index 2139c71ce..fff2ce1a9 100644 --- a/spec/fixtures/mockrequest.ts +++ b/spec/fixtures/mockrequest.ts @@ -68,7 +68,7 @@ export function mockFetchPublicKeys(): nock.Scope { */ export function generateIdToken(projectId: string): string { const claims = {}; - const options = { + const options: jwt.SignOptions = { audience: projectId, expiresIn: 60 * 60, // 1 hour in seconds issuer: 'https://securetoken.google.com/' + projectId, From d9fc8a6bb6e6a34e478bb6de98c64514e16ff1fa Mon Sep 17 00:00:00 2001 From: Tina Liang Date: Wed, 5 Feb 2020 14:37:44 -0800 Subject: [PATCH 101/437] Revert "Allow specifying failure policies" (#623) * Revert "Allow specifying failure policies (#482) --- changelog.txt | 1 - spec/function-builder.spec.ts | 42 +--------- src/cloud-functions.ts | 73 +++++++---------- src/function-builder.ts | 148 +++++++++++----------------------- src/function-configuration.ts | 26 ------ 5 files changed, 79 insertions(+), 211 deletions(-) diff --git a/changelog.txt b/changelog.txt index 404ffb503..e69de29bb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1 +0,0 @@ -feature - Allow specifying retry policies for event triggered functions. diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index 1ba12c7fa..b0c4d4c28 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -79,29 +79,14 @@ describe('FunctionBuilder', () => { it('should allow valid runtime options to be set', () => { const fn = functions .runWith({ - failurePolicy: { retry: {} }, - memory: '256MB', timeoutSeconds: 90, + memory: '256MB', }) .auth.user() .onCreate((user) => user); expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); expect(fn.__trigger.timeout).to.deep.equal('90s'); - expect(fn.__trigger.failurePolicy).to.deep.equal({ retry: {} }); - }); - - it("should apply a default failure policy if it's aliased with `true`", () => { - const fn = functions - .runWith({ - failurePolicy: true, - memory: '256MB', - timeoutSeconds: 90, - }) - .auth.user() - .onCreate((user) => user); - - expect(fn.__trigger.failurePolicy).to.deep.equal({ retry: {} }); }); it('should allow both supported region and valid runtime options to be set', () => { @@ -147,26 +132,7 @@ describe('FunctionBuilder', () => { functions .region('asia-northeast1') .runWith({ timeoutSeconds: 600, memory: '256MB' }); - }).to.throw(Error, 'RuntimeOptions.timeoutSeconds'); - }); - - it('should throw an error if user chooses a failurePolicy which is neither an object nor a boolean', () => { - expect(() => - functions.runWith({ - failurePolicy: (1234 as unknown) as functions.RuntimeOptions['failurePolicy'], - }) - ).to.throw( - Error, - 'RuntimeOptions.failurePolicy must be a boolean or an object' - ); - }); - - it('should throw an error if user chooses a failurePolicy.retry which is not an object', () => { - expect(() => - functions.runWith({ - failurePolicy: { retry: (1234 as unknown) as object }, - }) - ).to.throw(Error, 'RuntimeOptions.failurePolicy.retry'); + }).to.throw(Error, 'TimeoutSeconds'); }); it('should throw an error if user chooses an invalid memory allocation', () => { @@ -188,13 +154,13 @@ describe('FunctionBuilder', () => { return functions.runWith({ timeoutSeconds: 1000000, } as any); - }).to.throw(Error, 'RuntimeOptions.timeoutSeconds'); + }).to.throw(Error, 'TimeoutSeconds'); expect(() => { return functions.region('asia-east2').runWith({ timeoutSeconds: 1000000, } as any); - }).to.throw(Error, 'RuntimeOptions.timeoutSeconds'); + }).to.throw(Error, 'TimeoutSeconds'); }); it('should throw an error if user chooses an invalid region', () => { diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index b18c965d7..1309aba61 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -22,13 +22,7 @@ import { Request, Response } from 'express'; import * as _ from 'lodash'; -import { - DEFAULT_FAILURE_POLICY, - DeploymentOptions, - FailurePolicy, - MEMORY_LOOKUP, - Schedule, -} from './function-configuration'; +import { DeploymentOptions, Schedule } from './function-configuration'; export { Request, Response }; /** @hidden */ @@ -208,7 +202,6 @@ export namespace Change { if (json.fieldMask) { before = applyFieldMask(before, json.after, json.fieldMask); } - return Change.fromObjects( customizer(before || {}), customizer(json.after || {}) @@ -223,8 +216,7 @@ export namespace Change { ) { const before = _.assign({}, after); const masks = fieldMask.split(','); - - masks.forEach((mask) => { + _.forEach(masks, (mask) => { const val = _.get(sparseBefore, mask); if (typeof val === 'undefined') { _.unset(before, mask); @@ -232,7 +224,6 @@ export namespace Change { _.set(before, mask, val); } }); - return before; } } @@ -262,7 +253,6 @@ export interface TriggerAnnotated { resource: string; service: string; }; - failurePolicy?: FailurePolicy; httpsTrigger?: {}; labels?: { [key: string]: string }; regions?: string[]; @@ -322,40 +312,6 @@ export interface MakeCloudFunctionArgs { triggerResource: () => string; } -/** @hidden */ -export function optionsToTrigger({ - failurePolicy: failurePolicyOrAlias, - memory, - regions, - schedule, - timeoutSeconds, -}: DeploymentOptions): TriggerAnnotated['__trigger'] { - /* - * FailurePolicy can be aliased with a boolean value in the public API. - * Convert aliases `true` and `false` to a standardized interface. - */ - const failurePolicy: FailurePolicy | undefined = - failurePolicyOrAlias === false - ? undefined - : failurePolicyOrAlias === true - ? DEFAULT_FAILURE_POLICY - : failurePolicyOrAlias; - - const availableMemoryMb: number | undefined = - memory === undefined ? undefined : MEMORY_LOOKUP[memory]; - - const timeout: string | undefined = - timeoutSeconds === undefined ? undefined : `${timeoutSeconds}s`; - - return { - ...(failurePolicy === undefined ? {} : { failurePolicy }), - ...(availableMemoryMb === undefined ? {} : { availableMemoryMb }), - ...(regions === undefined ? {} : { regions }), - ...(schedule === undefined ? {} : { schedule }), - ...(timeout === undefined ? {} : { timeout }), - }; -} - /** @hidden */ export function makeCloudFunction({ after = () => {}, @@ -507,3 +463,28 @@ function _detectAuthType(event: Event) { } return 'UNAUTHENTICATED'; } + +/** @hidden */ +export function optionsToTrigger(options: DeploymentOptions) { + const trigger: any = {}; + if (options.regions) { + trigger.regions = options.regions; + } + if (options.timeoutSeconds) { + trigger.timeout = options.timeoutSeconds.toString() + 's'; + } + if (options.memory) { + const memoryLookup = { + '128MB': 128, + '256MB': 256, + '512MB': 512, + '1GB': 1024, + '2GB': 2048, + }; + trigger.availableMemoryMb = _.get(memoryLookup, options.memory); + } + if (options.schedule) { + trigger.schedule = options.schedule; + } + return trigger; +} diff --git a/src/function-builder.ts b/src/function-builder.ts index 13fd30990..580e0498b 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -27,7 +27,6 @@ import { CloudFunction, EventContext } from './cloud-functions'; import { DeploymentOptions, MAX_TIMEOUT_SECONDS, - MIN_TIMEOUT_SECONDS, RuntimeOptions, SUPPORTED_REGIONS, VALID_MEMORY_OPTIONS, @@ -46,62 +45,28 @@ import * as testLab from './providers/testLab'; /** * Assert that the runtime options passed in are valid. * @param runtimeOptions object containing memory and timeout information. - * @throws { Error } FailurePolicy, Memory and TimeoutSeconds values must be - * valid. + * @throws { Error } Memory and TimeoutSeconds values must be valid. */ -function assertRuntimeOptionsValidity(runtimeOptions: RuntimeOptions): void { - if (_.isObjectLike(runtimeOptions) === false) { - throw new Error('RuntimeOptions must be an object.'); - } - - const { failurePolicy, memory, timeoutSeconds } = runtimeOptions; - - if (failurePolicy !== undefined) { - if ( - _.isBoolean(failurePolicy) === false && - _.isObjectLike(failurePolicy) === false - ) { - throw new Error( - `RuntimeOptions.failurePolicy must be a boolean or an object.` - ); - } - - if (typeof failurePolicy === 'object') { - if ( - _.isObjectLike(failurePolicy.retry) === false || - _.isEmpty(failurePolicy.retry) === false - ) { - throw new Error( - 'RuntimeOptions.failurePolicy.retry must be an empty object.' - ); - } - } - } - - if (memory !== undefined) { - if (_.includes(VALID_MEMORY_OPTIONS, memory) === false) { - throw new Error( - `RuntimeOptions.memory must be one of: ${VALID_MEMORY_OPTIONS.join( - ', ' - )}.` - ); - } +function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean { + if ( + runtimeOptions.memory && + !_.includes(VALID_MEMORY_OPTIONS, runtimeOptions.memory) + ) { + throw new Error( + `The only valid memory allocation values are: ${VALID_MEMORY_OPTIONS.join( + ', ' + )}` + ); } - - if (timeoutSeconds !== undefined) { - if (typeof timeoutSeconds !== 'number') { - throw new Error('RuntimeOptions.timeoutSeconds must be a number.'); - } - - if ( - timeoutSeconds < MIN_TIMEOUT_SECONDS || - timeoutSeconds > MAX_TIMEOUT_SECONDS - ) { - throw new Error( - `RuntimeOptions.timeoutSeconds must be between ${MIN_TIMEOUT_SECONDS} and ${MAX_TIMEOUT_SECONDS}.` - ); - } + if ( + runtimeOptions.timeoutSeconds > MAX_TIMEOUT_SECONDS || + runtimeOptions.timeoutSeconds < 0 + ) { + throw new Error( + `TimeoutSeconds must be between 0 and ${MAX_TIMEOUT_SECONDS}` + ); } + return true; } /** @@ -109,16 +74,16 @@ function assertRuntimeOptionsValidity(runtimeOptions: RuntimeOptions): void { * @param regions list of regions. * @throws { Error } Regions must be in list of supported regions. */ -function assertRegionsValidity(regions: string[]): void { - if (regions.length === 0) { - throw new Error('You must specify at least one region.'); +function assertRegionsAreValid(regions: string[]): boolean { + if (!regions.length) { + throw new Error('You must specify at least one region'); } - - if (_.difference(regions, SUPPORTED_REGIONS).length !== 0) { + if (_.difference(regions, SUPPORTED_REGIONS).length) { throw new Error( - `The only valid regions are: ${SUPPORTED_REGIONS.join(', ')}.` + `The only valid regions are: ${SUPPORTED_REGIONS.join(', ')}` ); } + return true; } /** @@ -132,27 +97,23 @@ function assertRegionsValidity(regions: string[]): void { export function region( ...regions: Array ): FunctionBuilder { - assertRegionsValidity(regions); - - return new FunctionBuilder({ regions }); + if (assertRegionsAreValid(regions)) { + return new FunctionBuilder({ regions }); + } } /** * Configure runtime options for the function. * @param runtimeOptions Object with three optional fields: - * 1. failurePolicy: failure policy of the function, with boolean `true` being - * equivalent to providing an empty retry object. - * 2. memory: amount of memory to allocate to the function, with possible - * values being '128MB', '256MB', '512MB', '1GB', and '2GB'. - * 3. timeoutSeconds: timeout for the function in seconds, with possible - * values being 0 to 540. - * - * Value must not be null. + * 1. memory: amount of memory to allocate to the function, possible values + * are: '128MB', '256MB', '512MB', '1GB', and '2GB'. + * 2. timeoutSeconds: timeout for the function in seconds, possible values are + * 0 to 540. */ export function runWith(runtimeOptions: RuntimeOptions): FunctionBuilder { - assertRuntimeOptionsValidity(runtimeOptions); - - return new FunctionBuilder(runtimeOptions); + if (assertRuntimeOptionsValid(runtimeOptions)) { + return new FunctionBuilder(runtimeOptions); + } } export class FunctionBuilder { @@ -167,40 +128,28 @@ export class FunctionBuilder { * functions.region('us-east1', 'us-central1') */ region(...regions: Array): FunctionBuilder { - assertRegionsValidity(regions); - - this.options.regions = regions; - - return this; + if (assertRegionsAreValid(regions)) { + this.options.regions = regions; + return this; + } } /** * Configure runtime options for the function. * @param runtimeOptions Object with three optional fields: - * 1. failurePolicy: failure policy of the function, with boolean `true` being - * equivalent to providing an empty retry object. - * 2. memory: amount of memory to allocate to the function, with possible - * values being '128MB', '256MB', '512MB', '1GB', and '2GB'. - * 3. timeoutSeconds: timeout for the function in seconds, with possible - * values being 0 to 540. - * - * Value must not be null. + * 1. memory: amount of memory to allocate to the function, possible values + * are: '128MB', '256MB', '512MB', '1GB', and '2GB'. + * 2. timeoutSeconds: timeout for the function in seconds, possible values are + * 0 to 540. */ runWith(runtimeOptions: RuntimeOptions): FunctionBuilder { - assertRuntimeOptionsValidity(runtimeOptions); - - this.options = _.assign(this.options, runtimeOptions); - - return this; + if (assertRuntimeOptionsValid(runtimeOptions)) { + this.options = _.assign(this.options, runtimeOptions); + return this; + } } get https() { - if (this.options.failurePolicy !== undefined) { - console.warn( - 'RuntimeOptions.failurePolicy is not supported in https functions.' - ); - } - return { /** * Handle HTTP requests. @@ -212,8 +161,7 @@ export class FunctionBuilder { ) => https._onRequestWithOptions(handler, this.options), /** * Declares a callable method for clients to call using a Firebase SDK. - * @param handler A method that takes a data and context and returns - * a value. + * @param handler A method that takes a data and context and returns a value. */ onCall: ( handler: ( diff --git a/src/function-configuration.ts b/src/function-configuration.ts index f3883155b..ec1695ccd 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -32,19 +32,6 @@ export const VALID_MEMORY_OPTIONS = [ '2GB', ] as const; -/** - * A mapping of memory options to its representation in the Cloud Functions API. - */ -export const MEMORY_LOOKUP: { - [Name in typeof VALID_MEMORY_OPTIONS[number]]: number; -} = { - '128MB': 128, - '256MB': 256, - '512MB': 512, - '1GB': 1024, - '2GB': 2048, -}; - /** * Scheduler retry options. Applies only to scheduled functions. */ @@ -65,20 +52,7 @@ export interface Schedule { retryConfig?: ScheduleRetryConfig; } -export interface FailurePolicy { - retry: {}; -} - -export const DEFAULT_FAILURE_POLICY: FailurePolicy = { - retry: {}, -}; - export interface RuntimeOptions { - /** - * Failure policy of the function, with boolean `true` being equivalent to - * providing an empty retry object. - */ - failurePolicy?: FailurePolicy | boolean; /** * Amount of memory to allocate to the function. */ From 2940a4b5230e6469ebca3102b11d45915a3a8939 Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Thu, 19 Mar 2020 10:40:58 -0700 Subject: [PATCH 102/437] Use process.env.PWD for finding .runtimeconfig.json (#634) --- CHANGELOG.md | 4 ++++ changelog.txt | 0 spec/config.spec.ts | 12 +++++++++--- src/config.ts | 15 +++++++++------ 4 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 CHANGELOG.md delete mode 100644 changelog.txt diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..69ffc7120 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +**IMPORTANT: Please update to this version of `firebase-functions` if you are using Node.js 10 functions, otherwise your functions will break in production.** + +- Prevents deployment and runtime issues caused by upcoming changes to Cloud Functions infrastructure for Node.js 10 functions. (Issue #630) +- Adds support for writing scheduled functions under handler namespace. diff --git a/changelog.txt b/changelog.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/spec/config.spec.ts b/spec/config.spec.ts index 64963be4e..b721dc3a5 100644 --- a/spec/config.spec.ts +++ b/spec/config.spec.ts @@ -25,6 +25,12 @@ import * as mockRequire from 'mock-require'; import { config, firebaseConfig } from '../src/config'; describe('config()', () => { + before(() => { + process.env.PWD = '/srv'; + }); + after(() => { + delete process.env.PWD; + }); afterEach(() => { mockRequire.stopAll(); delete config.singleton; @@ -33,19 +39,19 @@ describe('config()', () => { }); it('loads config values from .runtimeconfig.json', () => { - mockRequire('../../../.runtimeconfig.json', { foo: 'bar', firebase: {} }); + mockRequire('/srv/.runtimeconfig.json', { foo: 'bar', firebase: {} }); const loaded = config(); expect(loaded).to.not.have.property('firebase'); expect(loaded).to.have.property('foo', 'bar'); }); it('does not provide firebase config if .runtimeconfig.json not invalid', () => { - mockRequire('../../../.runtimeconfig.json', 'does-not-exist'); + mockRequire('/srv/.runtimeconfig.json', 'does-not-exist'); expect(firebaseConfig()).to.be.null; }); it('does not provide firebase config if .ruuntimeconfig.json has no firebase property', () => { - mockRequire('../../../.runtimeconfig.json', {}); + mockRequire('/srv/.runtimeconfig.json', {}); expect(firebaseConfig()).to.be.null; }); diff --git a/src/config.ts b/src/config.ts index 8cd4d3dd1..8925ff940 100644 --- a/src/config.ts +++ b/src/config.ts @@ -21,6 +21,7 @@ // SOFTWARE. import * as firebase from 'firebase-admin'; +import * as path from 'path'; export function config(): config.Config { if (typeof config.singleton === 'undefined') { @@ -68,9 +69,10 @@ export function firebaseConfig(): firebase.AppOptions | null { // Could have Runtime Config with Firebase in it as an ENV location or default. try { - const path = - process.env.CLOUD_RUNTIME_CONFIG || '../../../.runtimeconfig.json'; - const config = require(path); + const configPath = + process.env.CLOUD_RUNTIME_CONFIG || + path.join(process.env.PWD, '.runtimeconfig.json'); + const config = require(configPath); if (config.firebase) { return config.firebase; } @@ -92,9 +94,10 @@ function init() { } try { - const path = - process.env.CLOUD_RUNTIME_CONFIG || '../../../.runtimeconfig.json'; - const parsed = require(path); + const configPath = + process.env.CLOUD_RUNTIME_CONFIG || + path.join(process.env.PWD, '.runtimeconfig.json'); + const parsed = require(configPath); delete parsed.firebase; config.singleton = parsed; return; From 6873662f9814ed9262f0b2ac606bde1b3d1e0aea Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Thu, 19 Mar 2020 11:02:30 -0700 Subject: [PATCH 103/437] New Publish Path (#231) --- package.json | 7 +- scripts/publish-container/Dockerfile | 12 ++ scripts/publish-container/cloudbuild.yaml | 4 + scripts/publish.sh | 135 ++++++++++++++++++++++ scripts/publish/cloudbuild.yaml | 114 ++++++++++++++++++ scripts/publish/deploy_key.enc | Bin 0 -> 3505 bytes scripts/publish/hub.enc | Bin 0 -> 191 bytes scripts/publish/npmrc.enc | Bin 0 -> 185 bytes scripts/publish/twitter.json.enc | Bin 0 -> 337 bytes scripts/tweet.js | 52 +++++++++ 10 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 scripts/publish-container/Dockerfile create mode 100644 scripts/publish-container/cloudbuild.yaml create mode 100644 scripts/publish.sh create mode 100644 scripts/publish/cloudbuild.yaml create mode 100644 scripts/publish/deploy_key.enc create mode 100644 scripts/publish/hub.enc create mode 100644 scripts/publish/npmrc.enc create mode 100644 scripts/publish/twitter.json.enc create mode 100644 scripts/tweet.js diff --git a/package.json b/package.json index b9d7e31fc..35f0517b7 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/firebase/firebase-functions.git" + "url": "https://github.com/firebase/firebase-functions.git" }, "license": "MIT", "author": "Firebase Team", @@ -23,10 +23,13 @@ ], "main": "lib/index.js", "types": "lib/index.d.ts", + "publishConfig": { + "registry": "https://wombat-dressing-room.appspot.com" + }, "scripts": { "apidocs": "node docgen/generate-docs.js", "build:pack": "rm -rf lib && npm install && tsc -p tsconfig.release.json && npm pack", - "build:release": "npm install --production && npm install typescript firebase-admin && tsc -p tsconfig.release.json", + "build:release": "npm install --production && npm install --no-save typescript firebase-admin && tsc -p tsconfig.release.json", "build": "tsc -p tsconfig.release.json", "format": "prettier --check '**/*.{json,md,ts,yml,yaml}'", "format:fix": "prettier --write '**/*.{json,md,ts,yml,yaml}'", diff --git a/scripts/publish-container/Dockerfile b/scripts/publish-container/Dockerfile new file mode 100644 index 000000000..c8ba24c12 --- /dev/null +++ b/scripts/publish-container/Dockerfile @@ -0,0 +1,12 @@ +FROM node:8 + +# Install dependencies +RUN apt-get update && \ + apt-get install -y curl git jq + +# Install npm at latest. +RUN npm install --global npm@latest + +# Install hub +RUN curl -fsSL --output hub.tgz https://github.com/github/hub/releases/download/v2.13.0/hub-linux-amd64-2.13.0.tgz +RUN tar --strip-components=2 -C /usr/bin -xf hub.tgz hub-linux-amd64-2.13.0/bin/hub diff --git a/scripts/publish-container/cloudbuild.yaml b/scripts/publish-container/cloudbuild.yaml new file mode 100644 index 000000000..7a99a8cab --- /dev/null +++ b/scripts/publish-container/cloudbuild.yaml @@ -0,0 +1,4 @@ +steps: + - name: 'gcr.io/cloud-builders/docker' + args: ['build', '-t', 'gcr.io/$PROJECT_ID/package-builder', '.'] +images: ['gcr.io/$PROJECT_ID/package-builder'] diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100644 index 000000000..a1498460e --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,135 @@ +#!/bin/bash +set -e + +printusage() { + echo "publish.sh " + echo "REPOSITORY_ORG and REPOSITORY_NAME should be set in the environment." + echo "e.g. REPOSITORY_ORG=user, REPOSITORY_NAME=repo" + echo "" + echo "Arguments:" + echo " version: 'patch', 'minor', or 'major'." +} + +VERSION=$1 +if [[ $VERSION == "" ]]; then + printusage + exit 1 +elif [[ ! ($VERSION == "patch" || $VERSION == "minor" || $VERSION == "major") ]]; then + printusage + exit 1 +fi + +if [[ $REPOSITORY_ORG == "" ]]; then + printusage + exit 1 +fi +if [[ $REPOSITORY_NAME == "" ]]; then + printusage + exit 1 +fi + +WDIR=$(pwd) + +echo "Checking for commands..." +trap "echo 'Missing hub.'; exit 1" ERR +which hub &> /dev/null +trap - ERR + +trap "echo 'Missing node.'; exit 1" ERR +which node &> /dev/null +trap - ERR + +trap "echo 'Missing jq.'; exit 1" ERR +which jq &> /dev/null +trap - ERR +echo "Checked for commands." + +echo "Checking for Twitter credentials..." +trap "echo 'Missing Twitter credentials.'; exit 1" ERR +test -f "${WDIR}/scripts/twitter.json" +trap - ERR +echo "Checked for Twitter credentials..." + +echo "Checking for logged-in npm user..." +trap "echo 'Please login to npm using \`npm login --registry https://wombat-dressing-room.appspot.com\`'; exit 1" ERR +npm whoami --registry https://wombat-dressing-room.appspot.com +trap - ERR +echo "Checked for logged-in npm user." + +echo "Moving to temporary directory.." +TEMPDIR=$(mktemp -d) +echo "[DEBUG] ${TEMPDIR}" +cd "${TEMPDIR}" +echo "Moved to temporary directory." + +echo "Cloning repository..." +git clone "git@github.com:${REPOSITORY_ORG}/${REPOSITORY_NAME}.git" +cd "${REPOSITORY_NAME}" +echo "Cloned repository." + +echo "Making sure there is a changelog..." +if [ ! -s CHANGELOG.md ]; then + echo "CHANGELOG.md is empty. aborting." + exit 1 +fi +echo "Made sure there is a changelog." + +echo "Running npm install..." +npm install +echo "Ran npm install." + +echo "Running tests..." +npm test +echo "Ran tests." + +echo "Running publish build..." +npm run build:release +echo "Ran publish build." + +echo "Making a $VERSION version..." +npm version $VERSION +NEW_VERSION=$(jq -r ".version" package.json) +echo "Made a $VERSION version." + +echo "Making the release notes..." +RELEASE_NOTES_FILE=$(mktemp) +echo "[DEBUG] ${RELEASE_NOTES_FILE}" +echo "v${NEW_VERSION}" >> "${RELEASE_NOTES_FILE}" +echo "" >> "${RELEASE_NOTES_FILE}" +cat CHANGELOG.md >> "${RELEASE_NOTES_FILE}" +echo "Made the release notes." + +echo "Publishing to npm..." +if [[ $DRY_RUN == "" ]]; then + npm publish +else + echo "DRY RUN: running publish with --dry-run" + npm publish --dry-run +fi +echo "Published to npm." + +if [[ $DRY_RUN != "" ]]; then + echo "All other commands are mutations, and we are doing a dry run." + echo "Terminating." + exit +fi + +echo "Cleaning up release notes..." +rm CHANGELOG.md +touch CHANGELOG.md +git commit -m "[firebase-release] Removed change log and reset repo after ${NEW_VERSION} release" CHANGELOG.md +echo "Cleaned up release notes." + +echo "Pushing to GitHub..." +git push origin master --tags +echo "Pushed to GitHub." + +echo "Publishing release notes..." +hub release create --file "${RELEASE_NOTES_FILE}" "v${NEW_VERSION}" +echo "Published release notes." + +echo "Making the tweet..." +npm install --no-save twitter@1.7.1 +cp -v "${WDIR}/scripts/twitter.json" "${TEMPDIR}/${REPOSITORY_NAME}/scripts/" +node ./scripts/tweet.js ${NEW_VERSION} +echo "Made the tweet." diff --git a/scripts/publish/cloudbuild.yaml b/scripts/publish/cloudbuild.yaml new file mode 100644 index 000000000..d04fec25c --- /dev/null +++ b/scripts/publish/cloudbuild.yaml @@ -0,0 +1,114 @@ +steps: + # Decrypt the SSH key. + - name: 'gcr.io/cloud-builders/gcloud' + args: + [ + 'kms', + 'decrypt', + '--ciphertext-file=deploy_key.enc', + '--plaintext-file=/root/.ssh/id_rsa', + '--location=global', + '--keyring=${_KEY_RING}', + '--key=${_KEY_NAME}', + ] + + # Decrypt the Twitter credentials. + - name: 'gcr.io/cloud-builders/gcloud' + args: + [ + 'kms', + 'decrypt', + '--ciphertext-file=twitter.json.enc', + '--plaintext-file=twitter.json', + '--location=global', + '--keyring=${_KEY_RING}', + '--key=${_KEY_NAME}', + ] + + # Decrypt the npm credentials. + - name: 'gcr.io/cloud-builders/gcloud' + args: + [ + 'kms', + 'decrypt', + '--ciphertext-file=npmrc.enc', + '--plaintext-file=npmrc', + '--location=global', + '--keyring=${_KEY_RING}', + '--key=${_KEY_NAME}', + ] + + # Decrypt the hub (GitHub) credentials. + - name: 'gcr.io/cloud-builders/gcloud' + args: + [ + 'kms', + 'decrypt', + '--ciphertext-file=hub.enc', + '--plaintext-file=hub', + '--location=global', + '--keyring=${_KEY_RING}', + '--key=${_KEY_NAME}', + ] + + # Set up git with key and domain. + - name: 'gcr.io/cloud-builders/git' + entrypoint: 'bash' + args: + - '-c' + - | + chmod 600 /root/.ssh/id_rsa + cat </root/.ssh/config + Hostname github.com + IdentityFile /root/.ssh/id_rsa + EOF + ssh-keyscan github.com >> /root/.ssh/known_hosts + + # Clone the repository. + - name: 'gcr.io/cloud-builders/git' + args: ['clone', 'git@github.com:${_REPOSITORY_ORG}/${_REPOSITORY_NAME}'] + + # Set up the Git configuration. + - name: 'gcr.io/cloud-builders/git' + dir: '${_REPOSITORY_NAME}' + args: ['config', '--global', 'user.email', 'firebase-oss-bot@google.com'] + - name: 'gcr.io/cloud-builders/git' + dir: '${_REPOSITORY_NAME}' + args: ['config', '--global', 'user.name', 'Google Open Source Bot'] + + # Set up the Twitter credentials. + - name: 'gcr.io/$PROJECT_ID/package-builder' + entrypoint: 'cp' + args: ['-v', 'twitter.json', '${_REPOSITORY_NAME}/scripts/twitter.json'] + + # Set up the npm credentials. + - name: 'gcr.io/$PROJECT_ID/package-builder' + entrypoint: 'bash' + args: ['-c', 'cp -v npmrc ~/.npmrc'] + + # Set up the hub credentials for package-builder. + - name: 'gcr.io/$PROJECT_ID/package-builder' + entrypoint: 'bash' + args: ['-c', 'mkdir -vp ~/.config && cp -v hub ~/.config/hub'] + + # Publish the package. + - name: 'gcr.io/$PROJECT_ID/package-builder' + dir: '${_REPOSITORY_NAME}' + args: ['bash', './scripts/publish.sh', '${_VERSION}'] + env: + - 'REPOSITORY_ORG=${_REPOSITORY_ORG}' + - 'REPOSITORY_NAME=${_REPOSITORY_NAME}' + - 'DRY_RUN=${_DRY_RUN}' + +options: + volumes: + - name: 'ssh' + path: /root/.ssh + +substitutions: + _VERSION: '' + _DRY_RUN: '' + _KEY_RING: 'npm-publish-keyring' + _KEY_NAME: 'publish' + _REPOSITORY_ORG: 'firebase' + _REPOSITORY_NAME: 'firebase-functions' diff --git a/scripts/publish/deploy_key.enc b/scripts/publish/deploy_key.enc new file mode 100644 index 0000000000000000000000000000000000000000..127551f08a284b04645c1979c5e7582ff6d87cc2 GIT binary patch literal 3505 zcmV;i4Nme3Bmgb?`&t@CANF6;R;o>u7LKpmI5M&PT05}Z%Fvpli)}500uqQD0I@1* zqHJtSSX}$k?Rd7LKh&;a=vnd$={xB*q> zA&%j))nL-jB=2Pj(Wz1seZ^pKpHctdh-YHXB0r^La?C?~Mf9%}Ccsy^HCF zOb#!mG@W>%81NyFz!emyLQSG!Uddt)!yBRud|uy3``LlrL%;ZxRwax>PWc|VwwAFv z9OMa-Z9qqPEP|7LJu|tL`CKbh#O-KYZ2NW|tK~YPfT})dHaPHK#q>)nGZvxUV3?sg z(y{|s@DGBI4I2S0?@HJA*}p?tQT_02T_90NWwFME7-!aglgC0xKGiF?>tLU`mY$-@ zYl4i49l`mxevU$MQ51F82-l~~^2ci>=1!x1&Gr~KL&r69Z&ci^AJR^Rug*fdq8!F2 zvak|`Mi^klfVOdMIZH`>QPOo@2m^$C6=PwK|1+NC=iMZ}fMqrR;#b{dcw~YygKR_x zJ-NodT=TW8sb1C()1P?fZh6N9F^w|7@#8UifxZqLNsdPq@%;0u^K(Ae;M>JnAL1Sj zw%WcTocg@=05m^ixPm)pc~|YYLG8>kpRiejk7Z7^jnhVJF#h`%a~m>sMTZwih(#a^ zFy6_w{alNDyjwd9!NG@h#_pnsrgt&olf_5f3jkB4JPKk2HlEp%P@REPmRRLoYIQh$ zq(W3tr#&hus>2>|8z`zMxir(_zRWt6o+r(01v4P9bZRLJR`MGQMhHxZ8|L!jY`!~y zup{RtT&1eO^D`$qDX*%l1f8gaozR}|M+W}PSDVT)UkYCPMPMn*VbHK-?JeQO#e72- zi(o|L&s1b;-A8_)o$c#5Dn~m|tc67XXD9p{KCw6KabQCa6n{~4!%!OFGi$RER`}lb zp7wu)GL#sd79Z+Fj4lZ~cBhBAyV$=p)w1BnpIv1Y%+8{j*y>p;TQxlo9XbJ-dO$(z{FE2P6Z4-A2afjbAhPH&+>TXEfKrAxn)J0 zU>>UZyy@&BtJvRa8UOnOKlI9jQ*q3aSOy3OvZL2$dK z%PrOHR3^w^AO}8zlJz&VnII*NcS|L5AUq^QCb72fo!+YJ3!TD2pPk72$j}k+F)30CK5yHBPVm5Qu*Z`iKlVd4I+_+Z< zMjr6TP;kDgh5E+lkVIRzO>Y^L+}uv_WSmd1|*{CSVxYAM8o1ce0Gg3FU^@i zW5?gDOW3=>oI{o&@rcAZj+bwetel^sIqlf5vMInpHVA zA%ag{Tag1RdhuFY;9%^WbI*XZnel2VjPR=Sn@rH@Ap9?ODfff)c?i_YXE$w&i(f0l zwc*&T=X0E6eea)*?yo9@;bM=S!#GUs&lGUOaprtq;M^}kG<>l{3pT%o(f+C?Z!K4% zH%@F=RfezqRr=pJXw(x=puymA-n%mrFKx&ciEu}GE{yK0zAUsX4!h!?(%|D?cqW%D zaI)ZgAOg|&m$RSDQ0&wFmq#m%^6@7iSK#DmB76DU5nR3H6d!XyGF^j^ue7!~nOguMEA=l6h@uAsSL_S$%w`)dABi;=Q)a?I##7)Gx=$0(#Yz^@-2 zP}wb;T_!c>lO!ob%$VdSVSU$KE(~6vxAPJ~j`9I`$z4%%W!$TdVL@;qr7T6S!S@9= zNl;;06mDnka*i}jPE+TDX%%r=SE8CYW|ek`0Cpchp;Hm`>uyH!YNEhvbsze$S2=yp z8s=oVffyvYx+W>-{)@+dI_l-($R5pZWs~mL#^u7B;Gs$WSCc(wg`V#=seLPw6WYZ) zYUIJ1CfeJ^4+LM&!aaDDAP}?wt5w!OrzWfiJ}5Od){VA<#=G~0^9*JV%Ni9__nHB2 zkQ^%e*>!@ADxVe?f&xrC;_zUa`8BmXoJ?k_@?R0us*4s~m{(*lpf+#dO&k<^r~n*< z&<|y!znIMP#nt$PXi^*)<+;YHFSz=^w~JMwY;sL1k%Trl>;-*2C%Shwlv5V*L(W&5 zXZ-|v=@#Ye(O|~3?y{zGxa8oPt62rI5fC+S3*`2r|9LDt78?Z1m~^xYa<;VxzSGTY zM3S4T^Y@)f3!tL)?;FEB8ZkSg`;qQfwe7=xZ2?gJ0LL2xSk5ridpLYex&D;fYiViq zn+v(hY+#sHFYH2Dg$J}Fg1DI-)?be{T)hS+69w!*k@cOHuFI;!Unr)S`lV;iy1w=M zscVhlRjt0FKRnUSLc`2sRIZlBtqdpjTB1dPVsNGpW`c=Uv>%bFjVEppPHOqhH~-l^ zOIyetr4Qa~BW3IlHn(es;M5$k28lm1D#B-7>!g^ROUx7vCO*Z!>9H3KR))r9#Ez05 z9jDV8==JApZeN`V(M^%TyGfccf42us*()JpzY%OO{Vj=OoWK$yyzJA=c;5pG3R_ea z?f^pQ(rx3efxj@J0KHk-YVE@?K^L&uXiu5zcq(}?BZu5pIQ2y!MUx^t-SZJ8oB+s> z*j`FmRguK31g^$7Ln0D6Pp&|@4q0`qy%R)>YbY&31G5EPZ#~3JRBKB^_L1Kj_ f4Ub|Hgeu)7h18+LKzH?pr3a9uFy{Epm(_|36Y1*QWJQfJT0MJ9zwMi%Kd3wn#dG_QbRhG-GIt!<8_$jzFOm|VQ-rR?2l}i2{p^L{_ tc<&{%BIKp@wGJNlQ#WkDi)Ew#{#^Qs@9VYVi;<-_|HPcgwp_ON(Ql0_WTgNA literal 0 HcmV?d00001 diff --git a/scripts/publish/npmrc.enc b/scripts/publish/npmrc.enc new file mode 100644 index 0000000000000000000000000000000000000000..da8ea49bb39b447cf82e476e2fbe5cbea9c73ab2 GIT binary patch literal 185 zcmV;q07m}`Bmgb?`&x`B3vTk#mH`EO%n0vbM?y@JnAJ literal 0 HcmV?d00001 diff --git a/scripts/publish/twitter.json.enc b/scripts/publish/twitter.json.enc new file mode 100644 index 0000000000000000000000000000000000000000..82123a04d0ff081fb0e72b4ef55875715afc4a65 GIT binary patch literal 337 zcmV-X0j~ZEBmgb?`&w^yETddISLST9*Q73S3M0HD5u5QbgL|PqvifMB4-%*X0I@1* zqFru1Q`}3f;Ld){D@!s0@HGRmE3-;7)M2ircF{PyxjTknwRwMPZ9!f~Sxs)bbsh+J zE$e6}?hEB5or0?@>f=G5N#*klB1NbA$$(9y$E;0PSaEG%6=El1c4m^(o=8eGQkwhE zbx=AC2;_=16pQ2uAPJH6pLMi|Bf6za61;86Z|Mun0Z4f%*&wt5QAvta;5 jz>LyJ^e{Dl?7J^g|EMlTm%vy}Hf(R(%iC3z{0i>Dxt*iy literal 0 HcmV?d00001 diff --git a/scripts/tweet.js b/scripts/tweet.js new file mode 100644 index 000000000..be6229574 --- /dev/null +++ b/scripts/tweet.js @@ -0,0 +1,52 @@ +"use strict"; + +const fs = require("fs"); +const Twitter = require("twitter"); + +function printUsage() { + console.error( + ` +Usage: tweet.js + +Credentials must be stored in "twitter.json" in this directory. + +Arguments: + - version: Version of module that was released. e.g. "1.2.3" +` + ); + process.exit(1); +} + +function getUrl(version) { + return `https://github.com/firebase/firebase-functions/releases/tag/v${version}`; +} + +if (process.argv.length !== 3) { + console.error("Missing arguments."); + printUsage(); +} + +const version = process.argv.pop(); +if (!version.match(/^\d+\.\d+\.\d+$/)) { + console.error(`Version "${version}" not a version number.`); + printUsage(); +} + +if (!fs.existsSync(`${__dirname}/twitter.json`)) { + console.error("Missing credentials."); + printUsage(); +} +const creds = require("./twitter.json"); + +const client = new Twitter(creds); + +client.post( + "statuses/update", + { status: `v${version} of @Firebase SDK for Cloud Functions is available. Release notes: ${getUrl(version)}` }, + (err) => { + if (err) { + console.error(err); + process.exit(1); + } + } +); From f41bdaa9bd4adc479015ed9df2102a3c1bafc8bf Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 19 Mar 2020 18:10:16 +0000 Subject: [PATCH 104/437] 3.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 35f0517b7..3fe70ac72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.3.0", + "version": "3.4.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From 76bfc9c7563dc81dc65b9a3d90edbac9c0198ecf Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 19 Mar 2020 18:10:21 +0000 Subject: [PATCH 105/437] [firebase-release] Removed change log and reset repo after 3.4.0 release --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69ffc7120..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +0,0 @@ -**IMPORTANT: Please update to this version of `firebase-functions` if you are using Node.js 10 functions, otherwise your functions will break in production.** - -- Prevents deployment and runtime issues caused by upcoming changes to Cloud Functions infrastructure for Node.js 10 functions. (Issue #630) -- Adds support for writing scheduled functions under handler namespace. From bf52fa3b85ef4210e04074afcd401c943879cdac Mon Sep 17 00:00:00 2001 From: efi shtain Date: Thu, 19 Mar 2020 22:50:58 +0200 Subject: [PATCH 106/437] add support for maxInstances in RuntimeOptions (#624) Co-authored-by: joehan --- src/cloud-functions.ts | 4 ++++ src/function-configuration.ts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 1309aba61..ec2d64779 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -486,5 +486,9 @@ export function optionsToTrigger(options: DeploymentOptions) { if (options.schedule) { trigger.schedule = options.schedule; } + + if (options.maxInstances) { + trigger.maxInstances = options.maxInstances; + } return trigger; } diff --git a/src/function-configuration.ts b/src/function-configuration.ts index ec1695ccd..215ea81a8 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -61,6 +61,11 @@ export interface RuntimeOptions { * Timeout for the function in seconds, possible values are 0 to 540. */ timeoutSeconds?: number; + + /** + * Max number of actual instances allowed to be running in parallel + */ + maxInstances?: number; } export interface DeploymentOptions extends RuntimeOptions { From 9e05b7f4cf663e104c059e56b33ee0174d8dc086 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 19 Mar 2020 15:14:55 -0700 Subject: [PATCH 107/437] Add entry for maxInstances (#636) * add entry for maxInstances * prettier --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..166198e9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Adds support for maxInstances. From df543dc04d14441ce65e82d342f1377f859ca758 Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Fri, 20 Mar 2020 10:49:45 -0700 Subject: [PATCH 108/437] Update dependencies to fix TS build issue (#638) --- CHANGELOG.md | 12 +++++++++++- package.json | 8 ++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 166198e9a..c459e5afc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,11 @@ -- Adds support for maxInstances. +- Adds support for defining max number of instances for a function. Example: + +``` +functions.runWith({ + maxInstances: 10 +}).https.onRequest(...); +``` + +Learn more about max instances in the [Google Cloud documentation.](https://cloud.google.com/functions/docs/max-instances) + +- Fixes TypeScript build error when `package-lock.json` is present by updating dependencies (Issue #637). diff --git a/package.json b/package.json index 3fe70ac72..20f7c8c38 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "test": "mocha" }, "dependencies": { - "@types/express": "^4.17.0", + "@types/express": "^4.17.3", "cors": "^2.8.5", "express": "^4.17.1", "jsonwebtoken": "^8.5.1", @@ -61,7 +61,7 @@ "firebase-admin": "^8.2.0", "istanbul": "^0.4.5", "js-yaml": "^3.13.1", - "jsdom": "^15.2.0", + "jsdom": "^16.2.1", "mocha": "^6.1.4", "mock-require": "^3.0.3", "mz": "^2.7.0", @@ -73,9 +73,9 @@ "tslint-config-prettier": "^1.18.0", "tslint-no-unused-expression-chai": "^0.1.4", "tslint-plugin-prettier": "^2.0.1", - "typedoc": "^0.14.2", + "typedoc": "^0.17.1", "typescript": "^3.5.2", - "yargs": "^13.2.4" + "yargs": "^15.3.1" }, "peerDependencies": { "firebase-admin": "^8.0.0" From 15bf0da3b489c4a31f97bc171aa9b2ef251c4abd Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Fri, 20 Mar 2020 11:16:18 -0700 Subject: [PATCH 109/437] Update CHANGELOG (#639) --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c459e5afc..5b7b4bfe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ - Adds support for defining max number of instances for a function. Example: -``` -functions.runWith({ - maxInstances: 10 -}).https.onRequest(...); -``` + ``` + functions.runWith({ + maxInstances: 10 + }).https.onRequest(...); + ``` -Learn more about max instances in the [Google Cloud documentation.](https://cloud.google.com/functions/docs/max-instances) + Learn more about max instances in the [Google Cloud documentation.](https://cloud.google.com/functions/docs/max-instances) - Fixes TypeScript build error when `package-lock.json` is present by updating dependencies (Issue #637). From 1ed73456250333274653f874246433f3df89e7d4 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 20 Mar 2020 18:18:21 +0000 Subject: [PATCH 110/437] 3.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 20f7c8c38..0884f7af2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.4.0", + "version": "3.5.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From 0921c78ea48c4aee260fa687b8e9c96baa916120 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 20 Mar 2020 18:18:26 +0000 Subject: [PATCH 111/437] [firebase-release] Removed change log and reset repo after 3.5.0 release --- CHANGELOG.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b7b4bfe2..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +0,0 @@ -- Adds support for defining max number of instances for a function. Example: - - ``` - functions.runWith({ - maxInstances: 10 - }).https.onRequest(...); - ``` - - Learn more about max instances in the [Google Cloud documentation.](https://cloud.google.com/functions/docs/max-instances) - -- Fixes TypeScript build error when `package-lock.json` is present by updating dependencies (Issue #637). From 5250110912dbe9e5ff00debe542e33e30089f1a8 Mon Sep 17 00:00:00 2001 From: Martin H Date: Mon, 23 Mar 2020 21:32:01 +0100 Subject: [PATCH 112/437] Add support for europe-west3 region. (#627) --- spec/function-builder.spec.ts | 2 ++ src/function-configuration.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index b0c4d4c28..0044e5661 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -59,6 +59,7 @@ describe('FunctionBuilder', () => { 'us-east4', 'europe-west1', 'europe-west2', + 'europe-west3', 'asia-east2', 'asia-northeast1' ) @@ -71,6 +72,7 @@ describe('FunctionBuilder', () => { 'us-east4', 'europe-west1', 'europe-west2', + 'europe-west3', 'asia-east2', 'asia-northeast1', ]); diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 215ea81a8..05d4517a8 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -7,6 +7,7 @@ export const SUPPORTED_REGIONS = [ 'us-east4', 'europe-west1', 'europe-west2', + 'europe-west3', 'asia-east2', 'asia-northeast1', ] as const; From 468455d6e6b9c7712e0615f667592ff207a6df69 Mon Sep 17 00:00:00 2001 From: egilmorez Date: Wed, 25 Mar 2020 16:16:33 -0700 Subject: [PATCH 113/437] Updating docs TOC with Testlab paths. (#643) --- docgen/content-sources/toc.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docgen/content-sources/toc.yaml b/docgen/content-sources/toc.yaml index 378af2eef..7b0b70347 100644 --- a/docgen/content-sources/toc.yaml +++ b/docgen/content-sources/toc.yaml @@ -116,3 +116,15 @@ toc: path: /docs/reference/functions/providers_storage_.objectbuilder.html - title: 'ObjectMetadata' path: /docs/reference/functions/providers_storage_.objectmetadata.html + + - title: 'functions.testLab' + path: /docs/reference/functions/providers_testlab_.html + section: + - title: 'testLab.clientInfo' + path: /docs/reference/functions/providers_testlab_.clientinfo.html + - title: 'testLab.resultStorage' + path: /docs/reference/functions/providers_testlab_.resultstorage.html + - title: 'testLab.testMatrix' + path: /docs/reference/functions/providers_testlab_.testmatrix.html + - title: 'testLab.testMatrixBuilder' + path: /docs/reference/functions/providers_testlab_.testmatrixbuilder.html From e1df8236604c37cee7486209c08f0eb9c291ab77 Mon Sep 17 00:00:00 2001 From: egilmorez Date: Tue, 31 Mar 2020 14:23:10 -0700 Subject: [PATCH 114/437] Adding testlab event to eventTypes list (#649) --- src/cloud-functions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index ec2d64779..ea4acd7a2 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -104,11 +104,12 @@ export interface EventContext { * * `providers/cloud.firestore/eventTypes/document.update` * * `providers/cloud.firestore/eventTypes/document.delete` * * `google.pubsub.topic.publish` + * * `google.firebase.remoteconfig.update` * * `google.storage.object.finalize` * * `google.storage.object.archive` * * `google.storage.object.delete` * * `google.storage.object.metadataUpdate` - * * `google.firebase.remoteconfig.update` + * * `google.testing.testMatrix.complete` */ eventType: string; From 7f4c9572cb96881f9b0be7521799bcf6e73e42a1 Mon Sep 17 00:00:00 2001 From: Sebastian Kreft Date: Tue, 31 Mar 2020 18:29:20 -0300 Subject: [PATCH 115/437] Enable users to define async HTTP functions (#651) Fixes #606 --- src/providers/https.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/https.ts b/src/providers/https.ts index d941f67c8..36077affd 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -60,7 +60,7 @@ export function _onRequestWithOptions( ): HttpsFunction { // lets us add __trigger without altering handler: const cloudFunction: any = (req: Request, res: express.Response) => { - handler(req, res); + return handler(req, res); }; cloudFunction.__trigger = _.assign(optionsToTrigger(options), { httpsTrigger: {}, From 95d4a4a5b3850ddd69b14284f6823ec7cd66c36a Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Tue, 31 Mar 2020 15:56:48 -0700 Subject: [PATCH 116/437] Update CHANGELOG.md (#640) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..9b9881949 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +- Adds support for europe-west3 region (e.g. `functions.region("europe-west3")`). +- Adds support for async HTTP functions (Issue #606). From c9a3a0e06545171f3cc83361526773de44166a95 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 31 Mar 2020 23:02:56 +0000 Subject: [PATCH 117/437] 3.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0884f7af2..3fab11e33 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.5.0", + "version": "3.6.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From d3e8951e151102e2b1ccbee7e826a31c014712ed Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 31 Mar 2020 23:03:02 +0000 Subject: [PATCH 118/437] [firebase-release] Removed change log and reset repo after 3.6.0 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b9881949..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Adds support for europe-west3 region (e.g. `functions.region("europe-west3")`). -- Adds support for async HTTP functions (Issue #606). From 2784d5dad6d48215e024aae4678a99a317f6e3dd Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Fri, 24 Apr 2020 10:20:17 -0700 Subject: [PATCH 119/437] Update TypeScript dependency to v3.8 to fix build issues (Issue #667) (#668) --- CHANGELOG.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..e5a92e2f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Update TypeScript dependency to v.3.8 to fix build issues (Issue #667) diff --git a/package.json b/package.json index 3fab11e33..13c1e10c2 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "tslint-no-unused-expression-chai": "^0.1.4", "tslint-plugin-prettier": "^2.0.1", "typedoc": "^0.17.1", - "typescript": "^3.5.2", + "typescript": "^3.8.3", "yargs": "^15.3.1" }, "peerDependencies": { From 1fb57c58c15b3af9b14e6a0dab6678ebde35aa6b Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 24 Apr 2020 17:29:31 +0000 Subject: [PATCH 120/437] 3.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 13c1e10c2..2f45c7dc7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.6.0", + "version": "3.6.1", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From 1dde3dbc9a7c2d7caae945e43cb11b602c968331 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 24 Apr 2020 17:29:37 +0000 Subject: [PATCH 121/437] [firebase-release] Removed change log and reset repo after 3.6.1 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5a92e2f7..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Update TypeScript dependency to v.3.8 to fix build issues (Issue #667) From df35c1b4b18b7f2cd6c2d0d21090793b3b153e8b Mon Sep 17 00:00:00 2001 From: Sebastian Kreft Date: Mon, 27 Apr 2020 17:48:07 -0400 Subject: [PATCH 122/437] fix: onCreate, onUpdate and onDelete receive a DocumentQuerySnapshopt (#670) The arguments of Firestore triggers `onCreate`, `onUpdate` and `onDelete` cannot have an undefined data by definition, so we changed the arguments of those handlers to reflect that by using DocumentQuerySnapshot. fixes #659 --- src/providers/firestore.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/providers/firestore.ts b/src/providers/firestore.ts index 291d7d7f9..f91e17306 100644 --- a/src/providers/firestore.ts +++ b/src/providers/firestore.ts @@ -42,6 +42,7 @@ export const service = 'firestore.googleapis.com'; export const defaultDatabase = '(default)'; let firestoreInstance: any; export type DocumentSnapshot = firebase.firestore.DocumentSnapshot; +export type QueryDocumentSnapshot = firebase.firestore.QueryDocumentSnapshot; /** * Select the Firestore document to listen to for events. @@ -204,30 +205,30 @@ export class DocumentBuilder { /** Respond only to document updates. */ onUpdate( handler: ( - change: Change, + change: Change, context: EventContext ) => PromiseLike | any - ): CloudFunction> { + ): CloudFunction> { return this.onOperation(handler, 'document.update', changeConstructor); } /** Respond only to document creations. */ onCreate( handler: ( - snapshot: DocumentSnapshot, + snapshot: QueryDocumentSnapshot, context: EventContext ) => PromiseLike | any - ): CloudFunction { + ): CloudFunction { return this.onOperation(handler, 'document.create', snapshotConstructor); } /** Respond only to document deletions. */ onDelete( handler: ( - snapshot: DocumentSnapshot, + snapshot: QueryDocumentSnapshot, context: EventContext ) => PromiseLike | any - ): CloudFunction { + ): CloudFunction { return this.onOperation( handler, 'document.delete', From 5c18afeb2d93881c90ab3bc91cab075dcd3b3a85 Mon Sep 17 00:00:00 2001 From: Rich Hodgkins Date: Mon, 27 Apr 2020 22:57:07 +0100 Subject: [PATCH 123/437] Modify return type of DataSnapshot.forEach to `boolean | void` (#666) This is to match the Admin SDK: https://github.com/firebase/firebase-js-sdk/blob/37b98e9271c494a0fb58ca1960f8fcfaec49ade9/packages/database/src/api/DataSnapshot.ts#L137 Co-authored-by: joehan Co-authored-by: Lauren Long --- src/providers/database.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/database.ts b/src/providers/database.ts index e648be20e..78614a152 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -476,7 +476,7 @@ export class DataSnapshot { * @return `true` if enumeration was canceled due to your callback * returning `true`. */ - forEach(action: (a: DataSnapshot) => boolean): boolean { + forEach(action: (a: DataSnapshot) => boolean | void): boolean { const val = this.val(); if (_.isPlainObject(val)) { return _.some( From b1f9b5ad3d7227132cff82542c6222b6b5edcea0 Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Fri, 1 May 2020 10:20:04 -0700 Subject: [PATCH 124/437] Revise docs for handler namespace (#680) * Adding top-level comment and TOC for functions.handler. (#633) * Adding top-level comment and TOC for functions.handler. * Adding edits from feedback. * Refer specifically to Firebase CLI. * Adding top-level comment and TOC for functions.handler. (#633) * Adding top-level comment and TOC for functions.handler. * Adding edits from feedback. * Refer specifically to Firebase CLI. * Pin "typedoc" version to 0.14.2 (#655) * Add docstrings for handler.firestore, handler.database, and handler.https (#652) * Document a few providers * Remove extra sentence in http function docstring * Crashlytics handler details and example formatting (#656) * Adding comments for Crashlytics handler. * Removing parens for parameters per feedback. * Eg moar handlers (#657) * Adding RC and Analytics handlers. * Adding Storage object handler. * Adding Pub/Sub and Test Lab handlers. * Adding auth handler. * Fixing typo. * Removing async to be consistent among examples. (#658) * handler functions: copy-edits in comments only (#675) * Removing async syntax from Storage handlers. (#678) Co-authored-by: egilmorez Co-authored-by: rachelsaunders <52258509+rachelsaunders@users.noreply.github.com> --- deploy_key.enc | Bin 0 -> 3497 bytes docgen/content-sources/toc.yaml | 5 + package.json | 2 +- src/handler-builder.ts | 280 ++++++++++++++++++++++++-------- 4 files changed, 215 insertions(+), 72 deletions(-) create mode 100644 deploy_key.enc diff --git a/deploy_key.enc b/deploy_key.enc new file mode 100644 index 0000000000000000000000000000000000000000..4451e042b005ebc9eda67d58b62167f4c49daf6a GIT binary patch literal 3497 zcmV;a4Oa3BBmmVf@Zrdr)Xk)=bRAGj!{RH@SFgr^td|4+9tfBW^Z~L71QLK70H3|R zajlkRxfa@rcX>I9C~NhK{A6SLG=zTUY?Gzdo%dt4TzAs$G$%Eg94vVf6hVeHEUTRE z*3NSh~uGCn|R=X)&pZv zf?JJM(&zim_<}4u;>Hix(K|O{>gBe(_wgXp%R@{hrQ+rV1!o*KY{VK7t2b;&OO%0b zmaYzO>8gq1TJ?ijsfxS#HZp~m;M*N_*G2_GihAVKTbe<7ydAJ8h^=n59%!DAR`SO9 zI@%%3U4cW>uR!+LO^cgy?Wr5k9c0wZa`$%2mq_rksc_jcK#Gg0rzlv|%?n*j^8r=* z=1h@esuK?)h)e?Js&g0w9&cLyrb(2iv8E7NpVAxHa`ULDhM7cnp|+=Smq6(1kbA?h z)<00(wd_RMcR$nxp~F0an9CGc(l({P@_*@(?19xjFA8Ogqe=}f3=KRIYMSWzIjf+mz1Z;z;`Cqg7A5G(f34LOI%u8> zMSWL|1YP0t{PxWvP4EFE8&>1UH$R*G@S4))k_+vJ3L?a-poZq&k1%3(_iDGzPaC&` zKK&yBq`IUt!!f8nKY6|X^k43jD@_TTn%fEZA?-JMa3YF$H9GE_t5FTJlu-p$xaIX_ zO#I&Yw9uDWnpp2TOFR=GTal0yJmPiQ56t=aGbJfMlpU)V(byVZv~6;_(P^0w7(4u} z6h6tr0d9i?^s8jb6Dd);+od;stGFyV4mA}EW5zNh{`njzaZZ)|jl8Ajwdo(Qv4FG1 zcnz52kZD+HR~zpox)Hr$2AJXf7mBk*&YBUN`hO0E|rGF4v#)`yhU0*o5E|UTop&t;F|cB-mt_q zI_a%0>Z>Z;+8&vzbyX28=;)gK=rb&QZ0I)kEB_TzSZ8{`;*q+#9%Tn&Zn%r*(UDfd z+GN$ahV#aSZluYmq_2#y?lpZWv|-(Bm*1Bt4t@5uQw zolniyAwn|r^qROUaFl4My9AlN_Zx7$V7Rki6=?~u1aj(kw54qpqs(i4@fxA^07=jk z^brWB_a~^XTj2E!g&bR)we}HwiKv3QhaWr6Gn%jy!b$a{bNd{TKG(Rk6|6-0r>HAm zrZlXuf)$7`%3B~i$VN;M>|~dn`=H1w5I+@!Ov46I8R-#`&5Z>poMVE9?|Ev8S0RGd z=22d>MEY>&f~^<3V6FT#y_UXD0*c9t*u=hx$o?GoIgcnnM$cdH))S?ai zrO&XTnsn?~=A7?WEnw|oPpcRYNYgMMr7nzn5!==*vK-1gu&{e+&!H4DqHd&g$N9F}{(}T_X zvd#2Q6K{eY9gM%E&=vA?ys*1Ecb{K@%&G>=W}lGbjI2rA!(~pPCKE_w zV9W{U&WNX%s!Y85W-`P~q3QhJlXkma!!ap@goj79Aj1sQZ6G>>q z&uRQNoXZ;g2HVCIy>!Wg$BK|;5lnw1b2N&#Y{^+zA$&%Vm2su`Y}^i%9x1MIH%Q0L zR&*8olK>jMSc~pFNm%Z8ks|e@O-qUwW)hocP1PFN9MadNy&w@?^`W8>Ew7+QljLeJ zsc^5%Wik4LycK}31v7!^PAXN6JpIj!_le_{qK1TxXRPP)01O!NM=O2lCrCDQTTKuC zFzsX!U;gS9k;hb!avA32c<6idpD3X z@B!9=11BW7g@_2as9@|=T{<2}#HZe#f)nE&;_ZB;$bm4WF)#95kBCA3^)t04 z;BONYb#RcC5)3Zvjs@<5sum)Yva+mV)8ha{~fjjduoPowBXJyfc^g=u#;Fm!@XL?P|0d= z-*9rNZ5|D6m_3N?LktGD0^&LxVZ#1!QH3_1AIO_Q80GGSD2nbPImcJ6qEO5bW6>05jnYPFF79=9tO}g)WU$Z~IUt-| z`Dq201@Xr=S~#DeHK%`Aj}Sru5U4ox=$8a@wA_qS4>(a#+KxYG zSmwf2+K3EvWR#+X$qXg9*=MXnse)npW!mTIza@5!9sZ(bHo+Syi~~-rYhBB)BM*>d zp^P`{TNB}XUSnS@u|?t&H61_vtnv@lMSU$uV*#QUT#K9p_AW77-?6rAN~K$f;+(DM z?k?H)S2hNDJ5yC8+gZHk^iB%>R_jFwG`QlvZ0%Lt0HLR2INg*+D+L;M7L)-!^RN6` zWJ`5Ok=RO3ie$X(J52NZGB_;WdyZu2F~$zTs%9B(ECuiK z$w2MYk-bEC&pv=xF`h8Lt3ZKQuzwqsGf8fkB;DmLNVx2h4?lFfg$9C|%9##GZ-j4WaYIL$( z^tuu2Vy@cd#f_B+yg&0mpor5N*na)M;C2hta0t=Eu>^K>6ICTb=)R~a20LWz+l%_s z)cp&MypEH3kaMsW(1gzvAAGBdT1>66{zBWsESk3qV$k>h>jjA1ir3m;f!t+_p8e{( z6aTXDX%5B`=kKo$Nzmo3)JzCm2zR+#X_I_20RGp5W&H|Ywk_aV5jxQ58Ny= z_6#3#5}8uYJ{WAIx)XK|s!CR@yM`mZk8_2IsZCBST&kWrMt?W$BSg8vF*Qp1ao7cO<(Jj!2v1 zi8dHEQhkN;x2t&`LMK#rDr43TXg<+IeF-PqQXNVEUgR_k0my%5l-t-JsPeQtD*zL` z7*5tEMlMy`EyyDB$(A)!6K+RnohyHU { ... }) + * ``` + * + * `onCall` declares a callable function for clients to call using a Firebase SDK. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.https.onCall((data, context) => { ... }) + * ``` + */ get https() { return { - /** - * Handle HTTP requests. - * @param handler A function that takes a request and response object, - * same signature as an Express app. - */ onRequest: ( handler: (req: express.Request, resp: express.Response) => void ): HttpsFunction => { @@ -52,10 +73,6 @@ export class HandlerBuilder { func.__trigger = {}; return func; }, - /** - * Declares a callable method for clients to call using a Firebase SDK. - * @param handler A method that takes a data and context and returns a value. - */ onCall: ( handler: ( data: any, @@ -69,12 +86,40 @@ export class HandlerBuilder { }; } + /** + * Create a handler for Firebase Realtime Database events. + * + * `ref.onCreate` handles the creation of new data. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.database.ref.onCreate((snap, context) => { ... }) + * ``` + * + * `ref.onUpdate` handles updates to existing data. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.database.ref.onUpdate((change, context) => { ... }) + * ``` + + * `ref.onDelete` handles the deletion of existing data. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.database.ref.onDelete((snap, context) => { ... }) + * ``` + + * `ref.onWrite` handles the creation, update, or deletion of data. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.database.ref.onWrite((change, context) => { ... }) + * ``` + */ get database() { return { - /** - * Selects a database instance that will trigger the function. - * If omitted, will pick the default database for your project. - */ + /** @hidden */ get instance() { return { get ref() { @@ -82,41 +127,47 @@ export class HandlerBuilder { }, }; }, - - /** - * Select Firebase Realtime Database Reference to listen to. - * - * This method behaves very similarly to the method of the same name in the - * client and Admin Firebase SDKs. Any change to the Database that affects the - * data at or below the provided `path` will fire an event in Cloud Functions. - * - * There are three important differences between listening to a Realtime - * Database event in Cloud Functions and using the Realtime Database in the - * client and Admin SDKs: - * 1. Cloud Functions allows wildcards in the `path` name. Any `path` component - * in curly brackets (`{}`) is a wildcard that matches all strings. The value - * that matched a certain invocation of a Cloud Function is returned as part - * of the `context.params` object. For example, `ref("messages/{messageId}")` - * matches changes at `/messages/message1` or `/messages/message2`, resulting - * in `context.params.messageId` being set to `"message1"` or `"message2"`, - * respectively. - * 2. Cloud Functions do not fire an event for data that already existed before - * the Cloud Function was deployed. - * 3. Cloud Function events have access to more information, including information - * about the user who triggered the Cloud Function. - */ get ref() { return new database.RefBuilder(apps(), () => null, {}); }, }; } + /** + * Create a handler for Cloud Firestore events. + * + * `document.onCreate` handles the creation of new documents. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.firestore.document.onCreate((snap, context) => { ... }) + * ``` + + * `document.onUpdate` handles updates to existing documents. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.firestore.document.onUpdate((change, context) => { ... }) + * ``` + + * `document.onDelete` handles the deletion of existing documents. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.firestore.document.onDelete((snap, context) => + * { ... }) + * ``` + + * `document.onWrite` handles the creation, update, or deletion of documents. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.firestore.document.onWrite((change, context) => + * { ... }) + * ``` + */ get firestore() { return { - /** - * Listen for events on a Firestore document. A Firestore document contains a set of - * key-value pairs and may contain subcollections and nested objects. - */ get document() { return new firestore.DocumentBuilder(() => null, {}); }, @@ -131,26 +182,53 @@ export class HandlerBuilder { }; } + /** + * Create a handler for Firebase Crashlytics events. + + * `issue.onNew` handles events where the app experiences an issue for the first time. + + * @example + * ```javascript + * exports.myFunction = functions.handler.crashlytics.issue.onNew((issue) => { ... }) + * ``` + + * `issue.onRegressed` handles events where an issue reoccurs after it + * is closed in Crashlytics. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.crashlytics.issue.onRegressed((issue) => { ... }) + * ``` + + * `issue.onVelocityAlert` handles events where a statistically significant number + * of sessions in a given build crash. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.crashlytics.issue.onVelocityAlert((issue) => { ... }) + * ``` + + */ get crashlytics() { return { - /** - * Handle events related to Crashlytics issues. An issue in Crashlytics is an - * aggregation of crashes which have a shared root cause. - */ get issue() { return new crashlytics.IssueBuilder(() => null, {}); }, }; } + /** + * Create a handler for Firebase Remote Config events. + + * `remoteConfig.onUpdate` handles events that update a Remote Config template. + + * @example + * ```javascript + * exports.myFunction = functions.handler.remoteConfig.onUpdate() => { ... }) + * ``` + */ get remoteConfig() { return { - /** - * Handle all updates (including rollbacks) that affect a Remote Config - * project. - * @param handler A function that takes the updated Remote Config template - * version metadata as an argument. - */ onUpdate: ( handler: ( version: remoteConfig.TemplateVersion, @@ -162,67 +240,127 @@ export class HandlerBuilder { }; } + /** + * Create a handler for Google Analytics events. + + * `event.onLog` handles the logging of Analytics conversion events. + + * @example + * ```javascript + * exports.myFunction = functions.handler.analytics.event.onLog((event) => { ... }) + * ``` + */ get analytics() { return { - /** - * Select analytics events to listen to for events. - */ get event() { return new analytics.AnalyticsEventBuilder(() => null, {}); }, }; } + /** + * Create a handler for Cloud Storage for Firebase events. + * + * `object.onArchive` handles the archiving of Storage objects. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.storage.object.onArchive((object) => { ... }) + * ``` + + * `object.onDelete` handles the deletion of Storage objects. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.storage.object.onDelete((object) => { ... }) + * ``` + + * `object.onFinalize` handles the creation of Storage objects. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.storage.object.onFinalize((object) => + * { ... }) + * ``` + + * `object.onMetadataUpdate` handles changes to the metadata of existing Storage objects. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.storage.object.onMetadataUpdate((object) => + * { ... }) + * ``` + */ get storage() { return { - /** - * The optional bucket function allows you to choose which buckets' events to handle. - * This step can be bypassed by calling object() directly, which will use the default - * Cloud Storage for Firebase bucket. - */ get bucket() { return new storage.BucketBuilder(() => null, {}).object(); }, - /** - * Handle events related to Cloud Storage objects. - */ get object() { return new storage.ObjectBuilder(() => null, {}); }, }; } + /** + * Create a handler for Cloud Pub/Sub events. + + * `pubsub.onPublish` handles the publication of messages to a topic. + + * @example + * ```javascript + * exports.myFunction = functions.handler.pubsub.topic.onPublish((message) => { ... }) + * ``` + */ get pubsub() { return { - /** - * Select Cloud Pub/Sub topic to listen to. - */ get topic() { return new pubsub.TopicBuilder(() => null, {}); }, - /** - * Handle periodic events triggered by Cloud Scheduler. - */ get schedule() { return new pubsub.ScheduleBuilder(() => null, {}); }, }; } + /** + * Create a handler for Firebase Authentication events. + * + * `user.onCreate` handles the creation of users. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.auth.user.onCreate((user) => { ... }) + * ``` + + * `user.onDelete` handles the deletion of users. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.auth.user.onDelete((user => { ... }) + * ``` + + */ get auth() { return { - /** - * Handle events related to Firebase authentication users. - */ get user() { return new auth.UserBuilder(() => null, {}); }, }; } + /** + * Create a handler for Firebase Test Lab events. + + * `testMatrix.onComplete` handles the completion of a test matrix. + + * @example + * ```javascript + * exports.myFunction = functions.handler.testLab.testMatrix.onComplete((testMatrix) => { ... }) + * ``` + */ get testLab() { - /** Handle events related to Test Lab test matrices. */ return { get testMatrix() { return new testLab.TestMatrixBuilder(() => null, {}); From 1bd2736befdc0de87c0c00e36fe0a81b50d5c5b5 Mon Sep 17 00:00:00 2001 From: egilmorez Date: Wed, 27 May 2020 12:29:29 -0700 Subject: [PATCH 125/437] Fixes to reference doc generation for functions.https (#690) --- docgen/content-sources/HOME.md | 7 ++++++- docgen/content-sources/toc.yaml | 2 ++ src/providers/https.ts | 14 ++++++++++++-- src/setup.ts | 1 + src/utilities/assertions.ts | 2 +- src/utilities/path.ts | 2 +- src/utils.ts | 1 + 7 files changed, 24 insertions(+), 5 deletions(-) diff --git a/docgen/content-sources/HOME.md b/docgen/content-sources/HOME.md index c89df0a57..00d2090a0 100644 --- a/docgen/content-sources/HOME.md +++ b/docgen/content-sources/HOME.md @@ -1,3 +1,8 @@ # Firebase Functions SDK Reference -Functions SDK!!! +The `firebase-functions` package provides an SDK for defining Cloud Functions for Firebase. + +To get started using Cloud Functions, see +[Get started: write, test, and deploy your first functions](/docs/functions/get-started). + +For source code, see the [Cloud Functions for Firebase GitHub repo](https://github.com/firebase/firebase-functions). diff --git a/docgen/content-sources/toc.yaml b/docgen/content-sources/toc.yaml index 856b12640..91544d4f6 100644 --- a/docgen/content-sources/toc.yaml +++ b/docgen/content-sources/toc.yaml @@ -90,6 +90,8 @@ toc: section: - title: 'HttpsError' path: /docs/reference/functions/providers_https_.httpserror.html + - title: 'CallableContext' + path: /docs/reference/functions/providers_https_.callablecontext.html - title: 'functions.pubsub' path: /docs/reference/functions/providers_pubsub_.html diff --git a/src/providers/https.ts b/src/providers/https.ts index 36077affd..605c633f0 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -28,6 +28,7 @@ import { apps } from '../apps'; import { HttpsFunction, optionsToTrigger, Runnable } from '../cloud-functions'; import { DeploymentOptions } from '../function-configuration'; +/** @hidden */ export interface Request extends express.Request { rawBody: Buffer; } @@ -127,6 +128,7 @@ export type FunctionsErrorCode = | 'data-loss' | 'unauthenticated'; +/** @hidden */ export type CanonicalErrorCodeName = | 'OK' | 'CANCELLED' @@ -146,6 +148,7 @@ export type CanonicalErrorCodeName = | 'UNAVAILABLE' | 'DATA_LOSS'; +/** @hidden */ interface HttpErrorCode { canonicalName: CanonicalErrorCodeName; status: number; @@ -180,6 +183,7 @@ const errorCodeMap: { [name in FunctionsErrorCode]: HttpErrorCode } = { 'data-loss': { canonicalName: 'DATA_LOSS', status: 500 }, }; +/** @hidden */ interface HttpErrorWireFormat { details?: unknown; message: string; @@ -261,19 +265,22 @@ export interface CallableContext { rawRequest: Request; } -// The allowed interface for an http request for a callable function. +// The allowed interface for an HTTP request to a Callable function. +/** @hidden*/ interface HttpRequest extends Request { body: { data: any; }; } -// The format for the http body response to a callable function. +/** @hidden */ +// The format for an HTTP body response from a Callable function. interface HttpResponseBody { result?: any; error?: HttpsError; } +/** @hidden */ // Returns true if req is a properly formatted callable request. function isValidRequest(req: Request): req is HttpRequest { // The body must not be empty. @@ -317,7 +324,9 @@ function isValidRequest(req: Request): req is HttpRequest { return true; } +/** @hidden */ const LONG_TYPE = 'type.googleapis.com/google.protobuf.Int64Value'; +/** @hidden */ const UNSIGNED_LONG_TYPE = 'type.googleapis.com/google.protobuf.UInt64Value'; /** @@ -400,6 +409,7 @@ export function decode(data: any): any { return data; } +/** @hidden */ const corsHandler = cors({ origin: true, methods: 'POST' }); /** @hidden */ diff --git a/src/setup.ts b/src/setup.ts index 6a0796348..b30fa6226 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -20,6 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +/** @hidden */ import { firebaseConfig } from './config'; // Set up for config and vars diff --git a/src/utilities/assertions.ts b/src/utilities/assertions.ts index 49ff5e36d..982ca304d 100644 --- a/src/utilities/assertions.ts +++ b/src/utilities/assertions.ts @@ -1,4 +1,4 @@ -/** +/** @hidden * @file Provides common assertion helpers which can be used to improve * strictness of both type checking and runtime. */ diff --git a/src/utilities/path.ts b/src/utilities/path.ts index 40ab8f047..33e187f9e 100644 --- a/src/utilities/path.ts +++ b/src/utilities/path.ts @@ -1,4 +1,4 @@ -/** +/** @hidden * Removes leading and trailing slashes from a path. * * @param path A path to normalize, in POSIX format. diff --git a/src/utils.ts b/src/utils.ts index b52c1e54f..e41b1e1fb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -20,6 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +/** @hidden */ import * as _ from 'lodash'; export function applyChange(src: any, dest: any) { From 8d0a6c230984a058b798a3b58201930b87b06786 Mon Sep 17 00:00:00 2001 From: Adam Montgomery <1934358+montasaurus@users.noreply.github.com> Date: Wed, 27 May 2020 15:10:18 -0500 Subject: [PATCH 126/437] pin @types/express version (#686) --- CHANGELOG.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..b39bddd01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Pin `@types/express` version to 4.17.3 to fix type definition issue (#685) diff --git a/package.json b/package.json index ed26f220b..27bba5b72 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "test": "mocha" }, "dependencies": { - "@types/express": "^4.17.3", + "@types/express": "4.17.3", "cors": "^2.8.5", "express": "^4.17.1", "jsonwebtoken": "^8.5.1", From 9431102c8cd47432c091d873ef08aa45138f7f27 Mon Sep 17 00:00:00 2001 From: Sebastian Kreft Date: Wed, 27 May 2020 16:11:14 -0400 Subject: [PATCH 127/437] fix: move jsonwebtoken to dev dependencies (#677) jsonwebtoken was only used in tests and not present in the release output. ``` [0] firebase-functions$ ag jsonwebtoken spec/fixtures/mockrequest.ts 1:import * as jwt from 'jsonwebtoken'; package.json 50: "@types/jsonwebtoken": "^8.3.2", 64: "jsonwebtoken": "^8.5.1", [0] firebase-functions$ grep jsonwebtoken -R lib [1] firebase-functions$ ``` Co-authored-by: Lauren Long --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 27bba5b72..020998793 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "@types/express": "4.17.3", "cors": "^2.8.5", "express": "^4.17.1", - "jsonwebtoken": "^8.5.1", "lodash": "^4.17.14" }, "devDependencies": { @@ -62,6 +61,7 @@ "istanbul": "^0.4.5", "js-yaml": "^3.13.1", "jsdom": "^16.2.1", + "jsonwebtoken": "^8.5.1", "mocha": "^6.1.4", "mock-require": "^3.0.3", "mz": "^2.7.0", From dbf9b5e29d1bc1c31345151ce29c9ea13dc55847 Mon Sep 17 00:00:00 2001 From: Fred Zhang Date: Wed, 27 May 2020 13:26:20 -0700 Subject: [PATCH 128/437] Use `domain` in context to override RTDB domain #683 (#235) --- spec/providers/database.spec.ts | 56 +++++++++++++++++++++++---------- src/cloud-functions.ts | 1 + src/providers/database.ts | 34 +++++++++++++------- 3 files changed, 64 insertions(+), 27 deletions(-) diff --git a/spec/providers/database.spec.ts b/spec/providers/database.spec.ts index 25293ac11..b764a7ea0 100644 --- a/spec/providers/database.spec.ts +++ b/spec/providers/database.spec.ts @@ -32,7 +32,7 @@ describe('Database Functions', () => { before(() => { process.env.FIREBASE_CONFIG = JSON.stringify({ - databaseURL: 'https://subdomain.firebaseio.com', + databaseURL: 'https://subdomain.apse.firebasedatabase.app', }); appsNamespace.init(); }); @@ -424,29 +424,51 @@ describe('Database Functions', () => { }); }); - describe('resourceToInstanceAndPath', () => { - it('should return the correct instance and path strings', () => { - const [instance, path] = database.resourceToInstanceAndPath( - 'projects/_/instances/foo/refs/bar' + describe('extractInstanceAndPath', () => { + it('should return correct us-central prod instance and path strings if domain is missing', () => { + const [instance, path] = database.extractInstanceAndPath( + 'projects/_/instances/foo/refs/bar', + undefined ); expect(instance).to.equal('https://foo.firebaseio.com'); expect(path).to.equal('/bar'); }); + it('should return the correct staging instance and path strings if domain is present', () => { + const [instance, path] = database.extractInstanceAndPath( + 'projects/_/instances/foo/refs/bar', + 'firebaseio-staging.com' + ); + expect(instance).to.equal('https://foo.firebaseio-staging.com'); + expect(path).to.equal('/bar'); + }); + + it('should return the correct multi-region instance and path strings if domain is present', () => { + const [instance, path] = database.extractInstanceAndPath( + 'projects/_/instances/foo/refs/bar', + 'euw1.firebasedatabase.app' + ); + expect(instance).to.equal('https://foo.euw1.firebasedatabase.app'); + expect(path).to.equal('/bar'); + }); + it('should throw an error if the given instance name contains anything except alphanumerics and dashes', () => { expect(() => { - return database.resourceToInstanceAndPath( - 'projects/_/instances/a.bad.name/refs/bar' + return database.extractInstanceAndPath( + 'projects/_/instances/a.bad.name/refs/bar', + undefined ); }).to.throw(Error); expect(() => { - return database.resourceToInstanceAndPath( - 'projects/_/instances/a_different_bad_name/refs/bar' + return database.extractInstanceAndPath( + 'projects/_/instances/a_different_bad_name/refs/bar', + undefined ); }).to.throw(Error); expect(() => { - return database.resourceToInstanceAndPath( - 'projects/_/instances/BAD!!!!/refs/bar' + return database.extractInstanceAndPath( + 'projects/_/instances/BAD!!!!/refs/bar', + undefined ); }).to.throw(Error); }); @@ -457,8 +479,9 @@ describe('Database Functions', () => { const apps = new appsNamespace.Apps(); const populate = (data: any) => { - const [instance, path] = database.resourceToInstanceAndPath( - 'projects/_/instances/other-subdomain/refs/foo' + const [instance, path] = database.extractInstanceAndPath( + 'projects/_/instances/other-subdomain/refs/foo', + 'firebaseio-staging.com' ); subject = new database.DataSnapshot(data, path, apps.admin, instance); }; @@ -467,7 +490,7 @@ describe('Database Functions', () => { it('should return a ref for correct instance, not the default instance', () => { populate({}); expect(subject.ref.toJSON()).to.equal( - 'https://other-subdomain.firebaseio.com/foo' + 'https://other-subdomain.firebaseio-staging.com/foo' ); }); }); @@ -648,8 +671,9 @@ describe('Database Functions', () => { }); it('should return null for the root', () => { - const [instance, path] = database.resourceToInstanceAndPath( - 'projects/_/instances/foo/refs/' + const [instance, path] = database.extractInstanceAndPath( + 'projects/_/instances/foo/refs/', + undefined ); const snapshot = new database.DataSnapshot( null, diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index ea4acd7a2..f8b1b1c1e 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -39,6 +39,7 @@ export interface Event { timestamp: string; eventType: string; resource: Resource; + domain?: string; }; data: any; } diff --git a/src/providers/database.ts b/src/providers/database.ts index 78614a152..8c8de4ec6 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -40,8 +40,7 @@ export const provider = 'google.firebase.database'; /** @hidden */ export const service = 'firebaseio.com'; -// NOTE(inlined): Should we relax this a bit to allow staging or alternate implementations of our API? -const databaseURLRegex = new RegExp('https://([^.]+).firebaseio.com'); +const databaseURLRegex = new RegExp('^https://([^.]+).'); /** * Registers a function that triggers on events from a specific @@ -215,8 +214,9 @@ export class RefBuilder { ) => PromiseLike | any ): CloudFunction { const dataConstructor = (raw: Event) => { - const [dbInstance, path] = resourceToInstanceAndPath( - raw.context.resource.name + const [dbInstance, path] = extractInstanceAndPath( + raw.context.resource.name, + raw.context.domain ); return new DataSnapshot( raw.data.delta, @@ -243,8 +243,9 @@ export class RefBuilder { ) => PromiseLike | any ): CloudFunction { const dataConstructor = (raw: Event) => { - const [dbInstance, path] = resourceToInstanceAndPath( - raw.context.resource.name + const [dbInstance, path] = extractInstanceAndPath( + raw.context.resource.name, + raw.context.domain ); return new DataSnapshot(raw.data.data, path, this.apps.admin, dbInstance); }; @@ -271,8 +272,9 @@ export class RefBuilder { } private changeConstructor = (raw: Event): Change => { - const [dbInstance, path] = resourceToInstanceAndPath( - raw.context.resource.name + const [dbInstance, path] = extractInstanceAndPath( + raw.context.resource.name, + raw.context.domain ); const before = new DataSnapshot( raw.data.data, @@ -293,9 +295,19 @@ export class RefBuilder { }; } -/* Utility function to extract database reference from resource string */ +/** + * Utility function to extract database reference from resource string + * + * @param optional database domain override for the original of the source database. + * It defaults to `firebaseio.com`. + * Multi-region RTDB will be served from different domains. + * Since region is not part of the resource name, it is provided through context. + */ /** @hidden */ -export function resourceToInstanceAndPath(resource: string) { +export function extractInstanceAndPath( + resource: string, + domain = 'firebaseio.com' +) { const resourceRegex = `projects/([^/]+)/instances/([a-zA-Z0-9\-^/]+)/refs(/.+)?`; const match = resource.match(new RegExp(resourceRegex)); if (!match) { @@ -310,7 +322,7 @@ export function resourceToInstanceAndPath(resource: string) { `Expect project to be '_' in a Firebase Realtime Database event` ); } - const dbInstance = 'https://' + dbInstanceName + '.firebaseio.com'; + const dbInstance = 'https://' + dbInstanceName + '.' + domain; return [dbInstance, path]; } From f907d68e9e3d6500a357fbfd4722e6fc201e1e56 Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Thu, 28 May 2020 10:25:58 -0700 Subject: [PATCH 129/437] Update CHANGELOG.md (#694) --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b39bddd01..ecf68561e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,3 @@ -- Pin `@types/express` version to 4.17.3 to fix type definition issue (#685) +- Pin `@types/express` version to 4.17.3 to fix type definition issue (Issue #685). +- Firestore onCreate, onUpdate, and onDelete now receive a `QueryDocumentSnapshot` instead of `DocumentSnapshot`, which guarantees that data is not undefined (Issue #659). +- Modify return type of `DataSnapshot.forEach` to `boolean | void` match `firebase-admin` SDK. From 4fdf9dbdfc29eeb98c2c46f455ea17f353591dec Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 28 May 2020 17:28:56 +0000 Subject: [PATCH 130/437] 3.6.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 020998793..c0403aafa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.6.1", + "version": "3.6.2", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From bb6f038f76c35dd64f48e8293a7e1b3e464999bb Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 28 May 2020 17:29:01 +0000 Subject: [PATCH 131/437] [firebase-release] Removed change log and reset repo after 3.6.2 release --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecf68561e..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +0,0 @@ -- Pin `@types/express` version to 4.17.3 to fix type definition issue (Issue #685). -- Firestore onCreate, onUpdate, and onDelete now receive a `QueryDocumentSnapshot` instead of `DocumentSnapshot`, which guarantees that data is not undefined (Issue #659). -- Modify return type of `DataSnapshot.forEach` to `boolean | void` match `firebase-admin` SDK. From bb92d38238dda02e376e585b3e80af2e68008e68 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Tue, 2 Jun 2020 09:31:35 -0700 Subject: [PATCH 132/437] Adds structured logging API. (#665) --- CHANGELOG.md | 18 ++++++ spec/logger.spec.ts | 105 ++++++++++++++++++++++++++++++++ src/index.ts | 2 + src/logger.ts | 135 ++++++++++++++++++++++++++++++++++++++++++ src/logger/compat.ts | 8 +++ tsconfig.release.json | 2 +- 6 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 spec/logger.spec.ts create mode 100644 src/logger.ts create mode 100644 src/logger/compat.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..f85fd69b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +- Adds `functions.logger` SDK to enable structured logging in the Node.js 10 runtime. For example: + + ```js + const functions = require('firebase-functions'); + + functions.logger.debug('example log with structured data', { + uid: user.uid, + authorized: true, + }); + ``` + + The logger can also override default behavior of `console.*` methods through a special require: + + ```js + require('firebase-functions/logger/compat'); + ``` + + In older runtimes, logger prints to the console, and no structured data is saved. diff --git a/spec/logger.spec.ts b/spec/logger.spec.ts new file mode 100644 index 000000000..517f50f1d --- /dev/null +++ b/spec/logger.spec.ts @@ -0,0 +1,105 @@ +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import * as logger from '../src/logger'; + +const SUPPORTS_STRUCTURED_LOGS = + parseInt(process.versions?.node?.split('.')?.[0] || '8', 10) >= 10; + +describe(`logger (${ + SUPPORTS_STRUCTURED_LOGS ? 'structured' : 'unstructured' +})`, () => { + let sandbox: sinon.SinonSandbox; + let stdoutStub: sinon.SinonStub; + let stderrStub: sinon.SinonStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + stdoutStub = sandbox.stub(process.stdout, 'write'); + stderrStub = sandbox.stub(process.stderr, 'write'); + }); + + function expectOutput(stdStub: sinon.SinonStub, entry: any) { + if (SUPPORTS_STRUCTURED_LOGS) { + return expect( + JSON.parse((stdStub.getCalls()[0].args[0] as string).trim()) + ).to.deep.eq(entry); + } else { + // legacy logging is not structured, but do a sanity check + return expect(stdStub.getCalls()[0].args[0]).to.include(entry.message); + } + } + + function expectStdout(entry: any) { + return expectOutput(stdoutStub, entry); + } + + function expectStderr(entry: any) { + return expectOutput(stderrStub, entry); + } + + describe('logging methods', () => { + let writeStub: sinon.SinonStub; + beforeEach(() => { + writeStub = sinon.stub(logger, 'write'); + }); + + afterEach(() => { + writeStub.restore(); + }); + + it('should coalesce arguments into the message', () => { + logger.log('hello', { middle: 'obj' }, 'end message'); + expectStdout({ + severity: 'INFO', + message: "hello { middle: 'obj' } end message", + }); + sandbox.restore(); // to avoid swallowing test runner output + }); + + it('should merge structured data from the last argument', () => { + logger.log('hello', 'world', { additional: 'context' }); + expectStdout({ + severity: 'INFO', + message: 'hello world', + additional: 'context', + }); + sandbox.restore(); // to avoid swallowing test runner output + }); + }); + + describe('write', () => { + describe('structured logging', () => { + describe('write', () => { + for (const severity of ['DEBUG', 'INFO', 'NOTICE']) { + it(`should output ${severity} severity to stdout`, () => { + let entry: logger.LogEntry = { + severity: severity as logger.LogSeverity, + message: 'test', + }; + logger.write(entry); + expectStdout(entry); + sandbox.restore(); // to avoid swallowing test runner output + }); + } + + for (const severity of [ + 'WARNING', + 'ERROR', + 'CRITICAL', + 'ALERT', + 'EMERGENCY', + ]) { + it(`should output ${severity} severity to stderr`, () => { + let entry: logger.LogEntry = { + severity: severity as logger.LogSeverity, + message: 'test', + }; + logger.write(entry); + expectStderr(entry); + sandbox.restore(); // to avoid swallowing test runner output + }); + } + }); + }); + }); +}); diff --git a/src/index.ts b/src/index.ts index d4951c512..dcca98172 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,6 +33,7 @@ import * as storage from './providers/storage'; import * as testLab from './providers/testLab'; import * as apps from './apps'; +import * as logger from './logger'; import { handler } from './handler-builder'; import { setup } from './setup'; @@ -51,6 +52,7 @@ export { remoteConfig, storage, testLab, + logger, }; // Exported root types: diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 000000000..7d4c91cbf --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,135 @@ +import { format } from 'util'; + +// safely preserve unpatched console.* methods in case of compat require +const unpatchedConsole = { + debug: console.debug, + info: console.info, + log: console.log, + warn: console.warn, + error: console.error, +}; + +// Determine if structured logs are supported (node >= 10). If something goes wrong, +// assume no since unstructured is safer. +const SUPPORTS_STRUCTURED_LOGS = + parseInt(process.versions?.node?.split('.')?.[0] || '8', 10) >= 10; + +// Map LogSeverity types to their equivalent `console.*` method. +const CONSOLE_SEVERITY: { + [severity: string]: 'debug' | 'info' | 'warn' | 'error'; +} = { + DEBUG: 'debug', + INFO: 'info', + NOTICE: 'info', + WARNING: 'warn', + ERROR: 'error', + CRITICAL: 'error', + ALERT: 'error', + EMERGENCY: 'error', +}; + +/** + * `LogSeverity` indicates the detailed severity of the log entry. See [LogSeverity](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity) for more. + */ +export type LogSeverity = + | 'DEBUG' + | 'INFO' + | 'NOTICE' + | 'WARNING' + | 'ERROR' + | 'CRITICAL' + | 'ALERT' + | 'EMERGENCY'; + +/** + * `LogEntry` represents a structured Cloud Logging entry. All keys aside from `severity` and `message` are + * included in the `jsonPayload` of the logged entry. + */ +export interface LogEntry { + severity: LogSeverity; + message?: string; + [key: string]: any; +} + +/** + * Writes a `LogEntry` to `stdout`/`stderr` (depending on severity). + * @param entry The LogEntry including severity, message, and any additional structured metadata. + */ +export function write(entry: LogEntry) { + if (SUPPORTS_STRUCTURED_LOGS) { + unpatchedConsole[CONSOLE_SEVERITY[entry.severity]](JSON.stringify(entry)); + return; + } + + let message = entry.message || ''; + const jsonPayload: { [key: string]: any } = {}; + let jsonKeyCount = 0; + for (const k in entry) { + if (!['severity', 'message'].includes(k)) { + jsonKeyCount++; + jsonPayload[k] = entry[k]; + } + } + if (jsonKeyCount > 0) { + message = `${message} ${JSON.stringify(jsonPayload, null, 2)}`; + } + unpatchedConsole[CONSOLE_SEVERITY[entry.severity]](message); +} + +/** + * Writes a `DEBUG` severity log. If the last argument provided is a plain object, + * it will be added to the `jsonPayload` in the Cloud Logging entry. + * @param args Arguments, concatenated into the log message with space separators. + */ +export function debug(...args: any[]) { + write(entryFromArgs('DEBUG', args)); +} + +/** + * Writes an `INFO` severity log. If the last argument provided is a plain object, + * it will be added to the `jsonPayload` in the Cloud Logging entry. + * @param args Arguments, concatenated into the log message with space separators. + */ +export function log(...args: any[]) { + write(entryFromArgs('INFO', args)); +} + +/** + * Writes an `INFO` severity log. If the last argument provided is a plain object, + * it will be added to the `jsonPayload` in the Cloud Logging entry. + * @param args Arguments, concatenated into the log message with space separators. + */ +export function info(...args: any[]) { + write(entryFromArgs('INFO', args)); +} + +/** + * Writes a `WARNING` severity log. If the last argument provided is a plain object, + * it will be added to the `jsonPayload` in the Cloud Logging entry. + * @param args Arguments, concatenated into the log message with space separators. + */ +export function warn(...args: any[]) { + write(entryFromArgs('WARNING', args)); +} + +/** + * Writes an `ERROR` severity log. If the last argument provided is a plain object, + * it will be added to the `jsonPayload` in the Cloud Logging entry. + * @param args Arguments, concatenated into the log message with space separators. + */ +export function error(...args: any[]) { + write(entryFromArgs('ERROR', args)); +} + +function entryFromArgs(severity: LogSeverity, args: any[]): LogEntry { + let entry = {}; + const lastArg = args[args.length - 1]; + if (typeof lastArg == 'object' && lastArg.constructor == Object) { + entry = args.pop(); + } + return Object.assign({}, entry, { + severity, + // mimic `console.*` behavior, see https://nodejs.org/api/console.html#console_console_log_data_args + message: format.apply(null, args), + }); +} diff --git a/src/logger/compat.ts b/src/logger/compat.ts new file mode 100644 index 000000000..6f6eda03f --- /dev/null +++ b/src/logger/compat.ts @@ -0,0 +1,8 @@ +import { debug, info, warn, error } from '../logger'; + +// IMPORTANT -- "../logger" must be imported before monkeypatching! +console.debug = debug; +console.info = info; +console.log = info; +console.warn = warn; +console.error = error; diff --git a/tsconfig.release.json b/tsconfig.release.json index e93d5d4b6..a226eb290 100644 --- a/tsconfig.release.json +++ b/tsconfig.release.json @@ -10,5 +10,5 @@ "target": "es2017", "typeRoots": ["./node_modules/@types"] }, - "files": ["./src/index.ts"] + "files": ["./src/index.ts", "./src/logger.ts", "./src/logger/compat.ts"] } From e447949d0d6095b130ca5551e3396b45ff5d589a Mon Sep 17 00:00:00 2001 From: Sebastian Kreft Date: Tue, 9 Jun 2020 17:34:43 -0400 Subject: [PATCH 133/437] fix: onRequest type definitions (#696) --- src/cloud-functions.ts | 2 +- src/providers/https.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index f8b1b1c1e..f249bf7f9 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -281,7 +281,7 @@ export interface Runnable { * arguments. */ export type HttpsFunction = TriggerAnnotated & - ((req: Request, resp: Response) => void); + ((req: Request, resp: Response) => void | Promise); /** * The Cloud Function type for all non-HTTPS triggers. This should be exported diff --git a/src/providers/https.ts b/src/providers/https.ts index 605c633f0..cd9f1745d 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -39,7 +39,7 @@ export interface Request extends express.Request { * same signature as an Express app. */ export function onRequest( - handler: (req: Request, resp: express.Response) => void + handler: (req: Request, resp: express.Response) => void | Promise ): HttpsFunction { return _onRequestWithOptions(handler, {}); } @@ -56,7 +56,7 @@ export function onCall( /** @hidden */ export function _onRequestWithOptions( - handler: (req: Request, resp: express.Response) => void, + handler: (req: Request, resp: express.Response) => void | Promise, options: DeploymentOptions ): HttpsFunction { // lets us add __trigger without altering handler: From c26ed9e6bee56f285118975e8afa6680657af5f3 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Tue, 9 Jun 2020 15:10:47 -0700 Subject: [PATCH 134/437] Makes "logger/compat" more focused on compatibility. Fixes #697 (#701) --- CHANGELOG.md | 4 ++-- src/logger.ts | 36 +++++++----------------------------- src/logger/common.ts | 27 +++++++++++++++++++++++++++ src/logger/compat.ts | 30 ++++++++++++++++++++++++------ 4 files changed, 60 insertions(+), 37 deletions(-) create mode 100644 src/logger/common.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f85fd69b3..1b87ad8b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,10 @@ }); ``` - The logger can also override default behavior of `console.*` methods through a special require: +- Adds a special require that mimics Node.js 8 runtime logging in Node.js 10 and later runtimes: ```js require('firebase-functions/logger/compat'); ``` - In older runtimes, logger prints to the console, and no structured data is saved. + In newer runtimes, requiring this will emit text logs with multi-line support and appropriate severity. In the Node.js 8 runtime, the `compat` module has no effect. diff --git a/src/logger.ts b/src/logger.ts index 7d4c91cbf..258dbbdff 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,32 +1,10 @@ import { format } from 'util'; -// safely preserve unpatched console.* methods in case of compat require -const unpatchedConsole = { - debug: console.debug, - info: console.info, - log: console.log, - warn: console.warn, - error: console.error, -}; - -// Determine if structured logs are supported (node >= 10). If something goes wrong, -// assume no since unstructured is safer. -const SUPPORTS_STRUCTURED_LOGS = - parseInt(process.versions?.node?.split('.')?.[0] || '8', 10) >= 10; - -// Map LogSeverity types to their equivalent `console.*` method. -const CONSOLE_SEVERITY: { - [severity: string]: 'debug' | 'info' | 'warn' | 'error'; -} = { - DEBUG: 'debug', - INFO: 'info', - NOTICE: 'info', - WARNING: 'warn', - ERROR: 'error', - CRITICAL: 'error', - ALERT: 'error', - EMERGENCY: 'error', -}; +import { + SUPPORTS_STRUCTURED_LOGS, + CONSOLE_SEVERITY, + UNPATCHED_CONSOLE, +} from './logger/common'; /** * `LogSeverity` indicates the detailed severity of the log entry. See [LogSeverity](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity) for more. @@ -57,7 +35,7 @@ export interface LogEntry { */ export function write(entry: LogEntry) { if (SUPPORTS_STRUCTURED_LOGS) { - unpatchedConsole[CONSOLE_SEVERITY[entry.severity]](JSON.stringify(entry)); + UNPATCHED_CONSOLE[CONSOLE_SEVERITY[entry.severity]](JSON.stringify(entry)); return; } @@ -73,7 +51,7 @@ export function write(entry: LogEntry) { if (jsonKeyCount > 0) { message = `${message} ${JSON.stringify(jsonPayload, null, 2)}`; } - unpatchedConsole[CONSOLE_SEVERITY[entry.severity]](message); + UNPATCHED_CONSOLE[CONSOLE_SEVERITY[entry.severity]](message); } /** diff --git a/src/logger/common.ts b/src/logger/common.ts new file mode 100644 index 000000000..f8b1f0ed9 --- /dev/null +++ b/src/logger/common.ts @@ -0,0 +1,27 @@ +// Determine if structured logs are supported (node >= 10). If something goes wrong, +// assume no since unstructured is safer. +export const SUPPORTS_STRUCTURED_LOGS = + parseInt(process.versions?.node?.split('.')?.[0] || '8', 10) >= 10; + +// Map LogSeverity types to their equivalent `console.*` method. +export const CONSOLE_SEVERITY: { + [severity: string]: 'debug' | 'info' | 'warn' | 'error'; +} = { + DEBUG: 'debug', + INFO: 'info', + NOTICE: 'info', + WARNING: 'warn', + ERROR: 'error', + CRITICAL: 'error', + ALERT: 'error', + EMERGENCY: 'error', +}; + +// safely preserve unpatched console.* methods in case of compat require +export const UNPATCHED_CONSOLE = { + debug: console.debug, + info: console.info, + log: console.log, + warn: console.warn, + error: console.error, +}; diff --git a/src/logger/compat.ts b/src/logger/compat.ts index 6f6eda03f..48647f5e3 100644 --- a/src/logger/compat.ts +++ b/src/logger/compat.ts @@ -1,8 +1,26 @@ -import { debug, info, warn, error } from '../logger'; +import { + SUPPORTS_STRUCTURED_LOGS, + UNPATCHED_CONSOLE, + CONSOLE_SEVERITY, +} from './common'; +import { format } from 'util'; + +function patchedConsole(severity: string): (data: any, ...args: any[]) => void { + return function(data: any, ...args: any[]): void { + if (SUPPORTS_STRUCTURED_LOGS) { + UNPATCHED_CONSOLE[CONSOLE_SEVERITY[severity]]( + JSON.stringify({ severity, message: format(data, ...args) }) + ); + return; + } + + UNPATCHED_CONSOLE[CONSOLE_SEVERITY[severity]](data, ...args); + }; +} // IMPORTANT -- "../logger" must be imported before monkeypatching! -console.debug = debug; -console.info = info; -console.log = info; -console.warn = warn; -console.error = error; +console.debug = patchedConsole('DEBUG'); +console.info = patchedConsole('INFO'); +console.log = patchedConsole('INFO'); +console.warn = patchedConsole('WARNING'); +console.error = patchedConsole('ERROR'); From 0c684322f91410cdb8d77c97de174f9dbb17a909 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Tue, 9 Jun 2020 16:06:49 -0700 Subject: [PATCH 135/437] Fix compat path in CHANGELOG (#702) --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b87ad8b8..248ab7792 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,9 @@ - Adds a special require that mimics Node.js 8 runtime logging in Node.js 10 and later runtimes: ```js - require('firebase-functions/logger/compat'); + require('firebase-functions/lib/logger/compat'); ``` In newer runtimes, requiring this will emit text logs with multi-line support and appropriate severity. In the Node.js 8 runtime, the `compat` module has no effect. + +- Fixes `https.onRequest` type signature to allow Promises for `async` functions. From 505f357b1c57338e55ae5da16e62e4398d3dc746 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 9 Jun 2020 23:18:37 +0000 Subject: [PATCH 136/437] 3.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c0403aafa..6e198c416 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.6.2", + "version": "3.7.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From c23a8c11299f93f32a59507009f88999851082b6 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 9 Jun 2020 23:18:44 +0000 Subject: [PATCH 137/437] [firebase-release] Removed change log and reset repo after 3.7.0 release --- CHANGELOG.md | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 248ab7792..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +0,0 @@ -- Adds `functions.logger` SDK to enable structured logging in the Node.js 10 runtime. For example: - - ```js - const functions = require('firebase-functions'); - - functions.logger.debug('example log with structured data', { - uid: user.uid, - authorized: true, - }); - ``` - -- Adds a special require that mimics Node.js 8 runtime logging in Node.js 10 and later runtimes: - - ```js - require('firebase-functions/lib/logger/compat'); - ``` - - In newer runtimes, requiring this will emit text logs with multi-line support and appropriate severity. In the Node.js 8 runtime, the `compat` module has no effect. - -- Fixes `https.onRequest` type signature to allow Promises for `async` functions. From f9b24e03a5b9da08866f990304f6623a4b909be3 Mon Sep 17 00:00:00 2001 From: egilmorez Date: Tue, 23 Jun 2020 12:56:52 -0700 Subject: [PATCH 138/437] Adding logger SDK to reference, with some edits. (#714) * Adding logger SDK to reference, with some edits. * Adding link to structured logging details per feedback. --- docgen/content-sources/toc.yaml | 6 ++++++ src/logger.ts | 18 ++++++++++-------- src/logger/common.ts | 3 +++ src/logger/compat.ts | 1 + 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/docgen/content-sources/toc.yaml b/docgen/content-sources/toc.yaml index 91544d4f6..a394975df 100644 --- a/docgen/content-sources/toc.yaml +++ b/docgen/content-sources/toc.yaml @@ -93,6 +93,12 @@ toc: - title: 'CallableContext' path: /docs/reference/functions/providers_https_.callablecontext.html + - title: 'functions.logger' + path: /docs/reference/functions/logger_.html + section: + - title: 'LogEntry' + path: /docs/reference/functions/logger_.logentry.html + - title: 'functions.pubsub' path: /docs/reference/functions/providers_pubsub_.html section: diff --git a/src/logger.ts b/src/logger.ts index 258dbbdff..4bc851cec 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -7,7 +7,7 @@ import { } from './logger/common'; /** - * `LogSeverity` indicates the detailed severity of the log entry. See [LogSeverity](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity) for more. + * `LogSeverity` indicates the detailed severity of the log entry. See [LogSeverity](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity). */ export type LogSeverity = | 'DEBUG' @@ -20,7 +20,8 @@ export type LogSeverity = | 'EMERGENCY'; /** - * `LogEntry` represents a structured Cloud Logging entry. All keys aside from `severity` and `message` are + * `LogEntry` represents a [structured Cloud Logging](https://cloud.google.com/logging/docs/structured-logging) + * entry. All keys aside from `severity` and `message` are * included in the `jsonPayload` of the logged entry. */ export interface LogEntry { @@ -31,7 +32,7 @@ export interface LogEntry { /** * Writes a `LogEntry` to `stdout`/`stderr` (depending on severity). - * @param entry The LogEntry including severity, message, and any additional structured metadata. + * @param entry The `LogEntry` including severity, message, and any additional structured metadata. */ export function write(entry: LogEntry) { if (SUPPORTS_STRUCTURED_LOGS) { @@ -56,7 +57,7 @@ export function write(entry: LogEntry) { /** * Writes a `DEBUG` severity log. If the last argument provided is a plain object, - * it will be added to the `jsonPayload` in the Cloud Logging entry. + * it is added to the `jsonPayload` in the Cloud Logging entry. * @param args Arguments, concatenated into the log message with space separators. */ export function debug(...args: any[]) { @@ -65,7 +66,7 @@ export function debug(...args: any[]) { /** * Writes an `INFO` severity log. If the last argument provided is a plain object, - * it will be added to the `jsonPayload` in the Cloud Logging entry. + * it is added to the `jsonPayload` in the Cloud Logging entry. * @param args Arguments, concatenated into the log message with space separators. */ export function log(...args: any[]) { @@ -74,7 +75,7 @@ export function log(...args: any[]) { /** * Writes an `INFO` severity log. If the last argument provided is a plain object, - * it will be added to the `jsonPayload` in the Cloud Logging entry. + * it is added to the `jsonPayload` in the Cloud Logging entry. * @param args Arguments, concatenated into the log message with space separators. */ export function info(...args: any[]) { @@ -83,7 +84,7 @@ export function info(...args: any[]) { /** * Writes a `WARNING` severity log. If the last argument provided is a plain object, - * it will be added to the `jsonPayload` in the Cloud Logging entry. + * it is added to the `jsonPayload` in the Cloud Logging entry. * @param args Arguments, concatenated into the log message with space separators. */ export function warn(...args: any[]) { @@ -92,13 +93,14 @@ export function warn(...args: any[]) { /** * Writes an `ERROR` severity log. If the last argument provided is a plain object, - * it will be added to the `jsonPayload` in the Cloud Logging entry. + * it is added to the `jsonPayload` in the Cloud Logging entry. * @param args Arguments, concatenated into the log message with space separators. */ export function error(...args: any[]) { write(entryFromArgs('ERROR', args)); } +/** @hidden */ function entryFromArgs(severity: LogSeverity, args: any[]): LogEntry { let entry = {}; const lastArg = args[args.length - 1]; diff --git a/src/logger/common.ts b/src/logger/common.ts index f8b1f0ed9..f7ff0de78 100644 --- a/src/logger/common.ts +++ b/src/logger/common.ts @@ -1,9 +1,11 @@ // Determine if structured logs are supported (node >= 10). If something goes wrong, // assume no since unstructured is safer. +/** @hidden */ export const SUPPORTS_STRUCTURED_LOGS = parseInt(process.versions?.node?.split('.')?.[0] || '8', 10) >= 10; // Map LogSeverity types to their equivalent `console.*` method. +/** @hidden */ export const CONSOLE_SEVERITY: { [severity: string]: 'debug' | 'info' | 'warn' | 'error'; } = { @@ -18,6 +20,7 @@ export const CONSOLE_SEVERITY: { }; // safely preserve unpatched console.* methods in case of compat require +/** @hidden */ export const UNPATCHED_CONSOLE = { debug: console.debug, info: console.info, diff --git a/src/logger/compat.ts b/src/logger/compat.ts index 48647f5e3..a7c27bd16 100644 --- a/src/logger/compat.ts +++ b/src/logger/compat.ts @@ -5,6 +5,7 @@ import { } from './common'; import { format } from 'util'; +/** @hidden */ function patchedConsole(severity: string): (data: any, ...args: any[]) => void { return function(data: any, ...args: any[]): void { if (SUPPORTS_STRUCTURED_LOGS) { From d06bcbee7b147843e3de85363d6dd090767064c9 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Wed, 24 Jun 2020 10:51:39 -0700 Subject: [PATCH 139/437] Handles `null` in structured logging (Fixes #716) (#717) --- spec/logger.spec.ts | 9 +++++++++ src/logger.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/spec/logger.spec.ts b/spec/logger.spec.ts index 517f50f1d..c6859b60a 100644 --- a/spec/logger.spec.ts +++ b/spec/logger.spec.ts @@ -65,6 +65,15 @@ describe(`logger (${ }); sandbox.restore(); // to avoid swallowing test runner output }); + + it('should not recognize null as a structured logging object', () => { + logger.log('hello', 'world', null); + expectStdout({ + severity: 'INFO', + message: 'hello world null', + }); + sandbox.restore(); // to avoid swallowing test runner output + }); }); describe('write', () => { diff --git a/src/logger.ts b/src/logger.ts index 4bc851cec..7958ca457 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -104,7 +104,7 @@ export function error(...args: any[]) { function entryFromArgs(severity: LogSeverity, args: any[]): LogEntry { let entry = {}; const lastArg = args[args.length - 1]; - if (typeof lastArg == 'object' && lastArg.constructor == Object) { + if (lastArg && typeof lastArg == 'object' && lastArg.constructor == Object) { entry = args.pop(); } return Object.assign({}, entry, { From 5f7c7acc9deb4e7c9ab1ab32c660ae035a63f93c Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Thu, 25 Jun 2020 08:25:42 -0700 Subject: [PATCH 140/437] Changelog update (#718) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..43975c685 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fixes error when last argument to logger methods is `null`. (#716) From b6e611baab000303b55958b55bdcf6655069f221 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Tue, 7 Jul 2020 13:23:23 -0700 Subject: [PATCH 141/437] Adds newly available GCF regions. (#722) --- CHANGELOG.md | 2 ++ scripts/fetch-regions | 7 +++++++ spec/function-builder.spec.ts | 30 ------------------------------ src/function-builder.ts | 11 ++++------- src/function-configuration.ts | 6 +++++- 5 files changed, 18 insertions(+), 38 deletions(-) create mode 100755 scripts/fetch-regions diff --git a/CHANGELOG.md b/CHANGELOG.md index 43975c685..edab83fdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,3 @@ - Fixes error when last argument to logger methods is `null`. (#716) +- Adds newly available locations `us-west3`, `europe-west6`, `northamerica-northeast1`, and `australia-southeast1`. +- No longer throw errors for unrecognized regions (deploy will error instead). diff --git a/scripts/fetch-regions b/scripts/fetch-regions new file mode 100755 index 000000000..d529cdf9b --- /dev/null +++ b/scripts/fetch-regions @@ -0,0 +1,7 @@ +#!/bin/bash + +if [ -z $1 ]; then + echo "Must provide a project id as first argument." && exit 1 +fi; + +gcloud functions regions list --project $1 --format=json | jq 'map(.locationId)' \ No newline at end of file diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index 0044e5661..0ad4265ed 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -121,14 +121,6 @@ describe('FunctionBuilder', () => { expect(fn.__trigger.timeout).to.deep.equal('90s'); }); - it('should fail if valid runtime options but unsupported region are set (reverse order)', () => { - expect(() => { - functions - .runWith({ timeoutSeconds: 90, memory: '256MB' }) - .region('unsupported' as any); - }).to.throw(Error, 'region'); - }); - it('should fail if supported region but invalid runtime options are set (reverse order)', () => { expect(() => { functions @@ -165,28 +157,6 @@ describe('FunctionBuilder', () => { }).to.throw(Error, 'TimeoutSeconds'); }); - it('should throw an error if user chooses an invalid region', () => { - expect(() => { - return functions.region('unsupported' as any); - }).to.throw(Error, 'region'); - - expect(() => { - return functions.region('unsupported' as any).runWith({ - timeoutSeconds: 500, - } as any); - }).to.throw(Error, 'region'); - - expect(() => { - return functions.region('unsupported' as any, 'us-east1'); - }).to.throw(Error, 'region'); - - expect(() => { - return functions.region('unsupported' as any, 'us-east1').runWith({ - timeoutSeconds: 500, - } as any); - }).to.throw(Error, 'region'); - }); - it('should throw an error if user chooses no region when using .region()', () => { expect(() => { return functions.region(); diff --git a/src/function-builder.ts b/src/function-builder.ts index 580e0498b..76c4b6c9d 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -78,11 +78,6 @@ function assertRegionsAreValid(regions: string[]): boolean { if (!regions.length) { throw new Error('You must specify at least one region'); } - if (_.difference(regions, SUPPORTED_REGIONS).length) { - throw new Error( - `The only valid regions are: ${SUPPORTED_REGIONS.join(', ')}` - ); - } return true; } @@ -95,7 +90,7 @@ function assertRegionsAreValid(regions: string[]): boolean { * functions.region('us-east1', 'us-central1') */ export function region( - ...regions: Array + ...regions: Array ): FunctionBuilder { if (assertRegionsAreValid(regions)) { return new FunctionBuilder({ regions }); @@ -127,7 +122,9 @@ export class FunctionBuilder { * @example * functions.region('us-east1', 'us-central1') */ - region(...regions: Array): FunctionBuilder { + region( + ...regions: Array + ): FunctionBuilder { if (assertRegionsAreValid(regions)) { this.options.regions = regions; return this; diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 05d4517a8..2ec102fda 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -5,11 +5,15 @@ export const SUPPORTED_REGIONS = [ 'us-central1', 'us-east1', 'us-east4', + 'us-west3', 'europe-west1', 'europe-west2', 'europe-west3', + 'europe-west6', 'asia-east2', 'asia-northeast1', + 'northamerica-northeast1', + 'australia-southeast1', ] as const; /** @@ -70,6 +74,6 @@ export interface RuntimeOptions { } export interface DeploymentOptions extends RuntimeOptions { - regions?: Array; + regions?: Array; schedule?: Schedule; } From ab3ae2af275a0037eff905804a49d031f1734f93 Mon Sep 17 00:00:00 2001 From: Sam Stern Date: Wed, 8 Jul 2020 15:02:58 -0400 Subject: [PATCH 142/437] Fix issues with .ref in Database functions (#727) --- CHANGELOG.md | 1 + src/providers/database.ts | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edab83fdb..35049e3ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Fixes error when last argument to logger methods is `null`. (#716) - Adds newly available locations `us-west3`, `europe-west6`, `northamerica-northeast1`, and `australia-southeast1`. - No longer throw errors for unrecognized regions (deploy will error instead). +- Fixes error where `snap.ref` in database functions did not work when using the Emulator Suite (#726) diff --git a/src/providers/database.ts b/src/providers/database.ts index 8c8de4ec6..2aa50b55a 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -41,6 +41,7 @@ export const provider = 'google.firebase.database'; export const service = 'firebaseio.com'; const databaseURLRegex = new RegExp('^https://([^.]+).'); +const emulatorDatabaseURLRegex = new RegExp('^http://.*ns=([^&]+)'); /** * Registers a function that triggers on events from a specific @@ -138,14 +139,25 @@ export function _refWithOptions( '\n If you are unit testing, please set process.env.FIREBASE_CONFIG' ); } - const match = databaseURL.match(databaseURLRegex); - if (!match) { + + let instance = undefined; + const prodMatch = databaseURL.match(databaseURLRegex); + if (prodMatch) { + instance = prodMatch[1]; + } else { + const emulatorMatch = databaseURL.match(emulatorDatabaseURLRegex); + if (emulatorMatch) { + instance = emulatorMatch[1]; + } + } + + if (!instance) { throw new Error( 'Invalid value for config firebase.databaseURL: ' + databaseURL ); } - const subdomain = match[1]; - return `projects/_/instances/${subdomain}/refs/${normalized}`; + + return `projects/_/instances/${instance}/refs/${normalized}`; }; return new RefBuilder(apps(), resourceGetter, options); @@ -350,7 +362,10 @@ export class DataSnapshot { private app?: firebase.app.App, instance?: string ) { - if (instance) { + if (app && app.options.databaseURL.startsWith('http:')) { + // In this case we're dealing with an emulator + this.instance = app.options.databaseURL; + } else if (instance) { // SDK always supplies instance, but user's unit tests may not this.instance = instance; } else if (app) { From ad144e41c82e551fe323d473919d7e8426d11d84 Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Thu, 9 Jul 2020 17:09:42 -0700 Subject: [PATCH 143/437] Update ref docs for handler SDK to include scheduled functions. (#731) --- src/handler-builder.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/handler-builder.ts b/src/handler-builder.ts index 81232df59..ea0f4af74 100644 --- a/src/handler-builder.ts +++ b/src/handler-builder.ts @@ -305,13 +305,20 @@ export class HandlerBuilder { /** * Create a handler for Cloud Pub/Sub events. - - * `pubsub.onPublish` handles the publication of messages to a topic. - + * + * `topic.onPublish` handles messages published to a Pub/Sub topic from SDKs, Cloud Console, or gcloud CLI. + * * @example * ```javascript * exports.myFunction = functions.handler.pubsub.topic.onPublish((message) => { ... }) * ``` + + * `schedule.onPublish` handles messages published to a Pub/Sub topic on a schedule. + * + * @example + * ```javascript + * exports.myFunction = functions.handler.pubsub.schedule.onPublish((message) => { ... }) + * ``` */ get pubsub() { return { From 858cb9befd9468a9f1e1f2fb0d6f339cce3dec35 Mon Sep 17 00:00:00 2001 From: egilmorez Date: Fri, 10 Jul 2020 11:01:11 -0700 Subject: [PATCH 144/437] Eg pubsub fixes (#729) * Adding logger SDK to reference, with some edits. * Adding link to structured logging details per feedback. * Adding ScheduleBuilder class to TOC and adding minimal commenting. * Improving wording and format per feedback. * I think we need to document both the schedule function and the builder class this way. * Last edits from feedback. --- docgen/content-sources/toc.yaml | 2 ++ src/providers/pubsub.ts | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/docgen/content-sources/toc.yaml b/docgen/content-sources/toc.yaml index a394975df..37093f7c2 100644 --- a/docgen/content-sources/toc.yaml +++ b/docgen/content-sources/toc.yaml @@ -106,6 +106,8 @@ toc: path: /docs/reference/functions/providers_pubsub_.message.html - title: 'TopicBuilder' path: /docs/reference/functions/providers_pubsub_.topicbuilder.html + - title: 'ScheduleBuilder' + path: /docs/reference/functions/providers_pubsub_.schedulebuilder.html - title: 'functions.remoteconfig' path: /docs/reference/functions/providers_remoteconfig_.html diff --git a/src/providers/pubsub.ts b/src/providers/pubsub.ts index 0f12f669c..264ef9480 100644 --- a/src/providers/pubsub.ts +++ b/src/providers/pubsub.ts @@ -98,6 +98,12 @@ export class TopicBuilder { } } +/** + * Registers a Cloud Function to run at specified times. + * + * @param schedule The schedule, in Unix Crontab or AppEngine syntax. + * @return ScheduleBuilder interface. + */ export function schedule(schedule: string): ScheduleBuilder { return _scheduleWithOptions(schedule, {}); } @@ -120,6 +126,15 @@ export function _scheduleWithOptions( }); } +/** + * The builder for scheduled functions, which are powered by + * Google Pub/Sub and Cloud Scheduler. Describes the Cloud Scheduler + * job that is deployed to trigger a scheduled function at the provided + * frequency. For more information, see + * [Schedule functions](/docs/functions/schedule-functions). + * + * Access via [`functions.pubsub.schedule()`](providers_pubsub_.html#schedule). + */ export class ScheduleBuilder { /** @hidden */ constructor( @@ -137,6 +152,14 @@ export class ScheduleBuilder { return this; } + /** + * Event handler for scheduled functions. Triggered whenever the associated + * scheduler job sends a Pub/Sub message. + * + * @param handler Handler that fires whenever the associated + * scheduler job sends a Pub/Sub message. + * @return A Cloud Function that you can export and deploy. + */ onRun(handler: (context: EventContext) => PromiseLike | any) { const cloudFunction = makeCloudFunction({ contextOnlyHandler: handler, From b424fd3ccb4284a663213993fe2349eaef057992 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Fri, 10 Jul 2020 15:21:09 -0700 Subject: [PATCH 145/437] Adds four new regions launched July 10. (#733) --- CHANGELOG.md | 10 +++++++++- src/function-configuration.ts | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35049e3ca..d85f674fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ - Fixes error when last argument to logger methods is `null`. (#716) -- Adds newly available locations `us-west3`, `europe-west6`, `northamerica-northeast1`, and `australia-southeast1`. +- Adds eight new available regions: + - `us-west2` + - `us-west3` + - `us-west4` + - `europe-west6` + - `asia-northeast2` + - `northamerica-northeast1` + - `southamerica-east1` + - `australia-southeast1` - No longer throw errors for unrecognized regions (deploy will error instead). - Fixes error where `snap.ref` in database functions did not work when using the Emulator Suite (#726) diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 2ec102fda..0ab2f9c31 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -5,14 +5,18 @@ export const SUPPORTED_REGIONS = [ 'us-central1', 'us-east1', 'us-east4', + 'us-west2', 'us-west3', + 'us-west4', 'europe-west1', 'europe-west2', 'europe-west3', 'europe-west6', 'asia-east2', 'asia-northeast1', + 'asia-northeast2', 'northamerica-northeast1', + 'southamerica-east1', 'australia-southeast1', ] as const; From 1e4de88ed658289f6c7d4e94ac24732726da04c0 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 14 Jul 2020 00:00:43 +0000 Subject: [PATCH 146/437] 3.8.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6e198c416..bef1bb506 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.7.0", + "version": "3.8.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From a07d6efeb39600a6b8b31a583eef8fc4a694df50 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Tue, 14 Jul 2020 00:00:49 +0000 Subject: [PATCH 147/437] [firebase-release] Removed change log and reset repo after 3.8.0 release --- CHANGELOG.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d85f674fa..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +0,0 @@ -- Fixes error when last argument to logger methods is `null`. (#716) -- Adds eight new available regions: - - `us-west2` - - `us-west3` - - `us-west4` - - `europe-west6` - - `asia-northeast2` - - `northamerica-northeast1` - - `southamerica-east1` - - `australia-southeast1` -- No longer throw errors for unrecognized regions (deploy will error instead). -- Fixes error where `snap.ref` in database functions did not work when using the Emulator Suite (#726) From 8bc84dda6ed1b9dc485c0f462aec2d9066453442 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Fri, 31 Jul 2020 15:27:46 -0700 Subject: [PATCH 148/437] Updates logging in https callable functions. (#745) --- README.md | 2 +- src/cloud-functions.ts | 3 ++- src/providers/crashlytics.ts | 2 +- src/providers/https.ts | 34 ++++++++++++++++++---------------- src/setup.ts | 5 +++-- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 59c5c0936..33fbaa746 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ const notifyUsers = require('./notify-users'); exports.newPost = functions.database .ref('/posts/{postId}') .onCreate((snapshot, context) => { - console.log('Received new post with ID:', context.params.postId); + functions.logger.info('Received new post with ID:', context.params.postId); return notifyUsers(snapshot.val()); }); ``` diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index f249bf7f9..b97c4ad7e 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -23,6 +23,7 @@ import { Request, Response } from 'express'; import * as _ from 'lodash'; import { DeploymentOptions, Schedule } from './function-configuration'; +import { warn } from './logger'; export { Request, Response }; /** @hidden */ @@ -379,7 +380,7 @@ export function makeCloudFunction({ promise = handler(dataOrChange, context); } if (typeof promise === 'undefined') { - console.warn('Function returned undefined, expected Promise or value'); + warn('Function returned undefined, expected Promise or value'); } return Promise.resolve(promise) .then((result) => { diff --git a/src/providers/crashlytics.ts b/src/providers/crashlytics.ts index e7a6fe882..54fc26469 100644 --- a/src/providers/crashlytics.ts +++ b/src/providers/crashlytics.ts @@ -77,7 +77,7 @@ export class IssueBuilder { * const slackMessage = ` There's a new issue (${issueId}) ` + * `in your app - ${issueTitle}`; * return notifySlack(slackMessage).then(() => { - * console.log(`Posted new issue ${issueId} successfully to Slack`); + * functions.logger.info(`Posted new issue ${issueId} successfully to Slack`); * }); * }); * ``` diff --git a/src/providers/https.ts b/src/providers/https.ts index cd9f1745d..ec397fc64 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -24,9 +24,11 @@ import * as cors from 'cors'; import * as express from 'express'; import * as firebase from 'firebase-admin'; import * as _ from 'lodash'; + import { apps } from '../apps'; import { HttpsFunction, optionsToTrigger, Runnable } from '../cloud-functions'; import { DeploymentOptions } from '../function-configuration'; +import { warn, error } from '../logger'; /** @hidden */ export interface Request extends express.Request { @@ -285,13 +287,13 @@ interface HttpResponseBody { function isValidRequest(req: Request): req is HttpRequest { // The body must not be empty. if (!req.body) { - console.warn('Request is missing body.'); + warn('Request is missing body.'); return false; } // Make sure it's a POST. if (req.method !== 'POST') { - console.warn('Request has invalid method.', req.method); + warn('Request has invalid method.', req.method); return false; } @@ -303,13 +305,13 @@ function isValidRequest(req: Request): req is HttpRequest { contentType = contentType.substr(0, semiColon).trim(); } if (contentType !== 'application/json') { - console.warn('Request has incorrect Content-Type.', contentType); + warn('Request has incorrect Content-Type.', contentType); return false; } // The body must have data. if (_.isUndefined(req.body.data)) { - console.warn('Request body is missing data.', req.body); + warn('Request body is missing data.', req.body); return false; } @@ -318,7 +320,7 @@ function isValidRequest(req: Request): req is HttpRequest { // Verify that the body does not have any extra fields. const extras = _.omit(req.body, 'data'); if (!_.isEmpty(extras)) { - console.warn('Request body has extra fields.', extras); + warn('Request body has extra fields.', extras); return false; } return true; @@ -363,7 +365,7 @@ export function encode(data: any): any { return _.mapValues(data, encode); } // If we got this far, the data is not encodable. - console.error('Data cannot be encoded in JSON.', data); + error('Data cannot be encoded in JSON.', data); throw new Error('Data cannot be encoded in JSON: ' + data); } @@ -386,13 +388,13 @@ export function decode(data: any): any { // worth all the extra code to detect that case. const value = parseFloat(data.value); if (_.isNaN(value)) { - console.error('Data cannot be decoded from JSON.', data); + error('Data cannot be decoded from JSON.', data); throw new Error('Data cannot be decoded from JSON: ' + data); } return value; } default: { - console.error('Data cannot be decoded from JSON.', data); + error('Data cannot be decoded from JSON.', data); throw new Error('Data cannot be decoded from JSON: ' + data); } } @@ -420,7 +422,7 @@ export function _onCallWithOptions( const func = async (req: Request, res: express.Response) => { try { if (!isValidRequest(req)) { - console.error('Invalid request', req); + error('Invalid request, unable to process.'); throw new HttpsError('invalid-argument', 'Bad Request'); } @@ -441,7 +443,7 @@ export function _onCallWithOptions( uid: authToken.uid, token: authToken, }; - } catch (e) { + } catch (err) { throw new HttpsError('unauthenticated', 'Unauthenticated'); } } @@ -464,15 +466,15 @@ export function _onCallWithOptions( // If there was some result, encode it in the body. const responseBody: HttpResponseBody = { result }; res.status(200).send(responseBody); - } catch (error) { - if (!(error instanceof HttpsError)) { + } catch (err) { + if (!(err instanceof HttpsError)) { // This doesn't count as an 'explicit' error. - console.error('Unhandled error', error); - error = new HttpsError('internal', 'INTERNAL'); + error('Unhandled error', error); + err = new HttpsError('internal', 'INTERNAL'); } - const { status } = error.httpErrorCode; - const body = { error: error.toJSON() }; + const { status } = err.httpErrorCode; + const body = { error: err.toJSON() }; res.status(status).send(body); } diff --git a/src/setup.ts b/src/setup.ts index b30fa6226..d2935af15 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -22,6 +22,7 @@ /** @hidden */ import { firebaseConfig } from './config'; +import { warn } from './logger'; // Set up for config and vars export function setup() { @@ -45,7 +46,7 @@ export function setup() { // If FIREBASE_CONFIG is still not found, try using GCLOUD_PROJECT to estimate if (!process.env.FIREBASE_CONFIG) { if (process.env.GCLOUD_PROJECT) { - console.warn( + warn( 'Warning, estimating Firebase Config based on GCLOUD_PROJECT. Initializing firebase-admin may fail' ); process.env.FIREBASE_CONFIG = JSON.stringify({ @@ -58,7 +59,7 @@ export function setup() { projectId: process.env.GCLOUD_PROJECT, }); } else { - console.warn( + warn( 'Warning, FIREBASE_CONFIG and GCLOUD_PROJECT environment variables are missing. Initializing firebase-admin will fail' ); } From e4f2eba2367fd6863e340617e49b55b214d4d8b6 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Fri, 31 Jul 2020 15:34:06 -0700 Subject: [PATCH 149/437] Adds support for three new regions. (#750) --- CHANGELOG.md | 2 ++ src/function-configuration.ts | 3 +++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..3c25216f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +- Updates HTTP callable functions to use structured logging for Node 10+ environments. +- Adds type hints for new Cloud Functions regions `asia-northeast3`, `asia-south1`, and `asia-southeast2`. diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 0ab2f9c31..a7d87b726 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -15,6 +15,9 @@ export const SUPPORTED_REGIONS = [ 'asia-east2', 'asia-northeast1', 'asia-northeast2', + 'asia-northeast3', + 'asia-south1', + 'asia-southeast2', 'northamerica-northeast1', 'southamerica-east1', 'australia-southeast1', From 0ae7c3c8b8fd985fe669c2768df7df3ce847e5f3 Mon Sep 17 00:00:00 2001 From: p-young <35713583+p-young@users.noreply.github.com> Date: Fri, 31 Jul 2020 15:48:04 -0700 Subject: [PATCH 150/437] Fix onRequest handler return type to allow promises (async) (#705) Co-authored-by: Michael Bleigh --- src/function-builder.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/function-builder.ts b/src/function-builder.ts index 76c4b6c9d..27fcd821a 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -154,7 +154,10 @@ export class FunctionBuilder { * same signature as an Express app. */ onRequest: ( - handler: (req: https.Request, resp: express.Response) => void + handler: ( + req: https.Request, + resp: express.Response + ) => void | Promise ) => https._onRequestWithOptions(handler, this.options), /** * Declares a callable method for clients to call using a Firebase SDK. From 78fb92f7943ccb5e244ae4b5a164cfa302faa719 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Fri, 31 Jul 2020 16:11:18 -0700 Subject: [PATCH 151/437] Update CHANGELOG.md (#751) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c25216f0..3b20c6064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Updates HTTP callable functions to use structured logging for Node 10+ environments. - Adds type hints for new Cloud Functions regions `asia-northeast3`, `asia-south1`, and `asia-southeast2`. +- Updates type definition of `https.onRequest` to allow for promises (async functions). From e91d16a0c7d39c8894a7226e3f79700d6c6f1c01 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 31 Jul 2020 23:20:59 +0000 Subject: [PATCH 152/437] 3.9.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bef1bb506..cec716c20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.8.0", + "version": "3.9.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From 02f61cf1dc3828cb8af842ed15c1cbab3466b074 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 31 Jul 2020 23:21:05 +0000 Subject: [PATCH 153/437] [firebase-release] Removed change log and reset repo after 3.9.0 release --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b20c6064..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +0,0 @@ -- Updates HTTP callable functions to use structured logging for Node 10+ environments. -- Adds type hints for new Cloud Functions regions `asia-northeast3`, `asia-south1`, and `asia-southeast2`. -- Updates type definition of `https.onRequest` to allow for promises (async functions). From c69c974f18431f27244f03abfb67955718b4efb7 Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Fri, 7 Aug 2020 14:17:48 -0700 Subject: [PATCH 154/437] Update reference docs for EventContext.EventType (#743) --- src/cloud-functions.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index b97c4ad7e..1850b0d62 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -89,22 +89,22 @@ export interface EventContext { eventId: string; /** - * Type of event. Valid values are: + * Type of event. Possible values are: * - * * `providers/google.firebase.analytics/eventTypes/event.log` - * * `providers/firebase.auth/eventTypes/user.create` - * * `providers/firebase.auth/eventTypes/user.delete` - * * `providers/firebase.crashlytics/eventTypes/issue.new` - * * `providers/firebase.crashlytics/eventTypes/issue.regressed` - * * `providers/firebase.crashlytics/eventTypes/issue.velocityAlert` - * * `providers/google.firebase.database/eventTypes/ref.write` - * * `providers/google.firebase.database/eventTypes/ref.create` - * * `providers/google.firebase.database/eventTypes/ref.update` - * * `providers/google.firebase.database/eventTypes/ref.delete` - * * `providers/cloud.firestore/eventTypes/document.write` - * * `providers/cloud.firestore/eventTypes/document.create` - * * `providers/cloud.firestore/eventTypes/document.update` - * * `providers/cloud.firestore/eventTypes/document.delete` + * * `google.analytics.event.log` + * * `google.firebase.auth.user.create` + * * `google.firebase.auth.user.delete` + * * `google.firebase.crashlytics.issue.new` + * * `google.firebase.crashlytics.issue.regressed` + * * `google.firebase.crashlytics.issue.velocityAlert` + * * `google.firebase.database.ref.write` + * * `google.firebase.database.ref.create` + * * `google.firebase.database.ref.update` + * * `google.firebase.database.ref.delete` + * * `google.firestore.document.write` + * * `google.firestore.document.create` + * * `google.firestore.document.update` + * * `google.firestore.document.delete` * * `google.pubsub.topic.publish` * * `google.firebase.remoteconfig.update` * * `google.storage.object.finalize` From d1432c50863dec4b27834241279180f12f6148d5 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Mon, 10 Aug 2020 12:44:09 -0700 Subject: [PATCH 155/437] Updates firebase-admin peerDependency (#756) --- CHANGELOG.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..bbf6a741b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Updates `firebase-admin` dependency to support `^9.0.0` in addition to `^8.0.0`. Note that `firebase-admin` no longer supports Node.js 8.x as of `9.0.0`. diff --git a/package.json b/package.json index cec716c20..8cb004a34 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "yargs": "^15.3.1" }, "peerDependencies": { - "firebase-admin": "^8.0.0" + "firebase-admin": "^8.0.0 || ^9.0.0" }, "engines": { "node": "^8.13.0 || >=10.10.0" From 84433df63147d61a5330f9b8d3e5c5e4837ad658 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Wed, 12 Aug 2020 10:27:37 -0700 Subject: [PATCH 156/437] Fixes logging of unhandled exceptions in callable functions. (#759) --- CHANGELOG.md | 1 + src/providers/https.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf6a741b..6a558f5e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Updates `firebase-admin` dependency to support `^9.0.0` in addition to `^8.0.0`. Note that `firebase-admin` no longer supports Node.js 8.x as of `9.0.0`. +- Fixes logging of unexpected errors in `https.onCall()` functions. diff --git a/src/providers/https.ts b/src/providers/https.ts index ec397fc64..cf64c9d90 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -469,7 +469,7 @@ export function _onCallWithOptions( } catch (err) { if (!(err instanceof HttpsError)) { // This doesn't count as an 'explicit' error. - error('Unhandled error', error); + error('Unhandled error', err); err = new HttpsError('internal', 'INTERNAL'); } From ae9e856f0be28e68ed8bf68a23f581eccfd7aed3 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 12 Aug 2020 19:58:53 +0000 Subject: [PATCH 157/437] 3.9.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8cb004a34..6560a8d07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.9.0", + "version": "3.9.1", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From b7514295540b0e872e9be46656fb63b158fa9484 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 12 Aug 2020 19:58:58 +0000 Subject: [PATCH 158/437] [firebase-release] Removed change log and reset repo after 3.9.1 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a558f5e0..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Updates `firebase-admin` dependency to support `^9.0.0` in addition to `^8.0.0`. Note that `firebase-admin` no longer supports Node.js 8.x as of `9.0.0`. -- Fixes logging of unexpected errors in `https.onCall()` functions. From 5e287286c7aa6bbf66f36d21e49db5a7d5e06e20 Mon Sep 17 00:00:00 2001 From: Sam Stern Date: Mon, 17 Aug 2020 05:44:48 -0400 Subject: [PATCH 159/437] Restore failurePolicy (#760) --- CHANGELOG.md | 1 + spec/function-builder.spec.ts | 31 +++++++++++++++++++++++ src/cloud-functions.ts | 25 +++++++++++++++++-- src/function-builder.ts | 47 +++++++++++++++++++++++++++++------ src/function-configuration.ts | 13 ++++++++++ 5 files changed, 107 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..7c0e3b45c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Adds support for functions failure policies (#482) diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index 0ad4265ed..1f9122061 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -82,6 +82,7 @@ describe('FunctionBuilder', () => { const fn = functions .runWith({ timeoutSeconds: 90, + failurePolicy: { retry: {} }, memory: '256MB', }) .auth.user() @@ -89,6 +90,20 @@ describe('FunctionBuilder', () => { expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); expect(fn.__trigger.timeout).to.deep.equal('90s'); + expect(fn.__trigger.failurePolicy).to.deep.equal({ retry: {} }); + }); + + it("should apply a default failure policy if it's aliased with `true`", () => { + const fn = functions + .runWith({ + failurePolicy: true, + memory: '256MB', + timeoutSeconds: 90, + }) + .auth.user() + .onCreate((user) => user); + + expect(fn.__trigger.failurePolicy).to.deep.equal({ retry: {} }); }); it('should allow both supported region and valid runtime options to be set', () => { @@ -129,6 +144,22 @@ describe('FunctionBuilder', () => { }).to.throw(Error, 'TimeoutSeconds'); }); + it('should throw an error if user chooses a failurePolicy which is neither an object nor a boolean', () => { + expect(() => + functions.runWith({ + failurePolicy: (1234 as unknown) as functions.RuntimeOptions['failurePolicy'], + }) + ).to.throw(Error, 'failurePolicy must be a boolean or an object'); + }); + + it('should throw an error if user chooses a failurePolicy.retry which is not an object', () => { + expect(() => + functions.runWith({ + failurePolicy: { retry: (1234 as unknown) as object }, + }) + ).to.throw(Error, 'failurePolicy.retry'); + }); + it('should throw an error if user chooses an invalid memory allocation', () => { expect(() => { return functions.runWith({ diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 1850b0d62..387953bdc 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -22,8 +22,13 @@ import { Request, Response } from 'express'; import * as _ from 'lodash'; -import { DeploymentOptions, Schedule } from './function-configuration'; import { warn } from './logger'; +import { + DEFAULT_FAILURE_POLICY, + DeploymentOptions, + FailurePolicy, + Schedule, +} from './function-configuration'; export { Request, Response }; /** @hidden */ @@ -205,6 +210,7 @@ export namespace Change { if (json.fieldMask) { before = applyFieldMask(before, json.after, json.fieldMask); } + return Change.fromObjects( customizer(before || {}), customizer(json.after || {}) @@ -219,7 +225,8 @@ export namespace Change { ) { const before = _.assign({}, after); const masks = fieldMask.split(','); - _.forEach(masks, (mask) => { + + masks.forEach((mask) => { const val = _.get(sparseBefore, mask); if (typeof val === 'undefined') { _.unset(before, mask); @@ -227,6 +234,7 @@ export namespace Change { _.set(before, mask, val); } }); + return before; } } @@ -256,6 +264,7 @@ export interface TriggerAnnotated { resource: string; service: string; }; + failurePolicy?: FailurePolicy; httpsTrigger?: {}; labels?: { [key: string]: string }; regions?: string[]; @@ -473,6 +482,18 @@ export function optionsToTrigger(options: DeploymentOptions) { if (options.regions) { trigger.regions = options.regions; } + if (options.failurePolicy !== undefined) { + switch (options.failurePolicy) { + case false: + trigger.failurePolicy = undefined; + break; + case true: + trigger.failurePolicy = DEFAULT_FAILURE_POLICY; + break; + default: + trigger.failurePolicy = options.failurePolicy; + } + } if (options.timeoutSeconds) { trigger.timeout = options.timeoutSeconds.toString() + 's'; } diff --git a/src/function-builder.ts b/src/function-builder.ts index 27fcd821a..6050927ea 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -66,6 +66,23 @@ function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean { `TimeoutSeconds must be between 0 and ${MAX_TIMEOUT_SECONDS}` ); } + if (runtimeOptions.failurePolicy !== undefined) { + if ( + _.isBoolean(runtimeOptions.failurePolicy) === false && + _.isObjectLike(runtimeOptions.failurePolicy) === false + ) { + throw new Error(`failurePolicy must be a boolean or an object.`); + } + + if (typeof runtimeOptions.failurePolicy === 'object') { + if ( + _.isObjectLike(runtimeOptions.failurePolicy.retry) === false || + _.isEmpty(runtimeOptions.failurePolicy.retry) === false + ) { + throw new Error('failurePolicy.retry must be an empty object.'); + } + } + } return true; } @@ -100,10 +117,14 @@ export function region( /** * Configure runtime options for the function. * @param runtimeOptions Object with three optional fields: - * 1. memory: amount of memory to allocate to the function, possible values - * are: '128MB', '256MB', '512MB', '1GB', and '2GB'. - * 2. timeoutSeconds: timeout for the function in seconds, possible values are - * 0 to 540. + * 1. failurePolicy: failure policy of the function, with boolean `true` being + * equivalent to providing an empty retry object. + * 2. memory: amount of memory to allocate to the function, with possible + * values being '128MB', '256MB', '512MB', '1GB', and '2GB'. + * 3. timeoutSeconds: timeout for the function in seconds, with possible + * values being 0 to 540. + * + * Value must not be null. */ export function runWith(runtimeOptions: RuntimeOptions): FunctionBuilder { if (assertRuntimeOptionsValid(runtimeOptions)) { @@ -134,10 +155,14 @@ export class FunctionBuilder { /** * Configure runtime options for the function. * @param runtimeOptions Object with three optional fields: - * 1. memory: amount of memory to allocate to the function, possible values - * are: '128MB', '256MB', '512MB', '1GB', and '2GB'. - * 2. timeoutSeconds: timeout for the function in seconds, possible values are - * 0 to 540. + * 1. failurePolicy: failure policy of the function, with boolean `true` being + * equivalent to providing an empty retry object. + * 2. memory: amount of memory to allocate to the function, with possible + * values being '128MB', '256MB', '512MB', '1GB', and '2GB'. + * 3. timeoutSeconds: timeout for the function in seconds, with possible + * values being 0 to 540. + * + * Value must not be null. */ runWith(runtimeOptions: RuntimeOptions): FunctionBuilder { if (assertRuntimeOptionsValid(runtimeOptions)) { @@ -147,6 +172,12 @@ export class FunctionBuilder { } get https() { + if (this.options.failurePolicy !== undefined) { + console.warn( + 'RuntimeOptions.failurePolicy is not supported in https functions.' + ); + } + return { /** * Handle HTTP requests. diff --git a/src/function-configuration.ts b/src/function-configuration.ts index a7d87b726..31c708f3f 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -64,7 +64,20 @@ export interface Schedule { retryConfig?: ScheduleRetryConfig; } +export interface FailurePolicy { + retry: {}; +} + +export const DEFAULT_FAILURE_POLICY: FailurePolicy = { + retry: {}, +}; + export interface RuntimeOptions { + /** + * Failure policy of the function, with boolean `true` being equivalent to + * providing an empty retry object. + */ + failurePolicy?: FailurePolicy | boolean; /** * Amount of memory to allocate to the function. */ From 7bd795e2fac0a87d9c3c57b31594b67cfc59ad8c Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 20 Aug 2020 15:59:59 +0000 Subject: [PATCH 160/437] 3.10.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6560a8d07..48a71e881 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.9.1", + "version": "3.10.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From bc9d8caef0e44a84b7a1a4ca749268467bbe80df Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Thu, 20 Aug 2020 16:00:07 +0000 Subject: [PATCH 161/437] [firebase-release] Removed change log and reset repo after 3.10.0 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c0e3b45c..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Adds support for functions failure policies (#482) From 6b15ff7da69721bc1a1ecd8e44f9c76481061213 Mon Sep 17 00:00:00 2001 From: David Hagege Date: Fri, 21 Aug 2020 08:28:36 +0900 Subject: [PATCH 162/437] Add support for VPC connectors in `functions.runWith` (#752) --- spec/function-builder.spec.ts | 39 +++++++++++++++++++++++++++++++++++ src/cloud-functions.ts | 11 ++++++++++ src/function-builder.ts | 31 ++++++++++++++++++++++------ src/function-configuration.ts | 19 +++++++++++++++++ 4 files changed, 94 insertions(+), 6 deletions(-) diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index 1f9122061..eec531fdf 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -199,4 +199,43 @@ describe('FunctionBuilder', () => { } as any); }).to.throw(Error, 'at least one region'); }); + + it('should allow a vpcConnector to be set', () => { + const fn = functions + .runWith({ + vpcConnector: 'test-connector', + }) + .auth.user() + .onCreate((user) => user); + + expect(fn.__trigger.vpcConnector).to.equal('test-connector'); + }); + + it('should allow a vpcConnectorEgressSettings to be set', () => { + const fn = functions + .runWith({ + vpcConnector: 'test-connector', + vpcConnectorEgressSettings: 'PRIVATE_RANGES_ONLY', + }) + .auth.user() + .onCreate((user) => user); + + expect(fn.__trigger.vpcConnectorEgressSettings).to.equal( + 'PRIVATE_RANGES_ONLY' + ); + }); + + it('should throw an error if user chooses an invalid vpcConnectorEgressSettings', () => { + expect(() => { + return functions.runWith({ + vpcConnector: 'test-connector', + vpcConnectorEgressSettings: 'INCORRECT_OPTION', + } as any); + }).to.throw( + Error, + `The only valid vpcConnectorEgressSettings values are: ${functions.VPC_EGRESS_SETTINGS_OPTIONS.join( + ',' + )}` + ); + }); }); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 387953bdc..109a1ad05 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -270,6 +270,8 @@ export interface TriggerAnnotated { regions?: string[]; schedule?: Schedule; timeout?: string; + vpcConnector?: string; + vpcConnectorEgressSettings?: string; }; } @@ -514,5 +516,14 @@ export function optionsToTrigger(options: DeploymentOptions) { if (options.maxInstances) { trigger.maxInstances = options.maxInstances; } + + if (options.vpcConnector) { + trigger.vpcConnector = options.vpcConnector; + } + + if (options.vpcConnectorEgressSettings) { + trigger.vpcConnectorEgressSettings = options.vpcConnectorEgressSettings; + } + return trigger; } diff --git a/src/function-builder.ts b/src/function-builder.ts index 6050927ea..919f9f09b 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -30,6 +30,7 @@ import { RuntimeOptions, SUPPORTED_REGIONS, VALID_MEMORY_OPTIONS, + VPC_EGRESS_SETTINGS_OPTIONS, } from './function-configuration'; import * as analytics from './providers/analytics'; import * as auth from './providers/auth'; @@ -66,6 +67,21 @@ function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean { `TimeoutSeconds must be between 0 and ${MAX_TIMEOUT_SECONDS}` ); } + + if ( + runtimeOptions.vpcConnectorEgressSettings && + !_.includes( + VPC_EGRESS_SETTINGS_OPTIONS, + runtimeOptions.vpcConnectorEgressSettings + ) + ) { + throw new Error( + `The only valid vpcConnectorEgressSettings values are: ${VPC_EGRESS_SETTINGS_OPTIONS.join( + ',' + )}` + ); + } + if (runtimeOptions.failurePolicy !== undefined) { if ( _.isBoolean(runtimeOptions.failurePolicy) === false && @@ -116,13 +132,16 @@ export function region( /** * Configure runtime options for the function. - * @param runtimeOptions Object with three optional fields: - * 1. failurePolicy: failure policy of the function, with boolean `true` being + * @param runtimeOptions Object with optional fields: + * 1. memory: amount of memory to allocate to the function, possible values + * are: '128MB', '256MB', '512MB', '1GB', and '2GB'. + * 2. timeoutSeconds: timeout for the function in seconds, possible values are + * 0 to 540. + * 3. failurePolicy: failure policy of the function, with boolean `true` being * equivalent to providing an empty retry object. - * 2. memory: amount of memory to allocate to the function, with possible - * values being '128MB', '256MB', '512MB', '1GB', and '2GB'. - * 3. timeoutSeconds: timeout for the function in seconds, with possible - * values being 0 to 540. + * 4. vpcConnector: id of a VPC connector in same project and region + * 5. vpcConnectorEgressSettings: when a vpcConnector is set, control which + * egress traffic is sent through the vpcConnector. * * Value must not be null. */ diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 31c708f3f..558895148 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -44,6 +44,15 @@ export const VALID_MEMORY_OPTIONS = [ '2GB', ] as const; +/** + * List of available options for VpcConnectorEgressSettings. + */ +export const VPC_EGRESS_SETTINGS_OPTIONS = [ + 'VPC_CONNECTOR_EGRESS_SETTINGS_UNSPECIFIED', + 'PRIVATE_RANGES_ONLY', + 'ALL_TRAFFIC', +] as const; + /** * Scheduler retry options. Applies only to scheduled functions. */ @@ -91,6 +100,16 @@ export interface RuntimeOptions { * Max number of actual instances allowed to be running in parallel */ maxInstances?: number; + + /** + * Connect cloud function to specified VPC connector + */ + vpcConnector?: string; + + /** + * Egress settings for VPC connector + */ + vpcConnectorEgressSettings?: typeof VPC_EGRESS_SETTINGS_OPTIONS[number]; } export interface DeploymentOptions extends RuntimeOptions { From f4faaad9c914f7744ae15d61f1ee88a1e8d3ffad Mon Sep 17 00:00:00 2001 From: Lauren Long Date: Thu, 20 Aug 2020 17:00:18 -0700 Subject: [PATCH 163/437] Update CHANGELOG.md for 3.11.0 (#764) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..f46f8b3f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Adds support for `vpcConnector` and `vpcConnectorEgressSettings` fields in `functions.runWith()`. **Must be used in conjunction with firebase-tools v8.9.0 or higher.** Thanks @pcboy! (#752) From f9d71aab9278b375761d28b5584916f2da2fe66b Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 21 Aug 2020 16:10:08 +0000 Subject: [PATCH 164/437] 3.11.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48a71e881..4eb5986d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.10.0", + "version": "3.11.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From 85af5e1a1a3792ce67c638fc98f522f6b3644923 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 21 Aug 2020 16:10:15 +0000 Subject: [PATCH 165/437] [firebase-release] Removed change log and reset repo after 3.11.0 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f46f8b3f4..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Adds support for `vpcConnector` and `vpcConnectorEgressSettings` fields in `functions.runWith()`. **Must be used in conjunction with firebase-tools v8.9.0 or higher.** Thanks @pcboy! (#752) From 7e2c0ec13e781d829bd37e66efc61c6ee74191a2 Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 14 Sep 2020 13:36:00 -0700 Subject: [PATCH 166/437] Update tests to change region based on env variable FIREBASE_FUNCTIONS_TEST_REGION (#780) * update tests to change region based on environment variable FIREBASE_FUNCTIONS_TEST_REGION * formats --- integration_test/functions/src/auth-tests.ts | 108 ++++++++++-------- .../functions/src/database-tests.ts | 6 +- .../functions/src/firestore-tests.ts | 2 + integration_test/functions/src/https-tests.ts | 4 +- integration_test/functions/src/index.ts | 4 +- .../functions/src/pubsub-tests.ts | 12 +- .../functions/src/remoteConfig-tests.ts | 10 +- .../functions/src/storage-tests.ts | 3 + .../functions/src/testLab-tests.ts | 2 + integration_test/run_tests.sh | 27 ++++- 10 files changed, 118 insertions(+), 60 deletions(-) diff --git a/integration_test/functions/src/auth-tests.ts b/integration_test/functions/src/auth-tests.ts index 2c1e1d4e6..c97c907bc 100644 --- a/integration_test/functions/src/auth-tests.ts +++ b/integration_test/functions/src/auth-tests.ts @@ -3,68 +3,82 @@ import * as functions from 'firebase-functions'; import { expectEq, TestSuite } from './testing'; import UserMetadata = admin.auth.UserRecord; -export const createUserTests: any = functions.auth.user().onCreate((u, c) => { - const testId: string = u.displayName; - console.log(`testId is ${testId}`); +const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1'; - return new TestSuite('auth user onCreate') - .it('should have a project as resource', (user, context) => - expectEq(context.resource.name, `projects/${process.env.GCLOUD_PROJECT}`) - ) +export const createUserTests: any = functions + .region(REGION) + .auth.user() + .onCreate((u, c) => { + const testId: string = u.displayName; + console.log(`testId is ${testId}`); - .it('should not have a path', (user, context) => - expectEq((context as any).path, undefined) - ) + return new TestSuite('auth user onCreate') + .it('should have a project as resource', (user, context) => + expectEq( + context.resource.name, + `projects/${process.env.GCLOUD_PROJECT}` + ) + ) - .it('should have the correct eventType', (user, context) => - expectEq(context.eventType, 'google.firebase.auth.user.create') - ) + .it('should not have a path', (user, context) => + expectEq((context as any).path, undefined) + ) - .it('should have an eventId', (user, context) => context.eventId) + .it('should have the correct eventType', (user, context) => + expectEq(context.eventType, 'google.firebase.auth.user.create') + ) - .it('should have a timestamp', (user, context) => context.timestamp) + .it('should have an eventId', (user, context) => context.eventId) - .it('should not have auth', (user, context) => - expectEq((context as any).auth, undefined) - ) + .it('should have a timestamp', (user, context) => context.timestamp) - .it('should not have action', (user, context) => - expectEq((context as any).action, undefined) - ) + .it('should not have auth', (user, context) => + expectEq((context as any).auth, undefined) + ) - .it('should have properly defined meta', (user, context) => user.metadata) + .it('should not have action', (user, context) => + expectEq((context as any).action, undefined) + ) - .run(testId, u, c); -}); + .it('should have properly defined meta', (user, context) => user.metadata) -export const deleteUserTests: any = functions.auth.user().onDelete((u, c) => { - const testId: string = u.displayName; - console.log(`testId is ${testId}`); + .run(testId, u, c); + }); - return new TestSuite('auth user onDelete') - .it('should have a project as resource', (user, context) => - expectEq(context.resource.name, `projects/${process.env.GCLOUD_PROJECT}`) - ) +export const deleteUserTests: any = functions + .region(REGION) + .auth.user() + .onDelete((u, c) => { + const testId: string = u.displayName; + console.log(`testId is ${testId}`); - .it('should not have a path', (user, context) => - expectEq((context as any).path, undefined) - ) + return new TestSuite('auth user onDelete') + .it('should have a project as resource', (user, context) => + expectEq( + context.resource.name, + `projects/${process.env.GCLOUD_PROJECT}` + ) + ) - .it('should have the correct eventType', (user, context) => - expectEq(context.eventType, 'google.firebase.auth.user.delete') - ) + .it('should not have a path', (user, context) => + expectEq((context as any).path, undefined) + ) - .it('should have an eventId', (user, context) => context.eventId) + .it('should have the correct eventType', (user, context) => + expectEq(context.eventType, 'google.firebase.auth.user.delete') + ) - .it('should have a timestamp', (user, context) => context.timestamp) + .it('should have an eventId', (user, context) => context.eventId) - .it('should not have auth', (user, context) => - expectEq((context as any).auth, undefined) - ) + .it('should have a timestamp', (user, context) => context.timestamp) - .it('should not have action', (user, context) => - expectEq((context as any).action, undefined) - ) + .it('should not have auth', (user, context) => + expectEq((context as any).auth, undefined) + ) - .run(testId, u, c); -}); + .it('should not have action', (user, context) => + expectEq((context as any).action, undefined) + ) + + .run(testId, u, c); + }); diff --git a/integration_test/functions/src/database-tests.ts b/integration_test/functions/src/database-tests.ts index de3030d3a..568d26bf4 100644 --- a/integration_test/functions/src/database-tests.ts +++ b/integration_test/functions/src/database-tests.ts @@ -4,9 +4,11 @@ import { expectEq, expectMatches, TestSuite } from './testing'; import DataSnapshot = admin.database.DataSnapshot; const testIdFieldName = 'testId'; +const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1'; -export const databaseTests: any = functions.database - .ref('dbTests/{testId}/start') +export const databaseTests: any = functions + .region(REGION) + .database.ref('dbTests/{testId}/start') .onWrite((ch, ctx) => { if (ch.after.val() === null) { console.log( diff --git a/integration_test/functions/src/firestore-tests.ts b/integration_test/functions/src/firestore-tests.ts index 49d1ae919..97daadea5 100644 --- a/integration_test/functions/src/firestore-tests.ts +++ b/integration_test/functions/src/firestore-tests.ts @@ -4,11 +4,13 @@ import { expectDeepEq, expectEq, TestSuite } from './testing'; import DocumentSnapshot = admin.firestore.DocumentSnapshot; const testIdFieldName = 'documentId'; +const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1'; export const firestoreTests: any = functions .runWith({ timeoutSeconds: 540, }) + .region(REGION) .firestore.document('tests/{documentId}') .onCreate((s, c) => { return new TestSuite('firestore document onWrite') diff --git a/integration_test/functions/src/https-tests.ts b/integration_test/functions/src/https-tests.ts index 55c7df983..6af0f9fac 100644 --- a/integration_test/functions/src/https-tests.ts +++ b/integration_test/functions/src/https-tests.ts @@ -2,7 +2,9 @@ import * as functions from 'firebase-functions'; import * as _ from 'lodash'; import { expectEq, TestSuite } from './testing'; -export const callableTests: any = functions.https.onCall((d) => { +const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1'; + +export const callableTests: any = functions.region(REGION).https.onCall((d) => { return new TestSuite('https onCall') .it('should have the correct data', (data) => expectEq(_.get(data, 'foo'), 'bar') diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index 9988fe636..2ac16e5fa 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -20,13 +20,14 @@ import * as testLab from './testLab-utils'; import 'firebase-functions'; // temporary shim until process.env.FIREBASE_CONFIG available natively in GCF(BUG 63586213) const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG); admin.initializeApp(); +const REGION = functions.config().functions.test_region; // TODO(klimt): Get rid of this once the JS client SDK supports callable triggers. function callHttpsTrigger(name: string, data: any, baseUrl) { return utils.makeRequest( { method: 'POST', - host: 'us-central1-' + firebaseConfig.projectId + '.' + baseUrl, + host: REGION + '-' + firebaseConfig.projectId + '.' + baseUrl, path: '/' + name, headers: { 'Content-Type': 'application/json', @@ -62,6 +63,7 @@ function callScheduleTrigger(functionName: string, region: string) { } export const integrationTests: any = functions + .region(REGION) .runWith({ timeoutSeconds: 540, }) diff --git a/integration_test/functions/src/pubsub-tests.ts b/integration_test/functions/src/pubsub-tests.ts index 3c1b1ecc7..e2a3f1bd0 100644 --- a/integration_test/functions/src/pubsub-tests.ts +++ b/integration_test/functions/src/pubsub-tests.ts @@ -3,10 +3,13 @@ import * as functions from 'firebase-functions'; import { evaluate, expectEq, success, TestSuite } from './testing'; import PubsubMessage = functions.pubsub.Message; +const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1'; + // TODO(inlined) use multiple queues to run inline. // Expected message data: {"hello": "world"} -export const pubsubTests: any = functions.pubsub - .topic('pubsubTests') +export const pubsubTests: any = functions + .region(REGION) + .pubsub.topic('pubsubTests') .onPublish((m, c) => { let testId: string; try { @@ -59,8 +62,9 @@ export const pubsubTests: any = functions.pubsub .run(testId, m, c); }); -export const schedule: any = functions.pubsub - .schedule('every 10 hours') // This is a dummy schedule, since we need to put a valid one in. +export const schedule: any = functions + .region(REGION) + .pubsub.schedule('every 10 hours') // This is a dummy schedule, since we need to put a valid one in. // For the test, the job is triggered by the jobs:run api .onRun((context) => { let testId; diff --git a/integration_test/functions/src/remoteConfig-tests.ts b/integration_test/functions/src/remoteConfig-tests.ts index 3474e1fc2..3f2cc8993 100644 --- a/integration_test/functions/src/remoteConfig-tests.ts +++ b/integration_test/functions/src/remoteConfig-tests.ts @@ -2,8 +2,11 @@ import * as functions from 'firebase-functions'; import { expectEq, TestSuite } from './testing'; import TemplateVersion = functions.remoteConfig.TemplateVersion; -export const remoteConfigTests: any = functions.remoteConfig.onUpdate( - (v, c) => { +const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1'; + +export const remoteConfigTests: any = functions + .region(REGION) + .remoteConfig.onUpdate((v, c) => { return new TestSuite('remoteConfig onUpdate') .it('should have a project as resource', (version, context) => expectEq( @@ -25,5 +28,4 @@ export const remoteConfigTests: any = functions.remoteConfig.onUpdate( ) .run(v.description, v, c); - } -); + }); diff --git a/integration_test/functions/src/storage-tests.ts b/integration_test/functions/src/storage-tests.ts index 43cdef5e2..df3032f78 100644 --- a/integration_test/functions/src/storage-tests.ts +++ b/integration_test/functions/src/storage-tests.ts @@ -2,10 +2,13 @@ import * as functions from 'firebase-functions'; import { expectEq, TestSuite } from './testing'; import ObjectMetadata = functions.storage.ObjectMetadata; +const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1'; + export const storageTests: any = functions .runWith({ timeoutSeconds: 540, }) + .region(REGION) .storage.bucket() .object() .onFinalize((s, c) => { diff --git a/integration_test/functions/src/testLab-tests.ts b/integration_test/functions/src/testLab-tests.ts index a6350c57d..cf4b2f062 100644 --- a/integration_test/functions/src/testLab-tests.ts +++ b/integration_test/functions/src/testLab-tests.ts @@ -2,11 +2,13 @@ import * as functions from 'firebase-functions'; import * as _ from 'lodash'; import { TestSuite, expectEq } from './testing'; import TestMatrix = functions.testLab.TestMatrix; +const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1'; export const testLabTests: any = functions .runWith({ timeoutSeconds: 540, }) + .region(REGION) .testLab.testMatrix() .onComplete((matrix, context) => { return new TestSuite('test matrix complete') diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index 903936b13..ba567536a 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -38,6 +38,26 @@ function build_sdk { mv firebase-functions-*.tgz "integration_test/functions/firebase-functions-${TIMESTAMP}.tgz" } +function set_region { + if [[ "${FIREBASE_FUNCTIONS_TEST_REGION}" == "" ]]; then + FIREBASE_FUNCTIONS_TEST_REGION="us-central1" + fi + if [[ "${TOKEN}" == "" ]]; then + firebase functions:config:set functions.test_region=$FIREBASE_FUNCTIONS_TEST_REGION --project=$PROJECT_ID + else + firebase functions:config:set functions.test_region=$FIREBASE_FUNCTIONS_TEST_REGION --project=$PROJECT_ID --token=$TOKEN + fi + announce "Set region to ${FIREBASE_FUNCTIONS_TEST_REGION}" +} + +function unset_region { + if [[ "${TOKEN}" == "" ]]; then + firebase functions:config:unset functions.test_region --project=$PROJECT_ID + else + firebase functions:config:unset functions.test_region --project=$PROJECT_ID --token=$TOKEN + fi +} + function pick_node8 { cd "${DIR}" cp package.node8.json functions/package.json @@ -98,7 +118,10 @@ function run_tests { if [[ "${FIREBASE_FUNCTIONS_URL}" == "https://preprod-cloudfunctions.sandbox.googleapis.com" ]]; then TEST_DOMAIN="txcloud.net" fi - TEST_URL="https://us-central1-${PROJECT_ID}.${TEST_DOMAIN}/integrationTests" + if [[ "${FIREBASE_FUNCTIONS_TEST_REGION}" == "" ]]; then + FIREBASE_FUNCTIONS_TEST_REGION="us-central1" + fi + TEST_URL="https://${FIREBASE_FUNCTIONS_TEST_REGION}-${PROJECT_ID}.${TEST_DOMAIN}/integrationTests" echo "${TEST_URL}" curl --fail "${TEST_URL}" @@ -107,6 +130,7 @@ function run_tests { function cleanup { announce "Performing cleanup..." delete_all_functions + unset_region rm "${DIR}/functions/firebase-functions-${TIMESTAMP}.tgz" rm "${DIR}/functions/package.json" rm -f "${DIR}/functions/firebase-debug.log" @@ -117,6 +141,7 @@ function cleanup { # Setup build_sdk delete_all_functions +set_region # Node 8 tests pick_node8 From f59ff8cde4de1cf2335fbc9525a123aa740ef1dd Mon Sep 17 00:00:00 2001 From: egilmorez Date: Mon, 2 Nov 2020 07:30:50 -0800 Subject: [PATCH 167/437] Adding required tags to page template. (#804) * Adding required tags to page template. * Adding formatting and fixing typo discovered in internal review. * More fixes/additions discovered in internal review. * Fixing format of toc.yaml file. --- docgen/content-sources/toc.yaml | 25 ++++++++++++++++++++----- docgen/theme/layouts/default.hbs | 2 ++ src/function-builder.ts | 27 +++++++++++++++------------ 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/docgen/content-sources/toc.yaml b/docgen/content-sources/toc.yaml index 37093f7c2..5758eb74e 100644 --- a/docgen/content-sources/toc.yaml +++ b/docgen/content-sources/toc.yaml @@ -23,6 +23,20 @@ toc: - title: 'config.Config' path: /docs/reference/functions/config_.config.config.html + - title: 'functions.function-configuration' + path: /docs/reference/functions/function_configuration_.html + section: + - title: 'config.DeploymentOptions' + path: /docs/reference/functions/function_configuration_.deploymentoptions.html + - title: 'config.FailurePolicy' + path: /docs/reference/functions/function_configuration_.failurepolicy.html + - title: 'config.RuntimeOptions' + path: /docs/reference/functions/function_configuration_.runtimeoptions.html + - title: 'config.Schedule' + path: /docs/reference/functions/function_configuration_.schedule.html + - title: 'config.ScheduleRetryConfig' + path: /docs/reference/functions/function_configuration_.scheduleretryconfig.html + - title: 'functions.analytics' path: /docs/reference/functions/providers_analytics_.html section: @@ -127,11 +141,6 @@ toc: - title: 'ObjectMetadata' path: /docs/reference/functions/providers_storage_.objectmetadata.html - - title: 'functions.handler' - path: /docs/reference/functions/handler_builder_.html - section: - - title: 'HandlerBuilder' - path: /docs/reference/functions/handler_builder_.handlerbuilder.html - title: 'functions.testLab' path: /docs/reference/functions/providers_testlab_.html section: @@ -143,3 +152,9 @@ toc: path: /docs/reference/functions/providers_testlab_.testmatrix.html - title: 'testLab.testMatrixBuilder' path: /docs/reference/functions/providers_testlab_.testmatrixbuilder.html + + - title: 'functions.handler' + path: /docs/reference/functions/handler_builder_.html + section: + - title: 'HandlerBuilder' + path: /docs/reference/functions/handler_builder_.handlerbuilder.html diff --git a/docgen/theme/layouts/default.hbs b/docgen/theme/layouts/default.hbs index 72111fb4e..17106e8d8 100644 --- a/docgen/theme/layouts/default.hbs +++ b/docgen/theme/layouts/default.hbs @@ -7,6 +7,8 @@ + + {{#ifCond model.name '==' project.name}}{{project.name}}{{else}}{{model.name}} | {{project.name}}{{/ifCond}} diff --git a/src/function-builder.ts b/src/function-builder.ts index 919f9f09b..9b5f5660b 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -133,15 +133,15 @@ export function region( /** * Configure runtime options for the function. * @param runtimeOptions Object with optional fields: - * 1. memory: amount of memory to allocate to the function, possible values + * 1. `memory`: amount of memory to allocate to the function, possible values * are: '128MB', '256MB', '512MB', '1GB', and '2GB'. - * 2. timeoutSeconds: timeout for the function in seconds, possible values are + * 2. `timeoutSeconds`: timeout for the function in seconds, possible values are * 0 to 540. - * 3. failurePolicy: failure policy of the function, with boolean `true` being + * 3. `failurePolicy`: failure policy of the function, with boolean `true` being * equivalent to providing an empty retry object. - * 4. vpcConnector: id of a VPC connector in same project and region - * 5. vpcConnectorEgressSettings: when a vpcConnector is set, control which - * egress traffic is sent through the vpcConnector. + * 4. `vpcConnector`: id of a VPC connector in the same project and region + * 5. `vpcConnectorEgressSettings`: when a `vpcConnector` is set, control which + * egress traffic is sent through the `vpcConnector`. * * Value must not be null. */ @@ -173,13 +173,16 @@ export class FunctionBuilder { /** * Configure runtime options for the function. - * @param runtimeOptions Object with three optional fields: - * 1. failurePolicy: failure policy of the function, with boolean `true` being + * @param runtimeOptions Object with optional fields: + * 1. `memory`: amount of memory to allocate to the function, possible values + * are: '128MB', '256MB', '512MB', '1GB', and '2GB'. + * 2. `timeoutSeconds`: timeout for the function in seconds, possible values are + * 0 to 540. + * 3. `failurePolicy`: failure policy of the function, with boolean `true` being * equivalent to providing an empty retry object. - * 2. memory: amount of memory to allocate to the function, with possible - * values being '128MB', '256MB', '512MB', '1GB', and '2GB'. - * 3. timeoutSeconds: timeout for the function in seconds, with possible - * values being 0 to 540. + * 4. `vpcConnector`: id of a VPC connector in the same project and region + * 5. `vpcConnectorEgressSettings`: when a `vpcConnector` is set, control which + * egress traffic is sent through the `vpcConnector`. * * Value must not be null. */ From 2988a2b3b1f17674446504c3b24c5c10335aa728 Mon Sep 17 00:00:00 2001 From: joehan Date: Wed, 18 Nov 2020 09:54:18 -0800 Subject: [PATCH 168/437] Adds 4GB as a memory option. (#814) --- src/function-configuration.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 558895148..6743d463e 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -42,6 +42,7 @@ export const VALID_MEMORY_OPTIONS = [ '512MB', '1GB', '2GB', + '4GB', ] as const; /** From ecfefd1bcfd26311e7a4de4665f3d02b49f7dc94 Mon Sep 17 00:00:00 2001 From: Marcel Goya <3046751+marcelgoya@users.noreply.github.com> Date: Wed, 25 Nov 2020 22:22:07 +0100 Subject: [PATCH 169/437] Add ingress settings support (#815) * Add IngressSettings support * Add IngressSettings support * Format Co-authored-by: Marcel Goya Co-authored-by: joehan --- src/function-configuration.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 6743d463e..10098a745 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -54,6 +54,16 @@ export const VPC_EGRESS_SETTINGS_OPTIONS = [ 'ALL_TRAFFIC', ] as const; +/** + * List of available options for IngressSettings. + */ +export const INGRESS_SETTINGS_OPTIONS = [ + 'INGRESS_SETTINGS_UNSPECIFIED', + 'ALLOW_ALL', + 'ALLOW_INTERNAL_ONLY', + 'ALLOW_INTERNAL_AND_GCLB', +] as const; + /** * Scheduler retry options. Applies only to scheduled functions. */ @@ -111,6 +121,11 @@ export interface RuntimeOptions { * Egress settings for VPC connector */ vpcConnectorEgressSettings?: typeof VPC_EGRESS_SETTINGS_OPTIONS[number]; + + /** + * Ingress settings + */ + ingressSettings?: typeof INGRESS_SETTINGS_OPTIONS[number]; } export interface DeploymentOptions extends RuntimeOptions { From c11e5b3b2a6cce9bd87674e68cc3a7239a99489a Mon Sep 17 00:00:00 2001 From: Bryan Kendall Date: Mon, 30 Nov 2020 08:05:36 -0800 Subject: [PATCH 170/437] introduce `package-lock.json` (#781) * initial package-lock * remove npmrc * update package-lock * update typedoc * remove istanbul; audit fixes Co-authored-by: joehan --- .npmrc | 1 - package-lock.json | 4688 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- 3 files changed, 4689 insertions(+), 3 deletions(-) delete mode 100644 .npmrc create mode 100644 package-lock.json diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 43c97e719..000000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=false diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..17b67b6d0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4688 @@ +{ + "name": "firebase-functions", + "version": "3.11.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@firebase/app-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", + "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", + "dev": true + }, + "@firebase/auth-interop-types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", + "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==", + "dev": true + }, + "@firebase/component": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", + "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "dev": true, + "requires": { + "@firebase/util": "0.3.2", + "tslib": "^1.11.1" + } + }, + "@firebase/database": { + "version": "0.6.12", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.12.tgz", + "integrity": "sha512-OLUxp8TkXiML4X5LWM5IACsSDvo3fcf4mTbTe5RF+N6TRFv0Svzlet5OgGIa3ET1dQvNiisrMX7zzRa0OTLs7Q==", + "dev": true, + "requires": { + "@firebase/auth-interop-types": "0.1.5", + "@firebase/component": "0.1.19", + "@firebase/database-types": "0.5.2", + "@firebase/logger": "0.2.6", + "@firebase/util": "0.3.2", + "faye-websocket": "0.11.3", + "tslib": "^1.11.1" + } + }, + "@firebase/database-types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", + "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", + "dev": true, + "requires": { + "@firebase/app-types": "0.6.1" + } + }, + "@firebase/logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", + "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==", + "dev": true + }, + "@firebase/util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", + "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "dev": true, + "requires": { + "tslib": "^1.11.1" + } + }, + "@google-cloud/common": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", + "integrity": "sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg==", + "dev": true, + "optional": true, + "requires": { + "@google-cloud/projectify": "^1.0.0", + "@google-cloud/promisify": "^1.0.0", + "arrify": "^2.0.0", + "duplexify": "^3.6.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^5.5.0", + "retry-request": "^4.0.0", + "teeny-request": "^6.0.0" + } + }, + "@google-cloud/firestore": { + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-3.8.6.tgz", + "integrity": "sha512-ox80NbrM1MLJgvAAUd1quFLx/ie/nSjrk1PtscSicpoYDlKb9e6j7pHrVpbopBMyliyfNl3tLJWaDh+x+uCXqw==", + "dev": true, + "optional": true, + "requires": { + "deep-equal": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^1.15.3", + "readable-stream": "^3.4.0", + "through2": "^3.0.0" + } + }, + "@google-cloud/paginator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz", + "integrity": "sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg==", + "dev": true, + "optional": true, + "requires": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + } + }, + "@google-cloud/projectify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz", + "integrity": "sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg==", + "dev": true, + "optional": true + }, + "@google-cloud/promisify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz", + "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ==", + "dev": true, + "optional": true + }, + "@google-cloud/storage": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-4.7.0.tgz", + "integrity": "sha512-f0guAlbeg7Z0m3gKjCfBCu7FG9qS3M3oL5OQQxlvGoPtK7/qg3+W+KQV73O2/sbuS54n0Kh2mvT5K2FWzF5vVQ==", + "dev": true, + "optional": true, + "requires": { + "@google-cloud/common": "^2.1.1", + "@google-cloud/paginator": "^2.0.0", + "@google-cloud/promisify": "^1.0.0", + "arrify": "^2.0.0", + "compressible": "^2.0.12", + "concat-stream": "^2.0.0", + "date-and-time": "^0.13.0", + "duplexify": "^3.5.0", + "extend": "^3.0.2", + "gaxios": "^3.0.0", + "gcs-resumable-upload": "^2.2.4", + "hash-stream-validation": "^0.2.2", + "mime": "^2.2.0", + "mime-types": "^2.0.8", + "onetime": "^5.1.0", + "p-limit": "^2.2.0", + "pumpify": "^2.0.0", + "readable-stream": "^3.4.0", + "snakeize": "^0.1.0", + "stream-events": "^1.0.1", + "through2": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "gaxios": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz", + "integrity": "sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q==", + "dev": true, + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "dev": true, + "optional": true + } + } + }, + "@grpc/grpc-js": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.5.tgz", + "integrity": "sha512-Hm+xOiqAhcpT9RYM8lc15dbQD7aQurM7ZU8ulmulepiPlN7iwBXXwP3vSBUimoFoApRqz7pSIisXU8pZaCB4og==", + "dev": true, + "optional": true, + "requires": { + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "optional": true + } + } + }, + "@grpc/proto-loader": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.5.tgz", + "integrity": "sha512-WwN9jVNdHRQoOBo9FDH7qU+mgfjPc8GygPYms3M+y3fbQLfnCe/Kv/E01t7JRgnrsOHH8euvSbed3mIalXhwqQ==", + "dev": true, + "optional": true, + "requires": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", + "dev": true, + "optional": true + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha1-TIVzDlm5ofHzSQR9vyQpYDS7JzU=", + "dev": true, + "optional": true + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha1-fvN/DQEPsCitGtWXIuUG2SYoFcs=", + "dev": true, + "optional": true + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", + "dev": true, + "optional": true + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "dev": true, + "optional": true, + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", + "dev": true, + "optional": true + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", + "dev": true, + "optional": true + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", + "dev": true, + "optional": true + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", + "dev": true, + "optional": true + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", + "dev": true, + "optional": true + }, + "@sinonjs/commons": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", + "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "@sinonjs/samsam": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true, + "optional": true + }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/chai": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.12.tgz", + "integrity": "sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ==", + "dev": true + }, + "@types/chai-as-promised": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.3.tgz", + "integrity": "sha512-FQnh1ohPXJELpKhzjuDkPLR2BZCAqed+a6xV4MI/T3XzHfd2FlarfUGUdZYgqYe8oxkYn0fchHEeHfHqdZ96sg==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "requires": { + "@types/node": "*" + } + }, + "@types/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-GmK8AKu8i+s+EChK/uZ5IbrXPcPaQKWaNSGevDT/7o3gFObwSUQwqb1jMqxuo+YPvj0ckGzINI+EO7EHcmJjKg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", + "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.12.tgz", + "integrity": "sha512-EaEdY+Dty1jEU7U6J4CUWwxL+hyEGMkO5jan5gplfegUgCUsIUWqXxqw47uGjimeT4Qgkz/XUfwoau08+fgvKA==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/fs-extra": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz", + "integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "@types/jsonwebtoken": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.3.2.tgz", + "integrity": "sha512-Mkjljd9DTpkPlrmGfTJvcP4aBU7yO2QmW7wNVhV4/6AEUxYoacqU7FJU/N0yFEHTsIrE4da3rUrjrR5ejicFmA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/lodash": { + "version": "4.14.161", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.161.tgz", + "integrity": "sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA==", + "dev": true + }, + "@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", + "dev": true, + "optional": true + }, + "@types/mime": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", + "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" + }, + "@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true + }, + "@types/mock-require": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mock-require/-/mock-require-2.0.0.tgz", + "integrity": "sha512-nOgjoE5bBiDeiA+z41i95makyHUSMWQMOPocP+J67Pqx/68HAXaeWN1NFtrAYYV6LrISIZZ8vKHm/a50k0f6Sg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/nock": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@types/nock/-/nock-10.0.3.tgz", + "integrity": "sha512-OthuN+2FuzfZO3yONJ/QVjKmLEuRagS9TV9lEId+WHL9KhftYG+/2z+pxlr0UgVVXSpVD8woie/3fzQn8ft/Ow==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "8.10.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.63.tgz", + "integrity": "sha512-g+nSkeHFDd2WOQChfmy9SAXLywT47WZBrGS/NC5ym5PJ8c8RC6l4pbGaUW/X0+eZJnXw6/AVNEouXWhV4iz72Q==" + }, + "@types/qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==" + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + }, + "@types/serve-static": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz", + "integrity": "sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ==", + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "@types/sinon": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.2.tgz", + "integrity": "sha512-T+m89VdXj/eidZyejvmoP9jivXgBDdkOSBVQjU9kF349NEx10QdPNGxHeZUaj1IlJ32/ewdyXJjnJxyxJroYwg==", + "dev": true + }, + "abab": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "dev": true + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "optional": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", + "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", + "dev": true + }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, + "agent-base": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", + "dev": true, + "optional": true, + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + } + } + }, + "ajv": { + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "dev": true, + "optional": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true, + "optional": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha1-5gtrDo8wG9l+U3UhW9pAbIURjAs=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, + "available-typed-arrays": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", + "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", + "dev": true, + "optional": true, + "requires": { + "array-filter": "^1.0.0" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true, + "optional": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "dev": true, + "optional": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha1-uqVZ7hTO1zRSIputcyZGfGH6vWA=", + "dev": true + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha1-MnE7wCj3XAL9txDXx7zsHyxgcO8=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "requires": { + "check-error": "^1.0.2" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "child-process-promise": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/child-process-promise/-/child-process-promise-2.2.1.tgz", + "integrity": "sha1-RzChHvYQ+tRQuPIjx50x172tgHQ=", + "dev": true, + "requires": { + "cross-spawn": "^4.0.2", + "node-version": "^1.0.0", + "promise-polyfill": "^6.0.1" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha1-vXerfebelCBc6sxy8XFtKfIKd78=", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "optional": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + }, + "dependencies": { + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true, + "optional": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "dev": true, + "optional": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "optional": true, + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "optional": true + }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "date-and-time": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.13.1.tgz", + "integrity": "sha512-/Uge9DJAT+s+oAcDxtBhyR8+sKjUnZbYmyhbmWjTHNtX7B7oWD8YyYdeXcBRbwSj6hVvj+IQegJam7m7czhbFw==", + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decimal.js": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz", + "integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw==", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-equal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.3.tgz", + "integrity": "sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==", + "dev": true, + "optional": true, + "requires": { + "es-abstract": "^1.17.5", + "es-get-iterator": "^1.1.0", + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.2", + "is-regex": "^1.0.5", + "isarray": "^2.0.5", + "object-is": "^1.1.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dicer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", + "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "dev": true, + "requires": { + "streamsearch": "0.1.2" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha1-gAwN0eCov7yVg1wgKtIg/jF+WhI=", + "dev": true + }, + "domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "dev": true, + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true + } + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "optional": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "optional": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "optional": true, + "requires": { + "once": "^1.4.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true, + "optional": true + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "dev": true, + "optional": true, + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint-plugin-prettier": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.7.0.tgz", + "integrity": "sha512-CStQYJgALoQBw3FsBzH0VOVDRnJ/ZimUlpLm226U8qgqYJfPOY/CPK6wyRInMxh73HSKg5wyRwdS4BVYYHwokA==", + "dev": true, + "requires": { + "fast-diff": "^1.1.1", + "jest-docblock": "^21.0.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "optional": true + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha1-+LETa0Bx+9jrFAr/hYsQGewpFfo=", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==", + "dev": true, + "optional": true + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "firebase-admin": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-8.13.0.tgz", + "integrity": "sha512-krXj5ncWMJBhCpXSn9UFY6zmDWjFjqgx+1e9ATXKFYndEjmKtNBuJzqdrAdDh7aTUR7X6+0TPx4Hbc08kd0lwQ==", + "dev": true, + "requires": { + "@firebase/database": "^0.6.0", + "@google-cloud/firestore": "^3.0.0", + "@google-cloud/storage": "^4.1.2", + "@types/node": "^8.10.59", + "dicer": "^0.3.0", + "jsonwebtoken": "^8.5.1", + "node-forge": "^0.7.6" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + } + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true, + "optional": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true, + "optional": true + }, + "gaxios": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", + "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", + "dev": true, + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz", + "integrity": "sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA==", + "dev": true, + "optional": true, + "requires": { + "gaxios": "^2.1.0", + "json-bigint": "^0.3.0" + } + }, + "gcs-resumable-upload": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-2.3.3.tgz", + "integrity": "sha512-sf896I5CC/1AxeaGfSFg3vKMjUq/r+A3bscmVzZm10CElyRanN0XwPu/MxeIO4LSP+9uF6yKzXvNsaTsMXUG6Q==", + "dev": true, + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "configstore": "^5.0.0", + "gaxios": "^2.0.0", + "google-auth-library": "^5.0.0", + "pumpify": "^2.0.0", + "stream-events": "^1.0.4" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha1-OWCDLT8VdBCDQtr9OmezMsCWnfE=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "google-auth-library": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", + "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "dev": true, + "optional": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^2.1.0", + "gcp-metadata": "^3.4.0", + "gtoken": "^4.1.0", + "jws": "^4.0.0", + "lru-cache": "^5.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dev": true, + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "optional": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "optional": true + } + } + }, + "google-gax": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-1.15.3.tgz", + "integrity": "sha512-3JKJCRumNm3x2EksUTw4P1Rad43FTpqrtW9jzpf3xSMYXx+ogaqTM1vGo7VixHB4xkAyATXVIa3OcNSh8H9zsQ==", + "dev": true, + "optional": true, + "requires": { + "@grpc/grpc-js": "~1.0.3", + "@grpc/proto-loader": "^0.5.1", + "@types/fs-extra": "^8.0.1", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^3.6.0", + "google-auth-library": "^5.0.0", + "is-stream-ended": "^0.1.4", + "lodash.at": "^4.6.0", + "lodash.has": "^4.5.2", + "node-fetch": "^2.6.0", + "protobufjs": "^6.8.9", + "retry-request": "^4.0.0", + "semver": "^6.0.0", + "walkdir": "^0.4.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "optional": true + } + } + }, + "google-p12-pem": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz", + "integrity": "sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg==", + "dev": true, + "optional": true, + "requires": { + "node-forge": "^0.9.0" + }, + "dependencies": { + "node-forge": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.2.tgz", + "integrity": "sha512-naKSScof4Wn+aoHU6HBsifh92Zeicm1GDQKd1vp3Y/kOi8ub0DozCa9KpvYNCXslFHYRmLNiqRopGdTGwNLpNw==", + "dev": true, + "optional": true + } + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha1-8nNdwig2dPpnR4sQGBBZNVw2nl4=", + "dev": true + }, + "gtoken": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz", + "integrity": "sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA==", + "dev": true, + "optional": true, + "requires": { + "gaxios": "^2.1.0", + "google-p12-pem": "^2.0.0", + "jws": "^4.0.0", + "mime": "^2.2.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dev": true, + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dev": true, + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "dev": true, + "optional": true + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "hash-stream-validation": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", + "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", + "dev": true, + "optional": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "highlight.js": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.2.0.tgz", + "integrity": "sha512-OryzPiqqNCfO/wtFo619W+nPYALM6u7iCQkum4bqRmmlcTikOkmlL06i009QelynBPAlNByTQU6cBB2cOBQtCw==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "http-parser-js": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", + "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==", + "dev": true + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "optional": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "optional": true, + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "optional": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "is-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", + "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==", + "dev": true, + "optional": true + }, + "is-boolean-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", + "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", + "dev": true, + "optional": true + }, + "is-callable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", + "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", + "dev": true, + "optional": true + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true + }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "dev": true, + "optional": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "optional": true + }, + "is-potential-custom-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", + "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "dev": true, + "optional": true + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true, + "optional": true + }, + "is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha1-9QIk6V4GvODjVtRApIJ801smfto=", + "dev": true, + "optional": true + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true, + "optional": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typed-array": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", + "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", + "dev": true, + "optional": true, + "requires": { + "available-typed-arrays": "^1.0.0", + "es-abstract": "^1.17.4", + "foreach": "^2.0.5", + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "optional": true + }, + "is-weakset": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", + "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", + "dev": true, + "optional": true + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "optional": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jest-docblock": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz", + "integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=", + "dev": true + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", + "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "acorn": "^7.1.1", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.2.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.0", + "domexception": "^2.0.1", + "escodegen": "^1.14.1", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "5.1.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.8", + "saxes": "^5.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0", + "ws": "^7.2.3", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "json-bigint": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.1.tgz", + "integrity": "sha512-DGWnSzmusIreWlEupsUelHrhwmPPE+FiQvg+drKfk2p+bdEYa5mp4PJ8JsCWqae0M2jQNb0HPvnwvf1qOTThzQ==", + "dev": true, + "optional": true, + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", + "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^1.0.0" + } + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dev": true, + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "just-extend": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", + "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", + "dev": true + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dev": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dev": true, + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "lodash.at": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz", + "integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g=", + "dev": true, + "optional": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "dev": true, + "optional": true + }, + "lodash.has": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", + "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=", + "dev": true, + "optional": true + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=", + "dev": true + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=", + "dev": true + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=", + "dev": true + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "lolex": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", + "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", + "dev": true + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha1-mntxz7fTYaGU6lVSQckvdGjVvyg=", + "dev": true, + "optional": true + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "optional": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "optional": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "marked": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.1.1.tgz", + "integrity": "sha512-mJzT8D2yPxoPh7h0UXkB+dBj4FykPJ2OIfxAWeIHrvoHDkFxukV/29QxoFQoPM6RLEwhIFdJpmKBlqVM3s2ZIw==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, + "mocha": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", + "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.4", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + } + } + }, + "mock-require": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/mock-require/-/mock-require-3.0.3.tgz", + "integrity": "sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg==", + "dev": true, + "requires": { + "get-caller-file": "^1.0.2", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "nise": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", + "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^5.0.1", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "lolex": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "nock": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz", + "integrity": "sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w==", + "dev": true, + "requires": { + "chai": "^4.1.2", + "debug": "^4.1.0", + "deep-equal": "^1.0.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.5", + "mkdirp": "^0.5.0", + "propagate": "^1.0.0", + "qs": "^6.5.1", + "semver": "^5.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true, + "optional": true + }, + "node-forge": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", + "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==", + "dev": true + }, + "node-version": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/node-version/-/node-version-1.2.0.tgz", + "integrity": "sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ==", + "dev": true + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object-is": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", + "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz", + "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "optional": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "optional": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise-polyfill": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz", + "integrity": "sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=", + "dev": true + }, + "propagate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz", + "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=", + "dev": true + }, + "protobufjs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.1.tgz", + "integrity": "sha512-pb8kTchL+1Ceg4lFd5XUpK8PdWacbvV5SK2ULH2ebrYtl4GjJmS24m6CKME67jzV53tbJxHlnNOSqQHbTsR9JQ==", + "dev": true, + "optional": true, + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "13.13.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.20.tgz", + "integrity": "sha512-1kx55tU3AvGX2Cjk2W4GMBxbgIz892V+X10S2gUreIAq8qCWgaQH+tZBOWc0bi2BKFhQt+CX0BTx28V9QPNa+A==", + "dev": true, + "optional": true + } + } + }, + "proxy-addr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "optional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "dev": true, + "optional": true, + "requires": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + }, + "dependencies": { + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "dev": true, + "optional": true, + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "requires": { + "lodash": "^4.17.19" + } + }, + "request-promise-native": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", + "dev": true, + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "retry-request": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz", + "integrity": "sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ==", + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "optional": true + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" + }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha1-fnQlb7qknHWqfHogXMInmcrIAAQ=", + "dev": true + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "shelljs": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "side-channel": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz", + "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==", + "dev": true, + "optional": true, + "requires": { + "es-abstract": "^1.18.0-next.0", + "object-inspect": "^1.8.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz", + "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==", + "dev": true, + "optional": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true, + "optional": true + }, + "sinon": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", + "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.3", + "diff": "^3.5.0", + "lolex": "^4.2.0", + "nise": "^1.5.2", + "supports-color": "^5.5.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "snakeize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", + "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", + "dev": true, + "optional": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "dev": true, + "optional": true, + "requires": { + "stubs": "^3.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true, + "optional": true + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", + "dev": true, + "optional": true + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "teeny-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.3.tgz", + "integrity": "sha512-TZG/dfd2r6yeji19es1cUIwAlVD8y+/svB1kAC2Y0bjEyysrfbO8EZvJBRwIE6WkwmUoB7uvWLwTIhJbMXZ1Dw==", + "dev": true, + "optional": true, + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.2.0", + "stream-events": "^1.0.5", + "uuid": "^7.0.0" + } + }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dev": true, + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "optional": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "optional": true + } + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dev": true, + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", + "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "ts-node": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", + "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "tslint": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "tslint-config-prettier": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz", + "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==", + "dev": true + }, + "tslint-no-unused-expression-chai": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/tslint-no-unused-expression-chai/-/tslint-no-unused-expression-chai-0.1.4.tgz", + "integrity": "sha512-frEWKNTcq7VsaWKgUxMDOB2N/cmQadVkUtUGIut+2K4nv/uFXPfgJyPjuNC/cHyfUVqIkHMAvHOCL+d/McU3nQ==", + "dev": true, + "requires": { + "tsutils": "^3.0.0" + }, + "dependencies": { + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "tslint-plugin-prettier": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslint-plugin-prettier/-/tslint-plugin-prettier-2.3.0.tgz", + "integrity": "sha512-F9e4K03yc9xuvv+A0v1EmjcnDwpz8SpCD8HzqSDe0eyg34cBinwn9JjmnnRrNAs4HdleRQj7qijp+P/JTxt4vA==", + "dev": true, + "requires": { + "eslint-plugin-prettier": "^2.2.0", + "lines-and-columns": "^1.1.6", + "tslib": "^1.7.1" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true, + "optional": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "optional": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typedoc": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.19.1.tgz", + "integrity": "sha512-EqZpRJQUnkwHA1yBhaDExEXUZIiWKddkrDXhRcfUzpnu6pizxNmVTw5IZ3mu682Noa4zQCniE0YNjaAwHQodrA==", + "dev": true, + "requires": { + "fs-extra": "^9.0.1", + "handlebars": "^4.7.6", + "highlight.js": "^10.0.0", + "lodash": "^4.17.20", + "lunr": "^2.3.9", + "marked": "^1.1.1", + "minimatch": "^3.0.0", + "progress": "^2.0.3", + "semver": "^7.3.2", + "shelljs": "^0.8.4", + "typedoc-default-themes": "^0.11.1" + }, + "dependencies": { + "handlebars": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + } + } + }, + "typedoc-default-themes": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.11.3.tgz", + "integrity": "sha512-SwyN188QGNA2iFS5mdWYTGzohKqJ1PWAXVmGolKnVc2NnpX234FEPF2nUvEg+O9jjwAu7ZSVZ5UrZri0raJOjQ==", + "dev": true + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha1-rwLxgMEgfXZDLkc+0koo9KeCuuM=", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "dev": true, + "optional": true + } + } + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "optional": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true, + "optional": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "dev": true, + "optional": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "walkdir": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", + "dev": true, + "optional": true + }, + "webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-PcVnO6NiewhkmzV0qn7A+UZ9Xx4maNTI+O+TShmfE4pqjoCMwUMjkvoNhNHPTvgR7QH9Xt3R13iHuWy2sToFxQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo=", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", + "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", + "dev": true, + "optional": true, + "requires": { + "is-bigint": "^1.0.0", + "is-boolean-object": "^1.0.0", + "is-number-object": "^1.0.3", + "is-string": "^1.0.4", + "is-symbol": "^1.0.2" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "optional": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "which-typed-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", + "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", + "dev": true, + "optional": true, + "requires": { + "available-typed-arrays": "^1.0.2", + "es-abstract": "^1.17.5", + "foreach": "^2.0.5", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-typed-array": "^1.1.3" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "optional": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", + "dev": true + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true, + "optional": true + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + } + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 4eb5986d8..72a217fb7 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "chai-as-promised": "^7.1.1", "child-process-promise": "^2.2.1", "firebase-admin": "^8.2.0", - "istanbul": "^0.4.5", "js-yaml": "^3.13.1", "jsdom": "^16.2.1", "jsonwebtoken": "^8.5.1", @@ -73,7 +72,7 @@ "tslint-config-prettier": "^1.18.0", "tslint-no-unused-expression-chai": "^0.1.4", "tslint-plugin-prettier": "^2.0.1", - "typedoc": "0.14.2", + "typedoc": "^0.19.1", "typescript": "^3.8.3", "yargs": "^15.3.1" }, From 93047d5046e589f6d0ae4d321e546c0e85a769a4 Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 30 Nov 2020 13:49:32 -0800 Subject: [PATCH 171/437] Switches to Github Actions from travis, and adds CHANGELOG (#818) * Switches to Github Actions over travis, and adds CHNAGELOG entries for recent PRs * formats --- .github/workflows/test.yaml | 35 +++++++++++++++++++++++++++++++++++ .travis.yml | 14 -------------- CHANGELOG.md | 2 ++ 3 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/test.yaml delete mode 100644 .travis.yml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 000000000..9a31678c2 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,35 @@ +name: CI Tests + +on: + - pull_request + - push + +env: + CI: true + +jobs: + unit: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: + - 8.x + - 10.x + - 12.x + - 14.x + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + - name: Cache npm + uses: actions/cache@v1 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} + + - run: npm install + - run: npm run lint + - run: npm run format + - run: npm run test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 18dff4114..000000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -cache: npm -jobs: - include: - - name: lint - script: npm run lint - stage: verify - - name: format - script: npm run format - stage: verify -language: node_js -node_js: - - '8' - - '10' -sudo: false diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..e0c94613b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +- Adds `4GB` as a `memory` option for `runWith()`. +- Adds support for choosing `ingressSettings` via `runWith()`. From 3932876901d977d7957b57a69b6f2b0b9b2b94a5 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 30 Nov 2020 21:54:33 +0000 Subject: [PATCH 172/437] 3.12.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 17b67b6d0..2ff1ff5cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.11.0", + "version": "3.12.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 72a217fb7..a27384f60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.11.0", + "version": "3.12.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From 9055e0fc9f3bd8fb72c4cc8702f8ea1a1051ee70 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 30 Nov 2020 21:54:38 +0000 Subject: [PATCH 173/437] [firebase-release] Removed change log and reset repo after 3.12.0 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0c94613b..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Adds `4GB` as a `memory` option for `runWith()`. -- Adds support for choosing `ingressSettings` via `runWith()`. From 07139c8d9cdaff5dccbe790fd3ebc66a717ae8ac Mon Sep 17 00:00:00 2001 From: Egor Miasnikov Date: Tue, 8 Dec 2020 01:05:26 +0300 Subject: [PATCH 174/437] Add support for service account in `functions.runWith` (#770) * Add support for service account in `functions.runWith` * Add email validation and email generation by project id * Changing to serviceAccount, and adding default as an option * adds a test case for default * refactoring to checxk for @ at the end of service account, and throw erros earlier when service account is set to something invalid * gets rid of repeated @ for generated service account emails Co-authored-by: joehan --- spec/function-builder.spec.ts | 48 +++++++++++++++++++++++++++++++++++ src/cloud-functions.ts | 20 +++++++++++++++ src/function-builder.ts | 19 +++++++++++--- src/function-configuration.ts | 5 ++++ 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index eec531fdf..c31a82d4a 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -238,4 +238,52 @@ describe('FunctionBuilder', () => { )}` ); }); + + it('should allow a serviceAccount to be set as-is', () => { + const serviceAccount = 'test-service-account@test.iam.gserviceaccount.com'; + const fn = functions + .runWith({ + serviceAccount, + }) + .auth.user() + .onCreate((user) => user); + + expect(fn.__trigger.serviceAccountEmail).to.equal(serviceAccount); + }); + + it('should allow a serviceAccount to be set with generated service account email', () => { + const serviceAccount = 'test-service-account@'; + const projectId = process.env.GCLOUD_PROJECT; + const fn = functions + .runWith({ + serviceAccount, + }) + .auth.user() + .onCreate((user) => user); + + expect(fn.__trigger.serviceAccountEmail).to.equal( + `test-service-account@${projectId}.iam.gserviceaccount.com` + ); + }); + + it('should not set a serviceAccountEmail if service account is set to `default`', () => { + const serviceAccount = 'default'; + const fn = functions + .runWith({ + serviceAccount, + }) + .auth.user() + .onCreate((user) => user); + + expect(fn.__trigger.serviceAccountEmail).to.be.undefined; + }); + + it('should throw an error if serviceAccount is set to an invalid value', () => { + const serviceAccount = 'test-service-account'; + expect(() => { + functions.runWith({ + serviceAccount, + }); + }).to.throw(); + }); }); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 109a1ad05..50d674022 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -272,6 +272,7 @@ export interface TriggerAnnotated { timeout?: string; vpcConnector?: string; vpcConnectorEgressSettings?: string; + serviceAccountEmail?: string; }; } @@ -525,5 +526,24 @@ export function optionsToTrigger(options: DeploymentOptions) { trigger.vpcConnectorEgressSettings = options.vpcConnectorEgressSettings; } + if (options.serviceAccount) { + if (options.serviceAccount === 'default') { + // Do nothing, since this is equivalent to not setting serviceAccount. + } else if (options.serviceAccount.endsWith('@')) { + if (!process.env.GCLOUD_PROJECT) { + throw new Error( + `Unable to determine email for service account '${options.serviceAccount}' because process.env.GCLOUD_PROJECT is not set.` + ); + } + trigger.serviceAccountEmail = `${options.serviceAccount}${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`; + } else if (options.serviceAccount.includes('@')) { + trigger.serviceAccountEmail = options.serviceAccount; + } else { + throw new Error( + `Invalid option for serviceAccount: '${options.serviceAccount}'. Valid options are 'default', a service account email, or '{serviceAccountName}@'` + ); + } + } + return trigger; } diff --git a/src/function-builder.ts b/src/function-builder.ts index 9b5f5660b..7411f72e2 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -99,6 +99,16 @@ function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean { } } } + + if ( + runtimeOptions.serviceAccount && + runtimeOptions.serviceAccount !== 'default' && + !_.includes(runtimeOptions.serviceAccount, '@') + ) { + throw new Error( + `serviceAccount must be set to 'default', a service account email, or '{serviceAccountName}@'` + ); + } return true; } @@ -139,9 +149,12 @@ export function region( * 0 to 540. * 3. `failurePolicy`: failure policy of the function, with boolean `true` being * equivalent to providing an empty retry object. - * 4. `vpcConnector`: id of a VPC connector in the same project and region - * 5. `vpcConnectorEgressSettings`: when a `vpcConnector` is set, control which - * egress traffic is sent through the `vpcConnector`. + * 4. `vpcConnector`: id of a VPC connector in same project and region. + * 5. `vpcConnectorEgressSettings`: when a vpcConnector is set, control which + * egress traffic is sent through the vpcConnector. + * 6. `serviceAccount`: Specific service account for the function. + * 7. `ingressSettings`: ingress settings for the function, which control where a HTTPS + * function can be called from. * * Value must not be null. */ diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 10098a745..f5a325b41 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -122,6 +122,11 @@ export interface RuntimeOptions { */ vpcConnectorEgressSettings?: typeof VPC_EGRESS_SETTINGS_OPTIONS[number]; + /** + * Specific service account for the function to run as + */ + serviceAccount?: 'default' | string; + /** * Ingress settings */ From 07ca97f68bcf3e724a81c4da62807a51e5712619 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Dec 2020 14:14:49 -0800 Subject: [PATCH 175/437] Bump highlight.js from 10.2.0 to 10.4.1 (#823) Bumps [highlight.js](https://github.com/highlightjs/highlight.js) from 10.2.0 to 10.4.1. - [Release notes](https://github.com/highlightjs/highlight.js/releases) - [Changelog](https://github.com/highlightjs/highlight.js/blob/master/CHANGES.md) - [Commits](https://github.com/highlightjs/highlight.js/compare/10.2.0...10.4.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: joehan --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2ff1ff5cd..167df1e2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1919,9 +1919,9 @@ "dev": true }, "highlight.js": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.2.0.tgz", - "integrity": "sha512-OryzPiqqNCfO/wtFo619W+nPYALM6u7iCQkum4bqRmmlcTikOkmlL06i009QelynBPAlNByTQU6cBB2cOBQtCw==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.4.1.tgz", + "integrity": "sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg==", "dev": true }, "html-encoding-sniffer": { From df592717075a9bbb1a17dd5e55c77a076b328cf9 Mon Sep 17 00:00:00 2001 From: joehan Date: Mon, 7 Dec 2020 15:43:39 -0800 Subject: [PATCH 176/437] Adds changelog entries for v3.13.0 (#824) * adds changelog entries for 3.13.0 * formats * Update CHANGELOG.md Co-authored-by: Sam Stern * Update CHANGELOG.md Co-authored-by: Sam Stern * Update CHANGELOG.md Co-authored-by: Sam Stern * formats * formats Co-authored-by: Sam Stern --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..c9cb1adff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +- Adds `serviceAccount` option to `runtimeOptions` to specify which service account Cloud Function should use at runtime. For example: + +``` +const functions = require('firebase-functions'); + +exports.myFunction = functions.runWith({ + serviceAccount: 'test-sa@project.iam.gserviceaccount.com' + // OR + // serviceAcount: 'test-sa@" + // OR + // serviceAccount: 'default' + }) + +``` + +Requires firebase-tools@8.18.0 or later. Thanks @egor-miasnikov! + +- Upgrades `highlight.js` to `10.4.1` to fix a vulnerability. From 21d2c0bb458bd2aa6628f8cf4e2648d51d8a0f13 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 7 Dec 2020 23:48:48 +0000 Subject: [PATCH 177/437] 3.13.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 167df1e2b..7a1981a69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.12.0", + "version": "3.13.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a27384f60..8f4bb09c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.12.0", + "version": "3.13.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From c69c9bc083a2ef55a3ea152839d6027b4bb20352 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 7 Dec 2020 23:48:54 +0000 Subject: [PATCH 178/437] [firebase-release] Removed change log and reset repo after 3.13.0 release --- CHANGELOG.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9cb1adff..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +0,0 @@ -- Adds `serviceAccount` option to `runtimeOptions` to specify which service account Cloud Function should use at runtime. For example: - -``` -const functions = require('firebase-functions'); - -exports.myFunction = functions.runWith({ - serviceAccount: 'test-sa@project.iam.gserviceaccount.com' - // OR - // serviceAcount: 'test-sa@" - // OR - // serviceAccount: 'default' - }) - -``` - -Requires firebase-tools@8.18.0 or later. Thanks @egor-miasnikov! - -- Upgrades `highlight.js` to `10.4.1` to fix a vulnerability. From 9b8fe654d090cb6f4721c9725aa0f0707afc7433 Mon Sep 17 00:00:00 2001 From: Sam Stern Date: Mon, 11 Jan 2021 17:37:14 +0000 Subject: [PATCH 179/437] Fix emulated database URL parse issue (#838) --- package.json | 1 + spec/providers/database.spec.ts | 11 +++++++++++ src/providers/database.ts | 11 +++++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8f4bb09c5..9b2c3cd8c 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "build:pack": "rm -rf lib && npm install && tsc -p tsconfig.release.json && npm pack", "build:release": "npm install --production && npm install --no-save typescript firebase-admin && tsc -p tsconfig.release.json", "build": "tsc -p tsconfig.release.json", + "build:watch": "npm run build -- -w", "format": "prettier --check '**/*.{json,md,ts,yml,yaml}'", "format:fix": "prettier --write '**/*.{json,md,ts,yml,yaml}'", "lint": "tslint --config tslint.json --project tsconfig.json ", diff --git a/spec/providers/database.spec.ts b/spec/providers/database.spec.ts index b764a7ea0..bf7e54b6b 100644 --- a/spec/providers/database.spec.ts +++ b/spec/providers/database.spec.ts @@ -472,6 +472,17 @@ describe('Database Functions', () => { ); }).to.throw(Error); }); + + it('should use the emulator host when present', () => { + process.env.FIREBASE_DATABASE_EMULATOR_HOST = 'localhost:1234'; + const [instance, path] = database.extractInstanceAndPath( + 'projects/_/instances/foo/refs/bar', + 'firebaseio-staging.com' + ); + expect(instance).to.equal('http://localhost:1234/?ns=foo'); + expect(path).to.equal('/bar'); + delete process.env.FIREBASE_DATABASE_EMULATOR_HOST; + }); }); describe('DataSnapshot', () => { diff --git a/src/providers/database.ts b/src/providers/database.ts index 2aa50b55a..0e4393d05 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -334,8 +334,15 @@ export function extractInstanceAndPath( `Expect project to be '_' in a Firebase Realtime Database event` ); } - const dbInstance = 'https://' + dbInstanceName + '.' + domain; - return [dbInstance, path]; + + const emuHost = process.env.FIREBASE_DATABASE_EMULATOR_HOST; + if (emuHost) { + const dbInstance = `http://${emuHost}/?ns=${dbInstanceName}`; + return [dbInstance, path]; + } else { + const dbInstance = 'https://' + dbInstanceName + '.' + domain; + return [dbInstance, path]; + } } /** From 2f72c33ab47a57dce7175eb4449dca00073d46cc Mon Sep 17 00:00:00 2001 From: Leon Radley Date: Wed, 13 Jan 2021 20:46:14 +0100 Subject: [PATCH 180/437] Add support for 4GB memory option (#842) * Add support for 4GB memory option The 4GB memory option was not added everywhere. Fixes #834 * Added test for 4GB memory option Co-authored-by: Leon Radley Co-authored-by: joehan --- spec/function-builder.spec.ts | 12 ++++++++++++ src/cloud-functions.ts | 1 + src/function-builder.ts | 4 ++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index c31a82d4a..209d63d60 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -286,4 +286,16 @@ describe('FunctionBuilder', () => { }); }).to.throw(); }); + + it('should allow setting 4GB memory option', () => { + const fn = functions + .runWith({ + memory: '4GB', + }) + .region('europe-west1') + .auth.user() + .onCreate((user) => user); + + expect(fn.__trigger.availableMemoryMb).to.deep.equal(4096); + }); }); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 50d674022..aef850da4 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -507,6 +507,7 @@ export function optionsToTrigger(options: DeploymentOptions) { '512MB': 512, '1GB': 1024, '2GB': 2048, + '4GB': 4096, }; trigger.availableMemoryMb = _.get(memoryLookup, options.memory); } diff --git a/src/function-builder.ts b/src/function-builder.ts index 7411f72e2..8d06b2142 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -144,7 +144,7 @@ export function region( * Configure runtime options for the function. * @param runtimeOptions Object with optional fields: * 1. `memory`: amount of memory to allocate to the function, possible values - * are: '128MB', '256MB', '512MB', '1GB', and '2GB'. + * are: '128MB', '256MB', '512MB', '1GB', '2GB', and '4GB'. * 2. `timeoutSeconds`: timeout for the function in seconds, possible values are * 0 to 540. * 3. `failurePolicy`: failure policy of the function, with boolean `true` being @@ -188,7 +188,7 @@ export class FunctionBuilder { * Configure runtime options for the function. * @param runtimeOptions Object with optional fields: * 1. `memory`: amount of memory to allocate to the function, possible values - * are: '128MB', '256MB', '512MB', '1GB', and '2GB'. + * are: '128MB', '256MB', '512MB', '1GB', '2GB', and '4GB'. * 2. `timeoutSeconds`: timeout for the function in seconds, possible values are * 0 to 540. * 3. `failurePolicy`: failure policy of the function, with boolean `true` being From f508951c651f755734dea6c8202c8a7ced034e2a Mon Sep 17 00:00:00 2001 From: huangjeff5 <64040981+huangjeff5@users.noreply.github.com> Date: Fri, 15 Jan 2021 10:31:54 -0800 Subject: [PATCH 181/437] Remove circular dependencies when logging. Fixes #737 (#844) * Remove circular dependencies when logging. Fixes #737 * Fix bug in binding the output logger * modify removeCircular to return a copy of the original object, rather than mutate * Modify removeCircular to return a new object, rather than mutating in place Co-authored-by: Michael Bleigh --- spec/logger.spec.ts | 84 ++++++++++++++++++++++++++++++--------------- src/logger.ts | 35 +++++++++++++++++-- 2 files changed, 89 insertions(+), 30 deletions(-) diff --git a/spec/logger.spec.ts b/spec/logger.spec.ts index c6859b60a..dda16087c 100644 --- a/spec/logger.spec.ts +++ b/spec/logger.spec.ts @@ -1,5 +1,4 @@ import { expect } from 'chai'; -import * as sinon from 'sinon'; import * as logger from '../src/logger'; const SUPPORTS_STRUCTURED_LOGS = @@ -8,52 +7,51 @@ const SUPPORTS_STRUCTURED_LOGS = describe(`logger (${ SUPPORTS_STRUCTURED_LOGS ? 'structured' : 'unstructured' })`, () => { - let sandbox: sinon.SinonSandbox; - let stdoutStub: sinon.SinonStub; - let stderrStub: sinon.SinonStub; + let stdoutWrite = process.stdout.write.bind(process.stdout); + let stderrWrite = process.stderr.write.bind(process.stderr); + let lastOut: string; + let lastErr: string; beforeEach(() => { - sandbox = sinon.createSandbox(); - stdoutStub = sandbox.stub(process.stdout, 'write'); - stderrStub = sandbox.stub(process.stderr, 'write'); + process.stdout.write = (msg: Buffer | string, cb?: any): boolean => { + lastOut = msg as string; + return stdoutWrite(msg, cb); + }; + process.stderr.write = (msg: Buffer | string, cb?: any): boolean => { + lastErr = msg as string; + return stderrWrite(msg, cb); + }; }); - function expectOutput(stdStub: sinon.SinonStub, entry: any) { + afterEach(() => { + process.stdout.write = stdoutWrite; + process.stderr.write = stderrWrite; + }); + + function expectOutput(last: string, entry: any) { if (SUPPORTS_STRUCTURED_LOGS) { - return expect( - JSON.parse((stdStub.getCalls()[0].args[0] as string).trim()) - ).to.deep.eq(entry); + return expect(JSON.parse(last.trim())).to.deep.eq(entry); } else { // legacy logging is not structured, but do a sanity check - return expect(stdStub.getCalls()[0].args[0]).to.include(entry.message); + return expect(last).to.include(entry.message); } } function expectStdout(entry: any) { - return expectOutput(stdoutStub, entry); + return expectOutput(lastOut, entry); } function expectStderr(entry: any) { - return expectOutput(stderrStub, entry); + return expectOutput(lastErr, entry); } describe('logging methods', () => { - let writeStub: sinon.SinonStub; - beforeEach(() => { - writeStub = sinon.stub(logger, 'write'); - }); - - afterEach(() => { - writeStub.restore(); - }); - it('should coalesce arguments into the message', () => { logger.log('hello', { middle: 'obj' }, 'end message'); expectStdout({ severity: 'INFO', message: "hello { middle: 'obj' } end message", }); - sandbox.restore(); // to avoid swallowing test runner output }); it('should merge structured data from the last argument', () => { @@ -63,7 +61,6 @@ describe(`logger (${ message: 'hello world', additional: 'context', }); - sandbox.restore(); // to avoid swallowing test runner output }); it('should not recognize null as a structured logging object', () => { @@ -72,13 +69,46 @@ describe(`logger (${ severity: 'INFO', message: 'hello world null', }); - sandbox.restore(); // to avoid swallowing test runner output }); }); describe('write', () => { describe('structured logging', () => { describe('write', () => { + it('should remove circular references', () => { + const circ: any = { b: 'foo' }; + circ.circ = circ; + + const entry: logger.LogEntry = { + severity: 'ERROR', + message: 'testing circular', + circ, + }; + logger.write(entry); + expectStderr({ + severity: 'ERROR', + message: 'testing circular', + circ: { b: 'foo', circ: '[Circular]' }, + }); + }); + + it('should remove circular references in arrays', () => { + const circ: any = { b: 'foo' }; + circ.circ = [circ]; + + const entry: logger.LogEntry = { + severity: 'ERROR', + message: 'testing circular', + circ, + }; + logger.write(entry); + expectStderr({ + severity: 'ERROR', + message: 'testing circular', + circ: { b: 'foo', circ: ['[Circular]'] }, + }); + }); + for (const severity of ['DEBUG', 'INFO', 'NOTICE']) { it(`should output ${severity} severity to stdout`, () => { let entry: logger.LogEntry = { @@ -87,7 +117,6 @@ describe(`logger (${ }; logger.write(entry); expectStdout(entry); - sandbox.restore(); // to avoid swallowing test runner output }); } @@ -105,7 +134,6 @@ describe(`logger (${ }; logger.write(entry); expectStderr(entry); - sandbox.restore(); // to avoid swallowing test runner output }); } }); diff --git a/src/logger.ts b/src/logger.ts index 7958ca457..d6c0d2220 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -30,13 +30,40 @@ export interface LogEntry { [key: string]: any; } +function removeCircular(obj: any, refs: any[] = []): any { + if (typeof obj !== 'object' || !obj) { + return obj; + } + if (refs.includes(obj)) { + return '[Circular]'; + } else { + refs.push(obj); + } + let returnObj: any; + if (Array.isArray(obj)) { + returnObj = new Array(obj.length); + } else { + returnObj = {}; + } + for (const k in obj) { + if (refs.includes(obj[k])) { + returnObj[k] = '[Circular]'; + } else { + returnObj[k] = removeCircular(obj[k], refs); + } + } + return returnObj; +} + /** * Writes a `LogEntry` to `stdout`/`stderr` (depending on severity). * @param entry The `LogEntry` including severity, message, and any additional structured metadata. */ export function write(entry: LogEntry) { if (SUPPORTS_STRUCTURED_LOGS) { - UNPATCHED_CONSOLE[CONSOLE_SEVERITY[entry.severity]](JSON.stringify(entry)); + UNPATCHED_CONSOLE[CONSOLE_SEVERITY[entry.severity]]( + JSON.stringify(removeCircular(entry)) + ); return; } @@ -50,7 +77,11 @@ export function write(entry: LogEntry) { } } if (jsonKeyCount > 0) { - message = `${message} ${JSON.stringify(jsonPayload, null, 2)}`; + message = `${message} ${JSON.stringify( + removeCircular(jsonPayload), + null, + 2 + )}`; } UNPATCHED_CONSOLE[CONSOLE_SEVERITY[entry.severity]](message); } From e920b01d1d2f0ba4fe528936196537e90d48ee4a Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 15 Jan 2021 10:41:17 -0800 Subject: [PATCH 182/437] Add changelog entry for #842 (#845) * add changelog for memory options * formats * Update CHANGELOG.md * Update CHANGELOG.md Co-authored-by: huangjeff5 <64040981+huangjeff5@users.noreply.github.com> --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..dba1ded30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +- Fixes a bug that prevented Functions from being deployed with `availableMemoryMb` set to `4GB`. +- Fixes bug where `functions.logger.log` crashes function if circular dependencies are passed in From daeda1dd38904c6c4c6c1dc2b09d3c2a275ccc4e Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 15 Jan 2021 18:47:18 +0000 Subject: [PATCH 183/437] 3.13.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7a1981a69..f1e9dc457 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.13.0", + "version": "3.13.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9b2c3cd8c..f6f9ae1c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.13.0", + "version": "3.13.1", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From 61dc5a16cab32a9e671d6fcee383ef1af0d25420 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Fri, 15 Jan 2021 18:47:23 +0000 Subject: [PATCH 184/437] [firebase-release] Removed change log and reset repo after 3.13.1 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dba1ded30..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Fixes a bug that prevented Functions from being deployed with `availableMemoryMb` set to `4GB`. -- Fixes bug where `functions.logger.log` crashes function if circular dependencies are passed in From 5f64797555e150ae8de93445edb304e6ea81fda2 Mon Sep 17 00:00:00 2001 From: takaaa220 Date: Wed, 20 Jan 2021 07:41:55 +0900 Subject: [PATCH 185/437] Fix IngressSettings (#827) Co-authored-by: joehan --- spec/function-builder.spec.ts | 21 +++++++++++++++++++++ src/cloud-functions.ts | 5 +++++ src/function-builder.ts | 12 ++++++++++++ 3 files changed, 38 insertions(+) diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index 209d63d60..28cf35c3a 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -200,6 +200,27 @@ describe('FunctionBuilder', () => { }).to.throw(Error, 'at least one region'); }); + it('should allow a ingressSettings to be set', () => { + const fn = functions + .runWith({ ingressSettings: 'ALLOW_INTERNAL_ONLY' }) + .https.onRequest(() => {}); + + expect(fn.__trigger.ingressSettings).to.equal('ALLOW_INTERNAL_ONLY'); + }); + + it('should throw an error if user chooses an invalid ingressSettings', () => { + expect(() => { + return functions.runWith({ + ingressSettings: 'INVALID_OPTION', + } as any); + }).to.throw( + Error, + `The only valid ingressSettings values are: ${functions.INGRESS_SETTINGS_OPTIONS.join( + ',' + )}` + ); + }); + it('should allow a vpcConnector to be set', () => { const fn = functions .runWith({ diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index aef850da4..126997c1d 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -273,6 +273,7 @@ export interface TriggerAnnotated { vpcConnector?: string; vpcConnectorEgressSettings?: string; serviceAccountEmail?: string; + ingressSettings?: string; }; } @@ -519,6 +520,10 @@ export function optionsToTrigger(options: DeploymentOptions) { trigger.maxInstances = options.maxInstances; } + if (options.ingressSettings) { + trigger.ingressSettings = options.ingressSettings; + } + if (options.vpcConnector) { trigger.vpcConnector = options.vpcConnector; } diff --git a/src/function-builder.ts b/src/function-builder.ts index 8d06b2142..6f50e6bde 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -31,6 +31,7 @@ import { SUPPORTED_REGIONS, VALID_MEMORY_OPTIONS, VPC_EGRESS_SETTINGS_OPTIONS, + INGRESS_SETTINGS_OPTIONS, } from './function-configuration'; import * as analytics from './providers/analytics'; import * as auth from './providers/auth'; @@ -68,6 +69,17 @@ function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean { ); } + if ( + runtimeOptions.ingressSettings && + !_.includes(INGRESS_SETTINGS_OPTIONS, runtimeOptions.ingressSettings) + ) { + throw new Error( + `The only valid ingressSettings values are: ${INGRESS_SETTINGS_OPTIONS.join( + ',' + )}` + ); + } + if ( runtimeOptions.vpcConnectorEgressSettings && !_.includes( From 88691699d875f47ea7dc5db382f3624c68dc3148 Mon Sep 17 00:00:00 2001 From: Reza Rahmati Date: Tue, 19 Jan 2021 17:45:42 -0500 Subject: [PATCH 186/437] Fixing issue reading env.DATABASE_URL and process.env.STORAGE_BUCKET_URL (#840) * #829 fixing issue reading env.DATABASE_URL and env.STORAGE_BUCKET_URL * #829 revert space changes Co-authored-by: joehan --- src/setup.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/setup.ts b/src/setup.ts index d2935af15..6a9db702d 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -51,10 +51,10 @@ export function setup() { ); process.env.FIREBASE_CONFIG = JSON.stringify({ databaseURL: - `${process.env.DATABASE_URL}` || + process.env.DATABASE_URL || `https://${process.env.GCLOUD_PROJECT}.firebaseio.com`, storageBucket: - `${process.env.STORAGE_BUCKET_URL}` || + process.env.STORAGE_BUCKET_URL || `${process.env.GCLOUD_PROJECT}.appspot.com`, projectId: process.env.GCLOUD_PROJECT, }); From 0e2e95c8540a6e7e136e5bf718ccacaf319f8587 Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 4 Feb 2021 06:52:32 -0800 Subject: [PATCH 187/437] Adds changelog for #829 and #827. (#848) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..06d7cfb4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +- Fixes issue where DATABASE_URL and STORAGE_BUCKET_URL could not be set to undefined. (#829) +- Fixes a bug where ingressSettings could not be set. (#827) From 2d81a6be9e31b610f9d66c7ab7f1172762698ed7 Mon Sep 17 00:00:00 2001 From: Sam Stern Date: Thu, 4 Feb 2021 16:56:10 +0000 Subject: [PATCH 188/437] Update issue templates (#857) --- .github/ISSUE_TEMPLATE/---feature-request.md | 17 ----------------- .github/ISSUE_TEMPLATE/---report-a-bug.md | 2 +- .github/ISSUE_TEMPLATE/config.yml | 8 ++++++++ 3 files changed, 9 insertions(+), 18 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/---feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/---feature-request.md b/.github/ISSUE_TEMPLATE/---feature-request.md deleted file mode 100644 index 93f7d6de1..000000000 --- a/.github/ISSUE_TEMPLATE/---feature-request.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: "\U0001F4A1 Feature Request" -about: - Have a feature you'd like to see in the functions SDK? Request it through our - support channel. -title: '' -labels: '' -assignees: '' ---- - - - -Great, we love hearing how we can improve our products! However, GitHub is not the place to submit them. Please submit your feature requests to: -https://firebase.google.com/support/contact/bugs-features/ diff --git a/.github/ISSUE_TEMPLATE/---report-a-bug.md b/.github/ISSUE_TEMPLATE/---report-a-bug.md index 694c4aedf..3ad82bf60 100644 --- a/.github/ISSUE_TEMPLATE/---report-a-bug.md +++ b/.github/ISSUE_TEMPLATE/---report-a-bug.md @@ -1,6 +1,6 @@ --- name: '⚠️ Report a Bug' -about: Think you found a bug in the SDK? Report it here. +about: Think you found a bug in the firebase-functions SDK? Report it here. Please do not use this form if your function is deployed successfully but not working as you expected. title: '' labels: '' assignees: '' diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..918e205f9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: 💻 Bug in the Firebase CLI + url: https://github.com/firebase/firebase-tools/issues/new/choose + about: Have you found a bug in the Firebase CLI? + - name: 🔥 Firebase Support + url: https://firebase.google.com/support/ + about: If you have an issue with your functions in production, please contact support. From 0c382ed0f7a94f3cc270b13111d635fe4caccd79 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 22 Feb 2021 18:01:36 +0000 Subject: [PATCH 189/437] 3.13.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index f1e9dc457..9f26affdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.13.1", + "version": "3.13.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index f6f9ae1c6..11a4dfb39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.13.1", + "version": "3.13.2", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From fc4788e61012306eda385428f25291094fce624d Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 22 Feb 2021 18:01:44 +0000 Subject: [PATCH 190/437] [firebase-release] Removed change log and reset repo after 3.13.2 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06d7cfb4c..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Fixes issue where DATABASE_URL and STORAGE_BUCKET_URL could not be set to undefined. (#829) -- Fixes a bug where ingressSettings could not be set. (#827) From e2c9475372d6eab47d038f29ebc386ec18029784 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 1 Mar 2021 13:28:43 -0800 Subject: [PATCH 191/437] Remove crashlyitcs (#866) --- package-lock.json | 18 +-- spec/providers/crashlytics.spec.ts | 157 ----------------------- src/function-builder.ts | 11 -- src/handler-builder.ts | 36 ------ src/index.ts | 2 - src/providers/crashlytics.ts | 197 ----------------------------- 6 files changed, 9 insertions(+), 412 deletions(-) delete mode 100644 spec/providers/crashlytics.spec.ts delete mode 100644 src/providers/crashlytics.ts diff --git a/package-lock.json b/package-lock.json index 9f26affdd..f1f20b89a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -490,7 +490,7 @@ "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + "integrity": "sha1-fuMwunyq+5gJC+zoal7kQRWQTCw=" }, "@types/serve-static": { "version": "1.13.5", @@ -665,7 +665,7 @@ "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "integrity": "sha1-jSR136tVO7M+d7VOWeiAu4ziMTY=", "dev": true, "requires": { "safer-buffer": "~2.1.0" @@ -1042,7 +1042,7 @@ "cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "integrity": "sha1-6sEdpRWS3Ya58G9uesKTs9+HXSk=", "requires": { "object-assign": "^4", "vary": "^1" @@ -1581,7 +1581,7 @@ "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "integrity": "sha1-3M5SwF9kTymManq5Nr1yTO/786Y=", "dev": true, "requires": { "asynckit": "^0.4.0", @@ -2356,7 +2356,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", "dev": true }, "json-stringify-safe": { @@ -2565,7 +2565,7 @@ "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "integrity": "sha1-i75Q6oW+1ZvJ4z3KuCNe6bz0Q80=", "dev": true, "requires": { "pseudomap": "^1.0.2", @@ -3012,7 +3012,7 @@ "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "integrity": "sha1-R6ewFrqmi1+g7PPe4IqFxnmsZFU=", "dev": true }, "object-assign": { @@ -3327,7 +3327,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=", "dev": true }, "qs": { @@ -3745,7 +3745,7 @@ "stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "integrity": "sha1-u8iY7E3zOkkC2JIzPUfam/HEBtU=", "dev": true, "optional": true, "requires": { diff --git a/spec/providers/crashlytics.spec.ts b/spec/providers/crashlytics.spec.ts deleted file mode 100644 index 59e745dfa..000000000 --- a/spec/providers/crashlytics.spec.ts +++ /dev/null @@ -1,157 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2017 Firebase -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import { expect } from 'chai'; - -import { apps as appsNamespace } from '../../src/apps'; -import * as functions from '../../src/index'; -import * as crashlytics from '../../src/providers/crashlytics'; - -describe('Crashlytics Functions', () => { - describe('Issue Builder', () => { - before(() => { - appsNamespace.init(); - process.env.GCLOUD_PROJECT = 'project1'; - }); - - after(() => { - delete appsNamespace.singleton; - delete process.env.GCLOUD_PROJECT; - }); - - it('should allow both region and runtime options to be set', () => { - const fn = functions - .region('us-east1') - .runWith({ - timeoutSeconds: 90, - memory: '256MB', - }) - .crashlytics.issue() - .onNew((issue) => issue); - - expect(fn.__trigger.regions).to.deep.equal(['us-east1']); - expect(fn.__trigger.availableMemoryMb).to.deep.equal(256); - expect(fn.__trigger.timeout).to.deep.equal('90s'); - }); - - describe('#onNew', () => { - it('should return a TriggerDefinition with appropriate values', () => { - const cloudFunction = crashlytics.issue().onNew((data) => null); - expect(cloudFunction.__trigger).to.deep.equal({ - eventTrigger: { - eventType: 'providers/firebase.crashlytics/eventTypes/issue.new', - resource: 'projects/project1', - service: 'fabric.io', - }, - }); - }); - }); - - describe('#onRegressed', () => { - it('should return a TriggerDefinition with appropriate values', () => { - const cloudFunction = crashlytics.issue().onRegressed((data) => null); - expect(cloudFunction.__trigger).to.deep.equal({ - eventTrigger: { - eventType: - 'providers/firebase.crashlytics/eventTypes/issue.regressed', - resource: 'projects/project1', - service: 'fabric.io', - }, - }); - }); - }); - - describe('#onVelocityAlert', () => { - it('should return a TriggerDefinition with appropriate values', () => { - const cloudFunction = crashlytics - .issue() - .onVelocityAlert((data) => null); - expect(cloudFunction.__trigger).to.deep.equal({ - eventTrigger: { - eventType: - 'providers/firebase.crashlytics/eventTypes/issue.velocityAlert', - resource: 'projects/project1', - service: 'fabric.io', - }, - }); - }); - }); - - describe('HandlerBuilder', () => { - describe('#onNew', () => { - it('should return a CloudFunction with appropriate values', () => { - const cloudFunction = functions.handler.crashlytics.issue.onNew( - (testIssue) => { - return ( - testIssue.issueId + testIssue.issueTitle + testIssue.createTime - ); - } - ); - expect(cloudFunction.__trigger).to.deep.equal({}); - }); - }); - - describe('#onRegressed', () => { - it('should return a CloudFunction with appropriate values', () => { - const cloudFunction = functions.handler.crashlytics.issue.onRegressed( - (testIssue) => { - return ( - testIssue.issueId + testIssue.issueTitle + testIssue.createTime - ); - } - ); - expect(cloudFunction.__trigger).to.deep.equal({}); - }); - }); - - describe('#onVelocityAlert', () => { - it('should return a CloudFunction with appropriate values', () => { - const cloudFunction = functions.handler.crashlytics.issue.onVelocityAlert( - (testIssue) => { - return ( - testIssue.issueId + testIssue.issueTitle + testIssue.createTime - ); - } - ); - expect(cloudFunction.__trigger).to.deep.equal({}); - }); - }); - }); - }); - - describe('process.env.GCLOUD_PROJECT not set', () => { - it('should not throw if __trigger is not accessed', () => { - expect(() => crashlytics.issue().onNew(() => null)).to.not.throw(Error); - }); - - it('should throw if __trigger is accessed', () => { - expect(() => crashlytics.issue().onNew(() => null).__trigger).to.throw( - Error - ); - }); - - it('should not throw when #run is called', () => { - const cf = crashlytics.issue().onNew(() => null); - expect(cf.run).to.not.throw(Error); - }); - }); -}); diff --git a/src/function-builder.ts b/src/function-builder.ts index 6f50e6bde..9d016d817 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -35,7 +35,6 @@ import { } from './function-configuration'; import * as analytics from './providers/analytics'; import * as auth from './providers/auth'; -import * as crashlytics from './providers/crashlytics'; import * as database from './providers/database'; import * as firestore from './providers/firestore'; import * as https from './providers/https'; @@ -311,16 +310,6 @@ export class FunctionBuilder { }; } - get crashlytics() { - return { - /** - * Handle events related to Crashlytics issues. An issue in Crashlytics is - * an aggregation of crashes which have a shared root cause. - */ - issue: () => crashlytics._issueWithOptions(this.options), - }; - } - get analytics() { return { /** diff --git a/src/handler-builder.ts b/src/handler-builder.ts index ea0f4af74..ad4cd1541 100644 --- a/src/handler-builder.ts +++ b/src/handler-builder.ts @@ -26,7 +26,6 @@ import { apps } from './apps'; import { CloudFunction, EventContext, HttpsFunction } from './cloud-functions'; import * as analytics from './providers/analytics'; import * as auth from './providers/auth'; -import * as crashlytics from './providers/crashlytics'; import * as database from './providers/database'; import * as firestore from './providers/firestore'; import * as https from './providers/https'; @@ -182,41 +181,6 @@ export class HandlerBuilder { }; } - /** - * Create a handler for Firebase Crashlytics events. - - * `issue.onNew` handles events where the app experiences an issue for the first time. - - * @example - * ```javascript - * exports.myFunction = functions.handler.crashlytics.issue.onNew((issue) => { ... }) - * ``` - - * `issue.onRegressed` handles events where an issue reoccurs after it - * is closed in Crashlytics. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.crashlytics.issue.onRegressed((issue) => { ... }) - * ``` - - * `issue.onVelocityAlert` handles events where a statistically significant number - * of sessions in a given build crash. - * - * @example - * ```javascript - * exports.myFunction = functions.handler.crashlytics.issue.onVelocityAlert((issue) => { ... }) - * ``` - - */ - get crashlytics() { - return { - get issue() { - return new crashlytics.IssueBuilder(() => null, {}); - }, - }; - } - /** * Create a handler for Firebase Remote Config events. diff --git a/src/index.ts b/src/index.ts index dcca98172..9d49414e8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,7 +23,6 @@ // Providers: import * as analytics from './providers/analytics'; import * as auth from './providers/auth'; -import * as crashlytics from './providers/crashlytics'; import * as database from './providers/database'; import * as firestore from './providers/firestore'; import * as https from './providers/https'; @@ -43,7 +42,6 @@ export { analytics, app, auth, - crashlytics, database, firestore, handler, diff --git a/src/providers/crashlytics.ts b/src/providers/crashlytics.ts deleted file mode 100644 index 54fc26469..000000000 --- a/src/providers/crashlytics.ts +++ /dev/null @@ -1,197 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2017 Firebase -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import { - CloudFunction, - EventContext, - makeCloudFunction, -} from '../cloud-functions'; -import { DeploymentOptions } from '../function-configuration'; - -/** @hidden */ -export const provider = 'google.firebase.crashlytics'; -/** @hidden */ -export const service = 'fabric.io'; - -/** - * Registers a Cloud Function to handle Crashlytics issue events. - * - * @returns Crashlytics issue event builder interface. - */ -export function issue() { - return _issueWithOptions({}); -} - -/** @hidden */ -export function _issueWithOptions(options: DeploymentOptions) { - return new IssueBuilder(() => { - if (!process.env.GCLOUD_PROJECT) { - throw new Error('process.env.GCLOUD_PROJECT is not set.'); - } - return 'projects/' + process.env.GCLOUD_PROJECT; - }, options); -} - -/** The Firebase Crashlytics issue builder interface. */ -export class IssueBuilder { - /** @hidden */ - constructor( - private triggerResource: () => string, - private options: DeploymentOptions - ) {} - - /** @hidden */ - onNewDetected(handler: any): Error { - throw new Error('"onNewDetected" is now deprecated, please use "onNew"'); - } - - /** - * Event handler that fires every time a new issue occurs in a project. - * - * @param handler Event handler that fires every time a new issue event occurs. - * @example - * ```javascript - * exports.postOnNewIssue = functions.crashlytics.issue().onNew(event => { - * const { data } = event; - * issueId = data.issueId; - * issueTitle = data.issueTitle; - * const slackMessage = ` There's a new issue (${issueId}) ` + - * `in your app - ${issueTitle}`; - * return notifySlack(slackMessage).then(() => { - * functions.logger.info(`Posted new issue ${issueId} successfully to Slack`); - * }); - * }); - * ``` - */ - onNew( - handler: (issue: Issue, context: EventContext) => PromiseLike | any - ): CloudFunction { - return this.onEvent(handler, 'issue.new'); - } - - /** - * Event handler that fires every time a regressed issue reoccurs in a project. - * - * @param handler Event handler that fires every time a regressed issue event occurs. - */ - onRegressed( - handler: (issue: Issue, context: EventContext) => PromiseLike | any - ): CloudFunction { - return this.onEvent(handler, 'issue.regressed'); - } - - /** - * Event handler that fires every time a velocity alert occurs in a project. - * - * @param handler handler that fires every time a velocity alert issue event occurs. - */ - onVelocityAlert( - handler: (issue: Issue, context: EventContext) => PromiseLike | any - ): CloudFunction { - return this.onEvent(handler, 'issue.velocityAlert'); - } - - private onEvent( - handler: (issue: Issue, context: EventContext) => PromiseLike | any, - eventType: string - ): CloudFunction { - return makeCloudFunction({ - handler, - provider, - eventType, - service, - legacyEventType: `providers/firebase.crashlytics/eventTypes/${eventType}`, - triggerResource: this.triggerResource, - options: this.options, - }); - } -} - -/** - * Interface representing a Firebase Crashlytics event that was logged for a specific issue. - */ -export interface Issue { - /** Crashlytics-provided issue ID. */ - issueId: string; - - /** Crashlytics-provided issue title. */ - issueTitle: string; - - /** AppInfo interface describing the App. */ - appInfo: AppInfo; - - /** - * UTC when the issue occurred in ISO8601 standard representation. - * - * Example: 1970-01-17T10:52:15.661-08:00 - */ - createTime: string; - - /** - * UTC When the issue was closed in ISO8601 standard representation. - * - * Example: 1970-01-17T10:52:15.661-08:00 - */ - resolvedTime?: string; - - /** Information about the velocity alert, like number of crashes and percentage of users affected by the issue. */ - velocityAlert?: VelocityAlert; -} - -/** Interface representing Firebase Crashlytics VelocityAlert data. */ -export interface VelocityAlert { - /** - * The percentage of sessions which have been impacted by this issue. - * - * Example: .04 - */ - crashPercentage: number; - - /** The number of crashes that this issue has caused. */ - crashes: number; -} - -/** Interface representing Firebase Crashlytics AppInfo data. */ -export interface AppInfo { - /** - * The app's name. - * - * Example: "My Awesome App". - */ - appName: string; - - /** The app's platform. - * - * Examples: "android", "ios". - */ - appPlatform: string; - - /** Unique application identifier within an app store, either the Android package name or the iOS bundle id. */ - appId: string; - - /** - * The app's version name. - * - * Examples: "1.0", "4.3.1.1.213361", "2.3 (1824253)", "v1.8b22p6". - */ - latestAppVersion: string; -} From f2435b1eefeaba80c0bbd1578d6dbd3650630996 Mon Sep 17 00:00:00 2001 From: joehan Date: Tue, 2 Mar 2021 14:30:10 -0800 Subject: [PATCH 192/437] Cleaning up crashlytics trigger (#868) --- src/cloud-functions.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 126997c1d..272920c20 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -99,9 +99,6 @@ export interface EventContext { * * `google.analytics.event.log` * * `google.firebase.auth.user.create` * * `google.firebase.auth.user.delete` - * * `google.firebase.crashlytics.issue.new` - * * `google.firebase.crashlytics.issue.regressed` - * * `google.firebase.crashlytics.issue.velocityAlert` * * `google.firebase.database.ref.write` * * `google.firebase.database.ref.create` * * `google.firebase.database.ref.update` From ee793c77f1931d44adb3500bc2eed37d4a674370 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 3 Mar 2021 13:44:43 -0800 Subject: [PATCH 193/437] Update integration test so that it works again. (#869) * Updated node versions under test * Updated node dependencies to modern versions * Removed lodash dependency * Minor updates to TypeScript annotations/style * Fix URL and authentication when invoking a cloud schedule * Fix Cloud Schedule test to not finish until tests are complete --- integration_test/firestore.rules | 2 + integration_test/functions/src/https-tests.ts | 5 +- integration_test/functions/src/index.ts | 234 +++++++++--------- .../functions/src/pubsub-tests.ts | 25 +- .../functions/src/testLab-tests.ts | 3 +- .../functions/src/testLab-utils.ts | 3 +- integration_test/functions/src/testing.ts | 64 +++-- ...ckage.node8.json => package.json.template} | 12 +- integration_test/package.node10.json | 23 -- integration_test/run_tests.sh | 39 ++- 10 files changed, 202 insertions(+), 208 deletions(-) rename integration_test/{package.node8.json => package.json.template} (55%) delete mode 100644 integration_test/package.node10.json diff --git a/integration_test/firestore.rules b/integration_test/firestore.rules index e8f8d7997..d9df6d5d1 100644 --- a/integration_test/firestore.rules +++ b/integration_test/firestore.rules @@ -1,3 +1,5 @@ +rules_version = "2"; + service cloud.firestore { match /databases/{database}/documents { match /{document=**} { diff --git a/integration_test/functions/src/https-tests.ts b/integration_test/functions/src/https-tests.ts index 6af0f9fac..18972bd47 100644 --- a/integration_test/functions/src/https-tests.ts +++ b/integration_test/functions/src/https-tests.ts @@ -1,13 +1,12 @@ import * as functions from 'firebase-functions'; -import * as _ from 'lodash'; import { expectEq, TestSuite } from './testing'; const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1'; export const callableTests: any = functions.region(REGION).https.onCall((d) => { return new TestSuite('https onCall') - .it('should have the correct data', (data) => - expectEq(_.get(data, 'foo'), 'bar') + .it('should have the correct data', (data: any) => + expectEq(data?.foo, 'bar') ) .run(d.testId, d); }); diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index 2ac16e5fa..0a980aa06 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -3,6 +3,7 @@ import * as admin from 'firebase-admin'; import * as functions from 'firebase-functions'; import * as fs from 'fs'; import * as https from 'https'; +import { PubSub } from '@google-cloud/pubsub'; export * from './pubsub-tests'; export * from './database-tests'; @@ -18,6 +19,7 @@ import * as utils from './test-utils'; import * as testLab from './testLab-utils'; import 'firebase-functions'; // temporary shim until process.env.FIREBASE_CONFIG available natively in GCF(BUG 63586213) +import { config } from 'firebase-functions'; const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG); admin.initializeApp(); const REGION = functions.config().functions.test_region; @@ -37,26 +39,42 @@ function callHttpsTrigger(name: string, data: any, baseUrl) { ); } -function callScheduleTrigger(functionName: string, region: string) { - return new Promise((resolve, reject) => { +async function callScheduleTrigger(functionName: string, region: string) { + const accessToken = await admin.credential + .applicationDefault() + .getAccessToken(); + return new Promise((resolve, reject) => { const request = https.request( { method: 'POST', host: 'cloudscheduler.googleapis.com', - path: `projects/${firebaseConfig.projectId}/locations/us-central1/jobs/firebase-schedule-${functionName}-${region}:run`, + path: `/v1/projects/${firebaseConfig.projectId}/locations/us-central1/jobs/firebase-schedule-${functionName}-${region}:run`, headers: { 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken.access_token}`, }, }, (response) => { + if (response.statusCode! / 100 != 2) { + reject( + new Error('Failed request with status ' + response.statusCode!) + ); + return; + } let body = ''; response.on('data', (chunk) => { body += chunk; }); - response.on('end', () => resolve(body)); + response.on('end', () => { + console.log(`Successfully scheduled function ${functionName}`); + resolve(body); + }); } ); - request.on('error', reject); + request.on('error', (err) => { + console.error('Failed to schedule cloud scheduler job with error', err); + reject(err); + }); request.write('{}'); request.end(); }); @@ -67,14 +85,13 @@ export const integrationTests: any = functions .runWith({ timeoutSeconds: 540, }) - .https.onRequest((req: Request, resp: Response) => { + .https.onRequest(async (req: Request, resp: Response) => { // We take the base url for our https call (cloudfunctions.net, txckloud.net, etc) from the request // so that it changes with the environment that the tests are run in const baseUrl = req.hostname .split('.') .slice(1) .join('.'); - const pubsub: any = require('@google-cloud/pubsub')(); const testId = admin .database() .ref() @@ -83,114 +100,107 @@ export const integrationTests: any = functions .database() .ref(`testRuns/${testId}/timestamp`) .set(Date.now()); + const testIdRef = admin.database().ref(`testRuns/${testId}`); console.log('testId is: ', testId); fs.writeFile('/tmp/' + testId + '.txt', 'test', () => {}); - return Promise.all([ - // A database write to trigger the Firebase Realtime Database tests. - admin - .database() - .ref(`dbTests/${testId}/start`) - .set({ '.sv': 'timestamp' }), - // A Pub/Sub publish to trigger the Cloud Pub/Sub tests. - pubsub - .topic('pubsubTests') - .publisher() - .publish(Buffer.from(JSON.stringify({ testId }))), - // A user creation to trigger the Firebase Auth user creation tests. - admin - .auth() - .createUser({ - email: `${testId}@fake.com`, - password: 'secret', - displayName: `${testId}`, - }) - .then((userRecord) => { - // A user deletion to trigger the Firebase Auth user deletion tests. - admin.auth().deleteUser(userRecord.uid); - }), - // A firestore write to trigger the Cloud Firestore tests. - admin - .firestore() - .collection('tests') - .doc(testId) - .set({ test: testId }), - // Invoke a callable HTTPS trigger. - callHttpsTrigger('callableTests', { foo: 'bar', testId }, baseUrl), - // A Remote Config update to trigger the Remote Config tests. - admin.credential - .applicationDefault() - .getAccessToken() - .then((accessToken) => { - const options = { - hostname: 'firebaseremoteconfig.googleapis.com', - path: `/v1/projects/${firebaseConfig.projectId}/remoteConfig`, - method: 'PUT', - headers: { - Authorization: 'Bearer ' + accessToken.access_token, - 'Content-Type': 'application/json; UTF-8', - 'Accept-Encoding': 'gzip', - 'If-Match': '*', - }, - }; - const request = https.request(options, (resp) => {}); - request.write(JSON.stringify({ version: { description: testId } })); - request.end(); - }), - // A storage upload to trigger the Storage tests - admin - .storage() - .bucket() - .upload('/tmp/' + testId + '.txt'), - testLab.startTestRun(firebaseConfig.projectId, testId), - // Invoke the schedule for our scheduled function to fire - callScheduleTrigger('schedule', 'us-central1'), - ]) - .then(() => { - // On test completion, check that all tests pass and reply "PASS", or provide further details. - console.log('Waiting for all tests to report they pass...'); - const ref = admin.database().ref(`testRuns/${testId}`); - return new Promise((resolve, reject) => { - let testsExecuted = 0; - ref.on('child_added', (snapshot) => { - testsExecuted += 1; - if (snapshot.key != 'timestamp' && !snapshot.val().passed) { - reject( - new Error( - `test ${snapshot.key} failed; see database for details.` - ) - ); - return; - } - console.log( - `${snapshot.key} passed (${testsExecuted} of ${numTests})` - ); - if (testsExecuted < numTests) { - // Not all tests have completed. Wait longer. - return; - } - // All tests have passed! - resolve(); - }); - }) - .then(() => { - ref.off(); // No more need to listen. - return Promise.resolve(); + try { + await Promise.all([ + // A database write to trigger the Firebase Realtime Database tests. + admin + .database() + .ref(`dbTests/${testId}/start`) + .set({ '.sv': 'timestamp' }), + // A Pub/Sub publish to trigger the Cloud Pub/Sub tests. + new PubSub() + .topic('pubsubTests') + .publish(Buffer.from(JSON.stringify({ testId }))), + // A user creation to trigger the Firebase Auth user creation tests. + admin + .auth() + .createUser({ + email: `${testId}@fake.com`, + password: 'secret', + displayName: `${testId}`, }) - .catch((err) => { - ref.off(); // No more need to listen. - return Promise.reject(err); - }); - }) - .then(() => { - console.log('All tests pass!'); - resp.status(200).send('PASS \n'); - }) - .catch((err) => { - console.log(`Some tests failed: ${err}`); - resp - .status(500) - .send( - `FAIL - details at https://${process.env.GCLOUD_PROJECT}.firebaseio.com/testRuns/${testId}` + .then((userRecord) => { + // A user deletion to trigger the Firebase Auth user deletion tests. + admin.auth().deleteUser(userRecord.uid); + }), + // A firestore write to trigger the Cloud Firestore tests. + admin + .firestore() + .collection('tests') + .doc(testId) + .set({ test: testId }), + // Invoke a callable HTTPS trigger. + callHttpsTrigger('callableTests', { foo: 'bar', testId }, baseUrl), + // A Remote Config update to trigger the Remote Config tests. + admin.credential + .applicationDefault() + .getAccessToken() + .then((accessToken) => { + const options = { + hostname: 'firebaseremoteconfig.googleapis.com', + path: `/v1/projects/${firebaseConfig.projectId}/remoteConfig`, + method: 'PUT', + headers: { + Authorization: 'Bearer ' + accessToken.access_token, + 'Content-Type': 'application/json; UTF-8', + 'Accept-Encoding': 'gzip', + 'If-Match': '*', + }, + }; + const request = https.request(options, (resp) => {}); + request.write(JSON.stringify({ version: { description: testId } })); + request.end(); + }), + // A storage upload to trigger the Storage tests + admin + .storage() + .bucket() + .upload('/tmp/' + testId + '.txt'), + testLab.startTestRun(firebaseConfig.projectId, testId), + // Invoke the schedule for our scheduled function to fire + callScheduleTrigger('schedule', 'us-central1'), + ]); + + // On test completion, check that all tests pass and reply "PASS", or provide further details. + console.log('Waiting for all tests to report they pass...'); + await new Promise((resolve, reject) => { + setTimeout(() => reject(new Error('Timeout')), 5 * 60 * 1000); + let testsExecuted = 0; + testIdRef.on('child_added', (snapshot) => { + testsExecuted += 1; + if (snapshot.key != 'timestamp' && !snapshot.val().passed) { + reject( + new Error( + `test ${snapshot.key} failed; see database for details.` + ) + ); + return; + } + console.log( + `${snapshot.key} passed (${testsExecuted} of ${numTests})` ); + if (testsExecuted < numTests) { + // Not all tests have completed. Wait longer. + return; + } + // All tests have passed! + resolve(); + }); }); + console.log('All tests pass!'); + resp.status(200).send('PASS \n'); + } catch (err) { + console.log(`Some tests failed: ${err}`); + resp + .status(500) + .send( + `FAIL - details at ${functions.firebaseConfig() + .databaseURL!}/testRuns/${testId}` + ); + } finally { + testIdRef.off('child_added'); + } }); diff --git a/integration_test/functions/src/pubsub-tests.ts b/integration_test/functions/src/pubsub-tests.ts index e2a3f1bd0..a21c2011a 100644 --- a/integration_test/functions/src/pubsub-tests.ts +++ b/integration_test/functions/src/pubsub-tests.ts @@ -66,20 +66,15 @@ export const schedule: any = functions .region(REGION) .pubsub.schedule('every 10 hours') // This is a dummy schedule, since we need to put a valid one in. // For the test, the job is triggered by the jobs:run api - .onRun((context) => { - let testId; + .onRun(async (context) => { const db = admin.database(); - return new Promise(async (resolve, reject) => { - await db - .ref('testRuns') - .orderByChild('timestamp') - .limitToLast(1) - .on('value', (snap) => { - testId = Object.keys(snap.val())[0]; - new TestSuite('pubsub scheduleOnRun') - .it('should trigger when the scheduler fires', () => success()) - .run(testId, null); - }); - resolve(); - }); + const snap = await db + .ref('testRuns') + .orderByChild('timestamp') + .limitToLast(1) + .once('value'); + const testId = Object.keys(snap.val())[0]; + return new TestSuite('pubsub scheduleOnRun') + .it('should trigger when the scheduler fires', () => success()) + .run(testId, null); }); diff --git a/integration_test/functions/src/testLab-tests.ts b/integration_test/functions/src/testLab-tests.ts index cf4b2f062..586c3a0cb 100644 --- a/integration_test/functions/src/testLab-tests.ts +++ b/integration_test/functions/src/testLab-tests.ts @@ -1,5 +1,4 @@ import * as functions from 'firebase-functions'; -import * as _ from 'lodash'; import { TestSuite, expectEq } from './testing'; import TestMatrix = functions.testLab.TestMatrix; const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1'; @@ -24,5 +23,5 @@ export const testLabTests: any = functions expectEq(matrix.state, 'INVALID') ) - .run(_.get(matrix, 'clientInfo.details.testId'), matrix, context); + .run(matrix?.clientInfo?.details?.testId, matrix, context); }); diff --git a/integration_test/functions/src/testLab-utils.ts b/integration_test/functions/src/testLab-utils.ts index 2f457e70d..6f86b0a1f 100644 --- a/integration_test/functions/src/testLab-utils.ts +++ b/integration_test/functions/src/testLab-utils.ts @@ -1,7 +1,6 @@ import * as http from 'http'; import * as https from 'https'; import * as admin from 'firebase-admin'; -import * as _ from 'lodash'; import * as utils from './test-utils'; interface AndroidDevice { @@ -35,7 +34,7 @@ async function fetchDefaultDevice( requestOptions(accessToken, 'GET', '/v1/testEnvironmentCatalog/ANDROID') ); const data = JSON.parse(response); - const models = _.get(data, 'androidDeviceCatalog.models', []); + const models = data?.androidDeviceCatalog?.models || []; const defaultModels = models.filter( (m) => m.tags !== undefined && diff --git a/integration_test/functions/src/testing.ts b/integration_test/functions/src/testing.ts index 43d3e69d5..228203d31 100644 --- a/integration_test/functions/src/testing.ts +++ b/integration_test/functions/src/testing.ts @@ -1,6 +1,5 @@ import * as firebase from 'firebase-admin'; import { EventContext } from 'firebase-functions'; -import * as _ from 'lodash'; export type TestCase = (data: T, context?: EventContext) => any; export interface TestCaseMap { @@ -66,43 +65,70 @@ function failure(reason: string) { return Promise.reject(reason); } -export function evaluate(value, errMsg) { +export function evaluate(value: boolean, errMsg: string) { if (value) { return success(); } return failure(errMsg); } -export function expectEq(left, right) { +export function expectEq(left: any, right: any) { return evaluate( - left === right, + left == right, JSON.stringify(left) + ' does not equal ' + JSON.stringify(right) ); } -export function expectDeepEq(left, right) { +function deepEq(left: any, right: any) { + if (left === right) { + return true; + } + + if (!(left instanceof Object && right instanceof Object)) { + return false; + } + + if (Object.keys(left).length != Object.keys(right).length) { + return false; + } + + for (let key in left) { + if (!right.hasOwnProperty(key)) { + return false; + } + if (!deepEq(left[key], right[key])) { + return false; + } + } + + return true; +} + +export function expectDeepEq(left: any, right: any) { return evaluate( - _.isEqual(left, right), - JSON.stringify(left) + ' does not equal ' + JSON.stringify(right) + deepEq(left, right), + `${JSON.stringify(left)} does not deep equal ${JSON.stringify(right)}` ); } -export function expectMatches(input: string, regexp) { +export function expectMatches(input: string, regexp: RegExp) { return evaluate( - input.match(regexp), + input.match(regexp) !== null, "Input '" + input + "' did not match regexp '" + regexp + "'" ); } -export function expectReject(f) { - return function(event) { - return Promise.resolve() - .then(() => f(event)) - .then( - () => { - throw new Error('Test should have returned a rejected promise'); - }, - () => true // A rejection is what we expected, and so is a positive result. - ); +export function expectReject(f: (e: EventType) => Promise) { + return async (event: EventType) => { + let rejected = false; + try { + await f(event); + } catch { + rejected = true; + } + + if (!rejected) { + throw new Error('Test should have returned a rejected promise'); + } }; } diff --git a/integration_test/package.node8.json b/integration_test/package.json.template similarity index 55% rename from integration_test/package.node8.json rename to integration_test/package.json.template index 8803f2fdc..5bcd8761b 100644 --- a/integration_test/package.node8.json +++ b/integration_test/package.json.template @@ -5,19 +5,17 @@ "build": "./node_modules/.bin/tsc" }, "dependencies": { - "@google-cloud/pubsub": "~0.19.0", - "@types/google-cloud__pubsub": "^0.18.0", - "@types/lodash": "~4.14.41", - "firebase-admin": "^8.0.0", - "firebase-functions": "./firebase-functions.tgz", + "@google-cloud/pubsub": "^2.10.0", + "firebase-admin": "^9.1.0", + "firebase-functions": "__SDK_TARBALL__", "lodash": "~4.17.2" }, "main": "lib/index.js", "devDependencies": { - "typescript": "~3.6.0" + "typescript": "~4.2.2" }, "engines": { - "node": "8" + "node": "__NODE_VERSION__" }, "private": true } diff --git a/integration_test/package.node10.json b/integration_test/package.node10.json deleted file mode 100644 index cf5eeeb25..000000000 --- a/integration_test/package.node10.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "functions", - "description": "Integration test for the Firebase SDK for Google Cloud Functions", - "scripts": { - "build": "./node_modules/.bin/tsc" - }, - "dependencies": { - "@google-cloud/pubsub": "~0.19.0", - "@types/google-cloud__pubsub": "^0.18.0", - "@types/lodash": "~4.14.41", - "firebase-admin": "^8.0.0", - "firebase-functions": "./firebase-functions.tgz", - "lodash": "~4.17.2" - }, - "main": "lib/index.js", - "devDependencies": { - "typescript": "~3.6.0" - }, - "engines": { - "node": "10" - }, - "private": true -} diff --git a/integration_test/run_tests.sh b/integration_test/run_tests.sh index ba567536a..100e24a32 100755 --- a/integration_test/run_tests.sh +++ b/integration_test/run_tests.sh @@ -58,21 +58,16 @@ function unset_region { fi } -function pick_node8 { +# Creates a Package.json from package.json.template +# @param timestmap of the current SDK build +# @param Node version to test under +function create_package_json { cd "${DIR}" - cp package.node8.json functions/package.json + cp package.json.template functions/package.json # we have to do the -e flag here so that it work both on linux and mac os, but that creates an extra # backup file called package.json-e that we should clean up afterwards. - sed -i -e "s/firebase-functions.tgz/firebase-functions-${TIMESTAMP}.tgz/g" functions/package.json - rm -f functions/package.json-e -} - -function pick_node10 { - cd "${DIR}" - cp package.node10.json functions/package.json - # we have to do the -e flag here so that it work both on linux and mac os, but that creates an extra - # backup file called package.json-e that we should clean up afterwards. - sed -i -e "s/firebase-functions.tgz/firebase-functions-${TIMESTAMP}.tgz/g" functions/package.json + sed -i -e "s/__SDK_TARBALL__/firebase-functions-$1.tgz/g" functions/package.json + sed -i -e "s/__NODE_VERSION__/$2/g" functions/package.json rm -f functions/package.json-e } @@ -143,19 +138,13 @@ build_sdk delete_all_functions set_region -# Node 8 tests -pick_node8 -install_deps -announce "Deploying functions to Node 8 runtime ..." -deploy -run_tests - -# Node 10 tests -pick_node10 -install_deps -announce "Re-deploying the same functions to Node 10 runtime ..." -deploy -run_tests +for version in 10 12 14; do + create_package_json $TIMESTAMP $version + install_deps + announce "Re-deploying the same functions to Node $version runtime ..." + deploy + run_tests +done # Cleanup cleanup From d7d455a40f1a0f3a25de08e85175e27ee7e17f7c Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Fri, 5 Mar 2021 13:30:52 -0800 Subject: [PATCH 194/437] Stop running Node 8 tests in GitHub (#870) --- .github/workflows/test.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9a31678c2..c56d65e12 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,7 +13,6 @@ jobs: strategy: matrix: node-version: - - 8.x - 10.x - 12.x - 14.x From 92916aa42b8b8e85fe7416ee0795744206ef9afa Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Wed, 17 Mar 2021 10:05:33 -0700 Subject: [PATCH 195/437] Add tests to verify logger does not alter its parameters (#873) --- spec/logger.spec.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/logger.spec.ts b/spec/logger.spec.ts index dda16087c..48258ffac 100644 --- a/spec/logger.spec.ts +++ b/spec/logger.spec.ts @@ -109,6 +109,22 @@ describe(`logger (${ }); }); + it('should not alter parameters that are logged', () => { + const circ: any = { b: 'foo' }; + circ.array = [circ]; + circ.object = circ; + const entry: logger.LogEntry = { + severity: 'ERROR', + message: 'testing circular', + circ, + }; + logger.write(entry); + + expect(circ.array[0].b).to.equal('foo'); + expect(circ.object.b).to.equal('foo'); + expect(circ.object.array[0].object.array[0].b).to.equal('foo'); + }); + for (const severity of ['DEBUG', 'INFO', 'NOTICE']) { it(`should output ${severity} severity to stdout`, () => { let entry: logger.LogEntry = { From a4019540dc0ced5abadb0a3c5ddaf21f7881f79e Mon Sep 17 00:00:00 2001 From: joehan Date: Fri, 19 Mar 2021 10:43:32 -0700 Subject: [PATCH 196/437] Remove crashlytics from toc.yaml, and dont try to delete css.map files if they dont exist (#871) --- docgen/content-sources/toc.yaml | 12 ------------ docgen/generate-docs.js | 4 +++- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/docgen/content-sources/toc.yaml b/docgen/content-sources/toc.yaml index 5758eb74e..e970fc80c 100644 --- a/docgen/content-sources/toc.yaml +++ b/docgen/content-sources/toc.yaml @@ -69,18 +69,6 @@ toc: - title: 'UserRecord' path: /docs/reference/functions/providers_auth_.html#userrecord - - title: 'functions.crashlytics' - path: /docs/reference/functions/providers_crashlytics_.html - section: - - title: 'Issue' - path: /docs/reference/functions/providers_crashlytics_.issue.html - - title: 'IssueBuilder' - path: /docs/reference/functions/providers_crashlytics_.issuebuilder.html - - title: 'AppInfo' - path: /docs/reference/functions/providers_crashlytics_.appinfo.html - - title: 'VelocityAlert' - path: /docs/reference/functions/providers_crashlytics_.velocityalert.html - - title: 'functions.firestore' path: /docs/reference/functions/providers_firestore_.html section: diff --git a/docgen/generate-docs.js b/docgen/generate-docs.js index 01275ae2d..1ce7ff910 100644 --- a/docgen/generate-docs.js +++ b/docgen/generate-docs.js @@ -323,7 +323,9 @@ Promise.all([ // Clean up temp home markdown file. (Nothing needs to wait for this.) fs.unlink(tempHomePath); // Devsite doesn't like css.map files. - return fs.unlink(`${docPath}/assets/css/main.css.map`); + if (fs.existsSync(`${docPath}/assets/css/main.css.map`)) { + return fs.unlink(`${docPath}/assets/css/main.css.map`); + } }) // Write out TOC file. Do this after Typedoc step to prevent Typedoc // erroring when it finds an unexpected file in the target dir. From 98bf467aa436f7e27969a8ad3887abe0345f66d3 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Tue, 23 Mar 2021 10:34:24 -0700 Subject: [PATCH 197/437] Update generate-docs to modern JS (#874) Update generate-docs to modern JS. Fixes a few places where promises were left dangling. --- docgen/generate-docs.js | 313 +++++++++++++++++++--------------------- 1 file changed, 145 insertions(+), 168 deletions(-) diff --git a/docgen/generate-docs.js b/docgen/generate-docs.js index 1ce7ff910..d36edb7e8 100644 --- a/docgen/generate-docs.js +++ b/docgen/generate-docs.js @@ -20,7 +20,6 @@ const fs = require('mz/fs'); const path = require('path'); const yargs = require('yargs'); const yaml = require('js-yaml'); -const _ = require('lodash'); const repoPath = path.resolve(`${__dirname}/..`); @@ -72,59 +71,52 @@ function runTypedoc() { * Moves files from subdir to root. * @param {string} subdir Subdir to move files out of. */ -function moveFilesToRoot(subdir) { - return exec(`mv ${docPath}/${subdir}/* ${docPath}`) - .then(() => { - exec(`rmdir ${docPath}/${subdir}`); - }) - .catch(e => console.error(e)); +async function moveFilesToRoot(subdir) { + await exec(`mv ${docPath}/${subdir}/* ${docPath}`) + await exec(`rmdir ${docPath}/${subdir}`); } /** * Renames files to remove the leading underscores. * We need to do this because devsite hides these files. - * Example: + * Example: * _cloud_functions_.resource.html => cloud_functions_.resource.html */ -function renameFiles() { - return fs.readdir(docPath).then(files => { - console.log(files); - files.forEach(file => { - let newFileName = file; - if (_.startsWith(file, "_") && _.endsWith(file, "html")) { - newFileName = _.trimStart(file, "_"); - fs.rename(`${docPath}/${file}`, `${docPath}/${newFileName}`, (err) => { - if (err) console.log(err) - }); - } - }) - }) +async function renameFiles() { + const files = await fs.readdir(docPath); + const renames = []; + for (const file of files) { + if (file.startsWith("_") && file.endsWith("html")) { + let newFileName = file.substring(1); + renames.push(fs.rename(`${docPath}/${file}`, `${docPath}/${newFileName}`)); + } + } + await Promise.all(renames); } /** * Reformat links to match flat structure. * @param {string} file File to fix links in. */ -function fixLinks(file) { - return fs.readFile(file, 'utf8').then(data => { - data = addTypeAliasLinks(data); - const flattenedLinks = data - .replace(/\.\.\//g, '') - .replace(/(modules|interfaces|classes)\//g, '') - .replace(/\"_/g, '"'); - let caseFixedLinks = flattenedLinks; - for (const lower in lowerToUpperLookup) { - const re = new RegExp(lower, 'g'); - caseFixedLinks = caseFixedLinks.replace(re, lowerToUpperLookup[lower]); - } - return fs.writeFile(file, caseFixedLinks); - }); +async function fixLinks(file) { + let data = await fs.readFile(file, 'utf8'); + data = addTypeAliasLinks(data); + const flattenedLinks = data + .replace(/\.\.\//g, '') + .replace(/(modules|interfaces|classes)\//g, '') + .replace(/\"_/g, '"'); + let caseFixedLinks = flattenedLinks; + for (const lower in lowerToUpperLookup) { + const re = new RegExp(lower, 'g'); + caseFixedLinks = caseFixedLinks.replace(re, lowerToUpperLookup[lower]); + } + return fs.writeFile(file, caseFixedLinks); } -/** +/** * Adds links to external documentation for type aliases that * reference an external library. - * + * * @param data File data to add external library links to. */ function addTypeAliasLinks(data) { @@ -136,22 +128,20 @@ function addTypeAliasLinks(data) { const fileTags = htmlDom.window.document.querySelectorAll(".tsd-signature-type"); fileTags.forEach(tag => { const mapping = typeMap[tag.textContent]; - if (mapping) { - console.log('Adding link to '+tag.textContent+" documentation."); - - // Add the corresponding document link to this type - const linkChild = htmlDom.window.document.createElement('a'); - linkChild.setAttribute('href', mapping); - linkChild.textContent = tag.textContent; - tag.textContent = null; - tag.appendChild(linkChild); - } - }); + if (mapping) { + console.log('Adding link to '+tag.textContent+" documentation."); + + // Add the corresponding document link to this type + const linkChild = htmlDom.window.document.createElement('a'); + linkChild.setAttribute('href', mapping); + linkChild.textContent = tag.textContent; + tag.textContent = null; + tag.appendChild(linkChild); + } + }); return htmlDom.serialize(); } -let tocText = ''; - /** * Generates temporary markdown file that will be sourced by Typedoc to * create index.html. @@ -182,37 +172,33 @@ const lowerToUpperLookup = {}; * Checks to see if any files listed in toc.yaml were not generated. * If files exist, fixes filename case to match toc.yaml version. */ -function checkForMissingFilesAndFixFilenameCase() { +async function checkForMissingFilesAndFixFilenameCase(tocText) { // Get filenames from toc.yaml. const filenames = tocText .split('\n') .filter(line => line.includes('path:')) .map(line => line.split(devsitePath)[1]); // Logs warning to console if a file from TOC is not found. - // console.log(filenames); - const fileCheckPromises = filenames.map(filename => { + const fileCheckPromises = filenames.map(async filename => { // Warns if file does not exist, fixes filename case if it does. // Preferred filename for devsite should be capitalized and taken from // toc.yaml. const tocFilePath = `${docPath}/${filename}`; // Generated filename from Typedoc will be lowercase. const generatedFilePath = `${docPath}/${filename.toLowerCase()}`; - return fs.exists(generatedFilePath).then(exists => { - if (exists) { - // Store in a lookup table for link fixing. - lowerToUpperLookup[ - `${filename.toLowerCase()}` - ] = `${filename}`; - return fs.rename(generatedFilePath, tocFilePath); - } else { - console.warn( - `Missing file: ${filename} requested ` + - `in toc.yaml but not found in ${docPath}` - ); - } - }); + if (await fs.exists(generatedFilePath)) { + // Store in a lookup table for link fixing. + lowerToUpperLookup[filename.toLowerCase()] = filename; + return fs.rename(generatedFilePath, tocFilePath); + } else { + console.warn( + `Missing file: ${filename} requested ` + + `in toc.yaml but not found in ${docPath}` + ); + } }); - return Promise.all(fileCheckPromises).then(() => filenames); + await Promise.all(fileCheckPromises); + return filenames; } /** @@ -223,40 +209,31 @@ function checkForMissingFilesAndFixFilenameCase() { * @param {Array} filenamesFromToc Filenames pulled from toc.yaml * @param {boolean} shouldRemove Should just remove the file */ -function checkForUnlistedFiles(filenamesFromToc, shouldRemove) { - return fs.readdir(docPath).then(files => { - const htmlFiles = files - .filter(filename => filename.slice(-4) === 'html'); - const removePromises = []; - htmlFiles.forEach(filename => { - if ( - !filenamesFromToc.includes(filename) && - filename !== 'index' && - filename !== 'globals' - ) { - if (shouldRemove) { - console.log( - `REMOVING ${docPath}/${filename} - not listed in toc.yaml.` - ); - removePromises.push(fs.unlink(`${docPath}/${filename}`)); - } else { - // This is just a warning, it doesn't need to finish before - // the process continues. - console.warn( - `Unlisted file: ${filename} generated ` + - `but not listed in toc.yaml.` - ); - } - } - }); - if (shouldRemove) { - return Promise.all(removePromises).then(() => - htmlFiles.filter(filename => filenamesFromToc.includes(filename)) - ); - } else { - return htmlFiles; - } - }); +async function checkForUnlistedFiles(filenamesFromToc, shouldRemove) { + const files = await fs.readdir(docPath); + const htmlFiles = files + .filter(filename => filename.slice(-4) === 'html'); + const removePromises = []; + const filesToRemove = htmlFiles + .filter(filename => !filenamesFromToc.includes(filename)) + .filter(filename => filename !== 'index' && filename != 'globals'); + if (filesToRemove.length && !shouldRemove) { + // This is just a warning, it doesn't need to finish before + // the process continues. + console.warn( + `Unlisted files: ${filesToRemove.join(", ")} generated ` + + `but not listed in toc.yaml.` + ); + return htmlFiles; + } + + await Promise.all(filesToRemove.map(filename => { + console.log( + `REMOVING ${docPath}/${filename} - not listed in toc.yaml.` + ); + return fs.unlink(`${docPath}/${filename})`); + })); + return htmlFiles.filter(filename => filenamesFromToc.includes(filename)) } /** @@ -265,7 +242,7 @@ function checkForUnlistedFiles(filenamesFromToc, shouldRemove) { * * @param {Array} htmlFiles List of html files found in generated dir. */ -function writeGeneratedFileList(htmlFiles) { +async function writeGeneratedFileList(htmlFiles) { const fileList = htmlFiles.map(filename => { return { title: filename, @@ -273,9 +250,8 @@ function writeGeneratedFileList(htmlFiles) { }; }); const generatedTocYAML = yaml.safeDump({ toc: fileList }); - return fs - .writeFile(`${docPath}/_toc_autogenerated.yaml`, generatedTocYAML) - .then(() => htmlFiles); + await fs.writeFile(`${docPath}/_toc_autogenerated.yaml`, generatedTocYAML); + return htmlFiles; } /** @@ -305,71 +281,72 @@ function fixAllLinks(htmlFiles) { * links as needed. * 5) Check for mismatches between TOC list and generated file list. */ -Promise.all([ - fs.readFile(`${contentPath}/toc.yaml`, 'utf8'), - fs.readFile(`${contentPath}/HOME.md`, 'utf8') -]) - // Read TOC and homepage text and assemble a homepage markdown file. - // This file will be sourced by Typedoc to generate index.html. - .then(([tocRaw, homeRaw]) => { - tocText = tocRaw; - return generateTempHomeMdFile(tocRaw, homeRaw); - }) - // Run main Typedoc process (uses index.d.ts and generated temp file above). - .then(runTypedoc) - .then(output => { +(async function() { + try { + const [tocRaw, homeRaw] = await Promise.all([ + fs.readFile(`${contentPath}/toc.yaml`, 'utf8'), + fs.readFile(`${contentPath}/HOME.md`, 'utf8') + ]) + + // Run main Typedoc process (uses index.d.ts and generated temp file above). + await generateTempHomeMdFile(tocRaw, homeRaw); + const output = await runTypedoc(); + // Typedoc output. console.log(output.stdout); - // Clean up temp home markdown file. (Nothing needs to wait for this.) - fs.unlink(tempHomePath); - // Devsite doesn't like css.map files. - if (fs.existsSync(`${docPath}/assets/css/main.css.map`)) { - return fs.unlink(`${docPath}/assets/css/main.css.map`); - } - }) - // Write out TOC file. Do this after Typedoc step to prevent Typedoc - // erroring when it finds an unexpected file in the target dir. - .then(() => fs.writeFile(`${docPath}/_toc.yaml`, tocText)) - // Flatten file structure. These categories don't matter to us and it makes - // it easier to manage the docs directory. - .then(() => { - return Promise.all([ + await Promise.all([ + // Clean up temp home markdown file. (Nothing needs to wait for this.) + fs.unlink(tempHomePath), + + // Devsite doesn't like css.map files. + // NOTE: This doesn't seem to actually get generated anymore, but we'll keep this here just in case. + async () => { + const cssMap = `${docPath}/assets/css/main.css.map`; + if (await fs.exists(cssMap)) { + await fs.unlink(); + } + }, + + // Write out TOC file. Do this after Typedoc step to prevent Typedoc + // erroring when it finds an unexpected file in the target dir. + fs.writeFile(`${docPath}/_toc.yaml`, tocRaw), + ]); + + // Flatten file structure. These categories don't matter to us and it makes + // it easier to manage the docs directory. + await Promise.all([ moveFilesToRoot('classes'), moveFilesToRoot('modules'), moveFilesToRoot('interfaces'), ]); - }) - // Rename files to remove the underscores since devsite hides those. - .then(renameFiles) - // Check for files listed in TOC that are missing and warn if so. - // Not blocking. - .then(checkForMissingFilesAndFixFilenameCase) - // Check for files that exist but aren't listed in the TOC and warn. - // (If API is node, actually remove the file.) - // Removal is blocking, warnings aren't. - .then(filenamesFromToc => - checkForUnlistedFiles(filenamesFromToc, false) - ) - // Write a _toc_autogenerated.yaml to record what files were created. - .then(htmlFiles => writeGeneratedFileList(htmlFiles)) - // Correct the links in all the generated html files now that files have - // all been moved to top level. - // .then(removeUnderscores) - .then(fixAllLinks) - .then(() => { - fs.readFile(`${docPath}/index.html`, 'utf8').then(data => { - // String to include devsite local variables. - const localVariablesIncludeString = `{% include "docs/web/_local_variables.html" %}\n`; - return fs.writeFile( - `${docPath}/index.html`, - localVariablesIncludeString + data - ); - }); - }) - .catch(e => { - if (e.stdout) { - console.error(e.stdout); - } else { - console.error(e); - } - }); + // Rename files to remove the underscores since devsite hides those. + await renameFiles(); + + // Check for files listed in TOC that are missing and warn if so. + // Not blocking. + const filenamesFromToc = await checkForMissingFilesAndFixFilenameCase(tocRaw); + + // Check for files that exist but aren't listed in the TOC and warn. + // (If API is node, actually remove the file.) + // Removal is blocking, warnings aren't. + const htmlFiles = await checkForUnlistedFiles(filenamesFromToc, false); + + // Write a _toc_autogenerated.yaml to record what files were created. + const fileList = await writeGeneratedFileList(htmlFiles); + + // Correct the links in all the generated html files now that files have + // all been moved to top level. + await fixAllLinks(fileList); + const data = await fs.readFile(`${docPath}/index.html`, 'utf8'); + // String to include devsite local variables. + const localVariablesIncludeString = `{% include "docs/web/_local_variables.html" %}\n`; + await fs.writeFile(`${docPath}/index.html`, localVariablesIncludeString + data); + } catch (err) { + if (err.stdout) { + console.error(err.stdout); + } else { + console.error(err); + } +} +})(); + From 29a55af955450d7758ddaa0cf3871836a2849c13 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Tue, 4 May 2021 18:46:29 -0700 Subject: [PATCH 198/437] Updating runWith enums (#884) Updating runWith enums --- CHANGELOG.md | 3 +++ src/cloud-functions.ts | 9 +++++++-- src/function-configuration.ts | 8 ++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..adc2430a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +- Functions may now be deployed with 8GB RAM +- Functions may now be deployed to europe-central2 (Warsaw) +- Functions may now reserve a minimum number of instances (updated CLI required) diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 272920c20..500e27144 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -203,7 +203,7 @@ export namespace Change { json: ChangeJson, customizer: (x: any) => T = reinterpretCast ): Change { - let before = _.assign({}, json.before); + let before = { ...json.before }; if (json.fieldMask) { before = applyFieldMask(before, json.after, json.fieldMask); } @@ -220,7 +220,7 @@ export namespace Change { after: any, fieldMask: string ) { - const before = _.assign({}, after); + const before = { ...after }; const masks = fieldMask.split(','); masks.forEach((mask) => { @@ -506,6 +506,7 @@ export function optionsToTrigger(options: DeploymentOptions) { '1GB': 1024, '2GB': 2048, '4GB': 4096, + '8GB': 8192, }; trigger.availableMemoryMb = _.get(memoryLookup, options.memory); } @@ -513,6 +514,10 @@ export function optionsToTrigger(options: DeploymentOptions) { trigger.schedule = options.schedule; } + if (options.minInstances) { + trigger.minInstances = options.minInstances; + } + if (options.maxInstances) { trigger.maxInstances = options.maxInstances; } diff --git a/src/function-configuration.ts b/src/function-configuration.ts index f5a325b41..8b2b28b81 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -8,6 +8,7 @@ export const SUPPORTED_REGIONS = [ 'us-west2', 'us-west3', 'us-west4', + 'europe-central2', 'europe-west1', 'europe-west2', 'europe-west3', @@ -43,6 +44,7 @@ export const VALID_MEMORY_OPTIONS = [ '1GB', '2GB', '4GB', + '8GB', ] as const; /** @@ -107,6 +109,12 @@ export interface RuntimeOptions { */ timeoutSeconds?: number; + /** + * Min number of actual instances allowed to be running in parallel + * Instances will be billed while idle. + */ + minInstances?: number; + /** * Max number of actual instances allowed to be running in parallel */ From 09120b64a931d41fc500f2c88e3cff0a5a72bee2 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Tue, 11 May 2021 20:16:37 -0700 Subject: [PATCH 199/437] Redactions (#886) --- CHANGELOG.md | 1 - src/function-configuration.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adc2430a1..d952d8984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,2 @@ - Functions may now be deployed with 8GB RAM - Functions may now be deployed to europe-central2 (Warsaw) -- Functions may now reserve a minimum number of instances (updated CLI required) diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 8b2b28b81..2d9925ac5 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -112,6 +112,7 @@ export interface RuntimeOptions { /** * Min number of actual instances allowed to be running in parallel * Instances will be billed while idle. + * @hidden */ minInstances?: number; From c29a8a518aba91aabe9bd02dd6d3a72718033ae7 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 12 May 2021 09:49:52 -0700 Subject: [PATCH 200/437] Support verification of AppCheck token in Callable Functions (#885) Callable Functions will now verify the AppCheck token included in the X-Firebase-AppCheck request header. Similar to auth, Callable Function will return 401 Unauthorized if the AppCheck token is invalid. --- CHANGELOG.md | 1 + package-lock.json | 1197 +++++++++++++++-------------- package.json | 3 +- spec/fixtures/credential/jwk.json | 14 + spec/fixtures/mockrequest.ts | 51 +- spec/providers/https.spec.ts | 54 ++ src/providers/https.ts | 134 +++- tsconfig.release.json | 4 +- 8 files changed, 851 insertions(+), 607 deletions(-) create mode 100644 spec/fixtures/credential/jwk.json diff --git a/CHANGELOG.md b/CHANGELOG.md index d952d8984..4ee96fbef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Functions may now be deployed with 8GB RAM - Functions may now be deployed to europe-central2 (Warsaw) +- Add support for validating AppCheck tokens for Callable Functions diff --git a/package-lock.json b/package-lock.json index f1f20b89a..8a4fd6be8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,49 +31,65 @@ } }, "@firebase/app-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", - "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.2.tgz", + "integrity": "sha512-2VXvq/K+n8XMdM4L2xy5bYp2ZXMawJXluUIDzUBvMthVR+lhxK4pfFiqr1mmDbv9ydXvEAuFsD+6DpcZuJcSSw==", "dev": true }, "@firebase/auth-interop-types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", - "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", "dev": true }, "@firebase/component": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.19.tgz", - "integrity": "sha512-L0S3g8eqaerg8y0zox3oOHSTwn/FE8RbcRHiurnbESvDViZtP5S5WnhuAPd7FnFxa8ElWK0z1Tr3ikzWDv1xdQ==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.0.tgz", + "integrity": "sha512-v18csWtXb0ri+3m7wuGLY/UDgcb89vuMlZGQ//+7jEPLIQeLbylvZhol1uzW9WzoOpxMxOS2W5qyVGX36wZvEA==", "dev": true, "requires": { - "@firebase/util": "0.3.2", - "tslib": "^1.11.1" + "@firebase/util": "1.1.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } } }, "@firebase/database": { - "version": "0.6.12", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.12.tgz", - "integrity": "sha512-OLUxp8TkXiML4X5LWM5IACsSDvo3fcf4mTbTe5RF+N6TRFv0Svzlet5OgGIa3ET1dQvNiisrMX7zzRa0OTLs7Q==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.10.0.tgz", + "integrity": "sha512-GsHvuES83Edtboij2h3txKg+yV/TD4b5Owc01SgXEQtvj1lulkHt4Ufmd9OZz1WreWQJMIqKpbVowIDHjlkZJQ==", "dev": true, "requires": { - "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.19", - "@firebase/database-types": "0.5.2", + "@firebase/auth-interop-types": "0.1.6", + "@firebase/component": "0.5.0", + "@firebase/database-types": "0.7.2", "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.2", + "@firebase/util": "1.1.0", "faye-websocket": "0.11.3", - "tslib": "^1.11.1" + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } } }, "@firebase/database-types": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.2.tgz", - "integrity": "sha512-ap2WQOS3LKmGuVFKUghFft7RxXTyZTDr0Xd8y2aqmWsbJVjgozi0huL/EUMgTjGFrATAjcf2A7aNs8AKKZ2a8g==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.2.tgz", + "integrity": "sha512-cdAd/dgwvC0r3oLEDUR+ULs1vBsEvy0b27nlzKhU6LQgm9fCDzgaH9nFGv8x+S9dly4B0egAXkONkVoWcOAisg==", "dev": true, "requires": { - "@firebase/app-types": "0.6.1" + "@firebase/app-types": "0.6.2" } }, "@firebase/logger": { @@ -83,50 +99,57 @@ "dev": true }, "@firebase/util": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.2.tgz", - "integrity": "sha512-Dqs00++c8rwKky6KCKLLY2T1qYO4Q+X5t+lF7DInXDNF4ae1Oau35bkD+OpJ9u7l1pEv7KHowP6CUKuySCOc8g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.1.0.tgz", + "integrity": "sha512-lfuSASuPKNdfebuFR8rjFamMQUPH9iiZHcKS755Rkm/5gRT0qC7BMhCh3ZkHf7NVbplzIc/GhmX2jM+igDRCag==", "dev": true, "requires": { - "tslib": "^1.11.1" + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } } }, "@google-cloud/common": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", - "integrity": "sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.6.0.tgz", + "integrity": "sha512-aHIFTqJZmeTNO9md8XxV+ywuvXF3xBm5WNmgWeeCK+XN5X+kGW0WEX94wGwj+/MdOnrVf4dL2RvSIt9J5yJG6Q==", "dev": true, "optional": true, "requires": { - "@google-cloud/projectify": "^1.0.0", - "@google-cloud/promisify": "^1.0.0", - "arrify": "^2.0.0", - "duplexify": "^3.6.0", + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", "ent": "^2.2.0", "extend": "^3.0.2", - "google-auth-library": "^5.5.0", - "retry-request": "^4.0.0", - "teeny-request": "^6.0.0" + "google-auth-library": "^7.0.2", + "retry-request": "^4.1.1", + "teeny-request": "^7.0.0" } }, "@google-cloud/firestore": { - "version": "3.8.6", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-3.8.6.tgz", - "integrity": "sha512-ox80NbrM1MLJgvAAUd1quFLx/ie/nSjrk1PtscSicpoYDlKb9e6j7pHrVpbopBMyliyfNl3tLJWaDh+x+uCXqw==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.11.0.tgz", + "integrity": "sha512-Do9WJzEkFBBB+zVFvFfrrrIFEz086lrdgKQic7XsdoTgtYtq0yMu2u3kGLyxMbdasl2c2yf49FE4YvO3AYjQMQ==", "dev": true, "optional": true, "requires": { - "deep-equal": "^2.0.0", + "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^1.15.3", - "readable-stream": "^3.4.0", - "through2": "^3.0.0" + "google-gax": "^2.9.2", + "protobufjs": "^6.8.6" } }, "@google-cloud/paginator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz", - "integrity": "sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.5.tgz", + "integrity": "sha512-N4Uk4BT1YuskfRhKXBs0n9Lg2YTROZc6IMpkO/8DIHODtm5s3xY8K5vVBo23v/2XulY3azwITQlYWgT4GdLsUw==", "dev": true, "optional": true, "requires": { @@ -135,103 +158,233 @@ } }, "@google-cloud/projectify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz", - "integrity": "sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz", + "integrity": "sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ==", "dev": true, "optional": true }, "@google-cloud/promisify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz", - "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", + "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==", "dev": true, "optional": true }, "@google-cloud/storage": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-4.7.0.tgz", - "integrity": "sha512-f0guAlbeg7Z0m3gKjCfBCu7FG9qS3M3oL5OQQxlvGoPtK7/qg3+W+KQV73O2/sbuS54n0Kh2mvT5K2FWzF5vVQ==", + "version": "5.8.5", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.8.5.tgz", + "integrity": "sha512-i0gB9CRwQeOBYP7xuvn14M40LhHCwMjceBjxE4CTvsqL519sVY5yVKxLiAedHWGwUZHJNRa7Q2CmNfkdRwVNPg==", "dev": true, "optional": true, "requires": { - "@google-cloud/common": "^2.1.1", - "@google-cloud/paginator": "^2.0.0", - "@google-cloud/promisify": "^1.0.0", + "@google-cloud/common": "^3.6.0", + "@google-cloud/paginator": "^3.0.0", + "@google-cloud/promisify": "^2.0.0", "arrify": "^2.0.0", + "async-retry": "^1.3.1", "compressible": "^2.0.12", - "concat-stream": "^2.0.0", - "date-and-time": "^0.13.0", - "duplexify": "^3.5.0", + "date-and-time": "^1.0.0", + "duplexify": "^4.0.0", "extend": "^3.0.2", - "gaxios": "^3.0.0", - "gcs-resumable-upload": "^2.2.4", + "gaxios": "^4.0.0", + "gcs-resumable-upload": "^3.1.4", + "get-stream": "^6.0.0", "hash-stream-validation": "^0.2.2", "mime": "^2.2.0", "mime-types": "^2.0.8", "onetime": "^5.1.0", - "p-limit": "^2.2.0", + "p-limit": "^3.0.1", "pumpify": "^2.0.0", - "readable-stream": "^3.4.0", "snakeize": "^0.1.0", "stream-events": "^1.0.1", - "through2": "^3.0.0", "xdg-basedir": "^4.0.0" }, "dependencies": { - "gaxios": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.2.0.tgz", - "integrity": "sha512-+6WPeVzPvOshftpxJwRi2Ozez80tn/hdtOUag7+gajDHRJvAblKxTFSSMPtr2hmnLy7p0mvYz0rMXLBl8pSO7Q==", + "mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "optional": true + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "optional": true, "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" + "yocto-queue": "^0.1.0" } - }, - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "dev": true, - "optional": true } } }, "@grpc/grpc-js": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.5.tgz", - "integrity": "sha512-Hm+xOiqAhcpT9RYM8lc15dbQD7aQurM7ZU8ulmulepiPlN7iwBXXwP3vSBUimoFoApRqz7pSIisXU8pZaCB4og==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.0.tgz", + "integrity": "sha512-fiL7ZaGg2HBiFtmv6m34d5jEgEtNXfctjzB3f7b3iuT7olBX4mHLMOqOBmGTTSOTfNRQJH5+vsyk6mEz3I0Q7Q==", "dev": true, "optional": true, "requires": { - "semver": "^6.2.0" + "@types/node": ">=12.12.47" }, "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "@types/node": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz", + "integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA==", "dev": true, "optional": true } } }, "@grpc/proto-loader": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.5.tgz", - "integrity": "sha512-WwN9jVNdHRQoOBo9FDH7qU+mgfjPc8GygPYms3M+y3fbQLfnCe/Kv/E01t7JRgnrsOHH8euvSbed3mIalXhwqQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.1.tgz", + "integrity": "sha512-4DIvEOZhw5nGj3RQngIoiMXRsre3InEH136krZTcirs/G2em3WMXdtx4Lqlnb4E2ertbWGs5gPeVDKU5BHffXw==", "dev": true, "optional": true, "requires": { + "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" + "long": "^4.0.0", + "protobufjs": "^6.10.0", + "yargs": "^16.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true, + "optional": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "optional": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "optional": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "optional": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "optional": true + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "optional": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", + "dev": true, + "optional": true + } } }, + "@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "dev": true + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -242,14 +395,14 @@ "@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha1-TIVzDlm5ofHzSQR9vyQpYDS7JzU=", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", "dev": true, "optional": true }, "@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha1-fvN/DQEPsCitGtWXIuUG2SYoFcs=", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", "dev": true, "optional": true }, @@ -406,6 +559,16 @@ "@types/serve-static": "*" } }, + "@types/express-jwt": { + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", + "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, "@types/express-serve-static-core": { "version": "4.17.12", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.12.tgz", @@ -416,14 +579,13 @@ "@types/range-parser": "*" } }, - "@types/fs-extra": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz", - "integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==", + "@types/express-unless": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.1.tgz", + "integrity": "sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw==", "dev": true, - "optional": true, "requires": { - "@types/node": "*" + "@types/express": "*" } }, "@types/jsonwebtoken": { @@ -555,9 +717,9 @@ "dev": true }, "agent-base": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", - "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "optional": true, "requires": { @@ -565,13 +727,13 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "optional": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -637,13 +799,6 @@ "sprintf-js": "~1.0.2" } }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", - "dev": true, - "optional": true - }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -671,6 +826,18 @@ "safer-buffer": "~2.1.0" } }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -683,6 +850,16 @@ "integrity": "sha1-5gtrDo8wG9l+U3UhW9pAbIURjAs=", "dev": true }, + "async-retry": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz", + "integrity": "sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==", + "dev": true, + "optional": true, + "requires": { + "retry": "0.12.0" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -695,16 +872,6 @@ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true }, - "available-typed-arrays": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", - "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", - "dev": true, - "optional": true, - "requires": { - "array-filter": "^1.0.0" - } - }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -724,9 +891,9 @@ "dev": true }, "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, "optional": true }, @@ -740,12 +907,18 @@ } }, "bignumber.js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", "dev": true, "optional": true }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -773,6 +946,12 @@ "concat-map": "0.0.1" } }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, "browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -968,9 +1147,9 @@ }, "dependencies": { "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", + "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", "dev": true, "optional": true } @@ -982,19 +1161,6 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "dev": true, - "optional": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, "configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -1109,9 +1275,9 @@ } }, "date-and-time": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.13.1.tgz", - "integrity": "sha512-/Uge9DJAT+s+oAcDxtBhyR8+sKjUnZbYmyhbmWjTHNtX7B7oWD8YyYdeXcBRbwSj6hVvj+IQegJam7m7czhbFw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-1.0.0.tgz", + "integrity": "sha512-477D7ypIiqlXBkxhU7YtG9wWZJEQ+RUpujt2quTfgf4+E8g5fNUkB0QIL0bVyP5/TKBg8y55Hfa1R/c4bt3dEw==", "dev": true, "optional": true }, @@ -1144,29 +1310,6 @@ "type-detect": "^4.0.0" } }, - "deep-equal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.3.tgz", - "integrity": "sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==", - "dev": true, - "optional": true, - "requires": { - "es-abstract": "^1.17.5", - "es-get-iterator": "^1.1.0", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.0.5", - "isarray": "^2.0.5", - "object-is": "^1.1.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.2", - "which-boxed-primitive": "^1.0.1", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.2" - } - }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -1241,41 +1384,16 @@ } }, "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", "dev": true, "optional": true, "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", "stream-shift": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } } }, "ecc-jsbn": { @@ -1302,6 +1420,29 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + } + } + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -1349,22 +1490,6 @@ "string.prototype.trimstart": "^1.0.1" } }, - "es-get-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", - "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", - "dev": true, - "optional": true, - "requires": { - "es-abstract": "^1.17.4", - "has-symbols": "^1.0.1", - "is-arguments": "^1.0.4", - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -1376,6 +1501,13 @@ "is-symbol": "^1.0.2" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "optional": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1534,18 +1666,28 @@ } }, "firebase-admin": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-8.13.0.tgz", - "integrity": "sha512-krXj5ncWMJBhCpXSn9UFY6zmDWjFjqgx+1e9ATXKFYndEjmKtNBuJzqdrAdDh7aTUR7X6+0TPx4Hbc08kd0lwQ==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.8.0.tgz", + "integrity": "sha512-v8B1qU8McZZT2hlLZ018TKz2FoKlfFkZq9mOIyzN7wJUOlAywqQX0JyqNpVGyPeU+B+77ojlvmkGTNXt2OFkgw==", "dev": true, "requires": { - "@firebase/database": "^0.6.0", - "@google-cloud/firestore": "^3.0.0", - "@google-cloud/storage": "^4.1.2", - "@types/node": "^8.10.59", + "@firebase/database": "^0.10.0", + "@firebase/database-types": "^0.7.2", + "@google-cloud/firestore": "^4.5.0", + "@google-cloud/storage": "^5.3.0", + "@types/node": ">=12.12.47", "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", - "node-forge": "^0.7.6" + "jwks-rsa": "^2.0.2", + "node-forge": "^0.10.0" + }, + "dependencies": { + "@types/node": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz", + "integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA==", + "dev": true + } } }, "flat": { @@ -1565,13 +1707,6 @@ } } }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true, - "optional": true - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -1631,9 +1766,9 @@ "optional": true }, "gaxios": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", - "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.2.1.tgz", + "integrity": "sha512-s+rTywpw6CmfB8r9TXYkpix7YFeuRjnR/AqhaJrQqsNhsAqej+IAiCc3hadzQH3gHyWth30tvYjxH8EVjQt/8Q==", "dev": true, "optional": true, "requires": { @@ -1645,27 +1780,28 @@ } }, "gcp-metadata": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz", - "integrity": "sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz", + "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==", "dev": true, "optional": true, "requires": { - "gaxios": "^2.1.0", - "json-bigint": "^0.3.0" + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" } }, "gcs-resumable-upload": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-2.3.3.tgz", - "integrity": "sha512-sf896I5CC/1AxeaGfSFg3vKMjUq/r+A3bscmVzZm10CElyRanN0XwPu/MxeIO4LSP+9uF6yKzXvNsaTsMXUG6Q==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.4.tgz", + "integrity": "sha512-5dyDfHrrVcIskiw/cPssVD4HRiwoHjhk1Nd6h5W3pQ/qffDvhfy4oNCr1f3ZXFPwTnxkCbibsB+73oOM+NvmJQ==", "dev": true, "optional": true, "requires": { "abort-controller": "^3.0.0", "configstore": "^5.0.0", - "gaxios": "^2.0.0", - "google-auth-library": "^5.0.0", + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "google-auth-library": "^7.0.0", "pumpify": "^2.0.0", "stream-events": "^1.0.4" } @@ -1682,6 +1818,13 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "optional": true + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -1706,9 +1849,9 @@ } }, "google-auth-library": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", - "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.0.4.tgz", + "integrity": "sha512-o8irYyeijEiecTXeoEe8UKNEzV1X+uhR4b2oNdapDMZixypp0J+eHimGOyx5Joa3UAeokGngdtDLXtq9vDqG2Q==", "dev": true, "optional": true, "requires": { @@ -1716,11 +1859,11 @@ "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "fast-text-encoding": "^1.0.0", - "gaxios": "^2.1.0", - "gcp-metadata": "^3.4.0", - "gtoken": "^4.1.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", "jws": "^4.0.0", - "lru-cache": "^5.0.0" + "lru-cache": "^6.0.0" }, "dependencies": { "jwa": { @@ -1747,74 +1890,53 @@ } }, "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "optional": true, "requires": { - "yallist": "^3.0.2" + "yallist": "^4.0.0" } }, "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "optional": true } } }, "google-gax": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-1.15.3.tgz", - "integrity": "sha512-3JKJCRumNm3x2EksUTw4P1Rad43FTpqrtW9jzpf3xSMYXx+ogaqTM1vGo7VixHB4xkAyATXVIa3OcNSh8H9zsQ==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.12.0.tgz", + "integrity": "sha512-UDx4ZZx85vXBe6GZ0sdRSzuegLrRQdRjCxlauX+U7i5YwOoSgcSaIM71BhcdHwGPhEkvO/SSHrEfc1wpL/J6JA==", "dev": true, "optional": true, "requires": { - "@grpc/grpc-js": "~1.0.3", - "@grpc/proto-loader": "^0.5.1", - "@types/fs-extra": "^8.0.1", + "@grpc/grpc-js": "~1.3.0", + "@grpc/proto-loader": "^0.6.1", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", - "duplexify": "^3.6.0", - "google-auth-library": "^5.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^7.0.2", "is-stream-ended": "^0.1.4", - "lodash.at": "^4.6.0", - "lodash.has": "^4.5.2", - "node-fetch": "^2.6.0", - "protobufjs": "^6.8.9", - "retry-request": "^4.0.0", - "semver": "^6.0.0", - "walkdir": "^0.4.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "optional": true - } + "node-fetch": "^2.6.1", + "object-hash": "^2.1.1", + "protobufjs": "^6.10.2", + "retry-request": "^4.0.0" } }, "google-p12-pem": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz", - "integrity": "sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", "dev": true, "optional": true, "requires": { - "node-forge": "^0.9.0" - }, - "dependencies": { - "node-forge": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.2.tgz", - "integrity": "sha512-naKSScof4Wn+aoHU6HBsifh92Zeicm1GDQKd1vp3Y/kOi8ub0DozCa9KpvYNCXslFHYRmLNiqRopGdTGwNLpNw==", - "dev": true, - "optional": true - } + "node-forge": "^0.10.0" } }, "graceful-fs": { @@ -1830,16 +1952,15 @@ "dev": true }, "gtoken": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz", - "integrity": "sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.2.1.tgz", + "integrity": "sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==", "dev": true, "optional": true, "requires": { - "gaxios": "^2.1.0", - "google-p12-pem": "^2.0.0", - "jws": "^4.0.0", - "mime": "^2.2.0" + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0" }, "dependencies": { "jwa": { @@ -1864,13 +1985,6 @@ "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } - }, - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "dev": true, - "optional": true } } }, @@ -1912,6 +2026,16 @@ "dev": true, "optional": true }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -1924,6 +2048,17 @@ "integrity": "sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg==", "dev": true }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -1946,9 +2081,9 @@ } }, "http-parser-js": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", - "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", + "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==", "dev": true }, "http-proxy-agent": { @@ -1964,13 +2099,13 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "optional": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -2005,13 +2140,13 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "optional": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -2076,20 +2211,6 @@ "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", "dev": true }, - "is-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", - "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==", - "dev": true, - "optional": true - }, - "is-boolean-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", - "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", - "dev": true, - "optional": true - }, "is-callable": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", @@ -2108,26 +2229,12 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "is-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", - "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", - "dev": true, - "optional": true - }, "is-negative-zero": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", "dev": true }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", - "dev": true, - "optional": true - }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -2150,13 +2257,6 @@ "has-symbols": "^1.0.1" } }, - "is-set": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", - "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", - "dev": true, - "optional": true - }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -2167,14 +2267,7 @@ "is-stream-ended": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha1-9QIk6V4GvODjVtRApIJ801smfto=", - "dev": true, - "optional": true - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", "dev": true, "optional": true }, @@ -2187,46 +2280,12 @@ "has-symbols": "^1.0.1" } }, - "is-typed-array": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", - "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", - "dev": true, - "optional": true, - "requires": { - "available-typed-arrays": "^1.0.0", - "es-abstract": "^1.17.4", - "foreach": "^2.0.5", - "has-symbols": "^1.0.1" - } - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "dev": true, - "optional": true - }, - "is-weakset": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", - "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", - "dev": true, - "optional": true - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "optional": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2245,6 +2304,15 @@ "integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==", "dev": true }, + "jose": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", + "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "dev": true, + "requires": { + "@panva/asn1.js": "^1.0.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2338,9 +2406,9 @@ } }, "json-bigint": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.1.tgz", - "integrity": "sha512-DGWnSzmusIreWlEupsUelHrhwmPPE+FiQvg+drKfk2p+bdEYa5mp4PJ8JsCWqae0M2jQNb0HPvnwvf1qOTThzQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "dev": true, "optional": true, "requires": { @@ -2430,6 +2498,47 @@ "safe-buffer": "^5.0.1" } }, + "jwk-to-pem": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-2.0.5.tgz", + "integrity": "sha512-L90jwellhO8jRKYwbssU9ifaMVqajzj3fpRjDKcsDzrslU9syRbFqfkXtT4B89HYAap+xsxNcxgBSB09ig+a7A==", + "dev": true, + "requires": { + "asn1.js": "^5.3.0", + "elliptic": "^6.5.4", + "safe-buffer": "^5.0.1" + } + }, + "jwks-rsa": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.3.tgz", + "integrity": "sha512-/rkjXRWAp0cS00tunsHResw68P5iTQru8+jHufLNv3JHc4nObFEndfEUSuPugh09N+V9XYxKUqi7QrkmCHSSSg==", + "dev": true, + "requires": { + "@types/express-jwt": "0.0.42", + "debug": "^4.1.0", + "jose": "^2.0.5", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.2" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -2450,6 +2559,12 @@ "type-check": "~0.3.2" } }, + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", + "dev": true + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -2471,13 +2586,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, - "lodash.at": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz", - "integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g=", - "dev": true, - "optional": true - }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -2485,12 +2593,11 @@ "dev": true, "optional": true }, - "lodash.has": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=", - "dev": true, - "optional": true + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true }, "lodash.includes": { "version": "4.3.0", @@ -2558,7 +2665,7 @@ "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha1-mntxz7fTYaGU6lVSQckvdGjVvyg=", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", "dev": true, "optional": true }, @@ -2572,6 +2679,28 @@ "yallist": "^2.1.2" } }, + "lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", + "dev": true, + "requires": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "dev": true, + "requires": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + } + } + }, "lunr": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", @@ -2649,6 +2778,18 @@ "dev": true, "optional": true }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -2983,9 +3124,9 @@ "optional": true }, "node-forge": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz", - "integrity": "sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", "dev": true }, "node-version": { @@ -3020,6 +3161,13 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-hash": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.1.1.tgz", + "integrity": "sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ==", + "dev": true, + "optional": true + }, "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", @@ -3209,13 +3357,6 @@ "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", "dev": true }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "optional": true - }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -3235,9 +3376,9 @@ "dev": true }, "protobufjs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.1.tgz", - "integrity": "sha512-pb8kTchL+1Ceg4lFd5XUpK8PdWacbvV5SK2ULH2ebrYtl4GjJmS24m6CKME67jzV53tbJxHlnNOSqQHbTsR9JQ==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", "dev": true, "optional": true, "requires": { @@ -3252,14 +3393,14 @@ "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/long": "^4.0.1", - "@types/node": "^13.7.0", + "@types/node": ">=13.7.0", "long": "^4.0.0" }, "dependencies": { "@types/node": { - "version": "13.13.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.20.tgz", - "integrity": "sha512-1kx55tU3AvGX2Cjk2W4GMBxbgIz892V+X10S2gUreIAq8qCWgaQH+tZBOWc0bi2BKFhQt+CX0BTx28V9QPNa+A==", + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz", + "integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA==", "dev": true, "optional": true } @@ -3307,21 +3448,6 @@ "duplexify": "^4.1.1", "inherits": "^2.0.3", "pump": "^3.0.0" - }, - "dependencies": { - "duplexify": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", - "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", - "dev": true, - "optional": true, - "requires": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - } } }, "punycode": { @@ -3490,6 +3616,13 @@ "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true, + "optional": true + }, "retry-request": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz", @@ -3501,13 +3634,13 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "optional": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -3604,40 +3737,6 @@ "rechoir": "^0.6.2" } }, - "side-channel": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz", - "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==", - "dev": true, - "optional": true, - "requires": { - "es-abstract": "^1.18.0-next.0", - "object-inspect": "^1.8.0" - }, - "dependencies": { - "es-abstract": { - "version": "1.18.0-next.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz", - "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==", - "dev": true, - "optional": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - } - } - }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -3745,7 +3844,7 @@ "stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha1-u8iY7E3zOkkC2JIzPUfam/HEBtU=", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", "dev": true, "optional": true, "requires": { @@ -3796,13 +3895,22 @@ } }, "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "optional": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "optional": true + } } }, "strip-ansi": { @@ -3834,17 +3942,17 @@ "dev": true }, "teeny-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.3.tgz", - "integrity": "sha512-TZG/dfd2r6yeji19es1cUIwAlVD8y+/svB1kAC2Y0bjEyysrfbO8EZvJBRwIE6WkwmUoB7uvWLwTIhJbMXZ1Dw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz", + "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==", "dev": true, "optional": true, "requires": { "http-proxy-agent": "^4.0.0", "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.2.0", + "node-fetch": "^2.6.1", "stream-events": "^1.0.5", - "uuid": "^7.0.0" + "uuid": "^8.0.0" } }, "thenify": { @@ -3865,26 +3973,6 @@ "thenify": ">= 3.1.0 < 4" } }, - "through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, - "optional": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - }, - "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "optional": true - } - } - }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -4060,13 +4148,6 @@ "mime-types": "~2.1.24" } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true, - "optional": true - }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -4198,9 +4279,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, "optional": true }, @@ -4238,13 +4319,6 @@ "xml-name-validator": "^3.0.0" } }, - "walkdir": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", - "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", - "dev": true, - "optional": true - }, "webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", @@ -4303,54 +4377,12 @@ "isexe": "^2.0.0" } }, - "which-boxed-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", - "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", - "dev": true, - "optional": true, - "requires": { - "is-bigint": "^1.0.0", - "is-boolean-object": "^1.0.0", - "is-number-object": "^1.0.3", - "is-string": "^1.0.4", - "is-symbol": "^1.0.2" - } - }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "dev": true, - "optional": true, - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "which-typed-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", - "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", - "dev": true, - "optional": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "es-abstract": "^1.17.5", - "foreach": "^2.0.5", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.1", - "is-typed-array": "^1.1.3" - } - }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -4683,6 +4715,13 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "optional": true } } } diff --git a/package.json b/package.json index 11a4dfb39..24ebbbcb4 100644 --- a/package.json +++ b/package.json @@ -58,10 +58,11 @@ "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "child-process-promise": "^2.2.1", - "firebase-admin": "^8.2.0", + "firebase-admin": "^9.8.0", "js-yaml": "^3.13.1", "jsdom": "^16.2.1", "jsonwebtoken": "^8.5.1", + "jwk-to-pem": "^2.0.5", "mocha": "^6.1.4", "mock-require": "^3.0.3", "mz": "^2.7.0", diff --git a/spec/fixtures/credential/jwk.json b/spec/fixtures/credential/jwk.json new file mode 100644 index 000000000..cde44767e --- /dev/null +++ b/spec/fixtures/credential/jwk.json @@ -0,0 +1,14 @@ +{ + "p": "9cTVRzGXbDfhIMQW9gXtWveDW0u_Hvwnbjx7TRPgSfawZ0MjgKfSbnyHTDXiqM1ifcN_Nk58KJ-PG9eZ7V7_mfTUnPv2puDaecn-kgHobnTJMoBR9hpzyyMpyNJuMvX4kqE7Qh8iFMBK_-p8ICiW15gK5WykswIKfIOkUZc52XM", + "kty": "RSA", + "q": "pYdUNL244sCoc4XrONKlu787AiHrjFFLHdTjoFLbvxSpszXM8iSjoiFAM_MCF-uWks2iBVDw9wlG4MB7MfNf_fD0i1wqyknSOtfMxknU7D4eU_Sp6tI99Jl8f_GAzODK__k_0MpqqXgZmJbUvYuIXMiha-5lddz8ENa4pYpbr7M", + "d": "MpkXqjmjvzwfmlq3o0uZAXjeeAnBlYQSNaSllBWKepgPjg4FxFIt_BlXex1NeP0npNy_oCgaM_x7NiALaaPhwPK52lhYThc-xomCic1KDkyPecODTPXi4Iw94Q_gp442SYMWz2ZktS-2DgXc3599fGHkY80u0rHNSO8ptdk8SUDUIZ82ZQ3pBhClF_uY3c1jZLuqVgCwKksInZmNPnv3ge088wmQC26t0Ph5u1HU6lISgaqZ8ol23iNWJPf4UEi8Twy1a73nphQS-y1yK9UC3c5Knk-WI2TMmjlxqC02ZjKqnRDxElTj9kpodasPRHRV_KJI8rTaStgxd7peMFODzQ", + "e": "AQAB", + "use": "sig", + "kid": "a12KBE", + "qi": "aJCrZVWeOjxYmMBTTI7aJhxcvvfS3I5Q7wwN4Oyb1rJZ4fgGYjDohlzeZz_3fNantPAgcDbzJfa3XS327sHJGaAVqvDugZUgyHeLZGzXGs-_mlL72wzcfvTa1C9_lIndLNZJle5_mg3xJAqRKV0s7kymSdYt0wL5fDaqo5SDNqQ", + "dp": "haBk2hWzoApt5HPZjCDC4g_rosr3enBdPAm0fL8O1whC95JAjmYw-xPIOH6f42nwYDLYSv23chr3I4tBTRe2382HgGdav3dIMqnKOTbCWrQy5LtyVN4jEVLoGCGZ-ylT4t25K4Vj8WZwIN8saAvJoCUx33YHwrCcZQDqadZQhNM", + "alg": "RS256", + "dq": "j6NdeN7hnzMbehPNyGNSmhcZd4JDymGI03w3gpokQi4GDJM1IzKUJE7CTdIkEOnIod97Jy3TzCrqrIGa5f-RXuVG79-s6hkhKxq0gaTz9YT6AFShVjnWtXizRrskz6SJw5JgxCfCYwjq_TR1q313eTxIh0Y6GQsIWPxbApuLcG0", + "n": "nunJGpOcPvVsP3q-NLgf3H6OycPhnXUxywMR2_H_JJP7BUIDSsYcOGBTFe7OphHYfyb1Gs14yAER243swndpNbQkuDJhj9a9kK6dJZmPGmvCySk_E5URj6MimZg1MBbwhsVAbRp2uerESZuoRrfdTdV87E3pGyg6Irl0IXRjy5w9SsFjjIi7E-Qxpf3TcNNjfVRLj9V2bSzmS7hlsPKBhDon0tWecuNKoNNMiGI46mz_MSUa2y1lPV6Cqhf1su_TRd7N7u9eP7xWArr7wqtqHiFTZ3qp1xoA_dr_xv_Ao2kBtohZiAFLV-PQShprSN5fafztRZFkSEF0m2tUkvmoaQ" +} diff --git a/spec/fixtures/mockrequest.ts b/spec/fixtures/mockrequest.ts index fff2ce1a9..8766ab06a 100644 --- a/spec/fixtures/mockrequest.ts +++ b/spec/fixtures/mockrequest.ts @@ -1,7 +1,9 @@ import * as jwt from 'jsonwebtoken'; +import * as jwkToPem from 'jwk-to-pem'; import * as _ from 'lodash'; import * as nock from 'nock'; -import * as mocks from '../fixtures/credential/key.json'; +import * as mockKey from '../fixtures/credential/key.json'; +import * as mockJWK from '../fixtures/credential/jwk.json'; // MockRequest mocks an https.Request. export class MockRequest { @@ -26,6 +28,7 @@ export function mockRequest( context: { authorization?: string; instanceIdToken?: string; + appCheckToken?: string; } = {} ) { const body: any = {}; @@ -37,6 +40,7 @@ export function mockRequest( 'content-type': contentType, authorization: context.authorization, 'firebase-instance-id-token': context.instanceIdToken, + 'x-firebase-appcheck': context.appCheckToken, origin: 'example.com', }; @@ -53,7 +57,7 @@ export const expectedResponseHeaders = { * verifying an id token. */ export function mockFetchPublicKeys(): nock.Scope { - const mockedResponse = { [mocks.key_id]: mocks.public_key }; + const mockedResponse = { [mockKey.key_id]: mockKey.public_key }; const headers = { 'cache-control': 'public, max-age=1, must-revalidate, no-transform', }; @@ -72,11 +76,48 @@ export function generateIdToken(projectId: string): string { audience: projectId, expiresIn: 60 * 60, // 1 hour in seconds issuer: 'https://securetoken.google.com/' + projectId, - subject: mocks.user_id, + subject: mockKey.user_id, algorithm: 'RS256', header: { - kid: mocks.key_id, + kid: mockKey.key_id, }, }; - return jwt.sign(claims, mocks.private_key, options); + return jwt.sign(claims, mockKey.private_key, options); +} + +/** + * Mocks out the http request used by the firebase-admin SDK to get the jwks for + * verifying an AppCheck token. + */ +export function mockFetchAppCheckPublicJwks(): nock.Scope { + const { kty, use, alg, kid, n, e } = mockJWK; + const mockedResponse = { + keys: [{ kty, use, alg, kid, n, e }], + }; + + return nock('https://firebaseappcheck.googleapis.com:443') + .get('/v1beta/jwks') + .reply(200, mockedResponse); +} + +/** + * Generates a mocked AppCheck token. + */ +export function generateAppCheckToken( + projectId: string, + appId: string +): string { + const claims = {}; + const options: jwt.SignOptions = { + audience: [`projects/${projectId}`], + expiresIn: 60 * 60, // 1 hour in seconds + issuer: `https://firebaseappcheck.googleapis.com/${projectId}`, + subject: appId, + header: { + alg: 'RS256', + typ: 'JWT', + kid: mockJWK.kid, + }, + }; + return jwt.sign(claims, jwkToPem(mockJWK, { private: true }), options); } diff --git a/spec/providers/https.spec.ts b/spec/providers/https.spec.ts index f858d5923..128ea0058 100644 --- a/spec/providers/https.spec.ts +++ b/spec/providers/https.spec.ts @@ -30,7 +30,9 @@ import * as https from '../../src/providers/https'; import * as mocks from '../fixtures/credential/key.json'; import { expectedResponseHeaders, + generateAppCheckToken, generateIdToken, + mockFetchAppCheckPublicJwks, mockFetchPublicKeys, MockRequest, mockRequest, @@ -414,6 +416,58 @@ describe('callable.FunctionBuilder', () => { }); }); + it('should handle AppCheck token', async () => { + const mock = mockFetchAppCheckPublicJwks(); + const projectId = appsNamespace().admin.options.projectId; + const appId = '1:65211879909:web:3ae38ef1cdcb2e01fe5f0c'; + const appCheckToken = generateAppCheckToken(projectId, appId); + await runTest({ + httpRequest: mockRequest(null, 'application/json', { appCheckToken }), + expectedData: null, + callableFunction: (data, context) => { + expect(context.app).to.not.be.undefined; + expect(context.app).to.not.be.null; + expect(context.app.appId).to.equal(appId); + expect(context.app.token.app_id).to.be.equal(appId); + expect(context.app.token.sub).to.be.equal(appId); + expect(context.app.token.aud).to.be.deep.equal([ + `projects/${projectId}`, + ]); + expect(context.auth).to.be.undefined; + expect(context.instanceIdToken).to.be.undefined; + return null; + }, + expectedHttpResponse: { + status: 200, + headers: expectedResponseHeaders, + body: { result: null }, + }, + }); + mock.done(); + }); + + it('should reject bad AppCheck token', async () => { + await runTest({ + httpRequest: mockRequest(null, 'application/json', { + appCheckToken: 'FAKE', + }), + expectedData: null, + callableFunction: (data, context) => { + return; + }, + expectedHttpResponse: { + status: 401, + headers: expectedResponseHeaders, + body: { + error: { + status: 'UNAUTHENTICATED', + message: 'Unauthenticated', + }, + }, + }, + }); + }); + it('should handle instance id', async () => { await runTest({ httpRequest: mockRequest(null, 'application/json', { diff --git a/src/providers/https.ts b/src/providers/https.ts index cf64c9d90..99f465215 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -28,7 +28,7 @@ import * as _ from 'lodash'; import { apps } from '../apps'; import { HttpsFunction, optionsToTrigger, Runnable } from '../cloud-functions'; import { DeploymentOptions } from '../function-configuration'; -import { warn, error } from '../logger'; +import { error, info, warn } from '../logger'; /** @hidden */ export interface Request extends express.Request { @@ -248,6 +248,14 @@ export class HttpsError extends Error { * The interface for metadata for the API as passed to the handler. */ export interface CallableContext { + /** + * The result of decoding and verifying a Firebase AppCheck token. + */ + app?: { + appId: string; + token: firebase.appCheck.DecodedAppCheckToken; + }; + /** * The result of decoding and verifying a Firebase Auth ID token. */ @@ -411,6 +419,108 @@ export function decode(data: any): any { return data; } +/** + * Be careful when changing token status values. + * + * Users are encouraged to setup log-based metric based on these values, and + * changing their values may cause their metrics to break. + * + */ +/** @hidden */ +type TokenStatus = 'MISSING' | 'VALID' | 'INVALID'; + +/** @hidden */ +interface CallableTokenStatus { + app: TokenStatus; + auth: TokenStatus; +} + +/** + * Check and verify tokens included in the requests. Once verified, tokens + * are injected into the callable context. + * + * @param {Request} req - Request sent to the Callable function. + * @param {CallableContext} ctx - Context to be sent to callable function handler. + * @return {CallableTokenStatus} Status of the token verifications. + */ +/** @hidden */ +async function checkTokens( + req: Request, + ctx: CallableContext +): Promise { + const verifications: CallableTokenStatus = { + app: 'MISSING', + auth: 'MISSING', + }; + + const appCheck = req.header('X-Firebase-AppCheck'); + if (appCheck) { + verifications.app = 'INVALID'; + try { + if (!apps().admin.appCheck) { + throw new Error( + 'Cannot validate AppCheck token. Please uupdate Firebase Admin SDK to >= v9.8.0' + ); + } + const appCheckToken = await apps() + .admin.appCheck() + .verifyToken(appCheck); + ctx.app = { + appId: appCheckToken.appId, + token: appCheckToken.token, + }; + verifications.app = 'VALID'; + } catch (err) { + warn('Failed to validate AppCheck token.', err); + } + } + + const authorization = req.header('Authorization'); + if (authorization) { + verifications.auth = 'INVALID'; + const match = authorization.match(/^Bearer (.*)$/); + if (match) { + const idToken = match[1]; + try { + const authToken = await apps() + .admin.auth() + .verifyIdToken(idToken); + + verifications.auth = 'VALID'; + ctx.auth = { + uid: authToken.uid, + token: authToken, + }; + } catch (err) { + warn('Failed to validate auth token.', err); + } + } + } + + const logPayload = { + verifications, + 'logging.googleapis.com/labels': { + 'firebase-log-type': 'callable-request-verification', + }, + }; + + const errs = []; + if (verifications.app === 'INVALID') { + errs.push('AppCheck token was rejected.'); + } + if (verifications.auth === 'INVALID') { + errs.push('Auth token was rejected.'); + } + + if (errs.length == 0) { + info('Callable request verification passed', logPayload); + } else { + warn(`Callable request verification failed: ${errs.join(' ')}`, logPayload); + } + + return verifications; +} + /** @hidden */ const corsHandler = cors({ origin: true, methods: 'POST' }); @@ -427,25 +537,9 @@ export function _onCallWithOptions( } const context: CallableContext = { rawRequest: req }; - - const authorization = req.header('Authorization'); - if (authorization) { - const match = authorization.match(/^Bearer (.*)$/); - if (!match) { - throw new HttpsError('unauthenticated', 'Unauthenticated'); - } - const idToken = match[1]; - try { - const authToken = await apps() - .admin.auth() - .verifyIdToken(idToken); - context.auth = { - uid: authToken.uid, - token: authToken, - }; - } catch (err) { - throw new HttpsError('unauthenticated', 'Unauthenticated'); - } + const tokenStatus = await checkTokens(req, context); + if (tokenStatus.app === 'INVALID' || tokenStatus.auth === 'INVALID') { + throw new HttpsError('unauthenticated', 'Unauthenticated'); } const instanceId = req.header('Firebase-Instance-ID-Token'); diff --git a/tsconfig.release.json b/tsconfig.release.json index a226eb290..1a45bd58b 100644 --- a/tsconfig.release.json +++ b/tsconfig.release.json @@ -1,13 +1,13 @@ { "compilerOptions": { "declaration": true, - "lib": ["es2017"], + "lib": ["es2018"], "module": "commonjs", "noImplicitAny": false, "noUnusedLocals": true, "outDir": "lib", "stripInternal": true, - "target": "es2017", + "target": "es2018", "typeRoots": ["./node_modules/@types"] }, "files": ["./src/index.ts", "./src/logger.ts", "./src/logger/compat.ts"] From 4ed7b7615302404bb0205c68ee49b1134aae5449 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 12 May 2021 13:03:08 -0700 Subject: [PATCH 201/437] Update release script to build packages using Node 14 (#889) * Update node version in package-builder imaage. * Update publish script to ignore local changes while updating package version. --- scripts/publish-container/Dockerfile | 2 +- scripts/publish.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/publish-container/Dockerfile b/scripts/publish-container/Dockerfile index c8ba24c12..02b15bf11 100644 --- a/scripts/publish-container/Dockerfile +++ b/scripts/publish-container/Dockerfile @@ -1,4 +1,4 @@ -FROM node:8 +FROM node:14 # Install dependencies RUN apt-get update && \ diff --git a/scripts/publish.sh b/scripts/publish.sh index a1498460e..d187e9f45 100644 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -87,7 +87,7 @@ npm run build:release echo "Ran publish build." echo "Making a $VERSION version..." -npm version $VERSION +npm version --no-git-tag-version $VERSION NEW_VERSION=$(jq -r ".version" package.json) echo "Made a $VERSION version." From f74adaa86a8a4ccd43ca6e0e4f59e7c1bbb19b8f Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Wed, 12 May 2021 20:09:26 +0000 Subject: [PATCH 202/437] [firebase-release] Removed change log and reset repo after 3.14.0 release --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ee96fbef..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +0,0 @@ -- Functions may now be deployed with 8GB RAM -- Functions may now be deployed to europe-central2 (Warsaw) -- Add support for validating AppCheck tokens for Callable Functions From 8e4a446ad0872297b873cfcd21f6847a9182b359 Mon Sep 17 00:00:00 2001 From: "Masayuki Ono (mono)" Date: Fri, 14 May 2021 04:29:10 +0900 Subject: [PATCH 203/437] Fix typo (#890) --- src/providers/https.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/providers/https.ts b/src/providers/https.ts index 99f465215..2af2117f3 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -459,7 +459,7 @@ async function checkTokens( try { if (!apps().admin.appCheck) { throw new Error( - 'Cannot validate AppCheck token. Please uupdate Firebase Admin SDK to >= v9.8.0' + 'Cannot validate AppCheck token. Please update Firebase Admin SDK to >= v9.8.0' ); } const appCheckToken = await apps() From e94f1c029f837bfb0738fd477fd8b6a0c78245d4 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 17 May 2021 10:54:13 -0700 Subject: [PATCH 204/437] Inline DecodedAppCheck definition (#891) Fixes an issue where old (but supported) versions of firebase-admin didn't expose AppCheck and then TSC failed. --- integration_test/functions/src/index.ts | 2 +- .../functions/src/testLab-tests.ts | 2 +- .../functions/src/testLab-utils.ts | 12 ++-- integration_test/functions/src/testing.ts | 2 +- spec/fixtures/mockrequest.ts | 2 +- spec/logger.spec.ts | 8 +-- spec/providers/testLab.spec.ts | 40 +++++++------- src/cloud-functions.ts | 2 +- src/function-builder.ts | 2 +- src/function-configuration.ts | 7 +++ src/index.ts | 2 +- src/logger.ts | 2 +- src/logger/compat.ts | 4 +- src/providers/database.ts | 2 +- src/providers/https.ts | 55 ++++++++++++++++++- 15 files changed, 102 insertions(+), 42 deletions(-) diff --git a/integration_test/functions/src/index.ts b/integration_test/functions/src/index.ts index 0a980aa06..6865d7dd5 100644 --- a/integration_test/functions/src/index.ts +++ b/integration_test/functions/src/index.ts @@ -1,9 +1,9 @@ +import { PubSub } from '@google-cloud/pubsub'; import { Request, Response } from 'express'; import * as admin from 'firebase-admin'; import * as functions from 'firebase-functions'; import * as fs from 'fs'; import * as https from 'https'; -import { PubSub } from '@google-cloud/pubsub'; export * from './pubsub-tests'; export * from './database-tests'; diff --git a/integration_test/functions/src/testLab-tests.ts b/integration_test/functions/src/testLab-tests.ts index 586c3a0cb..8e064928a 100644 --- a/integration_test/functions/src/testLab-tests.ts +++ b/integration_test/functions/src/testLab-tests.ts @@ -1,5 +1,5 @@ import * as functions from 'firebase-functions'; -import { TestSuite, expectEq } from './testing'; +import { expectEq, TestSuite } from './testing'; import TestMatrix = functions.testLab.TestMatrix; const REGION = process.env.FIREBASE_FUNCTIONS_TEST_REGION || 'us-central1'; diff --git a/integration_test/functions/src/testLab-utils.ts b/integration_test/functions/src/testLab-utils.ts index 6f86b0a1f..3dbb7b763 100644 --- a/integration_test/functions/src/testLab-utils.ts +++ b/integration_test/functions/src/testLab-utils.ts @@ -1,6 +1,6 @@ +import * as admin from 'firebase-admin'; import * as http from 'http'; import * as https from 'https'; -import * as admin from 'firebase-admin'; import * as utils from './test-utils'; interface AndroidDevice { @@ -50,12 +50,12 @@ async function fetchDefaultDevice( const model = defaultModels[0]; const versions = model.supportedVersionIds; - return { + return { androidModelId: model.id, androidVersionId: versions[versions.length - 1], locale: 'en', orientation: 'portrait', - }; + } as AndroidDevice; } function createTestMatrix( @@ -70,7 +70,7 @@ function createTestMatrix( '/v1/projects/' + projectId + '/testMatrices' ); const body = { - projectId: projectId, + projectId, testSpecification: { androidRoboTest: { appApk: { @@ -105,9 +105,9 @@ function requestOptions( path: string ): https.RequestOptions { return { - method: method, + method, hostname: TESTING_API_SERVICE_NAME, - path: path, + path, headers: { Authorization: 'Bearer ' + accessToken.access_token, 'Content-Type': 'application/json', diff --git a/integration_test/functions/src/testing.ts b/integration_test/functions/src/testing.ts index 228203d31..1cb9f7819 100644 --- a/integration_test/functions/src/testing.ts +++ b/integration_test/functions/src/testing.ts @@ -92,7 +92,7 @@ function deepEq(left: any, right: any) { return false; } - for (let key in left) { + for (const key in left) { if (!right.hasOwnProperty(key)) { return false; } diff --git a/spec/fixtures/mockrequest.ts b/spec/fixtures/mockrequest.ts index 8766ab06a..68827610b 100644 --- a/spec/fixtures/mockrequest.ts +++ b/spec/fixtures/mockrequest.ts @@ -2,8 +2,8 @@ import * as jwt from 'jsonwebtoken'; import * as jwkToPem from 'jwk-to-pem'; import * as _ from 'lodash'; import * as nock from 'nock'; -import * as mockKey from '../fixtures/credential/key.json'; import * as mockJWK from '../fixtures/credential/jwk.json'; +import * as mockKey from '../fixtures/credential/key.json'; // MockRequest mocks an https.Request. export class MockRequest { diff --git a/spec/logger.spec.ts b/spec/logger.spec.ts index 48258ffac..e32765d10 100644 --- a/spec/logger.spec.ts +++ b/spec/logger.spec.ts @@ -7,8 +7,8 @@ const SUPPORTS_STRUCTURED_LOGS = describe(`logger (${ SUPPORTS_STRUCTURED_LOGS ? 'structured' : 'unstructured' })`, () => { - let stdoutWrite = process.stdout.write.bind(process.stdout); - let stderrWrite = process.stderr.write.bind(process.stderr); + const stdoutWrite = process.stdout.write.bind(process.stdout); + const stderrWrite = process.stderr.write.bind(process.stderr); let lastOut: string; let lastErr: string; @@ -127,7 +127,7 @@ describe(`logger (${ for (const severity of ['DEBUG', 'INFO', 'NOTICE']) { it(`should output ${severity} severity to stdout`, () => { - let entry: logger.LogEntry = { + const entry: logger.LogEntry = { severity: severity as logger.LogSeverity, message: 'test', }; @@ -144,7 +144,7 @@ describe(`logger (${ 'EMERGENCY', ]) { it(`should output ${severity} severity to stderr`, () => { - let entry: logger.LogEntry = { + const entry: logger.LogEntry = { severity: severity as logger.LogSeverity, message: 'test', }; diff --git a/spec/providers/testLab.spec.ts b/spec/providers/testLab.spec.ts index bf74a6fd1..8a70cf680 100644 --- a/spec/providers/testLab.spec.ts +++ b/spec/providers/testLab.spec.ts @@ -66,23 +66,23 @@ describe('Test Lab Functions', () => { resource: {}, }, }; - const expected = { + const expected = { testMatrixId: 'matrix-375mfeu9mnw8t', state: 'INVALID', createTime: '2019-04-15T17:43:32.538Z', outcomeSummary: undefined, invalidMatrixDetails: 'INVALID_INPUT_APK', - resultStorage: { + resultStorage: { gcsPath: 'gs://test.appspot.com', resultsUrl: undefined, toolResultsHistoryId: undefined, toolResultsExecutionId: undefined, - }, - clientInfo: { + } as testLab.ResultStorage, + clientInfo: { name: 'test', details: {}, - }, - }; + } as testLab.ClientInfo, + } as testLab.TestMatrix; const func = testLab.testMatrix().onComplete((matrix) => matrix); return expect(func(event.data, event.context)).to.eventually.deep.equal( expected @@ -119,23 +119,23 @@ describe('Test Lab Functions', () => { resource: {}, }, }; - const expected = { + const expected = { testMatrixId: 'matrix-tsgjk8pnvxhya', state: 'FINISHED', createTime: '2019-04-15T18:03:11.115Z', outcomeSummary: 'FAILURE', invalidMatrixDetails: undefined, - resultStorage: { + resultStorage: { gcsPath: 'gs://test.appspot.com', toolResultsHistoryId: 'bh.9b6f4dac24d3049', toolResultsExecutionId: '6352915701487950333', resultsUrl: 'https://path/to/results', - }, - clientInfo: { + } as testLab.ResultStorage, + clientInfo: { name: 'test', details: {}, - }, - }; + } as testLab.ClientInfo, + } as testLab.TestMatrix; const func = testLab.testMatrix().onComplete((matrix) => matrix); return expect(func(event.data, event.context)).to.eventually.deep.equal( expected @@ -161,7 +161,7 @@ describe('Test Lab Functions', () => { describe('TestMatrix', () => { describe('constructor', () => { it('should populate basic fields', () => { - const expected = { + const expected = { testMatrixId: 'id1', createTime: '2019-02-08T18:50:32.178Z', state: 'FINISHED', @@ -169,7 +169,7 @@ describe('Test Lab Functions', () => { invalidMatrixDetails: 'DETAILS_UNAVAILABLE', resultStorage: new testLab.ResultStorage(), clientInfo: new testLab.ClientInfo(), - }; + } as testLab.TestMatrix; const actual = new testLab.TestMatrix({ testMatrixId: 'id1', timestamp: '2019-02-08T18:50:32.178Z', @@ -185,10 +185,10 @@ describe('Test Lab Functions', () => { describe('ClientInfo', () => { describe('constructor', () => { it('should populate basic fields', () => { - const expected = { + const expected = { name: 'client', details: {}, - }; + } as testLab.ClientInfo; const actual = new testLab.ClientInfo({ name: 'client', }); @@ -196,13 +196,13 @@ describe('Test Lab Functions', () => { }); it('should populate key/value details', () => { - const expected = { + const expected = { name: 'client', details: { k0: 'v0', k1: '', }, - }; + } as testLab.ClientInfo; const actual = new testLab.ClientInfo({ name: 'client', clientInfoDetails: [ @@ -223,12 +223,12 @@ describe('Test Lab Functions', () => { describe('ResultStorage', () => { describe('constructor', () => { it('should populate basic fields', () => { - const expected = { + const expected = { gcsPath: 'path', toolResultsHistoryId: 'h1', toolResultsExecutionId: 'e2', resultsUrl: 'http://example.com/', - }; + } as testLab.ResultStorage; const actual = new testLab.ResultStorage({ googleCloudStorage: { gcsPath: 'path', diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 500e27144..93377c822 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -22,13 +22,13 @@ import { Request, Response } from 'express'; import * as _ from 'lodash'; -import { warn } from './logger'; import { DEFAULT_FAILURE_POLICY, DeploymentOptions, FailurePolicy, Schedule, } from './function-configuration'; +import { warn } from './logger'; export { Request, Response }; /** @hidden */ diff --git a/src/function-builder.ts b/src/function-builder.ts index 9d016d817..f08b16117 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -26,12 +26,12 @@ import * as _ from 'lodash'; import { CloudFunction, EventContext } from './cloud-functions'; import { DeploymentOptions, + INGRESS_SETTINGS_OPTIONS, MAX_TIMEOUT_SECONDS, RuntimeOptions, SUPPORTED_REGIONS, VALID_MEMORY_OPTIONS, VPC_EGRESS_SETTINGS_OPTIONS, - INGRESS_SETTINGS_OPTIONS, } from './function-configuration'; import * as analytics from './providers/analytics'; import * as auth from './providers/auth'; diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 2d9925ac5..852482241 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -116,6 +116,13 @@ export interface RuntimeOptions { */ minInstances?: number; + /** + * Which version of the internal contract between the CLI and the SDK are + * we using? For internal testing only. + * @hidden + */ + apiVersion?: 1 | 2; + /** * Max number of actual instances allowed to be running in parallel */ diff --git a/src/index.ts b/src/index.ts index 9d49414e8..dbb42003c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,8 +32,8 @@ import * as storage from './providers/storage'; import * as testLab from './providers/testLab'; import * as apps from './apps'; -import * as logger from './logger'; import { handler } from './handler-builder'; +import * as logger from './logger'; import { setup } from './setup'; const app = apps.apps(); diff --git a/src/logger.ts b/src/logger.ts index d6c0d2220..1dd109179 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,8 +1,8 @@ import { format } from 'util'; import { - SUPPORTS_STRUCTURED_LOGS, CONSOLE_SEVERITY, + SUPPORTS_STRUCTURED_LOGS, UNPATCHED_CONSOLE, } from './logger/common'; diff --git a/src/logger/compat.ts b/src/logger/compat.ts index a7c27bd16..4239221f0 100644 --- a/src/logger/compat.ts +++ b/src/logger/compat.ts @@ -1,9 +1,9 @@ +import { format } from 'util'; import { + CONSOLE_SEVERITY, SUPPORTS_STRUCTURED_LOGS, UNPATCHED_CONSOLE, - CONSOLE_SEVERITY, } from './common'; -import { format } from 'util'; /** @hidden */ function patchedConsole(severity: string): (data: any, ...args: any[]) => void { diff --git a/src/providers/database.ts b/src/providers/database.ts index 0e4393d05..76252d515 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -140,7 +140,7 @@ export function _refWithOptions( ); } - let instance = undefined; + let instance; const prodMatch = databaseURL.match(databaseURLRegex); if (prodMatch) { instance = prodMatch[1]; diff --git a/src/providers/https.ts b/src/providers/https.ts index 2af2117f3..c6167c50a 100644 --- a/src/providers/https.ts +++ b/src/providers/https.ts @@ -253,7 +253,60 @@ export interface CallableContext { */ app?: { appId: string; - token: firebase.appCheck.DecodedAppCheckToken; + + // This is actually a firebase.appCheck.DecodedAppCheckToken, but + // that type may not be available in some supported SDK versions. + // Declare as an inline type, which DecodedAppCheckToken will be + // able to merge with. + // TODO: Replace with the real type once we bump the min-version of + // the admin SDK + token: { + /** + * The issuer identifier for the issuer of the response. + * + * This value is a URL with the format + * `https://firebaseappcheck.googleapis.com/`, where `` is the + * same project number specified in the [`aud`](#aud) property. + */ + iss: string; + + /** + * The Firebase App ID corresponding to the app the token belonged to. + * + * As a convenience, this value is copied over to the [`app_id`](#app_id) property. + */ + sub: string; + + /** + * The audience for which this token is intended. + * + * This value is a JSON array of two strings, the first is the project number of your + * Firebase project, and the second is the project ID of the same project. + */ + aud: string[]; + + /** + * The App Check token's expiration time, in seconds since the Unix epoch. That is, the + * time at which this App Check token expires and should no longer be considered valid. + */ + exp: number; + + /** + * The App Check token's issued-at time, in seconds since the Unix epoch. That is, the + * time at which this App Check token was issued and should start to be considered + * valid. + */ + iat: number; + + /** + * The App ID corresponding to the App the App Check token belonged to. + * + * This value is not actually one of the JWT token claims. It is added as a + * convenience, and is set as the value of the [`sub`](#sub) property. + */ + app_id: string; + [key: string]: any; + }; }; /** From ba2cd3be03041828d3e0b83e8a8f45d38302ee02 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 17 May 2021 13:08:02 -0700 Subject: [PATCH 205/437] Add relnotes (#892) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..f5d3834e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Fixes a bug where typescript would fail to compile with old (but supported) versions of firebase-admin From 2b3b15d5be1f17a166175e4f5d5cc5ffc7a7040a Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 17 May 2021 20:35:15 +0000 Subject: [PATCH 206/437] [firebase-release] Removed change log and reset repo after 3.13.3 release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5d3834e9..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +0,0 @@ -- Fixes a bug where typescript would fail to compile with old (but supported) versions of firebase-admin From 4053ee19e33add8418f5483caf8ea62b64b2253f Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 17 May 2021 14:04:34 -0700 Subject: [PATCH 207/437] Prep release 3.14.1 (#893) --- package.json | 2 +- scripts/publish/CHANGELOG.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 scripts/publish/CHANGELOG.md diff --git a/package.json b/package.json index 24ebbbcb4..fa00d759d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.13.2", + "version": "3.14.0", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", diff --git a/scripts/publish/CHANGELOG.md b/scripts/publish/CHANGELOG.md new file mode 100644 index 000000000..510e7e4e0 --- /dev/null +++ b/scripts/publish/CHANGELOG.md @@ -0,0 +1,2 @@ +- Fixes a bug where typescript would fail to compile with old (but supported) versions of firebase-admin +- Replaces 3.13.3 which was an inappropriately numbered version From 242f4215668f2deb1931a74f006d9d8ef9f24330 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 17 May 2021 14:10:51 -0700 Subject: [PATCH 208/437] Fix changelog being in the wrong location (#894) --- CHANGELOG.md | 2 ++ scripts/publish/CHANGELOG.md | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 scripts/publish/CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..510e7e4e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +- Fixes a bug where typescript would fail to compile with old (but supported) versions of firebase-admin +- Replaces 3.13.3 which was an inappropriately numbered version diff --git a/scripts/publish/CHANGELOG.md b/scripts/publish/CHANGELOG.md deleted file mode 100644 index 510e7e4e0..000000000 --- a/scripts/publish/CHANGELOG.md +++ /dev/null @@ -1,2 +0,0 @@ -- Fixes a bug where typescript would fail to compile with old (but supported) versions of firebase-admin -- Replaces 3.13.3 which was an inappropriately numbered version From 1c2cc9c367ed1a979ae0039bebb8be37f0a79f37 Mon Sep 17 00:00:00 2001 From: Google Open Source Bot Date: Mon, 17 May 2021 21:12:48 +0000 Subject: [PATCH 209/437] [firebase-release] Removed change log and reset repo after 3.14.1 release --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 510e7e4e0..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +0,0 @@ -- Fixes a bug where typescript would fail to compile with old (but supported) versions of firebase-admin -- Replaces 3.13.3 which was an inappropriately numbered version From f369a2c35e65f2e484a1f6c770f4dfa1dbe1baae Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Thu, 20 May 2021 09:50:50 -0700 Subject: [PATCH 210/437] Update package.json to the latest release (#895) * Update package.json to the latest release * Bump version in package-lock.json --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8a4fd6be8..e199a60c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.13.2", + "version": "3.14.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index fa00d759d..a9c86bf87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-functions", - "version": "3.14.0", + "version": "3.14.1", "description": "Firebase SDK for Cloud Functions", "keywords": [ "firebase", From f7bcdabc1a1d67c962072fc8cd229372a1349ffc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 May 2021 08:51:16 -0700 Subject: [PATCH 211/437] Bump y18n from 4.0.0 to 4.0.3 (#888) Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.3. - [Release notes](https://github.com/yargs/y18n/releases) - [Changelog](https://github.com/yargs/y18n/blob/y18n-v4.0.3/CHANGELOG.md) - [Commits](https://github.com/yargs/y18n/compare/v4.0.0...y18n-v4.0.3) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index e199a60c2..41abc0d4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -347,13 +347,6 @@ "strip-ansi": "^6.0.0" } }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "optional": true - }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -2946,6 +2939,12 @@ "has-flag": "^3.0.0" } }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, "yargs": { "version": "13.3.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", @@ -4482,10 +4481,11 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "optional": true }, "yallist": { "version": "2.1.2", @@ -4631,6 +4631,12 @@ "strip-ansi": "^6.0.0" } }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, "yargs-parser": { "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", @@ -4690,6 +4696,12 @@ "ansi-regex": "^4.1.0" } }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, "yargs": { "version": "13.3.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", From 7e26ae36a8fb6f44b86279826068d63f366e568f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 May 2021 08:53:54 -0700 Subject: [PATCH 212/437] Bump lodash from 4.17.20 to 4.17.21 (#887) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: joehan --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 41abc0d4b..16606db37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2575,9 +2575,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.camelcase": { "version": "4.3.0", From 02a97a19b81a2e40f25f23907b6ae3f585820978 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 27 May 2021 15:41:30 -0700 Subject: [PATCH 213/437] Fix publish script to commit changes to version info in package{-lock}.json. (#896) * Fix publish script to commit changes to version info in package.json and package-lock.json. * Add debug message to make it clear what's going on. Co-authored-by: Thomas Bouldin --- scripts/publish.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/publish.sh b/scripts/publish.sh index d187e9f45..9246562ae 100644 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -87,7 +87,11 @@ npm run build:release echo "Ran publish build." echo "Making a $VERSION version..." -npm version --no-git-tag-version $VERSION +# TODO: Remove the following command. +# npm version command had previously failed claiming unclean git repo, and we don't know why. +echo "DEBUG: Running git status to show dirty files..." +git status +npm version $VERSION NEW_VERSION=$(jq -r ".version" package.json) echo "Made a $VERSION version." From 2e718c74a382655a9756c0ff781278c0e8a9146b Mon Sep 17 00:00:00 2001 From: joehan Date: Thu, 27 May 2021 15:51:45 -0700 Subject: [PATCH 214/437] Adds support for setting user labels on functions (#899) * Adds support for runWith labels * Adds CHANGELOG entry and extra test cases * Minor style fixes * pr fixes * minor typo fix --- CHANGELOG.md | 1 + spec/function-builder.spec.ts | 120 ++++++++++++++++++++++++++++++++++ src/cloud-functions.ts | 6 +- src/function-builder.ts | 72 ++++++++++++++++++++ src/function-configuration.ts | 17 +++-- 5 files changed, 210 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..5283950c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Adds support for setting user labels on functions via `runWith()`. diff --git a/spec/function-builder.spec.ts b/spec/function-builder.spec.ts index 28cf35c3a..106f874ea 100644 --- a/spec/function-builder.spec.ts +++ b/spec/function-builder.spec.ts @@ -319,4 +319,124 @@ describe('FunctionBuilder', () => { expect(fn.__trigger.availableMemoryMb).to.deep.equal(4096); }); + + it('should allow labels to be set', () => { + const fn = functions + .runWith({ + labels: { + 'valid-key': 'valid-value', + }, + }) + .auth.user() + .onCreate((user) => user); + + expect(fn.__trigger.labels).to.deep.equal({ + 'valid-key': 'valid-value', + }); + }); + + it('should throw an error if more than 58 labels are set', () => { + const labels = {}; + for (let i = 0; i < 59; i++) { + labels[`label${i}`] = 'value'; + } + + expect(() => + functions.runWith({ + labels, + }) + ).to.throw(); + }); + + it('should throw an error if labels has a key that is too long', () => { + expect(() => + functions.runWith({ + labels: { + 'a-very-long-key-that-is-more-than-the-maximum-allowed-length-for-keys': + 'value', + }, + }) + ).to.throw(); + }); + + it('should throw an error if labels has key that is too short', () => { + expect(() => + functions.runWith({ + labels: { '': 'value' }, + }) + ).to.throw(); + }); + + it('should throw an error if labels has a value that is too long', () => { + expect(() => + functions.runWith({ + labels: { + key: + 'a-very-long-value-that-is-more-than-the-maximum-allowed-length-for-values', + }, + }) + ).to.throw(); + }); + + it('should throw an error if labels has a key that contains invalid characters', () => { + expect(() => + functions.runWith({ + labels: { + Key: 'value', + }, + }) + ).to.throw(); + + expect(() => + functions.runWith({ + labels: { + 'key ': 'value', + }, + }) + ).to.throw(); + + expect(() => + functions.runWith({ + labels: { + '1key': 'value', + }, + }) + ).to.throw(); + }); + + it('should throw an error if labels has a value that contains invalid characters', () => { + expect(() => + functions.runWith({ + labels: { + key: 'Value', + }, + }) + ).to.throw(); + + expect(() => + functions.runWith({ + labels: { + 'key ': 'va lue', + }, + }) + ).to.throw(); + }); + + it('should throw an error if a label key starts with a reserved namespace', () => { + expect(() => + functions.runWith({ + labels: { + 'firebase-foo': 'value', + }, + }) + ).to.throw(); + + expect(() => + functions.runWith({ + labels: { + 'deployment-bar': 'value', + }, + }) + ).to.throw(); + }); }); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index 93377c822..2a6c770e6 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -417,7 +417,7 @@ export function makeCloudFunction({ }, }); if (!_.isEmpty(labels)) { - trigger.labels = labels; + trigger.labels = { ...trigger.labels, ...labels }; } return trigger; }, @@ -553,5 +553,9 @@ export function optionsToTrigger(options: DeploymentOptions) { } } + if (options.labels) { + trigger.labels = options.labels; + } + return trigger; } diff --git a/src/function-builder.ts b/src/function-builder.ts index f08b16117..b421668d4 100644 --- a/src/function-builder.ts +++ b/src/function-builder.ts @@ -27,6 +27,7 @@ import { CloudFunction, EventContext } from './cloud-functions'; import { DeploymentOptions, INGRESS_SETTINGS_OPTIONS, + MAX_NUMBER_USER_LABELS, MAX_TIMEOUT_SECONDS, RuntimeOptions, SUPPORTED_REGIONS, @@ -120,6 +121,77 @@ function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean { `serviceAccount must be set to 'default', a service account email, or '{serviceAccountName}@'` ); } + + if (runtimeOptions.labels) { + // Labels must follow the rules listed in + // https://cloud.google.com/resource-manager/docs/creating-managing-labels#requirements + + if (Object.keys(runtimeOptions.labels).length > MAX_NUMBER_USER_LABELS) { + throw new Error( + `A function must not have more than ${MAX_NUMBER_USER_LABELS} user-defined labels.` + ); + } + + // We reserve the 'deployment' and 'firebase' namespaces for future feature development. + const reservedKeys = Object.keys(runtimeOptions.labels).filter( + (key) => key.startsWith('deployment') || key.startsWith('firebase') + ); + if (reservedKeys.length) { + throw new Error( + `Invalid labels: ${reservedKeys.join( + ', ' + )}. Labels may not start with reserved names 'deployment' or 'firebase'` + ); + } + + const invalidLengthKeys = Object.keys(runtimeOptions.labels).filter( + (key) => key.length < 1 || key.length > 63 + ); + if (invalidLengthKeys.length > 0) { + throw new Error( + `Invalid labels: ${invalidLengthKeys.join( + ', ' + )}. Label keys must be between 1 and 63 characters in length.` + ); + } + + const invalidLengthValues = Object.values(runtimeOptions.labels).filter( + (value) => value.length > 63 + ); + if (invalidLengthValues.length > 0) { + throw new Error( + `Invalid labels: ${invalidLengthValues.join( + ', ' + )}. Label values must be less than 64 charcters.` + ); + } + + // Keys can contain lowercase letters, foreign characters, numbers, _ or -. They must start with a letter. + const validKeyPattern = /^[\p{Ll}\p{Lo}][\p{Ll}\p{Lo}\p{N}_-]{0,62}$/u; + const invalidKeys = Object.keys(runtimeOptions.labels).filter( + (key) => !validKeyPattern.test(key) + ); + if (invalidKeys.length > 0) { + throw new Error( + `Invalid labels: ${invalidKeys.join( + ', ' + )}. Label keys can only contain lowercase letters, international characters, numbers, _ or -, and must start with a letter.` + ); + } + + // Values can contain lowercase letters, foreign characters, numbers, _ or -. + const validValuePattern = /^[\p{Ll}\p{Lo}\p{N}_-]{0,63}$/u; + const invalidValues = Object.values(runtimeOptions.labels).filter( + (value) => !validValuePattern.test(value) + ); + if (invalidValues.length > 0) { + throw new Error( + `Invalid labels: ${invalidValues.join( + ', ' + )}. Label values can only contain lowercase letters, international characters, numbers, _ or -.` + ); + } + } return true; } diff --git a/src/function-configuration.ts b/src/function-configuration.ts index 852482241..6faa7aa42 100644 --- a/src/function-configuration.ts +++ b/src/function-configuration.ts @@ -94,6 +94,8 @@ export const DEFAULT_FAILURE_POLICY: FailurePolicy = { retry: {}, }; +export const MAX_NUMBER_USER_LABELS = 58; + export interface RuntimeOptions { /** * Failure policy of the function, with boolean `true` being equivalent to @@ -124,29 +126,34 @@ export interface RuntimeOptions { apiVersion?: 1 | 2; /** - * Max number of actual instances allowed to be running in parallel + * Max number of actual instances allowed to be running in parallel. */ maxInstances?: number; /** - * Connect cloud function to specified VPC connector + * Connect cloud function to specified VPC connector. */ vpcConnector?: string; /** - * Egress settings for VPC connector + * Egress settings for VPC connector. */ vpcConnectorEgressSettings?: typeof VPC_EGRESS_SETTINGS_OPTIONS[number]; /** - * Specific service account for the function to run as + * Specific service account for the function to run as. */ serviceAccount?: 'default' | string; /** - * Ingress settings + * Ingress settings which control where this function can be called from. */ ingressSettings?: typeof INGRESS_SETTINGS_OPTIONS[number]; + + /** + * User labels to set on the function. + */ + labels?: Record; } export interface DeploymentOptions extends RuntimeOptions { From 88589371f69ec98723d0e0d8bcb58c6ef23a11e5 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Thu, 24 Jun 2021 13:26:13 -0700 Subject: [PATCH 215/437] Support documented case where FIREBASE_CONFIG is a json file. (#905) Support documented case where FIREBASE_CONFIG is a json file. Per https://firebase.google.com/docs/admin/setup#initialize-without-parameters the FIREBASE_CONFIG environment variable can be a name of a JSON file. Because this now requires fs.readFileSync to keep API compliance, firebaseConfg() now uses caching. --- CHANGELOG.md | 1 + spec/config.spec.ts | 50 ++++++++++++++++++++++++++------- spec/providers/database.spec.ts | 7 +++-- spec/providers/storage.spec.ts | 12 ++++++-- src/config.ts | 29 +++++++++++++++---- 5 files changed, 78 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5283950c7..900cd7d55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,2 @@ - Adds support for setting user labels on functions via `runWith()`. +- Adds support for FIREBASE_CONFIG env as the name of a JSON file diff --git a/spec/config.spec.ts b/spec/config.spec.ts index b721dc3a5..1f93ea10e 100644 --- a/spec/config.spec.ts +++ b/spec/config.spec.ts @@ -21,55 +21,85 @@ // SOFTWARE. import { expect } from 'chai'; +import * as fs from 'fs'; import * as mockRequire from 'mock-require'; -import { config, firebaseConfig } from '../src/config'; +import Sinon = require('sinon'); + +import * as config from '../src/config'; describe('config()', () => { + let readFileSync: Sinon.SinonStub; + before(() => { + readFileSync = Sinon.stub(fs, 'readFileSync'); + readFileSync.throws('Unexpected call'); process.env.PWD = '/srv'; }); + after(() => { delete process.env.PWD; + Sinon.verifyAndRestore(); }); + afterEach(() => { mockRequire.stopAll(); - delete config.singleton; + delete config.config.singleton; + (config as any).firebaseConfigCache = null; delete process.env.FIREBASE_CONFIG; delete process.env.CLOUD_RUNTIME_CONFIG; }); it('loads config values from .runtimeconfig.json', () => { mockRequire('/srv/.runtimeconfig.json', { foo: 'bar', firebase: {} }); - const loaded = config(); + const loaded = config.config(); expect(loaded).to.not.have.property('firebase'); expect(loaded).to.have.property('foo', 'bar'); }); it('does not provide firebase config if .runtimeconfig.json not invalid', () => { mockRequire('/srv/.runtimeconfig.json', 'does-not-exist'); - expect(firebaseConfig()).to.be.null; + expect(config.firebaseConfig()).to.be.null; }); it('does not provide firebase config if .ruuntimeconfig.json has no firebase property', () => { mockRequire('/srv/.runtimeconfig.json', {}); - expect(firebaseConfig()).to.be.null; + expect(config.firebaseConfig()).to.be.null; }); it('loads Firebase configs from FIREBASE_CONFIG env variable', () => { process.env.FIREBASE_CONFIG = JSON.stringify({ databaseURL: 'foo@firebaseio.com', }); - expect(firebaseConfig()).to.have.property( + expect(config.firebaseConfig()).to.have.property( 'databaseURL', 'foo@firebaseio.com' ); }); + it('loads Firebase configs from FIREBASE_CONFIG env variable pointing to a file', () => { + const oldEnv = process.env; + process.env = { + ...oldEnv, + FIREBASE_CONFIG: '.firebaseconfig.json', + }; + try { + readFileSync.returns( + Buffer.from('{"databaseURL": "foo@firebaseio.com"}') + ); + expect(config.firebaseConfig()).to.have.property( + 'databaseURL', + 'foo@firebaseio.com' + ); + } finally { + process.env = oldEnv; + } + }); + it('accepts alternative locations for config file', () => { process.env.CLOUD_RUNTIME_CONFIG = 'another.json'; mockRequire('another.json', { foo: 'bar', firebase: {} }); - expect(firebaseConfig()).to.not.be.null; - expect(config()).to.have.property('foo', 'bar'); + expect(config.firebaseConfig()).to.not.be.null; + expect(config.config()).to.have.property('foo', 'bar'); }); it('accepts full JSON in env.CLOUD_RUNTIME_CONFIG', () => { @@ -77,7 +107,7 @@ describe('config()', () => { foo: 'bar', firebase: {}, }); - expect(firebaseConfig()).to.not.be.null; - expect(config()).to.have.property('foo', 'bar'); + expect(config.firebaseConfig()).to.not.be.null; + expect(config.config()).to.have.property('foo', 'bar'); }); }); diff --git a/spec/providers/database.spec.ts b/spec/providers/database.spec.ts index bf7e54b6b..10e1b0efb 100644 --- a/spec/providers/database.spec.ts +++ b/spec/providers/database.spec.ts @@ -22,6 +22,7 @@ import { expect } from 'chai'; import { apps as appsNamespace } from '../../src/apps'; +import * as config from '../../src/config'; import * as functions from '../../src/index'; import * as database from '../../src/providers/database'; import { applyChange } from '../../src/utils'; @@ -31,14 +32,14 @@ describe('Database Functions', () => { // TODO add tests for building a data or change based on the type of operation before(() => { - process.env.FIREBASE_CONFIG = JSON.stringify({ + (config as any).firebaseConfigCache = { databaseURL: 'https://subdomain.apse.firebasedatabase.app', - }); + }; appsNamespace.init(); }); after(() => { - delete process.env.FIREBASE_CONFIG; + (config as any).firebaseConfigCache = null; delete appsNamespace.singleton; }); diff --git a/spec/providers/storage.spec.ts b/spec/providers/storage.spec.ts index 18326eab8..c104a076f 100644 --- a/spec/providers/storage.spec.ts +++ b/spec/providers/storage.spec.ts @@ -21,6 +21,7 @@ // SOFTWARE. import { expect } from 'chai'; +import * as config from '../../src/config'; import { Event, EventContext } from '../../src/index'; import * as functions from '../../src/index'; import * as storage from '../../src/providers/storage'; @@ -28,13 +29,13 @@ import * as storage from '../../src/providers/storage'; describe('Storage Functions', () => { describe('ObjectBuilder', () => { before(() => { - process.env.FIREBASE_CONFIG = JSON.stringify({ + (config as any).firebaseConfigCache = { storageBucket: 'bucket', - }); + }; }); after(() => { - delete process.env.FIREBASE_CONFIG; + (config as any).firebaseConfigcache = null; }); it('should allow both region and runtime options to be set', () => { @@ -542,6 +543,11 @@ describe('Storage Functions', () => { }); describe('process.env.FIREBASE_CONFIG not set', () => { + beforeEach(() => { + (config as any).firebaseConfigCache = null; + delete process.env.FIREBASE_CONFIG; + }); + it('should not throw if __trigger is not accessed', () => { expect(() => storage.object().onArchive(() => null)).to.not.throw(Error); }); diff --git a/src/config.ts b/src/config.ts index 8925ff940..c49e7d03f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -20,9 +20,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import * as firebase from 'firebase-admin'; +import * as fs from 'fs'; import * as path from 'path'; +import * as firebase from 'firebase-admin'; + export function config(): config.Config { if (typeof config.singleton === 'undefined') { init(); @@ -50,18 +52,34 @@ export namespace config { export let singleton: config.Config; } +/** @hidden */ +export let firebaseConfigCache: firebase.AppOptions | null = null; + /** @hidden */ export function firebaseConfig(): firebase.AppOptions | null { - const env = process.env.FIREBASE_CONFIG; + if (firebaseConfigCache) { + return firebaseConfigCache; + } + + let env = process.env.FIREBASE_CONFIG; if (env) { - return JSON.parse(env); + // Firebase Tools will always use a JSON blob in prod, but docs + // explicitly state that the user can set the env to a file: + // https://firebase.google.com/docs/admin/setup#initialize-without-parameters + if (!env.startsWith('{')) { + env = fs.readFileSync(path.join(process.env.PWD, env)).toString('utf8'); + } + + firebaseConfigCache = JSON.parse(env); + return firebaseConfigCache; } // Could have Runtime Config with Firebase in it as an ENV value. try { const config = JSON.parse(process.env.CLOUD_RUNTIME_CONFIG); if (config.firebase) { - return config.firebase; + firebaseConfigCache = config.firebase; + return firebaseConfigCache; } } catch (e) { // Do nothing @@ -74,7 +92,8 @@ export function firebaseConfig(): firebase.AppOptions | null { path.join(process.env.PWD, '.runtimeconfig.json'); const config = require(configPath); if (config.firebase) { - return config.firebase; + firebaseConfigCache = config.firebase; + return firebaseConfigCache; } } catch (e) { // Do nothing From 4028acc07d11cd81cd56ee816bf16f025c0d10df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20Nordstr=C3=B6m?= Date: Thu, 24 Jun 2021 22:51:15 +0200 Subject: [PATCH 216/437] Fall back to updateTime if no readTime exists (#843) Fixes #599 --- spec/providers/firestore.spec.ts | 13 +++++++++++++ src/providers/firestore.ts | 5 ++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/spec/providers/firestore.spec.ts b/spec/providers/firestore.spec.ts index 42cdf97f7..271701108 100644 --- a/spec/providers/firestore.spec.ts +++ b/spec/providers/firestore.spec.ts @@ -566,6 +566,7 @@ describe('Firestore Functions', () => { describe('Other DocumentSnapshot methods', () => { let snapshot: FirebaseFirestore.DocumentSnapshot; + let newSnapshot: FirebaseFirestore.DocumentSnapshot; before(() => { snapshot = firestore.snapshotConstructor( @@ -579,6 +580,16 @@ describe('Firestore Functions', () => { }, }) ); + newSnapshot = firestore.snapshotConstructor( + makeEvent({ + value: { + fields: { key: { integerValue: '2' } }, + createTime: '2017-06-17T14:45:17.876479Z', + updateTime: '2017-06-17T14:45:17.876479Z', + name: 'projects/pid/databases/(default)/documents/collection/124', + }, + }) + ); }); it('should support #exists', () => { @@ -606,6 +617,8 @@ describe('Firestore Functions', () => { it('should support #readTime', () => { expect(snapshot.readTime.seconds).to.be.a('number'); expect(snapshot.readTime.nanoseconds).to.be.a('number'); + expect(newSnapshot.readTime.seconds).to.be.a('number'); + expect(newSnapshot.readTime.nanoseconds).to.be.a('number'); }); }); diff --git a/src/providers/firestore.ts b/src/providers/firestore.ts index f91e17306..58a6de925 100644 --- a/src/providers/firestore.ts +++ b/src/providers/firestore.ts @@ -155,7 +155,10 @@ export function snapshotConstructor(event: Event): DocumentSnapshot { event.context.resource.name, 'value' ); - const readTime = dateToTimestampProto(_.get(event, 'data.value.readTime')); + const timeString = + _.get(event, 'data.value.readTime') ?? + _.get(event, 'data.value.updateTime'); + const readTime = dateToTimestampProto(timeString); return firestoreInstance.snapshot_(valueProto, readTime, 'json'); } From 33a68eaa5b228ac39c1f0b3a9610dc7457d4e921 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Thu, 8 Jul 2021 14:52:26 -0700 Subject: [PATCH 217/437] Create package exports (#906) * Create version packages * Changelog * Make exports list always use explicit file names --- CHANGELOG.md | 1 + package.json | 8 +++ spec/{ => v1}/apps.spec.ts | 2 +- spec/{ => v1}/cloud-functions.spec.ts | 2 +- spec/{ => v1}/config.spec.ts | 2 +- spec/{ => v1}/function-builder.spec.ts | 2 +- .../providers/analytics.spec.input.ts | 2 +- spec/{ => v1}/providers/analytics.spec.ts | 6 +- spec/{ => v1}/providers/auth.spec.ts | 10 ++- spec/{ => v1}/providers/database.spec.ts | 10 +-- spec/{ => v1}/providers/firestore.spec.ts | 4 +- spec/{ => v1}/providers/https.spec.ts | 10 +-- spec/{ => v1}/providers/pubsub.spec.ts | 6 +- spec/{ => v1}/providers/remoteConfig.spec.ts | 6 +- spec/{ => v1}/providers/storage.spec.ts | 8 +-- spec/{ => v1}/providers/testLab.spec.ts | 2 +- spec/{ => v1}/setup.spec.ts | 2 +- spec/{ => v1}/utils.spec.ts | 2 +- src/index.ts | 63 +------------------ src/{logger.ts => logger/index.ts} | 2 +- src/{ => v1}/apps.ts | 0 src/{ => v1}/cloud-functions.ts | 2 +- src/{ => v1}/config.ts | 0 src/{ => v1}/encoder.ts | 0 src/{ => v1}/function-builder.ts | 0 src/{ => v1}/function-configuration.ts | 0 src/{ => v1}/handler-builder.ts | 0 src/v1/index.ts | 62 ++++++++++++++++++ src/{ => v1}/providers/analytics.ts | 0 src/{ => v1}/providers/auth.ts | 0 src/{ => v1}/providers/database.ts | 2 +- src/{ => v1}/providers/firestore.ts | 0 src/{ => v1}/providers/https.ts | 2 +- src/{ => v1}/providers/pubsub.ts | 0 src/{ => v1}/providers/remoteConfig.ts | 0 src/{ => v1}/providers/storage.ts | 0 src/{ => v1}/providers/testLab.ts | 0 src/{ => v1}/setup.ts | 2 +- src/{ => v1}/utils.ts | 0 tsconfig.release.json | 2 +- 40 files changed, 118 insertions(+), 104 deletions(-) rename spec/{ => v1}/apps.spec.ts (98%) rename spec/{ => v1}/cloud-functions.spec.ts (99%) rename spec/{ => v1}/config.spec.ts (98%) rename spec/{ => v1}/function-builder.spec.ts (99%) rename spec/{ => v1}/providers/analytics.spec.input.ts (98%) rename spec/{ => v1}/providers/analytics.spec.ts (98%) rename spec/{ => v1}/providers/auth.spec.ts (97%) rename spec/{ => v1}/providers/database.spec.ts (98%) rename spec/{ => v1}/providers/firestore.spec.ts (99%) rename spec/{ => v1}/providers/https.spec.ts (98%) rename spec/{ => v1}/providers/pubsub.spec.ts (98%) rename spec/{ => v1}/providers/remoteConfig.spec.ts (96%) rename spec/{ => v1}/providers/storage.spec.ts (98%) rename spec/{ => v1}/providers/testLab.spec.ts (99%) rename spec/{ => v1}/setup.spec.ts (97%) rename spec/{ => v1}/utils.spec.ts (97%) rename src/{logger.ts => logger/index.ts} (99%) rename src/{ => v1}/apps.ts (100%) rename src/{ => v1}/cloud-functions.ts (99%) rename src/{ => v1}/config.ts (100%) rename src/{ => v1}/encoder.ts (100%) rename src/{ => v1}/function-builder.ts (100%) rename src/{ => v1}/function-configuration.ts (100%) rename src/{ => v1}/handler-builder.ts (100%) create mode 100644 src/v1/index.ts rename src/{ => v1}/providers/analytics.ts (100%) rename src/{ => v1}/providers/auth.ts (100%) rename src/{ => v1}/providers/database.ts (99%) rename src/{ => v1}/providers/firestore.ts (100%) rename src/{ => v1}/providers/https.ts (99%) rename src/{ => v1}/providers/pubsub.ts (100%) rename src/{ => v1}/providers/remoteConfig.ts (100%) rename src/{ => v1}/providers/storage.ts (100%) rename src/{ => v1}/providers/testLab.ts (100%) rename src/{ => v1}/setup.ts (98%) rename src/{ => v1}/utils.ts (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 900cd7d55..adfc05c3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,3 @@ - Adds support for setting user labels on functions via `runWith()`. - Adds support for FIREBASE_CONFIG env as the name of a JSON file +- Formalize module exports. Loggers can now be accessed at 'firebase-functions/logger' and 'firebase-functions/logger/compat' diff --git a/package.json b/package.json index a9c86bf87..413a81c45 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,14 @@ ], "main": "lib/index.js", "types": "lib/index.d.ts", + "exports": { + ".": "./lib/index.js", + "./v1": "./lib/v1/index.js", + "./logger": "./lib/logger/index.js", + "./logger/compat": "./lib/logger/compat.js", + "./lib/logger": "./lib/logger/index.js", + "./lib/logger/compat": "./lib/logger/compat.js" + }, "publishConfig": { "registry": "https://wombat-dressing-room.appspot.com" }, diff --git a/spec/apps.spec.ts b/spec/v1/apps.spec.ts similarity index 98% rename from spec/apps.spec.ts rename to spec/v1/apps.spec.ts index 9fff98c71..33c263f4f 100644 --- a/spec/apps.spec.ts +++ b/spec/v1/apps.spec.ts @@ -21,7 +21,7 @@ // SOFTWARE. import { expect } from 'chai'; -import { apps as appsNamespace } from '../src/apps'; +import { apps as appsNamespace } from '../../src/v1/apps'; import * as firebase from 'firebase-admin'; import * as _ from 'lodash'; diff --git a/spec/cloud-functions.spec.ts b/spec/v1/cloud-functions.spec.ts similarity index 99% rename from spec/cloud-functions.spec.ts rename to spec/v1/cloud-functions.spec.ts index ab17fbc3e..48ecb3072 100644 --- a/spec/cloud-functions.spec.ts +++ b/spec/v1/cloud-functions.spec.ts @@ -29,7 +29,7 @@ import { EventContext, makeCloudFunction, MakeCloudFunctionArgs, -} from '../src/cloud-functions'; +} from '../../src/v1/cloud-functions'; describe('makeCloudFunction', () => { const cloudFunctionArgs: MakeCloudFunctionArgs = { diff --git a/spec/config.spec.ts b/spec/v1/config.spec.ts similarity index 98% rename from spec/config.spec.ts rename to spec/v1/config.spec.ts index 1f93ea10e..8a40fe523 100644 --- a/spec/config.spec.ts +++ b/spec/v1/config.spec.ts @@ -25,7 +25,7 @@ import * as fs from 'fs'; import * as mockRequire from 'mock-require'; import Sinon = require('sinon'); -import * as config from '../src/config'; +import * as config from '../../src/v1/config'; describe('config()', () => { let readFileSync: Sinon.SinonStub; diff --git a/spec/function-builder.spec.ts b/spec/v1/function-builder.spec.ts similarity index 99% rename from spec/function-builder.spec.ts rename to spec/v1/function-builder.spec.ts index 106f874ea..431d018ac 100644 --- a/spec/function-builder.spec.ts +++ b/spec/v1/function-builder.spec.ts @@ -22,7 +22,7 @@ import { expect } from 'chai'; -import * as functions from '../src/index'; +import * as functions from '../../src/v1'; describe('FunctionBuilder', () => { before(() => { diff --git a/spec/providers/analytics.spec.input.ts b/spec/v1/providers/analytics.spec.input.ts similarity index 98% rename from spec/providers/analytics.spec.input.ts rename to spec/v1/providers/analytics.spec.input.ts index bd9768b16..74ad65a93 100644 --- a/spec/providers/analytics.spec.input.ts +++ b/spec/v1/providers/analytics.spec.input.ts @@ -21,7 +21,7 @@ // SOFTWARE. /* tslint:disable:max-line-length */ -import { AnalyticsEvent } from '../../src/providers/analytics'; +import { AnalyticsEvent } from '../../../src/v1/providers/analytics'; // A payload, as it might arrive over the wire. Every possible field is filled out at least once. export const fullPayload = JSON.parse(`{ diff --git a/spec/providers/analytics.spec.ts b/spec/v1/providers/analytics.spec.ts similarity index 98% rename from spec/providers/analytics.spec.ts rename to spec/v1/providers/analytics.spec.ts index 4a5e84a0c..a6e6b0af6 100644 --- a/spec/providers/analytics.spec.ts +++ b/spec/v1/providers/analytics.spec.ts @@ -22,9 +22,9 @@ import { expect } from 'chai'; -import { Event, EventContext } from '../../src/cloud-functions'; -import * as functions from '../../src/index'; -import * as analytics from '../../src/providers/analytics'; +import * as functions from '../../../src/v1'; +import { Event, EventContext } from '../../../src/v1/cloud-functions'; +import * as analytics from '../../../src/v1/providers/analytics'; import * as analytics_spec_input from './analytics.spec.input'; describe('Analytics Functions', () => { diff --git a/spec/providers/auth.spec.ts b/spec/v1/providers/auth.spec.ts similarity index 97% rename from spec/providers/auth.spec.ts rename to spec/v1/providers/auth.spec.ts index bb2ab7761..df0db01f7 100644 --- a/spec/providers/auth.spec.ts +++ b/spec/v1/providers/auth.spec.ts @@ -23,9 +23,13 @@ import { expect } from 'chai'; import * as firebase from 'firebase-admin'; -import { CloudFunction, Event, EventContext } from '../../src/cloud-functions'; -import * as functions from '../../src/index'; -import * as auth from '../../src/providers/auth'; +import * as functions from '../../../src/index'; +import { + CloudFunction, + Event, + EventContext, +} from '../../../src/v1/cloud-functions'; +import * as auth from '../../../src/v1/providers/auth'; describe('Auth Functions', () => { const event: Event = { diff --git a/spec/providers/database.spec.ts b/spec/v1/providers/database.spec.ts similarity index 98% rename from spec/providers/database.spec.ts rename to spec/v1/providers/database.spec.ts index 10e1b0efb..27d5854c6 100644 --- a/spec/providers/database.spec.ts +++ b/spec/v1/providers/database.spec.ts @@ -21,11 +21,11 @@ // SOFTWARE. import { expect } from 'chai'; -import { apps as appsNamespace } from '../../src/apps'; -import * as config from '../../src/config'; -import * as functions from '../../src/index'; -import * as database from '../../src/providers/database'; -import { applyChange } from '../../src/utils'; +import { apps as appsNamespace } from '../../../src/v1/apps'; +import * as config from '../../../src/v1/config'; +import * as functions from '../../../src/v1/index'; +import * as database from '../../../src/v1/providers/database'; +import { applyChange } from '../../../src/v1/utils'; describe('Database Functions', () => { describe('DatabaseBuilder', () => { diff --git a/spec/providers/firestore.spec.ts b/spec/v1/providers/firestore.spec.ts similarity index 99% rename from spec/providers/firestore.spec.ts rename to spec/v1/providers/firestore.spec.ts index 271701108..07014aeb0 100644 --- a/spec/providers/firestore.spec.ts +++ b/spec/v1/providers/firestore.spec.ts @@ -24,8 +24,8 @@ import { expect } from 'chai'; import * as admin from 'firebase-admin'; import * as _ from 'lodash'; -import * as functions from '../../src/index'; -import * as firestore from '../../src/providers/firestore'; +import * as functions from '../../../src/index'; +import * as firestore from '../../../src/v1/providers/firestore'; describe('Firestore Functions', () => { function constructValue(fields: any) { diff --git a/spec/providers/https.spec.ts b/spec/v1/providers/https.spec.ts similarity index 98% rename from spec/providers/https.spec.ts rename to spec/v1/providers/https.spec.ts index 128ea0058..b34e29e6e 100644 --- a/spec/providers/https.spec.ts +++ b/spec/v1/providers/https.spec.ts @@ -24,10 +24,10 @@ import { expect } from 'chai'; import * as express from 'express'; import * as firebase from 'firebase-admin'; import * as _ from 'lodash'; -import { apps as appsNamespace } from '../../src/apps'; -import * as functions from '../../src/index'; -import * as https from '../../src/providers/https'; -import * as mocks from '../fixtures/credential/key.json'; +import * as functions from '../../../src/index'; +import { apps as appsNamespace } from '../../../src/v1/apps'; +import * as https from '../../../src/v1/providers/https'; +import * as mocks from '../../fixtures/credential/key.json'; import { expectedResponseHeaders, generateAppCheckToken, @@ -36,7 +36,7 @@ import { mockFetchPublicKeys, MockRequest, mockRequest, -} from '../fixtures/mockrequest'; +} from '../../fixtures/mockrequest'; describe('CloudHttpsBuilder', () => { describe('#onRequest', () => { diff --git a/spec/providers/pubsub.spec.ts b/spec/v1/providers/pubsub.spec.ts similarity index 98% rename from spec/providers/pubsub.spec.ts rename to spec/v1/providers/pubsub.spec.ts index f64cd2119..3d0569057 100644 --- a/spec/providers/pubsub.spec.ts +++ b/spec/v1/providers/pubsub.spec.ts @@ -21,9 +21,9 @@ // SOFTWARE. import { expect } from 'chai'; -import { Event } from '../../src/index'; -import * as functions from '../../src/index'; -import * as pubsub from '../../src/providers/pubsub'; +import { Event } from '../../../src/index'; +import * as functions from '../../../src/index'; +import * as pubsub from '../../../src/v1/providers/pubsub'; describe('Pubsub Functions', () => { describe('pubsub.Message', () => { diff --git a/spec/providers/remoteConfig.spec.ts b/spec/v1/providers/remoteConfig.spec.ts similarity index 96% rename from spec/providers/remoteConfig.spec.ts rename to spec/v1/providers/remoteConfig.spec.ts index ef6d68572..2ccfe41fd 100644 --- a/spec/providers/remoteConfig.spec.ts +++ b/spec/v1/providers/remoteConfig.spec.ts @@ -22,14 +22,14 @@ import { expect } from 'chai'; import * as _ from 'lodash'; +import * as functions from '../../../src/index'; import { CloudFunction, Event, EventContext, TriggerAnnotated, -} from '../../src/cloud-functions'; -import * as functions from '../../src/index'; -import * as remoteConfig from '../../src/providers/remoteConfig'; +} from '../../../src/v1/cloud-functions'; +import * as remoteConfig from '../../../src/v1/providers/remoteConfig'; describe('RemoteConfig Functions', () => { function constructVersion() { diff --git a/spec/providers/storage.spec.ts b/spec/v1/providers/storage.spec.ts similarity index 98% rename from spec/providers/storage.spec.ts rename to spec/v1/providers/storage.spec.ts index c104a076f..aee2d72a9 100644 --- a/spec/providers/storage.spec.ts +++ b/spec/v1/providers/storage.spec.ts @@ -21,10 +21,10 @@ // SOFTWARE. import { expect } from 'chai'; -import * as config from '../../src/config'; -import { Event, EventContext } from '../../src/index'; -import * as functions from '../../src/index'; -import * as storage from '../../src/providers/storage'; +import { Event, EventContext } from '../../../src/v1'; +import * as functions from '../../../src/v1'; +import * as config from '../../../src/v1/config'; +import * as storage from '../../../src/v1/providers/storage'; describe('Storage Functions', () => { describe('ObjectBuilder', () => { diff --git a/spec/providers/testLab.spec.ts b/spec/v1/providers/testLab.spec.ts similarity index 99% rename from spec/providers/testLab.spec.ts rename to spec/v1/providers/testLab.spec.ts index 8a70cf680..5584bfbf7 100644 --- a/spec/providers/testLab.spec.ts +++ b/spec/v1/providers/testLab.spec.ts @@ -22,7 +22,7 @@ import { expect } from 'chai'; -import * as testLab from '../../src/providers/testLab'; +import * as testLab from '../../../src/v1/providers/testLab'; describe('Test Lab Functions', () => { describe('#onComplete', () => { diff --git a/spec/setup.spec.ts b/spec/v1/setup.spec.ts similarity index 97% rename from spec/setup.spec.ts rename to spec/v1/setup.spec.ts index 289b90fc1..20ca26d12 100644 --- a/spec/setup.spec.ts +++ b/spec/v1/setup.spec.ts @@ -21,7 +21,7 @@ // SOFTWARE. import { expect } from 'chai'; -import { setup } from '../src/setup'; +import { setup } from '../../src/v1/setup'; describe('setup()', () => { afterEach(() => { diff --git a/spec/utils.spec.ts b/spec/v1/utils.spec.ts similarity index 97% rename from spec/utils.spec.ts rename to spec/v1/utils.spec.ts index 51a8478fa..119d203f6 100644 --- a/spec/utils.spec.ts +++ b/spec/v1/utils.spec.ts @@ -21,7 +21,7 @@ // SOFTWARE. import { expect } from 'chai'; -import { applyChange } from '../src/utils'; +import { applyChange } from '../../src/v1/utils'; describe('utils', () => { describe('.applyChange(from: any, to: any): any', () => { diff --git a/src/index.ts b/src/index.ts index dbb42003c..5b98253d9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,62 +1 @@ -// The MIT License (MIT) -// -// Copyright (c) 2017 Firebase -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -// Providers: -import * as analytics from './providers/analytics'; -import * as auth from './providers/auth'; -import * as database from './providers/database'; -import * as firestore from './providers/firestore'; -import * as https from './providers/https'; -import * as pubsub from './providers/pubsub'; -import * as remoteConfig from './providers/remoteConfig'; -import * as storage from './providers/storage'; -import * as testLab from './providers/testLab'; - -import * as apps from './apps'; -import { handler } from './handler-builder'; -import * as logger from './logger'; -import { setup } from './setup'; - -const app = apps.apps(); - -export { - analytics, - app, - auth, - database, - firestore, - handler, - https, - pubsub, - remoteConfig, - storage, - testLab, - logger, -}; - -// Exported root types: -export * from './cloud-functions'; -export * from './config'; -export * from './function-builder'; -export * from './function-configuration'; - -setup(); +export * from './v1'; diff --git a/src/logger.ts b/src/logger/index.ts similarity index 99% rename from src/logger.ts rename to src/logger/index.ts index 1dd109179..24483087c 100644 --- a/src/logger.ts +++ b/src/logger/index.ts @@ -4,7 +4,7 @@ import { CONSOLE_SEVERITY, SUPPORTS_STRUCTURED_LOGS, UNPATCHED_CONSOLE, -} from './logger/common'; +} from './common'; /** * `LogSeverity` indicates the detailed severity of the log entry. See [LogSeverity](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity). diff --git a/src/apps.ts b/src/v1/apps.ts similarity index 100% rename from src/apps.ts rename to src/v1/apps.ts diff --git a/src/cloud-functions.ts b/src/v1/cloud-functions.ts similarity index 99% rename from src/cloud-functions.ts rename to src/v1/cloud-functions.ts index 2a6c770e6..ef59c0422 100644 --- a/src/cloud-functions.ts +++ b/src/v1/cloud-functions.ts @@ -22,13 +22,13 @@ import { Request, Response } from 'express'; import * as _ from 'lodash'; +import { warn } from '../logger'; import { DEFAULT_FAILURE_POLICY, DeploymentOptions, FailurePolicy, Schedule, } from './function-configuration'; -import { warn } from './logger'; export { Request, Response }; /** @hidden */ diff --git a/src/config.ts b/src/v1/config.ts similarity index 100% rename from src/config.ts rename to src/v1/config.ts diff --git a/src/encoder.ts b/src/v1/encoder.ts similarity index 100% rename from src/encoder.ts rename to src/v1/encoder.ts diff --git a/src/function-builder.ts b/src/v1/function-builder.ts similarity index 100% rename from src/function-builder.ts rename to src/v1/function-builder.ts diff --git a/src/function-configuration.ts b/src/v1/function-configuration.ts similarity index 100% rename from src/function-configuration.ts rename to src/v1/function-configuration.ts diff --git a/src/handler-builder.ts b/src/v1/handler-builder.ts similarity index 100% rename from src/handler-builder.ts rename to src/v1/handler-builder.ts diff --git a/src/v1/index.ts b/src/v1/index.ts new file mode 100644 index 000000000..ec83eeab5 --- /dev/null +++ b/src/v1/index.ts @@ -0,0 +1,62 @@ +// The MIT License (MIT) +// +// Copyright (c) 2017 Firebase +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// Providers: +import * as analytics from './providers/analytics'; +import * as auth from './providers/auth'; +import * as database from './providers/database'; +import * as firestore from './providers/firestore'; +import * as https from './providers/https'; +import * as pubsub from './providers/pubsub'; +import * as remoteConfig from './providers/remoteConfig'; +import * as storage from './providers/storage'; +import * as testLab from './providers/testLab'; + +import * as logger from '../logger'; +import * as apps from './apps'; +import { handler } from './handler-builder'; +import { setup } from './setup'; + +const app = apps.apps(); + +export { + analytics, + app, + auth, + database, + firestore, + handler, + https, + pubsub, + remoteConfig, + storage, + testLab, + logger, +}; + +// Exported root types: +export * from './cloud-functions'; +export * from './config'; +export * from './function-builder'; +export * from './function-configuration'; + +setup(); diff --git a/src/providers/analytics.ts b/src/v1/providers/analytics.ts similarity index 100% rename from src/providers/analytics.ts rename to src/v1/providers/analytics.ts diff --git a/src/providers/auth.ts b/src/v1/providers/auth.ts similarity index 100% rename from src/providers/auth.ts rename to src/v1/providers/auth.ts diff --git a/src/providers/database.ts b/src/v1/providers/database.ts similarity index 99% rename from src/providers/database.ts rename to src/v1/providers/database.ts index 76252d515..372419169 100644 --- a/src/providers/database.ts +++ b/src/v1/providers/database.ts @@ -22,6 +22,7 @@ import * as firebase from 'firebase-admin'; import * as _ from 'lodash'; +import { joinPath, normalizePath, pathParts } from '../../utilities/path'; import { apps } from '../apps'; import { Change, @@ -32,7 +33,6 @@ import { } from '../cloud-functions'; import { firebaseConfig } from '../config'; import { DeploymentOptions } from '../function-configuration'; -import { joinPath, normalizePath, pathParts } from '../utilities/path'; import { applyChange } from '../utils'; /** @hidden */ diff --git a/src/providers/firestore.ts b/src/v1/providers/firestore.ts similarity index 100% rename from src/providers/firestore.ts rename to src/v1/providers/firestore.ts diff --git a/src/providers/https.ts b/src/v1/providers/https.ts similarity index 99% rename from src/providers/https.ts rename to src/v1/providers/https.ts index c6167c50a..c6d5cc8a0 100644 --- a/src/providers/https.ts +++ b/src/v1/providers/https.ts @@ -25,10 +25,10 @@ import * as express from 'express'; import * as firebase from 'firebase-admin'; import * as _ from 'lodash'; +import { error, info, warn } from '../../logger'; import { apps } from '../apps'; import { HttpsFunction, optionsToTrigger, Runnable } from '../cloud-functions'; import { DeploymentOptions } from '../function-configuration'; -import { error, info, warn } from '../logger'; /** @hidden */ export interface Request extends express.Request { diff --git a/src/providers/pubsub.ts b/src/v1/providers/pubsub.ts similarity index 100% rename from src/providers/pubsub.ts rename to src/v1/providers/pubsub.ts diff --git a/src/providers/remoteConfig.ts b/src/v1/providers/remoteConfig.ts similarity index 100% rename from src/providers/remoteConfig.ts rename to src/v1/providers/remoteConfig.ts diff --git a/src/providers/storage.ts b/src/v1/providers/storage.ts similarity index 100% rename from src/providers/storage.ts rename to src/v1/providers/storage.ts diff --git a/src/providers/testLab.ts b/src/v1/providers/testLab.ts similarity index 100% rename from src/providers/testLab.ts rename to src/v1/providers/testLab.ts diff --git a/src/setup.ts b/src/v1/setup.ts similarity index 98% rename from src/setup.ts rename to src/v1/setup.ts index 6a9db702d..6b0eb3506 100644 --- a/src/setup.ts +++ b/src/v1/setup.ts @@ -21,8 +21,8 @@ // SOFTWARE. /** @hidden */ +import { warn } from '../logger'; import { firebaseConfig } from './config'; -import { warn } from './logger'; // Set up for config and vars export function setup() { diff --git a/src/utils.ts b/src/v1/utils.ts similarity index 100% rename from src/utils.ts rename to src/v1/utils.ts diff --git a/tsconfig.release.json b/tsconfig.release.json index 1a45bd58b..b8f2632fd 100644 --- a/tsconfig.release.json +++ b/tsconfig.release.json @@ -10,5 +10,5 @@ "target": "es2018", "typeRoots": ["./node_modules/@types"] }, - "files": ["./src/index.ts", "./src/logger.ts", "./src/logger/compat.ts"] + "files": ["./src/index.ts", "./src/logger/index.ts", "./src/logger/compat.ts"] } From 57ca58a1e3b8a4db652bc31e477c8b95fa5899a3 Mon Sep 17 00:00:00 2001 From: Thomas Bouldin Date: Mon, 12 Jul 2021 11:27:20 -0700 Subject: [PATCH 218/437] RemoteConfig can be loaded in windows CMD.exe (#913) Favors using process.cwd over process.env.PWD as the latter is POSIX only (I might need to go check some of my code in firebase-tools!). While I was at it, I fixed a probably useless but nasty vulnerability where a malformed .runtimeconfig.json file would allow arbitrary code execution. --- CHANGELOG.md | 1 + spec/v1/config.spec.ts | 29 +++++++++++++++++++---------- src/v1/config.ts | 10 ++++++---- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adfc05c3a..45844cd0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Adds support for setting user labels on functions via `runWith()`. - Adds support for FIREBASE_CONFIG env as the name of a JSON file - Formalize module exports. Loggers can now be accessed at 'firebase-functions/logger' and 'firebase-functions/logger/compat' +- Fixes an issue where Remote Config coiuld not be emulated in Windows machines on the classic Command Prompt. diff --git a/spec/v1/config.spec.ts b/spec/v1/config.spec.ts index 8a40fe523..d1deea4fd 100644 --- a/spec/v1/config.spec.ts +++ b/spec/v1/config.spec.ts @@ -22,27 +22,27 @@ import { expect } from 'chai'; import * as fs from 'fs'; -import * as mockRequire from 'mock-require'; +import * as process from 'process'; import Sinon = require('sinon'); import * as config from '../../src/v1/config'; describe('config()', () => { let readFileSync: Sinon.SinonStub; + let cwdStub: Sinon.SinonStub; before(() => { readFileSync = Sinon.stub(fs, 'readFileSync'); readFileSync.throws('Unexpected call'); - process.env.PWD = '/srv'; + cwdStub = Sinon.stub(process, 'cwd'); + cwdStub.returns('/srv'); }); after(() => { - delete process.env.PWD; Sinon.verifyAndRestore(); }); afterEach(() => { - mockRequire.stopAll(); delete config.config.singleton; (config as any).firebaseConfigCache = null; delete process.env.FIREBASE_CONFIG; @@ -50,19 +50,27 @@ describe('config()', () => { }); it('loads config values from .runtimeconfig.json', () => { - mockRequire('/srv/.runtimeconfig.json', { foo: 'bar', firebase: {} }); + const json = JSON.stringify({ + foo: 'bar', + firebase: {}, + }); + readFileSync + .withArgs('/srv/.runtimeconfig.json') + .returns(Buffer.from(json)); const loaded = config.config(); expect(loaded).to.not.have.property('firebase'); expect(loaded).to.have.property('foo', 'bar'); }); it('does not provide firebase config if .runtimeconfig.json not invalid', () => { - mockRequire('/srv/.runtimeconfig.json', 'does-not-exist'); + readFileSync.withArgs('/srv/.runtimeconfig.json').returns('invalid JSON'); expect(config.firebaseConfig()).to.be.null; }); it('does not provide firebase config if .ruuntimeconfig.json has no firebase property', () => { - mockRequire('/srv/.runtimeconfig.json', {}); + readFileSync + .withArgs('/srv/.runtimeconfig.json') + .returns(Buffer.from('{}')); expect(config.firebaseConfig()).to.be.null; }); @@ -78,7 +86,7 @@ describe('config()', () => { it('loads Firebase configs from FIREBASE_CONFIG env variable pointing to a file', () => { const oldEnv = process.env; - process.env = { + (process as any).env = { ...oldEnv, FIREBASE_CONFIG: '.firebaseconfig.json', }; @@ -91,13 +99,14 @@ describe('config()', () => { 'foo@firebaseio.com' ); } finally { - process.env = oldEnv; + (process as any).env = oldEnv; } }); it('accepts alternative locations for config file', () => { process.env.CLOUD_RUNTIME_CONFIG = 'another.json'; - mockRequire('another.json', { foo: 'bar', firebase: {} }); + const json = JSON.stringify({ foo: 'bar', firebase: {} }); + readFileSync.withArgs('another.json').returns(Buffer.from(json)); expect(config.firebaseConfig()).to.not.be.null; expect(config.config()).to.have.property('foo', 'bar'); }); diff --git a/src/v1/config.ts b/src/v1/config.ts index c49e7d03f..f0793957d 100644 --- a/src/v1/config.ts +++ b/src/v1/config.ts @@ -89,8 +89,9 @@ export function firebaseConfig(): firebase.AppOptions | null { try { const configPath = process.env.CLOUD_RUNTIME_CONFIG || - path.join(process.env.PWD, '.runtimeconfig.json'); - const config = require(configPath); + path.join(process.cwd(), '.runtimeconfig.json'); + const contents = fs.readFileSync(configPath); + const config = JSON.parse(contents.toString('utf8')); if (config.firebase) { firebaseConfigCache = config.firebase; return firebaseConfigCache; @@ -115,8 +116,9 @@ function init() { try { const configPath = process.env.CLOUD_RUNTIME_CONFIG || - path.join(process.env.PWD, '.runtimeconfig.json'); - const parsed = require(configPath); + path.join(process.cwd(), '.runtimeconfig.json'); + const contents = fs.readFileSync(configPath); + const parsed = JSON.parse(contents.toString('utf8')); delete parsed.firebase; config.singleton = parsed; return; From 5e7fb2cf68821b26fcb8519f88a270577bfe6f11 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Tue, 13 Jul 2021 16:19:17 -0700 Subject: [PATCH 219/437] Fixes docgen after file renames. (#914) --- .gitignore | 1 + docgen/content-sources/toc.yaml | 120 +- docgen/generate-docs.js | 95 +- docgen/theme/helpers/cleanBreadcrumb.js | 4 + docgen/theme/partials/breadcrumb.hbs | 2 +- docgen/theme/partials/header.hbs | 2 +- package-lock.json | 5923 ++++++++++++++++++++++- src/logger/index.ts | 1 + 8 files changed, 6020 insertions(+), 128 deletions(-) create mode 100644 docgen/theme/helpers/cleanBreadcrumb.js diff --git a/.gitignore b/.gitignore index ba3cdeec5..b5a890408 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ node_modules npm-debug.log typings yarn.lock +.DS_Store diff --git a/docgen/content-sources/toc.yaml b/docgen/content-sources/toc.yaml index e970fc80c..ac430151b 100644 --- a/docgen/content-sources/toc.yaml +++ b/docgen/content-sources/toc.yaml @@ -1,148 +1,148 @@ toc: - title: 'functions' - path: /docs/reference/functions/cloud_functions_.html + path: /docs/reference/functions/v1_cloud_functions_.html section: - title: 'CloudFunction' - path: /docs/reference/functions/cloud_functions_.html#cloudfunction + path: /docs/reference/functions/v1_cloud_functions_.html#cloudfunction - title: 'HttpsFunction' - path: /docs/reference/functions/cloud_functions_.html#httpsfunction + path: /docs/reference/functions/v1_cloud_functions_.html#httpsfunction - title: 'EventContext' - path: /docs/reference/functions/cloud_functions_.eventcontext.html + path: /docs/reference/functions/v1_cloud_functions_.eventcontext.html - title: 'FunctionBuilder' - path: /docs/reference/functions/function_builder_.functionbuilder.html + path: /docs/reference/functions/v1_function_builder_.functionbuilder.html - title: 'Change' - path: /docs/reference/functions/cloud_functions_.change.html + path: /docs/reference/functions/v1_cloud_functions_.change.html - title: 'ChangeJson' - path: /docs/reference/functions/cloud_functions_.changejson.html + path: /docs/reference/functions/v1_cloud_functions_.changejson.html - title: 'functions.config' - path: /docs/reference/functions/config_.html + path: /docs/reference/functions/v1_config_.html section: - title: 'Config' - path: /docs/reference/functions/config_.config.html + path: /docs/reference/functions/v1_config_.config.html - title: 'config.Config' - path: /docs/reference/functions/config_.config.config.html + path: /docs/reference/functions/v1_config_.config.config.html - title: 'functions.function-configuration' - path: /docs/reference/functions/function_configuration_.html + path: /docs/reference/functions/v1_function_configuration_.html section: - title: 'config.DeploymentOptions' - path: /docs/reference/functions/function_configuration_.deploymentoptions.html + path: /docs/reference/functions/v1_function_configuration_.deploymentoptions.html - title: 'config.FailurePolicy' - path: /docs/reference/functions/function_configuration_.failurepolicy.html + path: /docs/reference/functions/v1_function_configuration_.failurepolicy.html - title: 'config.RuntimeOptions' - path: /docs/reference/functions/function_configuration_.runtimeoptions.html + path: /docs/reference/functions/v1_function_configuration_.runtimeoptions.html - title: 'config.Schedule' - path: /docs/reference/functions/function_configuration_.schedule.html + path: /docs/reference/functions/v1_function_configuration_.schedule.html - title: 'config.ScheduleRetryConfig' - path: /docs/reference/functions/function_configuration_.scheduleretryconfig.html + path: /docs/reference/functions/v1_function_configuration_.scheduleretryconfig.html - title: 'functions.analytics' - path: /docs/reference/functions/providers_analytics_.html + path: /docs/reference/functions/v1_providers_analytics_.html section: - title: 'AnalyticsEvent' - path: /docs/reference/functions/providers_analytics_.analyticsevent.html + path: /docs/reference/functions/v1_providers_analytics_.analyticsevent.html - title: 'AnalyticsEventBuilder' - path: /docs/reference/functions/providers_analytics_.analyticseventbuilder.html + path: /docs/reference/functions/v1_providers_analytics_.analyticseventbuilder.html - title: 'AppInfo' - path: /docs/reference/functions/providers_analytics_.appinfo.html + path: /docs/reference/functions/v1_providers_analytics_.appinfo.html - title: 'DeviceInfo' - path: /docs/reference/functions/providers_analytics_.deviceinfo.html + path: /docs/reference/functions/v1_providers_analytics_.deviceinfo.html - title: 'ExportBundleInfo' - path: /docs/reference/functions/providers_analytics_.exportbundleinfo.html + path: /docs/reference/functions/v1_providers_analytics_.exportbundleinfo.html - title: 'GeoInfo' - path: /docs/reference/functions/providers_analytics_.geoinfo.html + path: /docs/reference/functions/v1_providers_analytics_.geoinfo.html - title: 'UserDimensions' - path: /docs/reference/functions/providers_analytics_.userdimensions.html + path: /docs/reference/functions/v1_providers_analytics_.userdimensions.html - title: 'UserPropertyValue' - path: /docs/reference/functions/providers_analytics_.userpropertyvalue.html + path: /docs/reference/functions/v1_providers_analytics_.userpropertyvalue.html - title: 'functions.auth' - path: /docs/reference/functions/providers_auth_.html + path: /docs/reference/functions/v1_providers_auth_.html section: - title: 'UserBuilder' - path: /docs/reference/functions/providers_auth_.userbuilder.html + path: /docs/reference/functions/v1_providers_auth_.userbuilder.html - title: 'UserInfo' - path: /docs/reference/functions/providers_auth_.html#userinfo + path: /docs/reference/functions/v1_providers_auth_.html#userinfo - title: 'UserRecordMetadata' - path: /docs/reference/functions/providers_auth_.userrecordmetadata.html + path: /docs/reference/functions/v1_providers_auth_.userrecordmetadata.html - title: 'UserRecord' - path: /docs/reference/functions/providers_auth_.html#userrecord + path: /docs/reference/functions/v1_providers_auth_.html#userrecord - title: 'functions.firestore' - path: /docs/reference/functions/providers_firestore_.html + path: /docs/reference/functions/v1_providers_firestore_.html section: - title: 'DocumentBuilder' - path: /docs/reference/functions/providers_firestore_.documentbuilder.html + path: /docs/reference/functions/v1_providers_firestore_.documentbuilder.html - title: 'DocumentSnapshot' - path: /docs/reference/functions/providers_firestore_.html#documentsnapshot + path: /docs/reference/functions/v1_providers_firestore_.html#documentsnapshot - title: 'functions.database' - path: /docs/reference/functions/providers_database_.html + path: /docs/reference/functions/v1_providers_database_.html section: - title: 'DataSnapshot' - path: /docs/reference/functions/providers_database_.datasnapshot.html + path: /docs/reference/functions/v1_providers_database_.datasnapshot.html - title: 'RefBuilder' - path: /docs/reference/functions/providers_database_.refbuilder.html + path: /docs/reference/functions/v1_providers_database_.refbuilder.html - title: 'InstanceBuilder' - path: /docs/reference/functions/providers_database_.instancebuilder.html + path: /docs/reference/functions/v1_providers_database_.instancebuilder.html - title: 'functions.https' - path: /docs/reference/functions/providers_https_.html + path: /docs/reference/functions/v1_providers_https_.html section: - title: 'HttpsError' - path: /docs/reference/functions/providers_https_.httpserror.html + path: /docs/reference/functions/v1_providers_https_.httpserror.html - title: 'CallableContext' - path: /docs/reference/functions/providers_https_.callablecontext.html + path: /docs/reference/functions/v1_providers_https_.callablecontext.html - title: 'functions.logger' - path: /docs/reference/functions/logger_.html + path: /docs/reference/functions/logger_index_.html section: - title: 'LogEntry' - path: /docs/reference/functions/logger_.logentry.html + path: /docs/reference/functions/logger_index_.logentry.html - title: 'functions.pubsub' - path: /docs/reference/functions/providers_pubsub_.html + path: /docs/reference/functions/v1_providers_pubsub_.html section: - title: 'Message' - path: /docs/reference/functions/providers_pubsub_.message.html + path: /docs/reference/functions/v1_providers_pubsub_.message.html - title: 'TopicBuilder' - path: /docs/reference/functions/providers_pubsub_.topicbuilder.html + path: /docs/reference/functions/v1_providers_pubsub_.topicbuilder.html - title: 'ScheduleBuilder' - path: /docs/reference/functions/providers_pubsub_.schedulebuilder.html + path: /docs/reference/functions/v1_providers_pubsub_.schedulebuilder.html - title: 'functions.remoteconfig' - path: /docs/reference/functions/providers_remoteconfig_.html + path: /docs/reference/functions/v1_providers_remoteconfig_.html section: - title: 'RemoteConfigUser' - path: /docs/reference/functions/providers_remoteconfig_.remoteconfiguser.html + path: /docs/reference/functions/v1_providers_remoteconfig_.remoteconfiguser.html - title: 'TemplateVersion' - path: /docs/reference/functions/providers_remoteconfig_.templateversion.html + path: /docs/reference/functions/v1_providers_remoteconfig_.templateversion.html - title: 'functions.storage' - path: /docs/reference/functions/providers_storage_.html + path: /docs/reference/functions/v1_providers_storage_.html section: - title: 'BucketBuilder' - path: /docs/reference/functions/providers_storage_.bucketbuilder.html + path: /docs/reference/functions/v1_providers_storage_.bucketbuilder.html - title: 'ObjectBuilder' - path: /docs/reference/functions/providers_storage_.objectbuilder.html + path: /docs/reference/functions/v1_providers_storage_.objectbuilder.html - title: 'ObjectMetadata' - path: /docs/reference/functions/providers_storage_.objectmetadata.html + path: /docs/reference/functions/v1_providers_storage_.objectmetadata.html - title: 'functions.testLab' - path: /docs/reference/functions/providers_testlab_.html + path: /docs/reference/functions/v1_providers_testlab_.html section: - title: 'testLab.clientInfo' - path: /docs/reference/functions/providers_testlab_.clientinfo.html + path: /docs/reference/functions/v1_providers_testlab_.clientinfo.html - title: 'testLab.resultStorage' - path: /docs/reference/functions/providers_testlab_.resultstorage.html + path: /docs/reference/functions/v1_providers_testlab_.resultstorage.html - title: 'testLab.testMatrix' - path: /docs/reference/functions/providers_testlab_.testmatrix.html + path: /docs/reference/functions/v1_providers_testlab_.testmatrix.html - title: 'testLab.testMatrixBuilder' - path: /docs/reference/functions/providers_testlab_.testmatrixbuilder.html + path: /docs/reference/functions/v1_providers_testlab_.testmatrixbuilder.html - title: 'functions.handler' - path: /docs/reference/functions/handler_builder_.html + path: /docs/reference/functions/v1_handler_builder_.html section: - title: 'HandlerBuilder' - path: /docs/reference/functions/handler_builder_.handlerbuilder.html + path: /docs/reference/functions/v1_handler_builder_.handlerbuilder.html diff --git a/docgen/generate-docs.js b/docgen/generate-docs.js index d36edb7e8..2f8b9abb0 100644 --- a/docgen/generate-docs.js +++ b/docgen/generate-docs.js @@ -26,9 +26,9 @@ const repoPath = path.resolve(`${__dirname}/..`); // Command-line options. const { source: sourceFile } = yargs .option('source', { - default: `${repoPath}/src`, + default: `${repoPath}/src/{v1,logger}`, describe: 'Typescript source file(s)', - type: 'string' + type: 'string', }) .version(false) .help().argv; @@ -38,7 +38,7 @@ const contentPath = path.resolve(`${__dirname}/content-sources`); const tempHomePath = path.resolve(`${contentPath}/HOME_TEMP.md`); const devsitePath = `/docs/reference/functions/`; -const { JSDOM } = require("jsdom"); +const { JSDOM } = require('jsdom'); const typeMap = require('./type-aliases.json'); @@ -72,7 +72,7 @@ function runTypedoc() { * @param {string} subdir Subdir to move files out of. */ async function moveFilesToRoot(subdir) { - await exec(`mv ${docPath}/${subdir}/* ${docPath}`) + await exec(`mv ${docPath}/${subdir}/* ${docPath}`); await exec(`rmdir ${docPath}/${subdir}`); } @@ -86,9 +86,11 @@ async function renameFiles() { const files = await fs.readdir(docPath); const renames = []; for (const file of files) { - if (file.startsWith("_") && file.endsWith("html")) { + if (file.startsWith('_') && file.endsWith('html')) { let newFileName = file.substring(1); - renames.push(fs.rename(`${docPath}/${file}`, `${docPath}/${newFileName}`)); + renames.push( + fs.rename(`${docPath}/${file}`, `${docPath}/${newFileName}`) + ); } } await Promise.all(renames); @@ -125,11 +127,13 @@ function addTypeAliasLinks(data) { * Select .tsd-signature-type because all potential external * links will have this identifier. */ - const fileTags = htmlDom.window.document.querySelectorAll(".tsd-signature-type"); - fileTags.forEach(tag => { + const fileTags = htmlDom.window.document.querySelectorAll( + '.tsd-signature-type' + ); + for (const tag of fileTags) { const mapping = typeMap[tag.textContent]; if (mapping) { - console.log('Adding link to '+tag.textContent+" documentation."); + console.log('Adding link to ' + tag.textContent + ' documentation.'); // Add the corresponding document link to this type const linkChild = htmlDom.window.document.createElement('a'); @@ -138,7 +142,7 @@ function addTypeAliasLinks(data) { tag.textContent = null; tag.appendChild(linkChild); } - }); + } return htmlDom.serialize(); } @@ -152,13 +156,13 @@ function addTypeAliasLinks(data) { function generateTempHomeMdFile(tocRaw, homeRaw) { const { toc } = yaml.safeLoad(tocRaw); let tocPageLines = [homeRaw, '# API Reference']; - toc.forEach(group => { + for (const group of toc) { tocPageLines.push(`\n## [${group.title}](${stripPath(group.path)})`); const section = group.section || []; - section.forEach(item => { + for (const item of section) { tocPageLines.push(`- [${item.title}](${stripPath(item.path)})`); - }); - }); + } + } return fs.writeFile(tempHomePath, tocPageLines.join('\n')); } @@ -176,10 +180,10 @@ async function checkForMissingFilesAndFixFilenameCase(tocText) { // Get filenames from toc.yaml. const filenames = tocText .split('\n') - .filter(line => line.includes('path:')) - .map(line => line.split(devsitePath)[1]); + .filter((line) => line.includes('path:')) + .map((line) => line.split(devsitePath)[1].replace(/#.*$/, '')); // Logs warning to console if a file from TOC is not found. - const fileCheckPromises = filenames.map(async filename => { + const fileCheckPromises = filenames.map(async (filename) => { // Warns if file does not exist, fixes filename case if it does. // Preferred filename for devsite should be capitalized and taken from // toc.yaml. @@ -211,29 +215,28 @@ async function checkForMissingFilesAndFixFilenameCase(tocText) { */ async function checkForUnlistedFiles(filenamesFromToc, shouldRemove) { const files = await fs.readdir(docPath); - const htmlFiles = files - .filter(filename => filename.slice(-4) === 'html'); + const htmlFiles = files.filter((filename) => filename.slice(-4) === 'html'); const removePromises = []; const filesToRemove = htmlFiles - .filter(filename => !filenamesFromToc.includes(filename)) - .filter(filename => filename !== 'index' && filename != 'globals'); + .filter((filename) => !filenamesFromToc.includes(filename)) + .filter((filename) => filename !== 'index' && filename != 'globals'); if (filesToRemove.length && !shouldRemove) { // This is just a warning, it doesn't need to finish before // the process continues. console.warn( - `Unlisted files: ${filesToRemove.join(", ")} generated ` + + `Unlisted files: ${filesToRemove.join(', ')} generated ` + `but not listed in toc.yaml.` ); return htmlFiles; } - await Promise.all(filesToRemove.map(filename => { - console.log( - `REMOVING ${docPath}/${filename} - not listed in toc.yaml.` - ); - return fs.unlink(`${docPath}/${filename})`); - })); - return htmlFiles.filter(filename => filenamesFromToc.includes(filename)) + await Promise.all( + filesToRemove.map((filename) => { + console.log(`REMOVING ${docPath}/${filename} - not listed in toc.yaml.`); + return fs.unlink(`${docPath}/${filename})`); + }) + ); + return htmlFiles.filter((filename) => filenamesFromToc.includes(filename)); } /** @@ -243,10 +246,10 @@ async function checkForUnlistedFiles(filenamesFromToc, shouldRemove) { * @param {Array} htmlFiles List of html files found in generated dir. */ async function writeGeneratedFileList(htmlFiles) { - const fileList = htmlFiles.map(filename => { + const fileList = htmlFiles.map((filename) => { return { title: filename, - path: `${devsitePath}${filename}` + path: `${devsitePath}${filename}`, }; }); const generatedTocYAML = yaml.safeDump({ toc: fileList }); @@ -262,10 +265,10 @@ async function writeGeneratedFileList(htmlFiles) { */ function fixAllLinks(htmlFiles) { const writePromises = []; - htmlFiles.forEach(file => { + for (const file of htmlFiles) { // Update links in each html file to match flattened file structure. writePromises.push(fixLinks(`${docPath}/${file}`)); - }); + } return Promise.all(writePromises); } @@ -281,12 +284,12 @@ function fixAllLinks(htmlFiles) { * links as needed. * 5) Check for mismatches between TOC list and generated file list. */ -(async function() { +(async function () { try { const [tocRaw, homeRaw] = await Promise.all([ fs.readFile(`${contentPath}/toc.yaml`, 'utf8'), - fs.readFile(`${contentPath}/HOME.md`, 'utf8') - ]) + fs.readFile(`${contentPath}/HOME.md`, 'utf8'), + ]); // Run main Typedoc process (uses index.d.ts and generated temp file above). await generateTempHomeMdFile(tocRaw, homeRaw); @@ -324,7 +327,9 @@ function fixAllLinks(htmlFiles) { // Check for files listed in TOC that are missing and warn if so. // Not blocking. - const filenamesFromToc = await checkForMissingFilesAndFixFilenameCase(tocRaw); + const filenamesFromToc = await checkForMissingFilesAndFixFilenameCase( + tocRaw + ); // Check for files that exist but aren't listed in the TOC and warn. // (If API is node, actually remove the file.) @@ -340,13 +345,15 @@ function fixAllLinks(htmlFiles) { const data = await fs.readFile(`${docPath}/index.html`, 'utf8'); // String to include devsite local variables. const localVariablesIncludeString = `{% include "docs/web/_local_variables.html" %}\n`; - await fs.writeFile(`${docPath}/index.html`, localVariablesIncludeString + data); + await fs.writeFile( + `${docPath}/index.html`, + localVariablesIncludeString + data + ); } catch (err) { - if (err.stdout) { - console.error(err.stdout); - } else { - console.error(err); + if (err.stdout) { + console.error(err.stdout); + } else { + console.error(err); + } } -} })(); - diff --git a/docgen/theme/helpers/cleanBreadcrumb.js b/docgen/theme/helpers/cleanBreadcrumb.js new file mode 100644 index 000000000..ad52e64a7 --- /dev/null +++ b/docgen/theme/helpers/cleanBreadcrumb.js @@ -0,0 +1,4 @@ +exports.cleanBreadcrumb = function (value) { + const parts = value.replace(/"/g, '').split('/'); + return parts[parts.length - 1]; +}; diff --git a/docgen/theme/partials/breadcrumb.hbs b/docgen/theme/partials/breadcrumb.hbs index db115163f..6a2147724 100644 --- a/docgen/theme/partials/breadcrumb.hbs +++ b/docgen/theme/partials/breadcrumb.hbs @@ -3,7 +3,7 @@ {{#with parent}}{{> breadcrumb}}{{/with}}