Skip to content

Migrate functions to component framework #2328

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 1 commit into from
Nov 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 2 additions & 31 deletions packages/functions/index.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,8 @@
* limitations under the License.
*/
import firebase from '@firebase/app';
import { FirebaseApp } from '@firebase/app-types';
import {
FirebaseServiceFactory,
_FirebaseNamespace
} from '@firebase/app-types/private';
import { Service } from './src/api/service';
import { _FirebaseNamespace } from '@firebase/app-types/private';
import { registerFunctions } from './src/config';
import 'isomorphic-fetch';

/**
* Type constant for Firebase Functions.
*/
const FUNCTIONS_TYPE = 'functions';

function factory(app: FirebaseApp, _unused: unknown, region?: string): Service {
return new Service(app, region);
}

export function registerFunctions(instance: _FirebaseNamespace): void {
const namespaceExports = {
// no-inline
Functions: Service
};
instance.INTERNAL.registerService(
FUNCTIONS_TYPE,
factory as FirebaseServiceFactory,
namespaceExports,
// We don't need to wait on any AppHooks.
undefined,
// Allow multiple functions instances per app.
true
);
}

registerFunctions(firebase as _FirebaseNamespace);
37 changes: 2 additions & 35 deletions packages/functions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,9 @@
* limitations under the License.
*/
import firebase from '@firebase/app';
import * as appTypes from '@firebase/app-types';
import {
FirebaseServiceFactory,
_FirebaseNamespace
} from '@firebase/app-types/private';
import { _FirebaseNamespace } from '@firebase/app-types/private';
import * as types from '@firebase/functions-types';
import { Service } from './src/api/service';

/**
* Type constant for Firebase Functions.
*/
const FUNCTIONS_TYPE = 'functions';

function factory(
app: appTypes.FirebaseApp,
_unused: unknown,
region?: string
): Service {
return new Service(app, region);
}

export function registerFunctions(instance: _FirebaseNamespace): void {
const namespaceExports = {
// no-inline
Functions: Service
};
instance.INTERNAL.registerService(
FUNCTIONS_TYPE,
factory as FirebaseServiceFactory,
namespaceExports,
// We don't need to wait on any AppHooks.
undefined,
// Allow multiple functions instances per app.
true
);
}
import { registerFunctions } from './src/config';

registerFunctions(firebase as _FirebaseNamespace);

