Skip to content

Specific interface for provider enhancement (1/3) #1021

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jan 27, 2022
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