Skip to content
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
- Parallelizes network calls that occur when validating authorization for onCall handlers.
- Adds new regions to V2 API
- Adds new provider for alerts
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
"./v2/params": "./lib/v2/params/index.js",
"./v2/pubsub": "./lib/v2/providers/pubsub.js",
"./v2/storage": "./lib/v2/providers/storage.js",
"./v2/alerts": "./lib/v2/providers/alerts/index.js"
"./v2/alerts": "./lib/v2/providers/alerts/index.js",
"./v2/alerts/appDistribution": "./lib/v2/providers/alerts/appDistribution.js"
},
"typesVersions": {
"*": {
Expand Down Expand Up @@ -118,6 +119,9 @@
],
"v2/alerts": [
"lib/v2/providers/alerts"
],
"v2/alerts/appDistribution": [
"lib/v2/providers/alerts/appDistribution"
]
}
},
Expand Down
127 changes: 127 additions & 0 deletions spec/v2/providers/alerts/appDistribution.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
107 changes: 107 additions & 0 deletions src/v2/providers/alerts/appDistribution.ts
Original file line number Diff line number Diff line change
@@ -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<T> = CloudEvent<
FirebaseAlertData<T>,
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<NewTesterDevicePayload>
) => any | Promise<any>
): CloudFunction<FirebaseAlertData<NewTesterDevicePayload>>;
export function onNewTesterIosDevicePublished(
appId: string,
handler: (
event: AppDistributionEvent<NewTesterDevicePayload>
) => any | Promise<any>
): CloudFunction<FirebaseAlertData<NewTesterDevicePayload>>;
export function onNewTesterIosDevicePublished(
opts: AppDistributionOptions,
handler: (
event: AppDistributionEvent<NewTesterDevicePayload>
) => any | Promise<any>
): CloudFunction<FirebaseAlertData<NewTesterDevicePayload>>;
export function onNewTesterIosDevicePublished(
appIdOrOptsOrHandler:
| string
| AppDistributionOptions
| ((
event: AppDistributionEvent<NewTesterDevicePayload>
) => any | Promise<any>),
handler?: (
event: AppDistributionEvent<NewTesterDevicePayload>
) => any | Promise<any>
): CloudFunction<FirebaseAlertData<NewTesterDevicePayload>> {
if (typeof appIdOrOptsOrHandler === 'function') {
handler = appIdOrOptsOrHandler as (
event: AppDistributionEvent<NewTesterDevicePayload>
) => any | Promise<any>;
appIdOrOptsOrHandler = {};
}

const [opts, appId] = getOptsAndApp(appIdOrOptsOrHandler);

const func = (raw: CloudEvent<unknown>) => {
return handler(raw as AppDistributionEvent<NewTesterDevicePayload>);
};

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];
}
26 changes: 26 additions & 0 deletions v2/alerts/appDistribution.js
Original file line number Diff line number Diff line change
@@ -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