diff --git a/package.json b/package.json index d30964d69..0be8d9ef9 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,8 @@ "./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/billing": "./lib/v2/providers/alerts/billing.js", + "./v2/alerts/crashlytics": "./lib/v2/providers/alerts/crashlytics.js" }, "typesVersions": { "*": { @@ -126,6 +127,9 @@ ], "v2/alerts/billing": [ "lib/v2/providers/alerts/billing" + ], + "v2/alerts/crashlytics": [ + "lib/v2/providers/alerts/crashlytics" ] } }, 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/providers/alerts/alerts.ts b/src/v2/providers/alerts/alerts.ts index 41af22dd7..4b7f0b952 100644 --- a/src/v2/providers/alerts/alerts.ts +++ b/src/v2/providers/alerts/alerts.ts @@ -94,7 +94,7 @@ export function getEndpointAnnotation( eventFilters: { alertType, }, - retry: false, + retry: !!opts.retry, }, }; if (appId) { 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 index 8290c38fb..ecf90a422 100644 --- a/src/v2/providers/alerts/index.ts +++ b/src/v2/providers/alerts/index.ts @@ -1,5 +1,6 @@ import * as appDistribution from './appDistribution'; import * as billing from './billing'; +import * as crashlytics from './crashlytics'; -export { appDistribution, billing }; +export { appDistribution, billing, crashlytics }; export * from './alerts'; 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