diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c8c9b14f..a909dbdbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ - Parallelizes network calls that occur when validating authorization for onCall handlers. - Adds new regions to V2 API - Fixes bug where the emulator crashed when given app without an `options` property. +- Adds new alerting providers diff --git a/package.json b/package.json index 50637bb74..0be8d9ef9 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,11 @@ "./v2/https": "./lib/v2/providers/https.js", "./v2/params": "./lib/v2/params/index.js", "./v2/pubsub": "./lib/v2/providers/pubsub.js", - "./v2/storage": "./lib/v2/providers/storage.js" + "./v2/storage": "./lib/v2/providers/storage.js", + "./v2/alerts": "./lib/v2/providers/alerts/index.js", + "./v2/alerts/appDistribution": "./lib/v2/providers/alerts/appDistribution.js", + "./v2/alerts/billing": "./lib/v2/providers/alerts/billing.js", + "./v2/alerts/crashlytics": "./lib/v2/providers/alerts/crashlytics.js" }, "typesVersions": { "*": { @@ -114,6 +118,18 @@ ], "v2/storage": [ "lib/v2/providers/storage" + ], + "v2/alerts": [ + "lib/v2/providers/alerts" + ], + "v2/alerts/appDistribution": [ + "lib/v2/providers/alerts/appDistribution" + ], + "v2/alerts/billing": [ + "lib/v2/providers/alerts/billing" + ], + "v2/alerts/crashlytics": [ + "lib/v2/providers/alerts/crashlytics" ] } }, diff --git a/spec/v2/providers/alerts/alerts.spec.ts b/spec/v2/providers/alerts/alerts.spec.ts new file mode 100644 index 000000000..b63f567aa --- /dev/null +++ b/spec/v2/providers/alerts/alerts.spec.ts @@ -0,0 +1,183 @@ +import { expect } from 'chai'; +import * as options from '../../../../src/v2/options'; +import * as alerts from '../../../../src/v2/providers/alerts'; +import { FULL_ENDPOINT, FULL_OPTIONS } from '../helpers'; + +const ALERT_TYPE = 'new-alert-type'; +const APPID = '123456789'; + +describe('alerts', () => { + describe('onAlertPublished', () => { + it('should create the function without opts', () => { + const result = alerts.onAlertPublished(ALERT_TYPE, () => 42); + + expect(result.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: ALERT_TYPE, + }, + retry: false, + }, + }); + }); + + it('should create the function with opts', () => { + const result = alerts.onAlertPublished( + { + ...FULL_OPTIONS, + alertType: ALERT_TYPE, + appId: APPID, + }, + () => 42 + ); + + expect(result.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: ALERT_TYPE, + appId: APPID, + }, + retry: false, + }, + }); + }); + + it('should have a .run method', () => { + const func = alerts.onAlertPublished(ALERT_TYPE, (event) => event); + + const res = func.run('input' as any); + + expect(res).to.equal('input'); + }); + }); + + describe('getEndpointAnnotation', () => { + beforeEach(() => { + process.env.GCLOUD_PROJECT = 'aProject'; + }); + + afterEach(() => { + options.setGlobalOptions({}); + delete process.env.GCLOUD_PROJECT; + }); + + it('should define the endpoint without appId and opts', () => { + expect(alerts.getEndpointAnnotation({}, ALERT_TYPE)).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: ALERT_TYPE, + }, + retry: false, + }, + }); + }); + + it('should define a complex endpoint without appId', () => { + expect( + alerts.getEndpointAnnotation({ ...FULL_OPTIONS }, ALERT_TYPE) + ).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: ALERT_TYPE, + }, + retry: false, + }, + }); + }); + + it('should define a complex endpoint', () => { + expect( + alerts.getEndpointAnnotation({ ...FULL_OPTIONS }, ALERT_TYPE, APPID) + ).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: ALERT_TYPE, + appId: APPID, + }, + retry: false, + }, + }); + }); + + it('should merge global & specific opts', () => { + options.setGlobalOptions({ + concurrency: 20, + region: 'europe-west1', + minInstances: 1, + }); + const specificOpts = { + region: 'us-west1', + minInstances: 3, + }; + + expect( + alerts.getEndpointAnnotation(specificOpts, ALERT_TYPE, APPID) + ).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + concurrency: 20, + region: ['us-west1'], + minInstances: 3, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: ALERT_TYPE, + appId: APPID, + }, + retry: false, + }, + }); + }); + }); + + describe('getOptsAndAlertTypeAndApp', () => { + it('should parse a string', () => { + const [opts, alertType, appId] = alerts.getOptsAndAlertTypeAndApp( + ALERT_TYPE + ); + + expect(opts).to.deep.equal({}); + expect(alertType).to.equal(ALERT_TYPE); + expect(appId).to.be.undefined; + }); + + it('should parse an options object without appId', () => { + const myOpts: alerts.FirebaseAlertOptions = { + alertType: ALERT_TYPE, + region: 'us-west1', + }; + + const [opts, alertType, appId] = alerts.getOptsAndAlertTypeAndApp(myOpts); + + expect(opts).to.deep.equal({ region: 'us-west1' }); + expect(alertType).to.equal(myOpts.alertType); + expect(appId).to.be.undefined; + }); + + it('should parse an options object with appId', () => { + const myOpts: alerts.FirebaseAlertOptions = { + alertType: ALERT_TYPE, + appId: APPID, + region: 'us-west1', + }; + + const [opts, alertType, appId] = alerts.getOptsAndAlertTypeAndApp(myOpts); + + expect(opts).to.deep.equal({ region: 'us-west1' }); + expect(alertType).to.equal(myOpts.alertType); + expect(appId).to.be.equal(myOpts.appId); + }); + }); +}); diff --git a/spec/v2/providers/alerts/appDistribution.spec.ts b/spec/v2/providers/alerts/appDistribution.spec.ts new file mode 100644 index 000000000..52fdfd3b9 --- /dev/null +++ b/spec/v2/providers/alerts/appDistribution.spec.ts @@ -0,0 +1,127 @@ +import { expect } from 'chai'; +import * as alerts from '../../../../src/v2/providers/alerts'; +import * as appDistribution from '../../../../src/v2/providers/alerts/appDistribution'; +import { FULL_ENDPOINT, FULL_OPTIONS } from '../helpers'; + +const APPID = '123456789'; +const myHandler = () => 42; + +describe('appDistribution', () => { + describe('onNewTesterIosDevicePublished', () => { + it('should create a function with alertType & appId', () => { + const func = appDistribution.onNewTesterIosDevicePublished( + APPID, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: appDistribution.newTesterIosDeviceAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + + it('should create a function with opts', () => { + const func = appDistribution.onNewTesterIosDevicePublished( + { ...FULL_OPTIONS }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: appDistribution.newTesterIosDeviceAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with appid in opts', () => { + const func = appDistribution.onNewTesterIosDevicePublished( + { ...FULL_OPTIONS, appId: APPID }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: appDistribution.newTesterIosDeviceAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + + it('should create a function without opts or appId', () => { + const func = appDistribution.onNewTesterIosDevicePublished(myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: appDistribution.newTesterIosDeviceAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with a run method', () => { + const func = appDistribution.onNewTesterIosDevicePublished( + APPID, + (event) => event + ); + + const res = func.run('input' as any); + + expect(res).to.equal('input'); + }); + }); + + describe('getOptsAndApp', () => { + it('should parse a string', () => { + const [opts, appId] = appDistribution.getOptsAndApp(APPID); + + expect(opts).to.deep.equal({}); + expect(appId).to.equal(APPID); + }); + + it('should parse an options object without appId', () => { + const myOpts: appDistribution.AppDistributionOptions = { + region: 'us-west1', + }; + + const [opts, appId] = appDistribution.getOptsAndApp(myOpts); + + expect(opts).to.deep.equal({ region: 'us-west1' }); + expect(appId).to.be.undefined; + }); + + it('should parse an options object with appId', () => { + const myOpts: appDistribution.AppDistributionOptions = { + appId: APPID, + region: 'us-west1', + }; + + const [opts, appId] = appDistribution.getOptsAndApp(myOpts); + + expect(opts).to.deep.equal({ region: 'us-west1' }); + expect(appId).to.equal(APPID); + }); + }); +}); diff --git a/spec/v2/providers/alerts/billing.spec.ts b/spec/v2/providers/alerts/billing.spec.ts new file mode 100644 index 000000000..a9e4e173d --- /dev/null +++ b/spec/v2/providers/alerts/billing.spec.ts @@ -0,0 +1,126 @@ +import { expect } from 'chai'; +import * as alerts from '../../../../src/v2/providers/alerts'; +import * as billing from '../../../../src/v2/providers/alerts/billing'; +import { FULL_ENDPOINT, FULL_OPTIONS } from '../helpers'; + +const ALERT_TYPE = 'new-alert-type'; +const myHandler = () => 42; + +describe('billing', () => { + describe('onPlanUpdatePublished', () => { + it('should create a function with only handler', () => { + const func = billing.onPlanUpdatePublished(myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: billing.planUpdateAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with opts & handler', () => { + const func = billing.onPlanUpdatePublished( + { ...FULL_OPTIONS }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: billing.planUpdateAlert, + }, + retry: false, + }, + }); + }); + }); + + describe('onAutomatedPlanUpdatePublished', () => { + it('should create a function with only handler', () => { + const func = billing.onAutomatedPlanUpdatePublished(myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: billing.automatedPlanUpdateAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with opts & handler', () => { + const func = billing.onAutomatedPlanUpdatePublished( + { ...FULL_OPTIONS }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: billing.automatedPlanUpdateAlert, + }, + retry: false, + }, + }); + }); + }); + + describe('onOperation', () => { + it('should create a function with alertType only', () => { + const func = billing.onOperation(ALERT_TYPE, myHandler, undefined); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: ALERT_TYPE, + }, + retry: false, + }, + }); + }); + + it('should create a function with opts', () => { + const func = billing.onOperation( + ALERT_TYPE, + { ...FULL_OPTIONS }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: ALERT_TYPE, + }, + retry: false, + }, + }); + }); + + it('should create a function with a run method', () => { + const func = billing.onOperation(ALERT_TYPE, (event) => event, undefined); + + const res = func.run('input' as any); + + expect(res).to.equal('input'); + }); + }); +}); diff --git a/spec/v2/providers/alerts/crashlytics.spec.ts b/spec/v2/providers/alerts/crashlytics.spec.ts new file mode 100644 index 000000000..1d5c1a8b6 --- /dev/null +++ b/spec/v2/providers/alerts/crashlytics.spec.ts @@ -0,0 +1,562 @@ +import { expect } from 'chai'; +import * as alerts from '../../../../src/v2/providers/alerts'; +import * as crashlytics from '../../../../src/v2/providers/alerts/crashlytics'; +import { FULL_ENDPOINT, FULL_OPTIONS } from '../helpers'; + +const ALERT_TYPE = 'new-alert-type'; +const APPID = '123456789'; +const myHandler = () => 42; + +describe('crashlytics', () => { + describe('onNewFatalIssuePublished', () => { + it('should create a function only handler', () => { + const func = crashlytics.onNewFatalIssuePublished(myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.newFatalIssueAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with appId', () => { + const func = crashlytics.onNewFatalIssuePublished(APPID, myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.newFatalIssueAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + + it('should create a function with base opts', () => { + const func = crashlytics.onNewFatalIssuePublished( + { ...FULL_OPTIONS }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.newFatalIssueAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with opts', () => { + const func = crashlytics.onNewFatalIssuePublished( + { ...FULL_OPTIONS, appId: APPID }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.newFatalIssueAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + }); + + describe('onNewNonfatalIssuePublished', () => { + it('should create a function only handler', () => { + const func = crashlytics.onNewNonfatalIssuePublished(myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.newNonfatalIssueAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with appId', () => { + const func = crashlytics.onNewNonfatalIssuePublished(APPID, myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.newNonfatalIssueAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + + it('should create a function with base opts', () => { + const func = crashlytics.onNewNonfatalIssuePublished( + { ...FULL_OPTIONS }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.newNonfatalIssueAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with opts', () => { + const func = crashlytics.onNewNonfatalIssuePublished( + { ...FULL_OPTIONS, appId: APPID }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.newNonfatalIssueAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + }); + + describe('onRegressionAlertPublished', () => { + it('should create a function only handler', () => { + const func = crashlytics.onRegressionAlertPublished(myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.regressionAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with appId', () => { + const func = crashlytics.onRegressionAlertPublished(APPID, myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.regressionAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + + it('should create a function with base opts', () => { + const func = crashlytics.onRegressionAlertPublished( + { ...FULL_OPTIONS }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.regressionAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with opts', () => { + const func = crashlytics.onRegressionAlertPublished( + { ...FULL_OPTIONS, appId: APPID }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.regressionAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + }); + + describe('onStabilityDigestPublished', () => { + it('should create a function only handler', () => { + const func = crashlytics.onStabilityDigestPublished(myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.stabilityDigestAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with appId', () => { + const func = crashlytics.onStabilityDigestPublished(APPID, myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.stabilityDigestAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + + it('should create a function with base opts', () => { + const func = crashlytics.onStabilityDigestPublished( + { ...FULL_OPTIONS }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.stabilityDigestAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with opts', () => { + const func = crashlytics.onStabilityDigestPublished( + { ...FULL_OPTIONS, appId: APPID }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.stabilityDigestAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + }); + + describe('onVelocityAlertPublished', () => { + it('should create a function only handler', () => { + const func = crashlytics.onVelocityAlertPublished(myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.velocityAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with appId', () => { + const func = crashlytics.onVelocityAlertPublished(APPID, myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.velocityAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + + it('should create a function with base opts', () => { + const func = crashlytics.onVelocityAlertPublished( + { ...FULL_OPTIONS }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.velocityAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with opts', () => { + const func = crashlytics.onVelocityAlertPublished( + { ...FULL_OPTIONS, appId: APPID }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.velocityAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + }); + + describe('onNewAnrIssuePublished', () => { + it('should create a function only handler', () => { + const func = crashlytics.onNewAnrIssuePublished(myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.newAnrIssueAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with appId', () => { + const func = crashlytics.onNewAnrIssuePublished(APPID, myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.newAnrIssueAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + + it('should create a function with base opts', () => { + const func = crashlytics.onNewAnrIssuePublished( + { ...FULL_OPTIONS }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.newAnrIssueAlert, + }, + retry: false, + }, + }); + }); + + it('should create a function with opts', () => { + const func = crashlytics.onNewAnrIssuePublished( + { ...FULL_OPTIONS, appId: APPID }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: crashlytics.newAnrIssueAlert, + appId: APPID, + }, + retry: false, + }, + }); + }); + }); + + describe('onOperation', () => { + it('should create a function with alertType only', () => { + const func = crashlytics.onOperation(ALERT_TYPE, myHandler, undefined); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: ALERT_TYPE, + }, + retry: false, + }, + }); + }); + + it('should create a function with alertType & appId', () => { + const func = crashlytics.onOperation(ALERT_TYPE, APPID, myHandler); + + expect(func.__endpoint).to.deep.equal({ + platform: 'gcfv2', + labels: {}, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: ALERT_TYPE, + appId: APPID, + }, + retry: false, + }, + }); + }); + + it('should create a function with base opts', () => { + const func = crashlytics.onOperation( + ALERT_TYPE, + { ...FULL_OPTIONS }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: ALERT_TYPE, + }, + retry: false, + }, + }); + }); + + it('should create a function with appid in opts', () => { + const func = crashlytics.onOperation( + ALERT_TYPE, + { ...FULL_OPTIONS, appId: APPID }, + myHandler + ); + + expect(func.__endpoint).to.deep.equal({ + ...FULL_ENDPOINT, + eventTrigger: { + eventType: alerts.eventType, + eventFilters: { + alertType: ALERT_TYPE, + appId: APPID, + }, + retry: false, + }, + }); + }); + + it('should create a function with a run method', () => { + const func = crashlytics.onOperation( + ALERT_TYPE, + (event) => event, + undefined + ); + + const res = func.run('input' as any); + + expect(res).to.equal('input'); + }); + }); + + describe('getOptsAndApp', () => { + it('should parse a string', () => { + const APPID = '123456789'; + + const [opts, appId] = crashlytics.getOptsAndApp(APPID); + + expect(opts).to.deep.equal({}); + expect(appId).to.equal(APPID); + }); + + it('should parse an options object without appId', () => { + const myOpts: crashlytics.CrashlyticsOptions = { + region: 'us-west1', + }; + + const [opts, appId] = crashlytics.getOptsAndApp(myOpts); + + expect(opts).to.deep.equal({ region: 'us-west1' }); + expect(appId).to.be.undefined; + }); + + it('should parse an options object with appId', () => { + const myOpts: crashlytics.CrashlyticsOptions = { + appId: '123456789', + region: 'us-west1', + }; + + const [opts, appId] = crashlytics.getOptsAndApp(myOpts); + + expect(opts).to.deep.equal({ region: 'us-west1' }); + expect(appId).to.equal(myOpts.appId); + }); + }); +}); diff --git a/src/v2/core.ts b/src/v2/core.ts index c790be7a8..845cf2990 100644 --- a/src/v2/core.ts +++ b/src/v2/core.ts @@ -49,10 +49,10 @@ export interface TriggerAnnotation { } /** - * A CloudEvent is a cross-platform format for encoding a serverless event. + * A CloudEventBase is the base of a cross-platform format for encoding a serverless event. * More information can be found in https://github.com/cloudevents/spec */ -export interface CloudEvent { +interface CloudEventBase { /** Version of the CloudEvents spec for this event. */ readonly specversion: '1.0'; @@ -89,6 +89,10 @@ export interface CloudEvent { params?: Record; } +/** + * A CloudEvent with custom extension attributes + */ +export type CloudEvent = CloudEventBase & Ext; /** A handler for CloudEvents. */ export interface CloudFunction { (raw: CloudEvent): any | Promise; diff --git a/src/v2/index.ts b/src/v2/index.ts index 5c99a4678..3de9b749f 100644 --- a/src/v2/index.ts +++ b/src/v2/index.ts @@ -22,11 +22,12 @@ import * as logger from '../logger'; import * as params from './params'; +import * as alerts from './providers/alerts'; import * as https from './providers/https'; import * as pubsub from './providers/pubsub'; import * as storage from './providers/storage'; -export { https, pubsub, storage, logger, params }; +export { https, pubsub, storage, logger, params, alerts }; export { setGlobalOptions, GlobalOptions } from './options'; diff --git a/src/v2/providers/alerts/alerts.ts b/src/v2/providers/alerts/alerts.ts new file mode 100644 index 000000000..4b7f0b952 --- /dev/null +++ b/src/v2/providers/alerts/alerts.ts @@ -0,0 +1,127 @@ +import { ManifestEndpoint } from '../../../common/manifest'; +import { CloudEvent, CloudFunction } from '../../core'; +import * as options from '../../options'; + +/** + * The CloudEvent data emitted by Firebase Alerts. + */ +export interface FirebaseAlertData { + createTime: string; + endTime: string; + payload: T; +} + +interface WithAlertTypeAndApp { + alertType: string; + appId?: string; +} +/** + * A custom CloudEvent for Firebase Alerts (with custom extension attributes). + */ +export type AlertEvent = CloudEvent< + FirebaseAlertData, + WithAlertTypeAndApp +>; + +/** @internal */ +export const eventType = 'firebase.firebasealerts.alerts.v1.published'; + +/** The underlying alert type of the Firebase Alerts provider. */ +export type AlertType = + | 'crashlytics.newFatalIssue' + | 'crashlytics.newNonfatalIssue' + | 'crashlytics.regression' + | 'crashlytics.stabilityDigest' + | 'crashlytics.velocity' + | 'crashlytics.newAnrIssue' + | 'billing.planUpdate' + | 'billing.automatedPlanUpdate' + | 'appDistribution.newTesterIosDevice' + | string; + +/** + * Configuration for Firebase Alert functions. + */ +export interface FirebaseAlertOptions extends options.EventHandlerOptions { + alertType: AlertType; + appId?: string; +} + +/** + * Declares a function that can handle Firebase Alerts from CloudEvents. + * @param alertTypeOrOpts the alert type or Firebase Alert function configuration. + * @param handler a function that can handle the Firebase Alert inside a CloudEvent. + */ +export function onAlertPublished( + alertTypeOrOpts: AlertType | FirebaseAlertOptions, + handler: (event: AlertEvent) => any | Promise +): CloudFunction> { + const [opts, alertType, appId] = getOptsAndAlertTypeAndApp(alertTypeOrOpts); + + const func = (raw: CloudEvent) => { + return handler( + raw as CloudEvent, WithAlertTypeAndApp> + ); + }; + + func.run = handler; + func.__endpoint = getEndpointAnnotation(opts, alertType, appId); + + return func; +} + +/** + * @internal + * Helper function for getting the endpoint annotation used in alert handling functions. + */ +export function getEndpointAnnotation( + opts: options.EventHandlerOptions, + alertType: string, + appId?: string +): ManifestEndpoint { + const baseOpts = options.optionsToEndpoint(options.getGlobalOptions()); + const specificOpts = options.optionsToEndpoint(opts); + const endpoint: ManifestEndpoint = { + platform: 'gcfv2', + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, + }, + eventTrigger: { + eventType, + eventFilters: { + alertType, + }, + retry: !!opts.retry, + }, + }; + if (appId) { + endpoint.eventTrigger.eventFilters.appId = appId; + } + return endpoint; +} + +/** + * @internal + * Helper function to parse the function opts, alert type, and appId. + */ +export function getOptsAndAlertTypeAndApp( + alertTypeOrOpts: AlertType | FirebaseAlertOptions +): [options.EventHandlerOptions, string, string | undefined] { + let opts: options.EventHandlerOptions; + let alertType: AlertType; + let appId: string | undefined; + if (typeof alertTypeOrOpts === 'string') { + alertType = alertTypeOrOpts; + opts = {}; + } else { + alertType = alertTypeOrOpts.alertType; + appId = alertTypeOrOpts.appId; + opts = { ...alertTypeOrOpts }; + delete (opts as any).alertType; + delete (opts as any).appId; + } + return [opts, alertType, appId]; +} diff --git a/src/v2/providers/alerts/appDistribution.ts b/src/v2/providers/alerts/appDistribution.ts new file mode 100644 index 000000000..58b54e9b6 --- /dev/null +++ b/src/v2/providers/alerts/appDistribution.ts @@ -0,0 +1,107 @@ +import { getEndpointAnnotation, FirebaseAlertData } from './alerts'; +import { CloudEvent, CloudFunction } from '../../core'; +import * as options from '../../options'; + +/** + * The internal payload object for adding a new tester device to app distribution. + * Payload is wrapped inside a FirebaseAlertData object. + */ +export interface NewTesterDevicePayload { + ['@type']: 'com.google.firebase.firebasealerts.NewTesterDevicePayload'; + testerName: string; + testerEmail: string; + testerDeviceModelName: string; + testerDeviceIdentifier: string; +} + +interface WithAlertTypeAndApp { + alertType: string; + appId: string; +} +/** + * A custom CloudEvent for Firebase Alerts (with custom extension attributes). + */ +export type AppDistributionEvent = CloudEvent< + FirebaseAlertData, + WithAlertTypeAndApp +>; + +/** @internal */ +export const newTesterIosDeviceAlert = 'appDistribution.newTesterIosDevice'; + +/** + * Configuration for app distribution functions. + */ +export interface AppDistributionOptions extends options.EventHandlerOptions { + appId?: string; +} + +/** + * Declares a function that can handle adding a new tester iOS device. + */ +export function onNewTesterIosDevicePublished( + handler: ( + event: AppDistributionEvent + ) => any | Promise +): CloudFunction>; +export function onNewTesterIosDevicePublished( + appId: string, + handler: ( + event: AppDistributionEvent + ) => any | Promise +): CloudFunction>; +export function onNewTesterIosDevicePublished( + opts: AppDistributionOptions, + handler: ( + event: AppDistributionEvent + ) => any | Promise +): CloudFunction>; +export function onNewTesterIosDevicePublished( + appIdOrOptsOrHandler: + | string + | AppDistributionOptions + | (( + event: AppDistributionEvent + ) => any | Promise), + handler?: ( + event: AppDistributionEvent + ) => any | Promise +): CloudFunction> { + if (typeof appIdOrOptsOrHandler === 'function') { + handler = appIdOrOptsOrHandler as ( + event: AppDistributionEvent + ) => any | Promise; + appIdOrOptsOrHandler = {}; + } + + const [opts, appId] = getOptsAndApp(appIdOrOptsOrHandler); + + const func = (raw: CloudEvent) => { + return handler(raw as AppDistributionEvent); + }; + + func.run = handler; + func.__endpoint = getEndpointAnnotation(opts, newTesterIosDeviceAlert, appId); + + return func; +} + +/** + * @internal + * Helper function to parse the function opts and appId. + */ +export function getOptsAndApp( + appIdOrOpts: string | AppDistributionOptions +): [options.EventHandlerOptions, string | undefined] { + let opts: options.EventHandlerOptions; + let appId: string | undefined; + if (typeof appIdOrOpts === 'string') { + opts = {}; + appId = appIdOrOpts; + } else { + appId = appIdOrOpts.appId; + opts = { ...appIdOrOpts }; + delete (opts as any).appId; + } + return [opts, appId]; +} diff --git a/src/v2/providers/alerts/billing.ts b/src/v2/providers/alerts/billing.ts new file mode 100644 index 000000000..f2ecd4289 --- /dev/null +++ b/src/v2/providers/alerts/billing.ts @@ -0,0 +1,110 @@ +import { getEndpointAnnotation, FirebaseAlertData } from '.'; +import { CloudEvent, CloudFunction } from '../../core'; +import * as options from '../../options'; + +/** + * The internal payload object for billing plan updates. + * Payload is wrapped inside a FirebaseAlertData object. + */ +export interface PlanUpdatePayload { + ['@type']: 'com.google.firebase.firebasealerts.PlanUpdatePayload'; + billingPlan: string; + principalEmail: string; +} + +/** + * The internal payload object for billing plan automated updates. + * Payload is wrapped inside a FirebaseAlertData object. + */ +export interface PlanAutomatedUpdatePayload { + ['@type']: 'com.google.firebase.firebasealerts.PlanAutomatedUpdatePayload'; + billingPlan: string; +} + +interface WithAlertType { + alertType: string; +} +/** + * A custom CloudEvent for billing Firebase Alerts (with custom extension attributes). + */ +export type BillingEvent = CloudEvent, WithAlertType>; + +/** @internal */ +export const planUpdateAlert = 'billing.planUpdate'; +/** @internal */ +export const automatedPlanUpdateAlert = 'billing.automatedPlanUpdate'; + +/** + * Declares a function that can handle a billing plan update event. + */ +export function onPlanUpdatePublished( + handler: (event: BillingEvent) => any | Promise +): CloudFunction>; +export function onPlanUpdatePublished( + opts: options.EventHandlerOptions, + handler: (event: BillingEvent) => any | Promise +): CloudFunction>; +export function onPlanUpdatePublished( + optsOrHandler: + | options.EventHandlerOptions + | ((event: BillingEvent) => any | Promise), + handler?: (event: BillingEvent) => any | Promise +): CloudFunction> { + return onOperation( + planUpdateAlert, + optsOrHandler, + handler + ); +} + +/** + * Declares a function that can handle an automated billing plan update event. + */ +export function onAutomatedPlanUpdatePublished( + handler: ( + event: BillingEvent + ) => any | Promise +): CloudFunction>; +export function onAutomatedPlanUpdatePublished( + opts: options.EventHandlerOptions, + handler: ( + event: BillingEvent + ) => any | Promise +): CloudFunction>; +export function onAutomatedPlanUpdatePublished( + optsOrHandler: + | options.EventHandlerOptions + | ((event: BillingEvent) => any | Promise), + handler?: ( + event: BillingEvent + ) => any | Promise +): CloudFunction> { + return onOperation( + automatedPlanUpdateAlert, + optsOrHandler, + handler + ); +} + +/** @internal */ +export function onOperation( + alertType: string, + optsOrHandler: + | options.EventHandlerOptions + | ((event: BillingEvent) => any | Promise), + handler: (event: BillingEvent) => any | Promise +): CloudFunction> { + if (typeof optsOrHandler === 'function') { + handler = optsOrHandler as (event: BillingEvent) => any | Promise; + optsOrHandler = {}; + } + + const func = (raw: CloudEvent) => { + return handler(raw as BillingEvent); + }; + + func.run = handler; + func.__endpoint = getEndpointAnnotation(optsOrHandler, alertType); + + return func; +} diff --git a/src/v2/providers/alerts/crashlytics.ts b/src/v2/providers/alerts/crashlytics.ts new file mode 100644 index 000000000..d9cece913 --- /dev/null +++ b/src/v2/providers/alerts/crashlytics.ts @@ -0,0 +1,360 @@ +import { getEndpointAnnotation, FirebaseAlertData } from '.'; +import { CloudEvent, CloudFunction } from '../../core'; +import * as options from '../../options'; + +/** Generic crashlytics issue interface */ +interface Issue { + id: string; + title: string; + subtitle: string; + appVersion: string; +} + +/** + * The internal payload object for a new fatal issue. + * Payload is wrapped inside a FirebaseAlertData object. + */ +export interface NewFatalIssuePayload { + ['@type']: 'com.google.firebase.firebasealerts.CrashlyticsNewFatalIssuePayload'; + issue: Issue; +} + +/** + * The internal payload object for a new non-fatal issue. + * Payload is wrapped inside a FirebaseAlertData object. + */ +export interface NewNonfatalIssuePayload { + ['@type']: 'com.google.firebase.firebasealerts.CrashlyticsNewNonfatalIssuePayload'; + issue: Issue; +} + +/** + * The internal payload object for a regression alert. + * Payload is wrapped inside a FirebaseAlertData object. + */ +export interface RegressionAlertPayload { + ['@type']: 'com.google.firebase.firebasealerts.CrashlyticsRegressionAlertPayload'; + type: string; + issue: Issue; + resolveTime: string; +} + +/** Generic crashlytics trending issue interface */ +interface TrendingIssueDetails { + type: string; + issue: Issue; + eventCount: number; + userCount: number; +} + +/** + * The internal payload object for a stability digest. + * Payload is wrapped inside a FirebaseAlertData object. + */ +export interface StabilityDigestPayload { + ['@type']: 'com.google.firebase.firebasealerts.CrashlyticsStabilityDigestPayload'; + digestDate: string; + trendingIssues: TrendingIssueDetails[]; +} + +/** + * The internal payload object for a velocity alert. + * Payload is wrapped inside a FirebaseAlertData object. + */ +export interface VelocityAlertPayload { + ['@type']: 'com.google.firebase.firebasealerts.VelocityAlertPayload'; + issue: Issue; + createTime: string; + crashCount: number; + crashPercentage: number; + firstVersion: string; +} + +/** + * The internal payload object for a new Application Not Responding issue. + * Payload is wrapped inside a FirebaseAlertData object. + */ +export interface NewAnrIssuePayload { + ['@type']: 'com.google.firebase.firebasealerts.NewAnrIssuePayload'; + issue: Issue; +} + +interface WithAlertTypeAndApp { + alertType: string; + appId: string; +} +/** + * A custom CloudEvent for Firebase Alerts (with custom extension attributes). + */ +export type CrashlyticsEvent = CloudEvent< + FirebaseAlertData, + WithAlertTypeAndApp +>; + +/** @internal */ +export const newFatalIssueAlert = 'crashlytics.newFatalIssue'; +/** @internal */ +export const newNonfatalIssueAlert = 'crashlytics.newNonfatalIssue'; +/** @internal */ +export const regressionAlert = 'crashlytics.regression'; +/** @internal */ +export const stabilityDigestAlert = 'crashlytics.stabilityDigest'; +/** @internal */ +export const velocityAlert = 'crashlytics.velocity'; +/** @internal */ +export const newAnrIssueAlert = 'crashlytics.newAnrIssue'; + +/** + * Configuration for crashlytics functions. + */ +export interface CrashlyticsOptions extends options.EventHandlerOptions { + appId?: string; +} + +/** + * Declares a function that can handle a new fatal issue published to crashlytics. + */ +export function onNewFatalIssuePublished( + handler: (event: CrashlyticsEvent) => any | Promise +): CloudFunction>; +export function onNewFatalIssuePublished( + appId: string, + handler: (event: CrashlyticsEvent) => any | Promise +): CloudFunction>; +export function onNewFatalIssuePublished( + opts: CrashlyticsOptions, + handler: (event: CrashlyticsEvent) => any | Promise +): CloudFunction>; +export function onNewFatalIssuePublished( + appIdOrOptsOrHandler: + | string + | CrashlyticsOptions + | ((event: CrashlyticsEvent) => any | Promise), + handler?: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction> { + return onOperation( + newFatalIssueAlert, + appIdOrOptsOrHandler, + handler + ); +} + +/** + * Declares a function that can handle aa new non-fatal issue published to crashlytics. + */ +export function onNewNonfatalIssuePublished( + handler: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction>; +export function onNewNonfatalIssuePublished( + appId: string, + handler: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction>; +export function onNewNonfatalIssuePublished( + opts: CrashlyticsOptions, + handler: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction>; +export function onNewNonfatalIssuePublished( + appIdOrOptsOrHandler: + | string + | CrashlyticsOptions + | (( + event: CrashlyticsEvent + ) => any | Promise), + handler?: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction> { + return onOperation( + newNonfatalIssueAlert, + appIdOrOptsOrHandler, + handler + ); +} + +/** + * Declares a function that can handle a regression alert published to crashlytics. + */ +export function onRegressionAlertPublished( + handler: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction>; +export function onRegressionAlertPublished( + appId: string, + handler: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction>; +export function onRegressionAlertPublished( + opts: CrashlyticsOptions, + handler: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction>; +export function onRegressionAlertPublished( + appIdOrOptsOrHandler: + | string + | CrashlyticsOptions + | ((event: CrashlyticsEvent) => any | Promise), + handler?: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction> { + return onOperation( + regressionAlert, + appIdOrOptsOrHandler, + handler + ); +} + +/** + * Declares a function that can handle a stability digest published to crashlytics. + */ +export function onStabilityDigestPublished( + handler: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction>; +export function onStabilityDigestPublished( + appId: string, + handler: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction>; +export function onStabilityDigestPublished( + opts: CrashlyticsOptions, + handler: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction>; +export function onStabilityDigestPublished( + appIdOrOptsOrHandler: + | string + | CrashlyticsOptions + | ((event: CrashlyticsEvent) => any | Promise), + handler?: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction> { + return onOperation( + stabilityDigestAlert, + appIdOrOptsOrHandler, + handler + ); +} + +/** + * Declares a function that can handle a velocity alert published to crashlytics. + */ +export function onVelocityAlertPublished( + handler: (event: CrashlyticsEvent) => any | Promise +): CloudFunction>; +export function onVelocityAlertPublished( + appId: string, + handler: (event: CrashlyticsEvent) => any | Promise +): CloudFunction>; +export function onVelocityAlertPublished( + opts: CrashlyticsOptions, + handler: (event: CrashlyticsEvent) => any | Promise +): CloudFunction>; +export function onVelocityAlertPublished( + appIdOrOptsOrHandler: + | string + | CrashlyticsOptions + | ((event: CrashlyticsEvent) => any | Promise), + handler?: ( + event: CrashlyticsEvent + ) => any | Promise +): CloudFunction> { + return onOperation( + velocityAlert, + appIdOrOptsOrHandler, + handler + ); +} + +/** + * Declares a function that can handle a new Application Not Responding issue published to crashlytics. + */ +export function onNewAnrIssuePublished( + handler: (event: CrashlyticsEvent) => any | Promise +): CloudFunction>; +export function onNewAnrIssuePublished( + appId: string, + handler: (event: CrashlyticsEvent) => any | Promise +): CloudFunction>; +export function onNewAnrIssuePublished( + opts: CrashlyticsOptions, + handler: (event: CrashlyticsEvent) => any | Promise +): CloudFunction>; +export function onNewAnrIssuePublished( + appIdOrOptsOrHandler: + | string + | CrashlyticsOptions + | ((event: CrashlyticsEvent) => any | Promise), + handler?: (event: CrashlyticsEvent) => any | Promise +): CloudFunction> { + return onOperation( + newAnrIssueAlert, + appIdOrOptsOrHandler, + handler + ); +} + +/** @internal */ +export function onOperation( + alertType: string, + appIdOrOptsOrHandler: + | string + | CrashlyticsOptions + | ((event: CrashlyticsEvent) => any | Promise), + handler: (event: CrashlyticsEvent) => any | Promise +): CloudFunction> { + if (typeof appIdOrOptsOrHandler === 'function') { + handler = appIdOrOptsOrHandler as ( + event: CrashlyticsEvent + ) => any | Promise; + appIdOrOptsOrHandler = {}; + } + + const [opts, appId] = getOptsAndApp( + appIdOrOptsOrHandler as string | CrashlyticsOptions + ); + + const func = (raw: CloudEvent) => { + return handler(raw as CrashlyticsEvent); + }; + + func.run = handler; + func.__endpoint = getEndpointAnnotation(opts, alertType, appId); + + return func; +} + +/** + * @internal + * Helper function to parse the function opts and appId. + */ +export function getOptsAndApp( + appIdOrOpts: string | CrashlyticsOptions +): [options.EventHandlerOptions, string | undefined] { + let opts: options.EventHandlerOptions; + let appId: string | undefined; + if (typeof appIdOrOpts === 'string') { + opts = {}; + appId = appIdOrOpts; + } else { + appId = appIdOrOpts.appId; + opts = { ...appIdOrOpts }; + delete (opts as any).appId; + } + return [opts, appId]; +} diff --git a/src/v2/providers/alerts/index.ts b/src/v2/providers/alerts/index.ts new file mode 100644 index 000000000..ecf90a422 --- /dev/null +++ b/src/v2/providers/alerts/index.ts @@ -0,0 +1,6 @@ +import * as appDistribution from './appDistribution'; +import * as billing from './billing'; +import * as crashlytics from './crashlytics'; + +export { appDistribution, billing, crashlytics }; +export * from './alerts'; diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index e03115df0..d07e1955a 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -359,7 +359,7 @@ export function onOperation( ...specificOpts?.labels, }, eventTrigger: { - eventType: eventType, + eventType, eventFilters: { bucket, }, diff --git a/v2/alerts/appDistribution.js b/v2/alerts/appDistribution.js new file mode 100644 index 000000000..7d725acc3 --- /dev/null +++ b/v2/alerts/appDistribution.js @@ -0,0 +1,26 @@ +// The MIT License (MIT) +// +// Copyright (c) 2021 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 not part of the firebase-functions SDK. It is used to silence the +// imports eslint plugin until it can understand import paths defined by node +// package exports. +// For more information, see github.com/import-js/eslint-plugin-import/issues/1810 diff --git a/v2/alerts/billing.js b/v2/alerts/billing.js new file mode 100644 index 000000000..7d725acc3 --- /dev/null +++ b/v2/alerts/billing.js @@ -0,0 +1,26 @@ +// The MIT License (MIT) +// +// Copyright (c) 2021 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 not part of the firebase-functions SDK. It is used to silence the +// imports eslint plugin until it can understand import paths defined by node +// package exports. +// For more information, see github.com/import-js/eslint-plugin-import/issues/1810 diff --git a/v2/alerts/crashlytics.js b/v2/alerts/crashlytics.js new file mode 100644 index 000000000..7d725acc3 --- /dev/null +++ b/v2/alerts/crashlytics.js @@ -0,0 +1,26 @@ +// The MIT License (MIT) +// +// Copyright (c) 2021 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 not part of the firebase-functions SDK. It is used to silence the +// imports eslint plugin until it can understand import paths defined by node +// package exports. +// For more information, see github.com/import-js/eslint-plugin-import/issues/1810 diff --git a/v2/alerts/index.js b/v2/alerts/index.js new file mode 100644 index 000000000..7d725acc3 --- /dev/null +++ b/v2/alerts/index.js @@ -0,0 +1,26 @@ +// The MIT License (MIT) +// +// Copyright (c) 2021 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 not part of the firebase-functions SDK. It is used to silence the +// imports eslint plugin until it can understand import paths defined by node +// package exports. +// For more information, see github.com/import-js/eslint-plugin-import/issues/1810