Skip to content

Commit 8d4146a

Browse files
authored
Merge 40e184b into fa0a7c0
2 parents fa0a7c0 + 40e184b commit 8d4146a

File tree

6 files changed

+74
-4
lines changed

6 files changed

+74
-4
lines changed

.changeset/proud-swans-tie.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@firebase/remote-config": minor
3+
---
4+
5+
[Remote Config] add `connectRemoteConfigEmulator` to allow the SDK to connect to the Remote Config emulator (#6486)

common/api-review/remote-config.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import { FirebaseApp } from '@firebase/app';
99
// @public
1010
export function activate(remoteConfig: RemoteConfig): Promise<boolean>;
1111

12+
// @public
13+
export function connectRemoteConfigEmulator(remoteConfig: RemoteConfig, url: string): void;
14+
1215
// @public
1316
export function ensureInitialized(remoteConfig: RemoteConfig): Promise<void>;
1417

packages/remote-config-compat/src/remoteConfig.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ import {
3535
getNumber,
3636
getString,
3737
getValue,
38-
isSupported
38+
isSupported,
39+
connectRemoteConfigEmulator
3940
} from '@firebase/remote-config';
4041

4142
export { isSupported };
@@ -73,6 +74,10 @@ export class RemoteConfigCompatImpl
7374
return activate(this._delegate);
7475
}
7576

77+
useEmulator(url: string): void {
78+
connectRemoteConfigEmulator(this._delegate, url);
79+
}
80+
7681
ensureInitialized(): Promise<void> {
7782
return ensureInitialized(this._delegate);
7883
}

packages/remote-config/src/api.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
} from './public_types';
2424
import { RemoteConfigAbortSignal } from './client/remote_config_fetch_client';
2525
import { RC_COMPONENT_NAME } from './constants';
26-
import { ErrorCode, hasErrorCode } from './errors';
26+
import { ErrorCode, ERROR_FACTORY, hasErrorCode } from './errors';
2727
import { RemoteConfig as RemoteConfigImpl } from './remote_config';
2828
import { Value as ValueImpl } from './value';
2929
import { LogLevel as FirebaseLogLevel } from '@firebase/logger';
@@ -73,6 +73,36 @@ export async function activate(remoteConfig: RemoteConfig): Promise<boolean> {
7373
return true;
7474
}
7575

76+
/**
77+
* Configures the Remote Config SDK to talk to a local emulator
78+
* instead of product.
79+
*
80+
* Must be called before performing any fetches against production
81+
* Remote Config.
82+
*
83+
* @param remoteConfig - The {@link RemoteConfig} instance.
84+
* @param url - The url of the local emulator
85+
*
86+
* @public
87+
*/
88+
export function connectRemoteConfigEmulator(
89+
remoteConfig: RemoteConfig,
90+
url: string
91+
): void {
92+
const rc = getModularInstance(remoteConfig) as RemoteConfigImpl;
93+
94+
// To avoid the footgun of fetching from prod first,
95+
// then the emulator, only allow emulator setup
96+
// if no fetches have been made.
97+
if (rc._storageCache.getLastFetchStatus() !== undefined) {
98+
throw ERROR_FACTORY.create(ErrorCode.ALREADY_FETCHED);
99+
}
100+
101+
window.FIREBASE_REMOTE_CONFIG_URL_BASE = url;
102+
103+
rc._logger.debug('Connected to the Remote Config emulator.');
104+
}
105+
76106
/**
77107
* Ensures the last activated config are available to the getters.
78108
* @param remoteConfig - The {@link RemoteConfig} instance.

packages/remote-config/src/errors.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ export const enum ErrorCode {
3131
FETCH_THROTTLE = 'fetch-throttle',
3232
FETCH_PARSE = 'fetch-client-parse',
3333
FETCH_STATUS = 'fetch-status',
34-
INDEXED_DB_UNAVAILABLE = 'indexed-db-unavailable'
34+
INDEXED_DB_UNAVAILABLE = 'indexed-db-unavailable',
35+
ALREADY_FETCHED = 'already-fetched'
3536
}
3637

3738
const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = {
@@ -67,7 +68,9 @@ const ERROR_DESCRIPTION_MAP: { readonly [key in ErrorCode]: string } = {
6768
[ErrorCode.FETCH_STATUS]:
6869
'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.',
6970
[ErrorCode.INDEXED_DB_UNAVAILABLE]:
70-
'Indexed DB is not supported by current browser'
71+
'Indexed DB is not supported by current browser',
72+
[ErrorCode.ALREADY_FETCHED]:
73+
'Cannot connect to emulator after a fetch has been made.'
7174
};
7275

7376
// Note this is effectively a type system binding a code to params. This approach overlaps with the

packages/remote-config/test/remote_config.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,4 +518,28 @@ describe('RemoteConfig', () => {
518518
);
519519
});
520520
});
521+
522+
describe('connectRemoteConfigEmulator', () => {
523+
it('changes the remote config API URL', () => {
524+
const emulatorUrl = 'http://localhost:9200';
525+
526+
// init storage as if it had never fetched
527+
storageCache.getLastFetchStatus = sinon.stub().returns(undefined);
528+
529+
api.connectRemoteConfigEmulator(rc, emulatorUrl);
530+
expect(window.FIREBASE_REMOTE_CONFIG_URL_BASE === emulatorUrl).to.be.true;
531+
});
532+
533+
it('can not be called if a fetch has already happened', () => {
534+
const emulatorUrl = 'http://localhost:9200';
535+
536+
// init storage as if it had already fetched
537+
storageCache.getLastFetchStatus = sinon.stub().returns('success');
538+
539+
const expectedError = ERROR_FACTORY.create(ErrorCode.ALREADY_FETCHED);
540+
expect(() => api.connectRemoteConfigEmulator(rc, emulatorUrl)).to.throw(
541+
expectedError.message
542+
);
543+
});
544+
});
521545
});

0 commit comments

Comments
 (0)