Phased rollout by country code; starting w/ desktop.stories remote flag

This commit is contained in:
Scott Nonnenberg 2022-10-20 14:02:22 -07:00 committed by GitHub
parent e14c3241c5
commit 1c89168301
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 318 additions and 25 deletions

View file

@ -5,6 +5,12 @@ import { get, throttle } from 'lodash';
import type { WebAPIType } from './textsecure/WebAPI';
import * as log from './logging/log';
import type { UUIDStringType } from './types/UUID';
import { parseIntOrThrow } from './util/parseIntOrThrow';
import * as Bytes from './Bytes';
import { hash, uuidToBytes } from './Crypto';
import { HashType } from './types/Crypto';
import { getCountryCode } from './types/PhoneNumber';
export type ConfigKeyType =
| 'desktop.announcementGroup'
@ -135,3 +141,86 @@ export function isEnabled(name: ConfigKeyType): boolean {
export function getValue(name: ConfigKeyType): string | undefined {
return get(config, [name, 'value'], undefined);
}
// See isRemoteConfigBucketEnabled in selectors/items.ts
export function isBucketValueEnabled(
name: ConfigKeyType,
e164: string | undefined,
uuid: UUIDStringType | undefined
): boolean {
return innerIsBucketValueEnabled(name, getValue(name), e164, uuid);
}
export function innerIsBucketValueEnabled(
name: ConfigKeyType,
flagValue: unknown,
e164: string | undefined,
uuid: UUIDStringType | undefined
): boolean {
if (e164 == null || uuid == null) {
return false;
}
const countryCode = getCountryCode(e164);
if (countryCode == null) {
return false;
}
if (typeof flagValue !== 'string') {
return false;
}
const remoteConfigValue = getCountryCodeValue(countryCode, flagValue, name);
if (remoteConfigValue == null) {
return false;
}
const bucketValue = getBucketValue(uuid, name);
return bucketValue < remoteConfigValue;
}
export function getCountryCodeValue(
countryCode: number,
flagValue: string,
flagName: string
): number | undefined {
const logId = `getCountryCodeValue/${flagName}`;
if (flagValue.length === 0) {
return undefined;
}
const countryCodeString = countryCode.toString();
const items = flagValue.split(',');
let wildcard: number | undefined;
for (const item of items) {
const [code, value] = item.split(':');
if (code == null || value == null) {
log.warn(`${logId}: '${code}:${value}' entry was invalid`);
continue;
}
const parsedValue = parseIntOrThrow(
value,
`${logId}: Country code '${code}' had an invalid number '${value}'`
);
if (code === '*') {
wildcard = parsedValue;
} else if (countryCodeString === code) {
return parsedValue;
}
}
return wildcard;
}
export function getBucketValue(uuid: UUIDStringType, flagName: string): number {
const hashInput = Bytes.concatenate([
Bytes.fromString(`${flagName}.`),
uuidToBytes(uuid),
]);
const hashResult = hash(HashType.size256, hashInput);
const buffer = Buffer.from(hashResult.slice(0, 8));
return Number(buffer.readBigUint64BE() % 1_000_000n);
}