Skip to content

Commit

Permalink
Add public SSRC methods (#2458)
Browse files Browse the repository at this point in the history
Add public SSRC methods

---------

Co-authored-by: Xin Wei <xinwei@google.com>
Co-authored-by: jen_h <harveyjen@google.com>
  • Loading branch information
3 people authored Mar 5, 2024
1 parent aed5646 commit 5c9b649
Show file tree
Hide file tree
Showing 5 changed files with 688 additions and 0 deletions.
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

0 comments on commit 5c9b649

Please sign in to comment.