Skip to content

Commit 44691fc

Browse files
committed
Mirgrate functions to component framework (#2328)
1 parent dc83446 commit 44691fc

File tree

10 files changed

+233
-132
lines changed

10 files changed

+233
-132
lines changed

packages/functions/index.node.ts

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,8 @@
1515
* limitations under the License.
1616
*/
1717
import firebase from '@firebase/app';
18-
import { FirebaseApp } from '@firebase/app-types';
19-
import {
20-
FirebaseServiceFactory,
21-
_FirebaseNamespace
22-
} from '@firebase/app-types/private';
23-
import { Service } from './src/api/service';
18+
import { _FirebaseNamespace } from '@firebase/app-types/private';
19+
import { registerFunctions } from './src/config';
2420
import 'isomorphic-fetch';
2521

26-
/**
27-
* Type constant for Firebase Functions.
28-
*/
29-
const FUNCTIONS_TYPE = 'functions';
30-
31-
function factory(app: FirebaseApp, _unused: unknown, region?: string): Service {
32-
return new Service(app, region);
33-
}
34-
35-
export function registerFunctions(instance: _FirebaseNamespace): void {
36-
const namespaceExports = {
37-
// no-inline
38-
Functions: Service
39-
};
40-
instance.INTERNAL.registerService(
41-
FUNCTIONS_TYPE,
42-
factory as FirebaseServiceFactory,
43-
namespaceExports,
44-
// We don't need to wait on any AppHooks.
45-
undefined,
46-
// Allow multiple functions instances per app.
47-
true
48-
);
49-
}
50-
5122
registerFunctions(firebase as _FirebaseNamespace);

packages/functions/index.ts

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,9 @@
1515
* limitations under the License.
1616
*/
1717
import firebase from '@firebase/app';
18-
import * as appTypes from '@firebase/app-types';
19-
import {
20-
FirebaseServiceFactory,
21-
_FirebaseNamespace
22-
} from '@firebase/app-types/private';
18+
import { _FirebaseNamespace } from '@firebase/app-types/private';
2319
import * as types from '@firebase/functions-types';
24-
import { Service } from './src/api/service';
25-
26-
/**
27-
* Type constant for Firebase Functions.
28-
*/
29-
const FUNCTIONS_TYPE = 'functions';
30-
31-
function factory(
32-
app: appTypes.FirebaseApp,
33-
_unused: unknown,
34-
region?: string
35-
): Service {
36-
return new Service(app, region);
37-
}
38-
39-
export function registerFunctions(instance: _FirebaseNamespace): void {
40-
const namespaceExports = {
41-
// no-inline
42-
Functions: Service
43-
};
44-
instance.INTERNAL.registerService(
45-
FUNCTIONS_TYPE,
46-
factory as FirebaseServiceFactory,
47-
namespaceExports,
48-
// We don't need to wait on any AppHooks.
49-
undefined,
50-
// Allow multiple functions instances per app.
51-
true
52-
);
53-
}
20+
import { registerFunctions } from './src/config';
5421

5522
registerFunctions(firebase as _FirebaseNamespace);
5623

packages/functions/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"dependencies": {
7474
"@firebase/functions-types": "0.3.10",
7575
"@firebase/messaging-types": "0.3.4",
76+
"@firebase/component": "0.1.0",
7677
"isomorphic-fetch": "2.2.1",
7778
"tslib": "1.10.0"
7879
},

packages/functions/src/api/service.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import {
2626
import { _errorForResponse, HttpsErrorImpl } from './error';
2727
import { ContextProvider } from '../context';
2828
import { Serializer } from '../serializer';
29+
import { Provider } from '@firebase/component';
30+
import { FirebaseAuthInternal } from '@firebase/auth-interop-types';
31+
import { FirebaseMessaging } from '@firebase/messaging-types';
2932

3033
/**
3134
* The response to an http request.
@@ -80,9 +83,11 @@ export class Service implements FirebaseFunctions, FirebaseService {
8083
*/
8184
constructor(
8285
private app_: FirebaseApp,
86+
authProvider: Provider<FirebaseAuthInternal>,
87+
messagingProvider: Provider<FirebaseMessaging>,
8388
private region_: string = 'us-central1'
8489
) {
85-
this.contextProvider = new ContextProvider(app_);
90+
this.contextProvider = new ContextProvider(authProvider, messagingProvider);
8691
// Cancels all ongoing requests when resolved.
8792
this.cancelAllRequests = new Promise(resolve => {
8893
this.deleteService = () => {

packages/functions/src/config.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* @license
3+
* Copyright 2019 Google Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { Service } from './api/service';
19+
import {
20+
Component,
21+
ComponentType,
22+
ComponentContainer
23+
} from '@firebase/component';
24+
import { _FirebaseNamespace } from '@firebase/app-types/private';
25+
26+
/**
27+
* Type constant for Firebase Functions.
28+
*/
29+
const FUNCTIONS_TYPE = 'functions';
30+
31+
function factory(container: ComponentContainer, region?: string): Service {
32+
// Dependencies
33+
const app = container.getProvider('app').getImmediate();
34+
const authProvider = container.getProvider('auth-internal');
35+
const messagingProvider = container.getProvider('messaging');
36+
37+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
38+
return new Service(app, authProvider, messagingProvider, region);
39+
}
40+
41+
export function registerFunctions(instance: _FirebaseNamespace): void {
42+
const namespaceExports = {
43+
// no-inline
44+
Functions: Service
45+
};
46+
instance.INTERNAL.registerComponent(
47+
new Component(FUNCTIONS_TYPE, factory, ComponentType.PUBLIC)
48+
.setServiceProps(namespaceExports)
49+
.setMultipleInstances(true)
50+
);
51+
}

packages/functions/src/context.ts

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17-
import { FirebaseApp } from '@firebase/app-types';
1817
import { _FirebaseApp } from '@firebase/app-types/private';
1918
import { FirebaseMessaging } from '@firebase/messaging-types';
19+
import { FirebaseAuthInternal } from '@firebase/auth-interop-types';
20+
import { Provider } from '@firebase/component';
2021

2122
/**
2223
* The metadata that should be supplied with function calls.
@@ -30,11 +31,43 @@ export interface Context {
3031
* Helper class to get metadata that should be included with a function call.
3132
*/
3233
export class ContextProvider {
33-
constructor(private readonly app: FirebaseApp) {}
34+
private auth: FirebaseAuthInternal | null = null;
35+
private messaging: FirebaseMessaging | null = null;
36+
constructor(
37+
authProvider: Provider<FirebaseAuthInternal>,
38+
messagingProvider: Provider<FirebaseMessaging>
39+
) {
40+
this.auth = authProvider.getImmediate(undefined, { optional: true });
41+
this.messaging = messagingProvider.getImmediate(undefined, {
42+
optional: true
43+
});
44+
45+
if (!this.auth) {
46+
authProvider.get().then(
47+
auth => (this.auth = auth),
48+
() => {
49+
/* get() never rejects */
50+
}
51+
);
52+
}
53+
54+
if (!this.messaging) {
55+
messagingProvider.get().then(
56+
messaging => (this.messaging = messaging),
57+
() => {
58+
/* get() never rejects */
59+
}
60+
);
61+
}
62+
}
3463

3564
async getAuthToken(): Promise<string | undefined> {
65+
if (!this.auth) {
66+
return undefined;
67+
}
68+
3669
try {
37-
const token = await (this.app as _FirebaseApp).INTERNAL.getToken();
70+
const token = await this.auth.getToken();
3871
if (!token) {
3972
return undefined;
4073
}
@@ -47,15 +80,11 @@ export class ContextProvider {
4780

4881
async getInstanceIdToken(): Promise<string | undefined> {
4982
try {
50-
// HACK: Until we have a separate instanceId package, this is a quick way
51-
// to load in the messaging instance for this app.
52-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
53-
if (!(this.app as any).messaging) {
83+
if (!this.messaging) {
5484
return undefined;
5585
}
56-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
57-
const messaging = (this.app as any).messaging() as FirebaseMessaging;
58-
const token = await messaging.getToken();
86+
87+
const token = await this.messaging.getToken();
5988
if (!token) {
6089
return undefined;
6190
}

packages/functions/test/browser/callable.test.ts

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,48 +18,49 @@ import { expect } from 'chai';
1818
import * as sinon from 'sinon';
1919
import { FirebaseApp } from '@firebase/app-types';
2020
import { _FirebaseApp } from '@firebase/app-types/private';
21-
import firebase from '@firebase/app';
22-
import { Service } from '../../src/api/service';
23-
24-
/* eslint-disable import/no-duplicates */
25-
import '@firebase/messaging';
26-
import { isSupported } from '@firebase/messaging';
21+
import { makeFakeApp, createTestService } from '../utils';
22+
import { FirebaseMessaging } from '@firebase/messaging-types';
23+
import {
24+
Provider,
25+
ComponentContainer,
26+
ComponentType,
27+
Component
28+
} from '@firebase/component';
2729

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

3133
describe('Firebase Functions > Call', () => {
32-
let app: FirebaseApp;
33-
let functions: Service;
34-
35-
before(() => {
36-
const projectId = TEST_PROJECT.projectId;
37-
const messagingSenderId = 'messaging-sender-id';
38-
const region = 'us-central1';
39-
try {
40-
app = firebase.app('TEST-APP');
41-
} catch (e) {
42-
app = firebase.initializeApp(
43-
{ projectId, messagingSenderId },
44-
'TEST-APP'
45-
);
46-
}
47-
functions = new Service(app, region);
34+
const app: FirebaseApp = makeFakeApp({
35+
projectId: TEST_PROJECT.projectId,
36+
messagingSenderId: 'messaging-sender-id'
4837
});
38+
const region = 'us-central1';
4939

5040
// TODO(klimt): Move this to the cross-platform tests and delete this file,
5141
// once instance id works there.
5242
it('instance id', async () => {
53-
if (!isSupported()) {
54-
// Current platform does not support messaging, skip test.
55-
return;
56-
}
43+
// mock firebase messaging
44+
const messagingMock: FirebaseMessaging = ({
45+
getToken: () => Promise.resolve('iid')
46+
} as unknown) as FirebaseMessaging;
47+
const messagingProvider = new Provider<FirebaseMessaging>(
48+
'messaging',
49+
new ComponentContainer('test')
50+
);
51+
messagingProvider.setComponent(
52+
new Component('messaging', () => messagingMock, ComponentType.PRIVATE)
53+
);
54+
55+
const functions = createTestService(
56+
app,
57+
region,
58+
undefined,
59+
messagingProvider
60+
);
5761

5862
// Stub out the messaging method get an instance id token.
59-
const messaging = firebase.messaging(app);
60-
const stub = sinon
61-
.stub(messaging, 'getToken')
62-
.returns(Promise.resolve('iid'));
63+
const stub = sinon.stub(messagingMock, 'getToken').callThrough();
6364

6465
const func = functions.httpsCallable('instanceIdTest');
6566
const result = await func({});

0 commit comments

Comments
 (0)