Skip to content

Commit 06dc136

Browse files
authored
Add Env Override Global Variable forceEnvironment (#6901)
* Add forceEnvironment and check value in isNode * Update API reports * Add tests for environment package * Update tests and remove console.log * Add changeset * Remove old changeset and update it with a new one * Update jsdoc * Move getGlobal into separate file to remove circular dep
1 parent a7622d4 commit 06dc136

File tree

9 files changed

+127
-32
lines changed

9 files changed

+127
-32
lines changed

.changeset/young-hornets-rescue.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@firebase/util': minor
3+
'firebase': minor
4+
---
5+
6+
Allow users to specify their environment as `node` or `browser` to override Firebase's runtime environment detection and force the SDK to act as if it were in the respective environment.

common/api-review/util.api.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ export interface FirebaseDefaults {
193193
config?: Record<string, string>;
194194
// (undocumented)
195195
emulatorHosts?: Record<string, string>;
196+
forceEnvironment?: 'browser' | 'node';
196197
}
197198

198199
// Warning: (ae-missing-release-tag) "FirebaseError" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -221,11 +222,12 @@ export const getDefaultEmulatorHost: (productName: string) => string | undefined
221222
// @public
222223
export const getDefaultEmulatorHostnameAndPort: (productName: string) => [hostname: string, port: number] | undefined;
223224

225+
// @public
226+
export const getDefaults: () => FirebaseDefaults | undefined;
227+
224228
// @public
225229
export const getExperimentalSetting: <T extends ExperimentalKey>(name: T) => FirebaseDefaults[`_${T}`];
226230

227-
// Warning: (ae-missing-release-tag) "getGlobal" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
228-
//
229231
// @public
230232
export function getGlobal(): typeof globalThis;
231233

packages/util/index.node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ export * from './src/uuid';
4242
export * from './src/exponential_backoff';
4343
export * from './src/formatters';
4444
export * from './src/compat';
45+
export * from './src/global';

packages/util/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ export * from './src/uuid';
3737
export * from './src/exponential_backoff';
3838
export * from './src/formatters';
3939
export * from './src/compat';
40+
export * from './src/global';

packages/util/src/defaults.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717

1818
import { base64Decode } from './crypt';
19-
import { getGlobal } from './environment';
19+
import { getGlobal } from './global';
2020

2121
/**
2222
* Keys for experimental properties on the `FirebaseDefaults` object.
@@ -39,6 +39,11 @@ export interface FirebaseDefaults {
3939
emulatorHosts?: Record<string, string>;
4040
_authTokenSyncURL?: string;
4141
_authIdTokenMaxAge?: number;
42+
/**
43+
* Override Firebase's runtime environment detection and
44+
* force the SDK to act as if it were in the specified environment.
45+
*/
46+
forceEnvironment?: 'browser' | 'node';
4247
[key: string]: unknown;
4348
}
4449