Expand Down
1 change: 1 addition & 0 deletions packages/functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"dependencies": {
"@firebase/functions-types": "0.3.10",
"@firebase/messaging-types": "0.3.4",
"@firebase/component": "0.1.0",
"isomorphic-fetch": "2.2.1",
"tslib": "1.10.0"
},
Expand Down
7 changes: 6 additions & 1 deletion packages/functions/src/api/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import {
import { _errorForResponse, HttpsErrorImpl } from './error';
import { ContextProvider } from '../context';
import { Serializer } from '../serializer';
import { Provider } from '@firebase/component';
import { FirebaseAuthInternal } from '@firebase/auth-interop-types';
import { FirebaseMessaging } from '@firebase/messaging-types';

/**
* The response to an http request.
Expand Down Expand Up @@ -80,9 +83,11 @@ export class Service implements FirebaseFunctions, FirebaseService {
*/
constructor(
private app_: FirebaseApp,
authProvider: Provider<FirebaseAuthInternal>,
messagingProvider: Provider<FirebaseMessaging>,
private region_: string = 'us-central1'
) {
this.contextProvider = new ContextProvider(app_);
this.contextProvider = new ContextProvider(authProvider, messagingProvider);
// Cancels all ongoing requests when resolved.
this.cancelAllRequests = new Promise(resolve => {
this.deleteService = () => {
Expand Down
51 changes: 51 additions & 0 deletions packages/functions/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @license
* Copyright 2019 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Service } from './api/service';
import {
Component,
ComponentType,
ComponentContainer
} from '@firebase/component';
import { _FirebaseNamespace } from '@firebase/app-types/private';

/**
* Type constant for Firebase Functions.
*/
const FUNCTIONS_TYPE = 'functions';

function factory(container: ComponentContainer, region?: string): Service {
// Dependencies
const app = container.getProvider('app').getImmediate();
const authProvider = container.getProvider('auth-internal');
const messagingProvider = container.getProvider('messaging');

// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new Service(app, authProvider, messagingProvider, region);
}

export function registerFunctions(instance: _FirebaseNamespace): void {
const namespaceExports = {
// no-inline
Functions: Service
};
instance.INTERNAL.registerComponent(
new Component(FUNCTIONS_TYPE, factory, ComponentType.PUBLIC)
.setServiceProps(namespaceExports)
.setMultipleInstances(true)
);
}
49 changes: 39 additions & 10 deletions packages/functions/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FirebaseApp } from '@firebase/app-types';
import { _FirebaseApp } from '@firebase/app-types/private';
import { FirebaseMessaging } from '@firebase/messaging-types';
import { FirebaseAuthInternal } from '@firebase/auth-interop-types';
import { Provider } from '@firebase/component';

/**
* The metadata that should be supplied with function calls.
Expand All @@ -30,11 +31,43 @@ export interface Context {
* Helper class to get metadata that should be included with a function call.
*/
export class ContextProvider {
constructor(private readonly app: FirebaseApp) {}
private auth: FirebaseAuthInternal | null = null;
private messaging: FirebaseMessaging | null = null;
constructor(
authProvider: Provider<FirebaseAuthInternal>,
messagingProvider: Provider<FirebaseMessaging>
) {
this.auth = authProvider.getImmediate(undefined, { optional: true });
this.messaging = messagingProvider.getImmediate(undefined, {
optional: true
});

if (!this.auth) {
authProvider.get().then(
auth => (this.auth = auth),
() => {
/* get() never rejects */
}
);
}

if (!this.messaging) {
messagingProvider.get().then(
messaging => (this.messaging = messaging),
() => {
/* get() never rejects */
}
);
}
}

async getAuthToken(): Promise<string | undefined> {
if (!this.auth) {
return undefined;
}

try {
const token = await (this.app as _FirebaseApp).INTERNAL.getToken();
const token = await this.auth.getToken();
if (!token) {
return undefined;
}
Expand All @@ -47,15 +80,11 @@ export class ContextProvider {

async getInstanceIdToken(): Promise<string | undefined> {
try {
// HACK: Until we have a separate instanceId package, this is a quick way
// to load in the messaging instance for this app.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (!(this.app as any).messaging) {
if (!this.messaging) {
return undefined;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const messaging = (this.app as any).messaging() as FirebaseMessaging;
const token = await messaging.getToken();

const token = await this.messaging.getToken();
if (!token) {
return undefined;
}
Expand Down
61 changes: 31 additions & 30 deletions packages/functions/test/browser/callable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,48 +18,49 @@ import { expect } from 'chai';
import * as sinon from 'sinon';
import { FirebaseApp } from '@firebase/app-types';
import { _FirebaseApp } from '@firebase/app-types/private';
import firebase from '@firebase/app';
import { Service } from '../../src/api/service';

/* eslint-disable import/no-duplicates */
import '@firebase/messaging';
import { isSupported } from '@firebase/messaging';
import { makeFakeApp, createTestService } from '../utils';
import { FirebaseMessaging } from '@firebase/messaging-types';
import {
Provider,
ComponentContainer,
ComponentType,
Component
} from '@firebase/component';

// eslint-disable-next-line @typescript-eslint/no-require-imports
export const TEST_PROJECT = require('../../../../config/project.json');

describe('Firebase Functions > Call', () => {
let app: FirebaseApp;
let functions: Service;

before(() => {
const projectId = TEST_PROJECT.projectId;
const messagingSenderId = 'messaging-sender-id';
const region = 'us-central1';
try {
app = firebase.app('TEST-APP');
} catch (e) {
app = firebase.initializeApp(
{ projectId, messagingSenderId },
'TEST-APP'
);
}
functions = new Service(app, region);
const app: FirebaseApp = makeFakeApp({
projectId: TEST_PROJECT.projectId,
messagingSenderId: 'messaging-sender-id'
});
const region = 'us-central1';

// TODO(klimt): Move this to the cross-platform tests and delete this file,
// once instance id works there.
it('instance id', async () => {
if (!isSupported()) {
// Current platform does not support messaging, skip test.
return;
}
// mock firebase messaging
const messagingMock: FirebaseMessaging = ({
getToken: () => Promise.resolve('iid')
} as unknown) as FirebaseMessaging;
const messagingProvider = new Provider<FirebaseMessaging>(
'messaging',
new ComponentContainer('test')
);
messagingProvider.setComponent(
new Component('messaging', () => messagingMock, ComponentType.PRIVATE)
);

const functions = createTestService(
app,
region,
undefined,
messagingProvider
);

// Stub out the messaging method get an instance id token.
const messaging = firebase.messaging(app);
const stub = sinon
.stub(messaging, 'getToken')
.returns(Promise.resolve('iid'));
const stub = sinon.stub(messagingMock, 'getToken').callThrough();

const func = functions.httpsCallable('instanceIdTest');
const result = await func({});
Expand Down
Loading