Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add public SSRC methods (#2458)
Add public SSRC methods

---------

Co-authored-by: Xin Wei <[email protected]>
Co-authored-by: jen_h <[email protected]>
  • Loading branch information
3 people authored Mar 5, 2024
commit 5c9b6491d52ca76f1d243c7c0e5653915e29e0a2
37 changes: 37 additions & 0 deletions etc/firebase-admin.remote-config.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ export class RemoteConfig {
// (undocumented)
readonly app: App;
createTemplateFromJSON(json: string): RemoteConfigTemplate;
getServerTemplate(options?: RemoteConfigServerTemplateOptions): Promise<RemoteConfigServerTemplate>;
getTemplate(): Promise<RemoteConfigTemplate>;
getTemplateAtVersion(versionNumber: number | string): Promise<RemoteConfigTemplate>;
initServerTemplate(options?: RemoteConfigServerTemplateOptions): RemoteConfigServerTemplate;
listVersions(options?: ListVersionsOptions): Promise<ListVersionsResult>;
publishTemplate(template: RemoteConfigTemplate, options?: {
force: boolean;
Expand Down Expand Up @@ -84,6 +86,41 @@ export interface RemoteConfigParameterGroup {
// @public
export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue;

// @public
export interface RemoteConfigServerCondition {
expression: string;
name: string;
}

// @public
export type RemoteConfigServerConfig = {
[key: string]: string | boolean | number;
};

// @public
export interface RemoteConfigServerTemplate {
cache: RemoteConfigServerTemplateData;
defaultConfig: RemoteConfigServerConfig;
evaluate(): RemoteConfigServerConfig;
load(): Promise<void>;
}

// @public
export interface RemoteConfigServerTemplateData {
conditions: RemoteConfigServerCondition[];
readonly etag: string;
parameters: {
[key: string]: RemoteConfigParameter;
};
version?: Version;
}

// @public
export interface RemoteConfigServerTemplateOptions {
defaultConfig?: RemoteConfigServerConfig;
template?: RemoteConfigServerTemplateData;
}

// @public
export interface RemoteConfigTemplate {
conditions: RemoteConfigCondition[];
Expand Down
5 changes: 5 additions & 0 deletions src/remote-config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export {
RemoteConfigParameterGroup,
RemoteConfigParameterValue,
RemoteConfigTemplate,
RemoteConfigServerCondition,
RemoteConfigServerConfig,
RemoteConfigServerTemplate,
RemoteConfigServerTemplateData,
RemoteConfigServerTemplateOptions,
RemoteConfigUser,
TagColor,
Version,
Expand Down
21 changes: 21 additions & 0 deletions src/remote-config/remote-config-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,27 @@ export interface RemoteConfigServerTemplateData {
version?: Version;
}

/**
* Represents optional arguments that can be used when instantiating {@link RemoteConfigServerTemplate}.
*/
export interface RemoteConfigServerTemplateOptions {

/**
* Defines in-app default parameter values, so that your app behaves as
* intended before it connects to the Remote Config backend, and so that
* default values are available if none are set on the backend.
*/
defaultConfig?: RemoteConfigServerConfig,

/**
* Enables integrations to use template data loaded independently. For
* 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.
*/
template?: RemoteConfigServerTemplateData,
}

/**
* Represents a stateful abstraction for a Remote Config server template.
*/
Expand Down
165 changes: 165 additions & 0 deletions src/remote-config/remote-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,16 @@ import {
RemoteConfigCondition,
RemoteConfigParameter,
RemoteConfigParameterGroup,
RemoteConfigServerTemplate,
RemoteConfigTemplate,
RemoteConfigUser,
Version,
ExplicitParameterValue,
InAppDefaultValue,
ParameterValueType,
RemoteConfigServerConfig,
RemoteConfigServerTemplateData,
RemoteConfigServerTemplateOptions,
} from './remote-config-api';

/**
Expand Down Expand Up @@ -168,6 +175,27 @@ export class RemoteConfig {

return new RemoteConfigTemplateImpl(template);
}

/**
* Instantiates {@link RemoteConfigServerTemplate} and then fetches and caches the latest
* template version of the project.
*/
public async getServerTemplate(options?: RemoteConfigServerTemplateOptions): Promise<RemoteConfigServerTemplate> {
const template = this.initServerTemplate(options);
await template.load();
return template;
}

/**
* Synchronously instantiates {@link RemoteConfigServerTemplate}.
*/
public initServerTemplate(options?: RemoteConfigServerTemplateOptions): RemoteConfigServerTemplate {
const template = new RemoteConfigServerTemplateImpl(this.client, options?.defaultConfig);
if (options?.template) {
template.cache = options?.template;
}
return template;
}
}

/**
Expand Down Expand Up @@ -254,6 +282,143 @@ class RemoteConfigTemplateImpl implements RemoteConfigTemplate {
}
}

/**
* Remote Config dataplane template data implementation.
*/
class RemoteConfigServerTemplateImpl implements RemoteConfigServerTemplate {
public cache: RemoteConfigServerTemplateData;

constructor(
private readonly apiClient: RemoteConfigApiClient,
public readonly defaultConfig: RemoteConfigServerConfig = {}
) { }

/**
* Fetches and caches the current active version of the project's {@link RemoteConfigServerTemplate}.
*/
public load(): Promise<void> {
return this.apiClient.getServerTemplate()
.then((template) => {
this.cache = new RemoteConfigServerTemplateDataImpl(template);
});
}

/**
* Evaluates the current template in cache to produce a {@link RemoteConfigServerConfig}.
*/
public evaluate(): RemoteConfigServerConfig {
if (!this.cache) {
throw new FirebaseRemoteConfigError(
'failed-precondition',
'No Remote Config Server template in cache. Call load() before calling evaluate().');
}

const evaluatedConfig: RemoteConfigServerConfig = {};

for (const [key, parameter] of Object.entries(this.cache.parameters)) {
const { defaultValue, valueType } = parameter;

if (!defaultValue) {
// TODO: add logging once we have a wrapped logger.
continue;
}

if ((defaultValue as InAppDefaultValue).useInAppDefault) {
// TODO: add logging once we have a wrapped logger.
continue;
}

const parameterDefaultValue = (defaultValue as ExplicitParameterValue).value;

evaluatedConfig[key] = this.parseRemoteConfigParameterValue(valueType, parameterDefaultValue);
}

// Merges rendered config over default config.
const mergedConfig = Object.assign(this.defaultConfig, evaluatedConfig);

// Enables config to be a convenient object, but with the ability to perform additional
// functionality when a value is retrieved.
const proxyHandler = {
get(target: RemoteConfigServerConfig, prop: string) {
return target[prop];
}
};

return new Proxy(mergedConfig, proxyHandler);
}

/**
* Private helper method that processes and parses a parameter value based on {@link ParameterValueType}.
*/
private parseRemoteConfigParameterValue(parameterType: ParameterValueType | undefined,
parameterDefaultValue: string): string | number | boolean {
const BOOLEAN_TRUTHY_VALUES = ['1', 'true', 't', 'yes', 'y', 'on'];
const DEFAULT_VALUE_FOR_NUMBER = 0;
const DEFAULT_VALUE_FOR_STRING = '';

if (parameterType === 'BOOLEAN') {
return BOOLEAN_TRUTHY_VALUES.indexOf(parameterDefaultValue) >= 0;
} else if (parameterType === 'NUMBER') {
const num = Number(parameterDefaultValue);
if (isNaN(num)) {
return DEFAULT_VALUE_FOR_NUMBER;
}
return num;
} else {
// Treat everything else as string
return parameterDefaultValue || DEFAULT_VALUE_FOR_STRING;
}
}
}

/**
* Remote Config dataplane template data implementation.
*/
class RemoteConfigServerTemplateDataImpl implements RemoteConfigServerTemplateData {
public parameters: { [key: string]: RemoteConfigParameter };
public parameterGroups: { [key: string]: RemoteConfigParameterGroup };
public conditions: RemoteConfigCondition[];
public readonly etag: string;
public version?: Version;

constructor(template: RemoteConfigServerTemplateData) {
if (!validator.isNonNullObject(template) ||
!validator.isNonEmptyString(template.etag)) {
throw new FirebaseRemoteConfigError(
'invalid-argument',
`Invalid Remote Config template: ${JSON.stringify(template)}`);
}

this.etag = template.etag;

if (typeof template.parameters !== 'undefined') {
if (!validator.isNonNullObject(template.parameters)) {
throw new FirebaseRemoteConfigError(
'invalid-argument',
'Remote Config parameters must be a non-null object');
}
this.parameters = template.parameters;
} else {
this.parameters = {};
}

if (typeof template.conditions !== 'undefined') {
if (!validator.isArray(template.conditions)) {
throw new FirebaseRemoteConfigError(
'invalid-argument',
'Remote Config conditions must be an array');
}
this.conditions = template.conditions;
} else {
this.conditions = [];
}

if (typeof template.version !== 'undefined') {
this.version = new VersionImpl(template.version);
}
}
}

/**
* Remote Config Version internal implementation.
*/
Expand Down
Loading