@@ -90,8 +95,9 @@ const getDefaultsFromCookie = (): FirebaseDefaults | undefined => {
9095
* (1) if such an object exists as a property of `globalThis`
9196
* (2) if such an object was provided on a shell environment variable
9297
* (3) if such an object exists in a cookie
98+
* @public
9399
*/
94-
const getDefaults = (): FirebaseDefaults | undefined => {
100+
export const getDefaults = (): FirebaseDefaults | undefined => {
95101
try {
96102
return (
97103
getDefaultsFromGlobal() ||

packages/util/src/environment.ts

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
import { CONSTANTS } from './constants';
19+
import { getDefaults } from './defaults';
1920

2021
/**
2122
* Returns navigator.userAgent string or '' if it's not defined.
@@ -52,10 +53,17 @@ export function isMobileCordova(): boolean {
5253
/**
5354
* Detect Node.js.
5455
*
55-
* @return true if Node.js environment is detected.
56+
* @return true if Node.js environment is detected or specified.
5657
*/
5758
// Node detection logic from: https://github.com/iliakan/detect-node/
5859
export function isNode(): boolean {
60+
const forceEnvironment = getDefaults()?.forceEnvironment;
61+
if (forceEnvironment === 'node') {
62+
return true;
63+
} else if (forceEnvironment === 'browser') {
64+
return false;
65+
}
66+
5967
try {
6068
return (
6169
Object.prototype.toString.call(global.process) === '[object process]'
@@ -193,20 +201,3 @@ export function areCookiesEnabled(): boolean {
193201
}
194202
return true;
195203
}
196-
197-
/**
198-
* Polyfill for `globalThis` object.
199-
* @returns the `globalThis` object for the given environment.
200-
*/
201-
export function getGlobal(): typeof globalThis {
202-
if (typeof self !== 'undefined') {
203-
return self;
204-
}
205-
if (typeof window !== 'undefined') {
206-
return window;
207-
}
208-
if (typeof global !== 'undefined') {
209-
return global;
210-
}
211-
throw new Error('Unable to locate global object.');
212-
}

packages/util/src/global.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @license
3+
* Copyright 2022 Google LLC
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+
/**
19+
* Polyfill for `globalThis` object.
20+
* @returns the `globalThis` object for the given environment.
21+
* @public
22+
*/
23+
export function getGlobal(): typeof globalThis {
24+
if (typeof self !== 'undefined') {
25+
return self;
26+
}
27+
if (typeof window !== 'undefined') {
28+
return window;
29+
}
30+
if (typeof global !== 'undefined') {
31+
return global;
32+
}
33+
throw new Error('Unable to locate global object.');
34+
}

packages/util/test/defaults.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ import {
2121
getDefaultEmulatorHost,
2222
getDefaultEmulatorHostnameAndPort
2323
} from '../src/defaults';
24-
import * as environment from '../src/environment';
24+
import * as global from '../src/global';
2525

2626
use(sinonChai);
2727

2828
describe('getDefaultEmulatorHost', () => {
2929
after(() => {
30-
delete environment.getGlobal().__FIREBASE_DEFAULTS__;
30+
delete global.getGlobal().__FIREBASE_DEFAULTS__;
3131
});
3232

3333
context('with no config', () => {
@@ -68,7 +68,7 @@ describe('getDefaultEmulatorHost', () => {
6868
context('with no config and something unexpected throws', () => {
6969
let consoleInfoStub: SinonStub;
7070
before(() => {
71-
stub(environment, 'getGlobal').throws(new Error('getGlobal threw!'));
71+
stub(global, 'getGlobal').throws(new Error('getGlobal threw!'));
7272
consoleInfoStub = stub(console, 'info');
7373
});
7474
after(() => {
@@ -83,7 +83,7 @@ describe('getDefaultEmulatorHost', () => {
8383

8484
context('with global config not listing the emulator', () => {
8585
before(() => {
86-
environment.getGlobal().__FIREBASE_DEFAULTS__ = {
86+
global.getGlobal().__FIREBASE_DEFAULTS__ = {
8787
emulatorHosts: {
8888
/* no firestore */
8989
database: '127.0.0.1:8080'
@@ -98,7 +98,7 @@ describe('getDefaultEmulatorHost', () => {
9898

9999
context('with IPv4 hostname in global config', () => {
100100
before(() => {
101-
environment.getGlobal().__FIREBASE_DEFAULTS__ = {
101+
global.getGlobal().__FIREBASE_DEFAULTS__ = {
102102
emulatorHosts: {
103103
firestore: '127.0.0.1:8080'
104104
}
@@ -112,7 +112,7 @@ describe('getDefaultEmulatorHost', () => {
112112

113113
context('with quoted IPv6 hostname in global config', () => {
114114
before(() => {
115-
environment.getGlobal().__FIREBASE_DEFAULTS__ = {
115+
global.getGlobal().__FIREBASE_DEFAULTS__ = {
116116
emulatorHosts: {
117117
firestore: '[::1]:8080'
118118
}
@@ -127,7 +127,7 @@ describe('getDefaultEmulatorHost', () => {
127127

128128
describe('getDefaultEmulatorHostnameAndPort', () => {
129129
after(() => {
130-
delete environment.getGlobal().__FIREBASE_DEFAULTS__;
130+
delete global.getGlobal().__FIREBASE_DEFAULTS__;
131131
});
132132

133133
context('with no config', () => {
@@ -138,7 +138,7 @@ describe('getDefaultEmulatorHostnameAndPort', () => {
138138

139139
context('with global config not listing the emulator', () => {
140140
before(() => {
141-
environment.getGlobal().__FIREBASE_DEFAULTS__ = {
141+
global.getGlobal().__FIREBASE_DEFAULTS__ = {
142142
emulatorHosts: {
143143
/* no firestore */
144144
database: '127.0.0.1:8080'
@@ -153,7 +153,7 @@ describe('getDefaultEmulatorHostnameAndPort', () => {
153153

154154
context('with IPv4 hostname in global config', () => {
155155
before(() => {
156-
environment.getGlobal().__FIREBASE_DEFAULTS__ = {
156+
global.getGlobal().__FIREBASE_DEFAULTS__ = {
157157
emulatorHosts: {
158158
firestore: '127.0.0.1:8080'
159159
}
@@ -170,7 +170,7 @@ describe('getDefaultEmulatorHostnameAndPort', () => {
170170

171171
context('with quoted IPv6 hostname in global config', () => {
172172
before(() => {
173-
environment.getGlobal().__FIREBASE_DEFAULTS__ = {
173+
global.getGlobal().__FIREBASE_DEFAULTS__ = {
174174
emulatorHosts: {
175175
firestore: '[::1]:8080'
176176
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @license
3+
* Copyright 2022 Google LLC
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 { expect } from 'chai';
19+
import { isNode } from '../src/environment';
20+
import { SinonStub, stub, restore } from 'sinon';
21+
import * as defaults from '../src/defaults';
22+
23+
const firebaseDefaults: defaults.FirebaseDefaults = {
24+
_authTokenSyncURL: 'string',
25+
_authIdTokenMaxAge: 200,
26+
forceEnvironment: 'node'
27+
};
28+
29+
describe('isNode()', () => {
30+
let getDefaultsFromGlobalStub: SinonStub;
31+
32+
beforeEach(async () => {
33+
getDefaultsFromGlobalStub = stub(defaults, 'getDefaults');
34+
});
35+
36+
afterEach(async () => {
37+
restore();
38+
});
39+
40+
it('returns true if forceEnvironment lists `node`', () => {
41+
getDefaultsFromGlobalStub.returns(firebaseDefaults);
42+
43+
expect(isNode()).to.be.true;
44+
});
45+
46+
it('returns false if forceEnvironment lists `browser`', () => {
47+
getDefaultsFromGlobalStub.returns({
48+
...firebaseDefaults,
49+
forceEnvironment: 'browser'
50+
});
51+
52+
expect(isNode()).to.be.false;
53+
});
54+
});

0 commit comments

Comments
 (0)