Signal-Desktop/ts/RemoteConfig.ts

119 lines
3.5 KiB
TypeScript
Raw Normal View History

2021-04-26 18:15:00 +00:00
// Copyright 2020-2021 Signal Messenger, LLC
2020-10-30 20:34:04 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
2020-05-27 21:37:06 +00:00
import { get, throttle } from 'lodash';
import { connectToServerWithStoredCredentials } from './util/connectToServerWithStoredCredentials';
2020-05-27 21:37:06 +00:00
2021-03-03 20:09:58 +00:00
export type ConfigKeyType =
| 'desktop.clientExpiration'
| 'desktop.disableGV1'
| 'desktop.groupCalling'
| 'desktop.gv2'
| 'desktop.mandatoryProfileSharing'
| 'desktop.messageRequests'
2021-06-08 21:51:58 +00:00
| 'desktop.retryReceiptLifespan'
| 'desktop.retryRespondMaxAge'
2021-06-03 18:50:12 +00:00
| 'desktop.screensharing2'
2021-06-08 21:51:58 +00:00
| 'desktop.sendSenderKey'
| 'desktop.storage'
| 'desktop.storageWrite3'
| 'desktop.worksAtSignal'
| 'global.groupsv2.maxGroupSize'
| 'global.groupsv2.groupSizeHardLimit';
2020-05-27 21:37:06 +00:00
type ConfigValueType = {
name: ConfigKeyType;
enabled: boolean;
enabledAt?: number;
value?: unknown;
2020-05-27 21:37:06 +00:00
};
export type ConfigMapType = { [key: string]: ConfigValueType };
2020-05-27 21:37:06 +00:00
type ConfigListenerType = (value: ConfigValueType) => unknown;
type ConfigListenersMapType = {
[key: string]: Array<ConfigListenerType>;
};
let config: ConfigMapType = {};
const listeners: ConfigListenersMapType = {};
2020-09-11 19:37:01 +00:00
export async function initRemoteConfig(): Promise<void> {
2020-05-27 21:37:06 +00:00
config = window.storage.get('remoteConfig') || {};
await maybeRefreshRemoteConfig();
}
2020-09-11 19:37:01 +00:00
export function onChange(
key: ConfigKeyType,
fn: ConfigListenerType
): () => void {
2020-05-27 21:37:06 +00:00
const keyListeners: Array<ConfigListenerType> = get(listeners, key, []);
keyListeners.push(fn);
listeners[key] = keyListeners;
return () => {
listeners[key] = listeners[key].filter(l => l !== fn);
};
}
2020-09-11 19:37:01 +00:00
export const refreshRemoteConfig = async (): Promise<void> => {
2020-05-27 21:37:06 +00:00
const now = Date.now();
const server = connectToServerWithStoredCredentials(
window.WebAPI,
window.storage
);
2020-05-27 21:37:06 +00:00
const newConfig = await server.getConfig();
// Process new configuration in light of the old configuration
// The old configuration is not set as the initial value in reduce because
// flags may have been deleted
const oldConfig = config;
config = newConfig.reduce((acc, { name, enabled, value }) => {
2020-05-27 21:37:06 +00:00
const previouslyEnabled: boolean = get(oldConfig, [name, 'enabled'], false);
const previousValue: unknown = get(oldConfig, [name, 'value'], undefined);
2020-09-11 19:37:01 +00:00
// If a flag was previously not enabled and is now enabled,
// record the time it was enabled
2020-05-27 21:37:06 +00:00
const enabledAt: number | undefined =
previouslyEnabled && enabled ? now : get(oldConfig, [name, 'enabledAt']);
const configValue = {
2020-05-27 21:37:06 +00:00
name: name as ConfigKeyType,
enabled,
enabledAt,
value,
2020-05-27 21:37:06 +00:00
};
const hasChanged =
previouslyEnabled !== enabled || previousValue !== configValue.value;
2020-05-27 21:37:06 +00:00
// If enablement changes at all, notify listeners
const currentListeners = listeners[name] || [];
if (hasChanged) {
window.log.info(`Remote Config: Flag ${name} has changed`);
2020-05-27 21:37:06 +00:00
currentListeners.forEach(listener => {
listener(configValue);
2020-05-27 21:37:06 +00:00
});
}
// Return new configuration object
return {
...acc,
[name]: configValue,
2020-05-27 21:37:06 +00:00
};
}, {});
window.storage.put('remoteConfig', config);
};
export const maybeRefreshRemoteConfig = throttle(
refreshRemoteConfig,
// Only fetch remote configuration if the last fetch was more than two hours ago
2 * 60 * 60 * 1000,
{ trailing: false }
);
export function isEnabled(name: ConfigKeyType): boolean {
return get(config, [name, 'enabled'], false);
}
export function getValue(name: ConfigKeyType): string | undefined {
return get(config, [name, 'value'], undefined);
}