diff --git a/etc/firebase-admin.remote-config.api.md b/etc/firebase-admin.remote-config.api.md index 746d72d914..25a09b0bf4 100644 --- a/etc/firebase-admin.remote-config.api.md +++ b/etc/firebase-admin.remote-config.api.md @@ -40,7 +40,7 @@ export interface InAppDefaultValue { // @public export interface InitServerTemplateOptions extends GetServerTemplateOptions { - template?: ServerTemplateData; + template?: ServerTemplateData | string; } // @public diff --git a/src/remote-config/remote-config-api.ts b/src/remote-config/remote-config-api.ts index 66d310daf8..f8d1a07392 100644 --- a/src/remote-config/remote-config-api.ts +++ b/src/remote-config/remote-config-api.ts @@ -375,8 +375,10 @@ export interface InitServerTemplateOptions extends GetServerTemplateOptions { * example, customers can reduce initialization latency by pre-fetching and * caching template data and then using this option to initialize the SDK with * that data. + * The template can be initialized with either a {@link ServerTemplateData} + * object or a JSON string. */ - template?: ServerTemplateData, + template?: ServerTemplateData|string, } /** diff --git a/src/remote-config/remote-config.ts b/src/remote-config/remote-config.ts index 887a8a1905..e0e7692e28 100644 --- a/src/remote-config/remote-config.ts +++ b/src/remote-config/remote-config.ts @@ -39,6 +39,7 @@ import { GetServerTemplateOptions, InitServerTemplateOptions, } from './remote-config-api'; +import { isString } from 'lodash'; /** * The Firebase `RemoteConfig` service interface. @@ -198,7 +199,19 @@ export class RemoteConfig { const template = new ServerTemplateImpl( this.client, new ConditionEvaluator(), options?.defaultConfig); if (options?.template) { - template.cache = options?.template; + // Check and instantiates the template via a json string + if (isString(options?.template)) { + try { + template.cache = new ServerTemplateDataImpl(JSON.parse(options?.template)); + } catch (e) { + throw new FirebaseRemoteConfigError( + 'invalid-argument', + `Failed to parse the JSON string: ${options?.template}. ` + e + ); + } + } else { + template.cache = options?.template; + } } return template; } @@ -430,7 +443,6 @@ class ServerTemplateDataImpl implements ServerTemplateData { } this.etag = template.etag; - if (typeof template.parameters !== 'undefined') { if (!validator.isNonNullObject(template.parameters)) { throw new FirebaseRemoteConfigError( diff --git a/test/unit/remote-config/remote-config.spec.ts b/test/unit/remote-config/remote-config.spec.ts index 19a1aed3d2..5f116363c8 100644 --- a/test/unit/remote-config/remote-config.spec.ts +++ b/test/unit/remote-config/remote-config.spec.ts @@ -35,7 +35,7 @@ import { } from '../../../src/remote-config/remote-config-api-client-internal'; import { deepCopy } from '../../../src/utils/deep-copy'; import { - NamedCondition, ServerTemplate, ServerTemplateData + NamedCondition, ServerTemplate, ServerTemplateData, Version } from '../../../src/remote-config/remote-config-api'; const expect = chai.expect; @@ -648,10 +648,61 @@ describe('RemoteConfig', () => { valueType: 'STRING' } }; - const initializedTemplate = remoteConfig.initServerTemplate({ template }).cache; - const parsed = JSON.parse(JSON.stringify(initializedTemplate)); + const initializedTemplate = remoteConfig.initServerTemplate({ template }); + const parsed = JSON.parse(JSON.stringify(initializedTemplate.cache)); expect(parsed).deep.equals(deepCopy(template)); }); + + it('should set and instantiates template when json string is passed', () => { + const template = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE) as ServerTemplateData; + template.parameters = { + dog_type: { + defaultValue: { + value: 'shiba' + }, + description: 'Type of dog breed', + valueType: 'STRING' + } + }; + const templateJson = JSON.stringify(template); + const initializedTemplate = remoteConfig.initServerTemplate({ template: templateJson }); + const parsed = JSON.parse(JSON.stringify(initializedTemplate.cache)); + const expectedVersion = deepCopy(VERSION_INFO); + expectedVersion.updateTime = new Date(expectedVersion.updateTime).toUTCString(); + template.version = expectedVersion as Version; + expect(parsed).deep.equals(deepCopy(template)); + }); + + describe('should throw error if invalid template JSON is passed', () => { + const INVALID_PARAMETERS: any[] = [null, '', 'abc', 1, true, []]; + const INVALID_CONDITIONS: any[] = [null, '', 'abc', 1, true, {}]; + + let sourceTemplate = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE); + const jsonString = '{invalidJson: null}'; + it('should throw if template is an invalid JSON', () => { + expect(() => remoteConfig.initServerTemplate({ template: jsonString })) + .to.throw(/Failed to parse the JSON string: ([\D\w]*)\./); + }); + + INVALID_PARAMETERS.forEach((invalidParameter) => { + sourceTemplate.parameters = invalidParameter; + const jsonString = JSON.stringify(sourceTemplate); + it(`should throw if the parameters is ${JSON.stringify(invalidParameter)}`, () => { + expect(() => remoteConfig.initServerTemplate({ template: jsonString })) + .to.throw('Remote Config parameters must be a non-null object'); + }); + }); + + sourceTemplate = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE); + INVALID_CONDITIONS.forEach((invalidConditions) => { + sourceTemplate.conditions = invalidConditions; + const jsonString = JSON.stringify(sourceTemplate); + it(`should throw if the conditions is ${JSON.stringify(invalidConditions)}`, () => { + expect(() => remoteConfig.initServerTemplate({ template: jsonString })) + .to.throw('Remote Config conditions must be an array'); + }); + }); + }); }); describe('RemoteConfigServerTemplate', () => { @@ -1039,12 +1090,12 @@ describe('RemoteConfig', () => { it('uses local default if parameter not in template', () => { const template = deepCopy(SERVER_REMOTE_CONFIG_RESPONSE) as ServerTemplateData; template.parameters = {}; - + const stub = sinon .stub(RemoteConfigApiClient.prototype, 'getServerTemplate') .resolves(template); stubs.push(stub); - + const defaultConfig = { dog_coat: 'blue merle', }; @@ -1061,12 +1112,12 @@ describe('RemoteConfig', () => { template.parameters = { dog_no_remote_default_value: {} }; - + const stub = sinon .stub(RemoteConfigApiClient.prototype, 'getServerTemplate') .resolves(template); stubs.push(stub); - + const defaultConfig = { dog_no_remote_default_value: 'local default' }; @@ -1083,7 +1134,7 @@ describe('RemoteConfig', () => { template.parameters = { dog_no_remote_default_value: {} }; - + const stub = sinon .stub(RemoteConfigApiClient.prototype, 'getServerTemplate') .resolves(template);