Skip to content

Commit c79740d

Browse files
authored
Merge 05e6e0a into 3759005
2 parents 3759005 + 05e6e0a commit c79740d

File tree

11 files changed

+107
-23
lines changed

11 files changed

+107
-23
lines changed

.changeset/poor-eagles-think.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/functions': minor
3+
---
4+
5+
Allow setting a custom domain for callable Cloud Functions.

packages-exp/functions-exp/src/api.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,22 @@ import {
3535
/**
3636
* Returns a Functions instance for the given app.
3737
* @param app - The FirebaseApp to use.
38-
* @param region - The region the callable functions are located in.
38+
* @param regionOrCustomDomain - one of:
39+
* a) The region the callable functions are located in (ex: us-central1)
40+
* b) A custom domain hosting the callable functions (ex: https://mydomain.com)
3941
* @public
4042
*/
4143
export function getFunctions(
4244
app: FirebaseApp,
43-
region: string = DEFAULT_REGION
45+
regionOrCustomDomain: string = DEFAULT_REGION
4446
): Functions {
4547
// Dependencies
4648
const functionsProvider: Provider<'functions'> = _getProvider(
4749
app,
4850
FUNCTIONS_TYPE
4951
);
5052
const functionsInstance = functionsProvider.getImmediate({
51-
identifier: region
53+
identifier: regionOrCustomDomain
5254
});
5355
return functionsInstance;
5456
}

packages-exp/functions-exp/src/config.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const DEFAULT_REGION = 'us-central1';
3030
export function registerFunctions(fetchImpl: typeof fetch): void {
3131
const factory: InstanceFactory<'functions'> = (
3232
container: ComponentContainer,
33-
region?: string
33+
regionOrCustomDomain?: string
3434
) => {
3535
// Dependencies
3636
const app = container.getProvider('app-exp').getImmediate();
@@ -42,7 +42,7 @@ export function registerFunctions(fetchImpl: typeof fetch): void {
4242
app,
4343
authProvider,
4444
messagingProvider,
45-
region,
45+
regionOrCustomDomain,
4646
fetchImpl
4747
);
4848
};

packages-exp/functions-exp/src/service.test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,19 @@ describe('Firebase Functions > Service', () => {
6464
'http://localhost:5005/my-project/my-region/foo'
6565
);
6666
});
67+
68+
it('correctly sets custom domain', () => {
69+
service = createTestService(app, 'https://mydomain.com');
70+
assert.equal(service._url('foo'), 'https://mydomain.com/foo');
71+
});
72+
73+
it('prefers emulator to custom domain', () => {
74+
const service = createTestService(app, 'https://mydomain.com');
75+
useFunctionsEmulator(service, 'http://localhost:5005');
76+
assert.equal(
77+
service._url('foo'),
78+
'http://localhost:5005/my-project/us-central1/foo'
79+
);
80+
});
6781
});
6882
});

packages-exp/functions-exp/src/service.ts

+20-4
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ export class FunctionsService implements _FirebaseService {
7575
emulatorOrigin: string | null = null;
7676
cancelAllRequests: Promise<void>;
7777
deleteService!: () => Promise<void>;
78+
region: string;
79+
customDomain: string | null;
7880

7981
/**
8082
* Creates a new Functions service for the given app.
@@ -84,7 +86,7 @@ export class FunctionsService implements _FirebaseService {
8486
readonly app: FirebaseApp,
8587
authProvider: Provider<FirebaseAuthInternalName>,
8688
messagingProvider: Provider<FirebaseMessagingName>,
87-
readonly region: string = DEFAULT_REGION,
89+
regionOrCustomDomain: string = DEFAULT_REGION,
8890
readonly fetchImpl: typeof fetch
8991
) {
9092
this.contextProvider = new ContextProvider(authProvider, messagingProvider);
@@ -94,6 +96,16 @@ export class FunctionsService implements _FirebaseService {
9496
return Promise.resolve(resolve());
9597
};
9698
});
99+
100+
// Resolve the region or custom domain overload by attempting to parse it.
101+
try {
102+
const url = new URL(regionOrCustomDomain);
103+
this.customDomain = url.origin;
104+
this.region = DEFAULT_REGION;
105+
} catch (e) {
106+
this.customDomain = null;
107+
this.region = regionOrCustomDomain;
108+
}
97109
}
98110

99111
_delete(): Promise<void> {
@@ -107,12 +119,16 @@ export class FunctionsService implements _FirebaseService {
107119
*/
108120
_url(name: string): string {
109121
const projectId = this.app.options.projectId;
110-
const region = this.region;
111122
if (this.emulatorOrigin !== null) {
112123
const origin = this.emulatorOrigin;
113-
return `${origin}/${projectId}/${region}/${name}`;
124+
return `${origin}/${projectId}/${this.region}/${name}`;
114125
}
115-
return `https://${region}-${projectId}.cloudfunctions.net/${name}`;
126+
127+
if (this.customDomain !== null) {
128+
return `${this.customDomain}/${name}`;
129+
}
130+
131+
return `https://${this.region}-${projectId}.cloudfunctions.net/${name}`;
116132
}
117133
}
118134

packages-exp/functions-types-exp/index.d.ts

+6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ export interface Functions {
5454
* Default is `us-central-1`.
5555
*/
5656
region: string;
57+
58+
/**
59+
* A custom domain hosting the callable Cloud Functions.
60+
* ex: https://mydomain.com
61+
*/
62+
customDomain: string | null;
5763
}
5864

