2022-01-12 11:35:47 -06:00
|
|
|
// Copyright 2020-2022 Signal Messenger, LLC
|
2020-10-30 15:34:04 -05:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2020-05-27 17:37:06 -04:00
|
|
|
import { get, throttle } from 'lodash';
|
2021-07-23 10:23:50 -07:00
|
|
|
|
|
|
|
import type { WebAPIType } from './textsecure/WebAPI';
|
2021-09-17 14:27:53 -04:00
|
|
|
import * as log from './logging/log';
|
2020-05-27 17:37:06 -04:00
|
|
|
|
2021-03-03 14:09:58 -06:00
|
|
|
export type ConfigKeyType =
|
2021-07-20 16:51:38 -04:00
|
|
|
| 'desktop.announcementGroup'
|
2022-03-01 17:39:09 -06:00
|
|
|
| 'desktop.calling.audioLevelForSpeaking'
|
2020-10-16 11:31:57 -07:00
|
|
|
| 'desktop.clientExpiration'
|
2021-08-25 16:42:51 -05:00
|
|
|
| 'desktop.groupCallOutboundRing'
|
2021-08-05 10:00:33 -07:00
|
|
|
| 'desktop.internalUser'
|
2020-10-16 11:31:57 -07:00
|
|
|
| 'desktop.mandatoryProfileSharing'
|
2021-06-25 12:08:16 -04:00
|
|
|
| 'desktop.mediaQuality.levels'
|
2022-02-11 13:09:35 -08:00
|
|
|
| 'desktop.messageCleanup'
|
2020-10-16 11:31:57 -07:00
|
|
|
| 'desktop.messageRequests'
|
2021-06-08 14:51:58 -07:00
|
|
|
| 'desktop.retryReceiptLifespan'
|
|
|
|
| 'desktop.retryRespondMaxAge'
|
2021-08-03 18:02:35 -07:00
|
|
|
| 'desktop.senderKey.retry'
|
2022-02-11 13:09:35 -08:00
|
|
|
| 'desktop.senderKey.send'
|
|
|
|
| 'desktop.senderKeyMaxAge'
|
2021-08-26 09:34:33 -07:00
|
|
|
| 'desktop.sendSenderKey3'
|
2021-11-17 15:25:17 -06:00
|
|
|
| 'desktop.showUserBadges.beta'
|
2022-02-11 13:09:35 -08:00
|
|
|
| 'desktop.showUserBadges2'
|
2022-03-04 16:14:52 -05:00
|
|
|
| 'desktop.stories'
|
2021-11-01 12:13:35 -07:00
|
|
|
| 'desktop.usernames'
|
2021-08-17 09:01:27 -05:00
|
|
|
| 'global.calling.maxGroupCallRingSize'
|
2021-07-15 16:48:09 -07:00
|
|
|
| 'global.groupsv2.groupSizeHardLimit'
|
|
|
|
| 'global.groupsv2.maxGroupSize';
|
2020-05-27 17:37:06 -04:00
|
|
|
type ConfigValueType = {
|
|
|
|
name: ConfigKeyType;
|
|
|
|
enabled: boolean;
|
|
|
|
enabledAt?: number;
|
2020-09-09 18:50:44 -04:00
|
|
|
value?: unknown;
|
2020-05-27 17:37:06 -04:00
|
|
|
};
|
2021-11-01 12:13:35 -07:00
|
|
|
export type ConfigMapType = {
|
|
|
|
[key in ConfigKeyType]?: ConfigValueType;
|
|
|
|
};
|
2020-05-27 17:37:06 -04:00
|
|
|
type ConfigListenerType = (value: ConfigValueType) => unknown;
|
|
|
|
type ConfigListenersMapType = {
|
|
|
|
[key: string]: Array<ConfigListenerType>;
|
|
|
|
};
|
|
|
|
|
|
|
|
let config: ConfigMapType = {};
|
|
|
|
const listeners: ConfigListenersMapType = {};
|
|
|
|
|
2021-07-23 10:23:50 -07:00
|
|
|
export async function initRemoteConfig(server: WebAPIType): Promise<void> {
|
2020-05-27 17:37:06 -04:00
|
|
|
config = window.storage.get('remoteConfig') || {};
|
2021-07-23 10:23:50 -07:00
|
|
|
await maybeRefreshRemoteConfig(server);
|
2020-05-27 17:37:06 -04:00
|
|
|
}
|
|
|
|
|
2020-09-11 12:37:01 -07:00
|
|
|
export function onChange(
|
|
|
|
key: ConfigKeyType,
|
|
|
|
fn: ConfigListenerType
|
|
|
|
): () => void {
|
2020-05-27 17:37:06 -04:00
|
|
|
const keyListeners: Array<ConfigListenerType> = get(listeners, key, []);
|
|
|
|
keyListeners.push(fn);
|
|
|
|
listeners[key] = keyListeners;
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
listeners[key] = listeners[key].filter(l => l !== fn);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-07-23 10:23:50 -07:00
|
|
|
export const refreshRemoteConfig = async (
|
|
|
|
server: WebAPIType
|
|
|
|
): Promise<void> => {
|
2020-05-27 17:37:06 -04:00
|
|
|
const now = Date.now();
|
|
|
|
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;
|
2020-09-09 18:50:44 -04:00
|
|
|
config = newConfig.reduce((acc, { name, enabled, value }) => {
|
2020-05-27 17:37:06 -04:00
|
|
|
const previouslyEnabled: boolean = get(oldConfig, [name, 'enabled'], false);
|
2020-09-09 18:50:44 -04:00
|
|
|
const previousValue: unknown = get(oldConfig, [name, 'value'], undefined);
|
2020-09-11 12:37:01 -07:00
|
|
|
// If a flag was previously not enabled and is now enabled,
|
|
|
|
// record the time it was enabled
|
2020-05-27 17:37:06 -04:00
|
|
|
const enabledAt: number | undefined =
|
|
|
|
previouslyEnabled && enabled ? now : get(oldConfig, [name, 'enabledAt']);
|
|
|
|
|
2020-09-09 18:50:44 -04:00
|
|
|
const configValue = {
|
2020-05-27 17:37:06 -04:00
|
|
|
name: name as ConfigKeyType,
|
|
|
|
enabled,
|
|
|
|
enabledAt,
|
2020-09-09 18:50:44 -04:00
|
|
|
value,
|
2020-05-27 17:37:06 -04:00
|
|
|
};
|
|
|
|
|
2020-09-09 18:50:44 -04:00
|
|
|
const hasChanged =
|
|
|
|
previouslyEnabled !== enabled || previousValue !== configValue.value;
|
|
|
|
|
2020-05-27 17:37:06 -04:00
|
|
|
// If enablement changes at all, notify listeners
|
|
|
|
const currentListeners = listeners[name] || [];
|
2020-09-09 18:50:44 -04:00
|
|
|
if (hasChanged) {
|
2021-09-17 14:27:53 -04:00
|
|
|
log.info(`Remote Config: Flag ${name} has changed`);
|
2020-05-27 17:37:06 -04:00
|
|
|
currentListeners.forEach(listener => {
|
2020-09-09 18:50:44 -04:00
|
|
|
listener(configValue);
|
2020-05-27 17:37:06 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return new configuration object
|
|
|
|
return {
|
2020-09-09 18:50:44 -04:00
|
|
|
...acc,
|
|
|
|
[name]: configValue,
|
2020-05-27 17:37:06 -04: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);
|
|
|
|
}
|
2020-12-01 08:42:35 -08:00
|
|
|
|
|
|
|
export function getValue(name: ConfigKeyType): string | undefined {
|
|
|
|
return get(config, [name, 'value'], undefined);
|
|
|
|
}
|