5965
/**

packages/functions/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ declare module '@firebase/app-types' {
3232
};
3333
}
3434
interface FirebaseApp {
35-
functions?(region?: string): types.FirebaseFunctions;
35+
functions?(regionOrCustomDomain?: string): types.FirebaseFunctions;
3636
}
3737
}

packages/functions/src/api/service.ts

+24-6
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,21 @@ export class Service implements FirebaseFunctions, FirebaseService {
8686
private emulatorOrigin: string | null = null;
8787
private cancelAllRequests: Promise<void>;
8888
private deleteService!: () => void;
89+
private region: string;
90+
private customDomain: string | null;
8991

9092
/**
91-
* Creates a new Functions service for the given app and (optional) region.
93+
* Creates a new Functions service for the given app and (optional) region or custom domain.
9294
* @param app_ The FirebaseApp to use.
93-
* @param region_ The region to call functions in.
95+
* @param regionOrCustomDomain_ one of:
96+
* a) A region to call functions from, such as us-central1
97+
* b) A custom domain to use as a functions prefix, such as https://mydomain.com
9498
*/
9599
constructor(
96100
private app_: FirebaseApp,
97101
authProvider: Provider<FirebaseAuthInternalName>,
98102
messagingProvider: Provider<FirebaseMessagingName>,
99-
private region_: string = 'us-central1',
103+
regionOrCustomDomain_: string = 'us-central1',
100104
readonly fetchImpl: typeof fetch
101105
) {
102106
this.contextProvider = new ContextProvider(authProvider, messagingProvider);
@@ -106,6 +110,16 @@ export class Service implements FirebaseFunctions, FirebaseService {
106110
return resolve();
107111
};
108112
});
113+
114+
// Resolve the region or custom domain overload by attempting to parse it.
115+
try {
116+
const url = new URL(regionOrCustomDomain_);
117+
this.customDomain = url.origin;
118+
this.region = 'us-central1';
119+
} catch (e) {
120+
this.customDomain = null;
121+
this.region = regionOrCustomDomain_;
122+
}
109123
}
110124

111125
get app(): FirebaseApp {
@@ -124,12 +138,16 @@ export class Service implements FirebaseFunctions, FirebaseService {
124138
*/
125139
_url(name: string): string {
126140
const projectId = this.app_.options.projectId;
127-
const region = this.region_;
128141
if (this.emulatorOrigin !== null) {
129142
const origin = this.emulatorOrigin;
130-
return `${origin}/${projectId}/${region}/${name}`;
143+
return `${origin}/${projectId}/${this.region}/${name}`;
131144
}
132-
return `https://${region}-${projectId}.cloudfunctions.net/${name}`;
145+
146+
if (this.customDomain !== null) {
147+
return `${this.customDomain}/${name}`;
148+
}
149+
150+
return `https://${this.region}-${projectId}.cloudfunctions.net/${name}`;
133151
}
134152

135153
/**

packages/functions/src/config.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,23 @@ export function registerFunctions(
3737
Functions: Service
3838
};
3939

40-
function factory(container: ComponentContainer, region?: string): Service {
40+
function factory(
41+
container: ComponentContainer,
42+
regionOrCustomDomain?: string
43+
): Service {
4144
// Dependencies
4245
const app = container.getProvider('app').getImmediate();
4346
const authProvider = container.getProvider('auth-internal');
4447
const messagingProvider = container.getProvider('messaging');
4548

4649
// eslint-disable-next-line @typescript-eslint/no-explicit-any
47-
return new Service(app, authProvider, messagingProvider, region, fetchImpl);
50+
return new Service(
51+
app,
52+
authProvider,
53+
messagingProvider,
54+
regionOrCustomDomain,
55+
fetchImpl
56+
);
4857
}
4958
instance.INTERNAL.registerComponent(
5059
new Component(FUNCTIONS_TYPE, factory, ComponentType.PUBLIC)

packages/functions/test/service.test.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,33 @@ describe('Firebase Functions > Service', () => {
4242
});
4343
});
4444

45-
describe('custom region constructor', () => {
45+
describe('custom region/domain constructor', () => {
4646
const app: any = {
4747
options: {
4848
projectId: 'my-project'
4949
}
5050
};
51-
const service = createTestService(app, 'my-region');
5251

53-
it('has valid urls', () => {
52+
it('can use custom region', () => {
53+
const service = createTestService(app, 'my-region');
5454
assert.equal(
5555
service._url('foo'),
5656
'https://my-region-my-project.cloudfunctions.net/foo'
5757
);
5858
});
59+
60+
it('can use custom domain', () => {
61+
const service = createTestService(app, 'https://mydomain.com');
62+
assert.equal(service._url('foo'), 'https://mydomain.com/foo');
63+
});
64+
65+
it('prefers emulator to custom domain', () => {
66+
const service = createTestService(app, 'https://mydomain.com');
67+
service.useFunctionsEmulator('http://localhost:5005');
68+
assert.equal(
69+
service._url('foo'),
70+
'http://localhost:5005/my-project/us-central1/foo'
71+
);
72+
});
5973
});
6074
});

packages/functions/test/utils.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function makeFakeApp(options: FirebaseOptions = {}): FirebaseApp {
4343

4444
export function createTestService(
4545
app: FirebaseApp,
46-
region?: string,
46+
regionOrCustomDomain?: string,
4747
authProvider = new Provider<FirebaseAuthInternalName>(
4848
'auth-internal',
4949
new ComponentContainer('test')
@@ -59,7 +59,7 @@ export function createTestService(
5959
app,
6060
authProvider,
6161
messagingProvider,
62-
region,
62+
regionOrCustomDomain,
6363
fetchImpl
6464
);
6565
const useEmulator = !!process.env.FIREBASE_FUNCTIONS_EMULATOR_ORIGIN;

0 commit comments

Comments
 (0)