fix: use releaseFeature from disappearing message PR as featureFlag

This commit is contained in:
Audric Ackermann 2023-05-02 12:06:08 +10:00
parent 8edb1275c2
commit 44483b7d23
12 changed files with 165 additions and 38 deletions

View File

@ -33,7 +33,6 @@ window.sessionFeatureFlags = {
),
useDebugLogging: !_.isEmpty(process.env.SESSION_DEBUG),
useClosedGroupV3: false || process.env.USE_CLOSED_GROUP_V3,
useSharedUtilForUserConfig: true,
debug: {
debugFileServerRequests: false,
debugNonSnodeRequests: false,

View File

@ -15,7 +15,6 @@ import { ConversationTypeEnum } from '../../models/conversationAttributes';
import { MAX_USERNAME_BYTES } from '../../session/constants';
import { getConversationController } from '../../session/conversations';
import { sanitizeSessionUsername } from '../../session/utils/String';
import { ConfigurationSync } from '../../session/utils/job_runners/jobs/ConfigurationSyncJob';
import { editProfileModal } from '../../state/ducks/modalDialog';
import { pickFileForAvatar } from '../../types/attachments/VisualAttachment';
import { saveQRCode } from '../../util/saveQRCode';
@ -357,12 +356,6 @@ async function commitProfileEdits(newName: string, scaledAvatarUrl: string | nul
// might be good to not trigger a sync if the name did not change
await conversation.commit();
await ConfigurationSync.queueNewJobIfNeeded();
if (window.sessionFeatureFlags.useSharedUtilForUserConfig) {
await setLastProfileUpdateTimestamp(Date.now());
} else {
await setLastProfileUpdateTimestamp(Date.now());
await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
}
await setLastProfileUpdateTimestamp(Date.now());
await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
}

View File

@ -43,6 +43,7 @@ import { SessionUtilUserGroups } from '../session/utils/libsession/libsession_ut
import { leaveClosedGroup } from '../session/group/closed-group';
import { SessionUtilContact } from '../session/utils/libsession/libsession_utils_contacts';
import { SettingsKey } from '../data/settings-key';
import { ReleasedFeatures } from '../util/releaseFeature';
export function copyPublicKeyByConvoId(convoId: string) {
if (OpenGroupUtils.isOpenGroupV2(convoId)) {
@ -461,8 +462,9 @@ export async function uploadOurAvatar(newAvatarDecrypted?: ArrayBuffer) {
if (newAvatarDecrypted) {
await setLastProfileUpdateTimestamp(Date.now());
await ConfigurationSync.queueNewJobIfNeeded();
const userConfigLibsession = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
if (!window.sessionFeatureFlags.useSharedUtilForUserConfig) {
if (!userConfigLibsession) {
await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
}
} else {

View File

@ -46,6 +46,7 @@ import { HexKeyPair } from './keypairs';
import { queueAllCachedFromSource } from './receiver';
import { EnvelopePlus } from './types';
import { SettingsKey } from '../data/settings-key';
import { ReleasedFeatures } from '../util/releaseFeature';
function groupByVariant(
incomingConfigs: Array<IncomingMessage<SignalService.ISharedConfigMessage>>
@ -639,7 +640,9 @@ async function processMergingResults(results: Map<ConfigWrapperObjectTypes, Inco
async function handleConfigMessagesViaLibSession(
configMessages: Array<IncomingMessage<SignalService.ISharedConfigMessage>>
) {
if (!window.sessionFeatureFlags.useSharedUtilForUserConfig) {
const userConfigLibsession = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
if (!userConfigLibsession) {
return;
}
@ -678,8 +681,9 @@ async function handleOurProfileUpdateLegacy(
sentAt: number | Long,
configMessage: SignalService.ConfigurationMessage
) {
const userConfigLibsession = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
// we want to allow if we are not registered, as we might need to fetch an old config message (can be removed once we released for a weeks the libsession util)
if (window.sessionFeatureFlags.useSharedUtilForUserConfig && Registration.isDone()) {
if (userConfigLibsession && Registration.isDone()) {
return;
}
const latestProfileUpdateTimestamp = getLastProfileUpdateTimestamp();
@ -703,7 +707,9 @@ async function handleGroupsAndContactsFromConfigMessageLegacy(
envelope: EnvelopePlus,
configMessage: SignalService.ConfigurationMessage
) {
if (window.sessionFeatureFlags.useSharedUtilForUserConfig && Registration.isDone()) {
const userConfigLibsession = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
if (userConfigLibsession && Registration.isDone()) {
return;
}
const envelopeTimestamp = toNumber(envelope.timestamp);
@ -736,7 +742,7 @@ async function handleGroupsAndContactsFromConfigMessageLegacy(
await handleClosedGroupsFromConfigLegacy(configMessage.closedGroups, envelope);
}
handleOpenGroupsFromConfigLegacy(configMessage.openGroups);
void handleOpenGroupsFromConfigLegacy(configMessage.openGroups);
if (configMessage.contacts?.length) {
await Promise.all(
@ -749,8 +755,10 @@ async function handleGroupsAndContactsFromConfigMessageLegacy(
* Trigger a join for all open groups we are not already in.
* @param openGroups string array of open group urls
*/
const handleOpenGroupsFromConfigLegacy = (openGroups: Array<string>) => {
if (window.sessionFeatureFlags.useSharedUtilForUserConfig && Registration.isDone()) {
const handleOpenGroupsFromConfigLegacy = async (openGroups: Array<string>) => {
const userConfigLibsession = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
if (userConfigLibsession && Registration.isDone()) {
return;
}
const numberOpenGroup = openGroups?.length || 0;
@ -778,7 +786,9 @@ const handleClosedGroupsFromConfigLegacy = async (
closedGroups: Array<SignalService.ConfigurationMessage.IClosedGroup>,
envelope: EnvelopePlus
) => {
if (window.sessionFeatureFlags.useSharedUtilForUserConfig && Registration.isDone()) {
const userConfigLibsession = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
if (userConfigLibsession && Registration.isDone()) {
return;
}
const numberClosedGroup = closedGroups?.length || 0;
@ -813,7 +823,9 @@ const handleContactFromConfigLegacy = async (
contactReceived: SignalService.ConfigurationMessage.IContact,
envelope: EnvelopePlus
) => {
if (window.sessionFeatureFlags.useSharedUtilForUserConfig && Registration.isDone()) {
const userConfigLibsession = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
if (userConfigLibsession && Registration.isDone()) {
return;
}
try {
@ -880,8 +892,9 @@ async function handleConfigurationMessageLegacy(
// when the useSharedUtilForUserConfig flag is ON, we want only allow a legacy config message if we are registering a new user.
// this is to allow users linking a device to find their config message if they do not have a shared config message yet.
// the process of those messages is always done after the process of the shared config messages, so that's only a fallback.
const userConfigLibsession = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
if (window.sessionFeatureFlags.useSharedUtilForUserConfig && Registration.isDone()) {
if (userConfigLibsession && Registration.isDone()) {
window?.log?.info(
'useSharedUtilForUserConfig is set, not handling config messages with "handleConfigurationMessageLegacy()"'
);

View File

@ -15,6 +15,7 @@ import { showMessageRequestBannerOutsideRedux } from '../state/ducks/userConfig'
import { getHideMessageRequestBannerOutsideRedux } from '../state/selectors/userConfig';
import { GoogleChrome } from '../util';
import { LinkPreviews } from '../util/linkPreviews';
import { ReleasedFeatures } from '../util/releaseFeature';
function contentTypeSupported(type: string): boolean {
const Chrome = GoogleChrome;
@ -254,7 +255,9 @@ async function handleRegularMessage(
await conversation.setDidApproveMe(true);
}
} else if (type === 'outgoing') {
if (!window.sessionFeatureFlags.useSharedUtilForUserConfig) {
const userConfigLibsession = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
if (!userConfigLibsession) {
// we want to do this for all types of conversations, not just private chats
handleSyncedReceiptsNoCommit(message, conversation);

View File

@ -21,6 +21,7 @@ import { perfEnd, perfStart } from '../../utils/Performance';
import { SnodeNamespace, SnodeNamespaces } from './namespaces';
import { SnodeAPIRetrieve } from './retrieveRequest';
import { RetrieveMessageItem, RetrieveMessagesResultsBatched } from './types';
import { ReleasedFeatures } from '../../../util/releaseFeature';
export function extractWebSocketContent(
message: string,
@ -151,9 +152,10 @@ export class SwarmPolling {
}
// we always poll as often as possible for our pubkey
const ourPubkey = UserUtils.getOurPubKeyFromCache();
const directPromise = Promise.all([
this.pollOnceForKey(ourPubkey, false, this.getUserNamespacesPolled()),
]).then(() => undefined);
const userNamespaces = await this.getUserNamespacesPolled();
const directPromise = Promise.all([this.pollOnceForKey(ourPubkey, false, userNamespaces)]).then(
() => undefined
);
const now = Date.now();
const groupPromises = this.groupPolling.map(async group => {
@ -222,10 +224,11 @@ export class SwarmPolling {
}
let allNamespacesWithoutUserConfigIfNeeded: Array<RetrieveMessageItem> = [];
const userConfigLibsession = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
// check if we just fetched the details from the config namespaces.
// If yes, merge them together and exclude them from the rest of the messages.
if (window.sessionFeatureFlags.useSharedUtilForUserConfig && resultsFromAllNamespaces) {
if (userConfigLibsession && resultsFromAllNamespaces) {
const userConfigMessages = resultsFromAllNamespaces
.filter(m => SnodeNamespace.isUserConfigNamespace(m.namespace))
.map(r => r.messages.messages);
@ -484,8 +487,9 @@ export class SwarmPolling {
return newMessages;
}
private getUserNamespacesPolled() {
return window.sessionFeatureFlags.useSharedUtilForUserConfig
private async getUserNamespacesPolled() {
const isUserConfigRelease = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
return isUserConfigRelease
? [
SnodeNamespaces.UserMessages,
SnodeNamespaces.UserProfile,

View File

@ -16,6 +16,8 @@ import {
PersistedJob,
RunJobResult,
} from '../PersistedJob';
import { ReleasedFeatures } from '../../../../util/releaseFeature';
import { allowOnlyOneAtATime } from '../../Promise';
const defaultMsBetweenRetries = 3000;
const defaultMaxAttempts = 3;
@ -182,9 +184,10 @@ class ConfigurationSyncJob extends PersistedJob<ConfigurationSyncPersistedData>
// save the dumps to DB even before trying to push them, so at least we have an up to date dumps in the DB in case of crash, no network etc
await saveDumpsNeededToDB(thisJobDestination);
const userConfigLibsession = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
// if the feature flag is not enabled, we want to keep updating the dumps, but just not sync them.
if (!window.sessionFeatureFlags.useSharedUtilForUserConfig) {
if (!userConfigLibsession) {
this.triggerConfSyncJobDone();
return RunJobResult.Success;
}
@ -305,5 +308,6 @@ async function queueNewJobIfNeeded() {
export const ConfigurationSync = {
ConfigurationSyncJob,
queueNewJobIfNeeded,
queueNewJobIfNeeded: () =>
allowOnlyOneAtATime(`ConfigurationSyncJob-oneAtAtTime`, queueNewJobIfNeeded),
};

View File

@ -31,6 +31,7 @@ import { ConfigurationSync } from '../job_runners/jobs/ConfigurationSyncJob';
import { fromBase64ToArray, fromHexToArray } from '../String';
import { getCompleteUrlFromRoom } from '../../apis/open_group_api/utils/OpenGroupUtils';
import { Storage } from '../../../util/storage';
import { ReleasedFeatures } from '../../../util/releaseFeature';
const ITEM_ID_LAST_SYNC_TIMESTAMP = 'lastSyncedTimestamp';
@ -46,7 +47,8 @@ const writeLastSyncTimestampToDb = async (timestamp: number) =>
export const syncConfigurationIfNeeded = async () => {
await ConfigurationSync.queueNewJobIfNeeded();
if (!window.sessionFeatureFlags.useSharedUtilForUserConfig) {
const userConfigLibsession = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
if (!userConfigLibsession) {
const lastSyncedTimestamp = (await getLastSyncTimestampFromDb()) || 0;
const now = Date.now();
@ -75,9 +77,9 @@ export const syncConfigurationIfNeeded = async () => {
}
};
export const forceSyncConfigurationNowIfNeeded = async (waitForMessageSent = false) =>
new Promise(resolve => {
const allConvos = getConversationController().getConversations();
export const forceSyncConfigurationNowIfNeeded = async (waitForMessageSent = false) => {
await ReleasedFeatures.checkIsUserConfigFeatureReleased();
return new Promise(resolve => {
// if we hang for more than 20sec, force resolve this promise.
setTimeout(() => {
resolve(false);
@ -90,17 +92,19 @@ export const forceSyncConfigurationNowIfNeeded = async (waitForMessageSent = fal
e.message
);
});
if (window.sessionFeatureFlags.useSharedUtilForUserConfig) {
if (ReleasedFeatures.isUserConfigFeatureReleasedCached()) {
if (waitForMessageSent) {
window.Whisper.events.once(ConfigurationSyncJobDone, () => {
resolve(true);
return;
});
return;
} else {
resolve(true);
return;
}
}
const allConvos = getConversationController().getConversations();
void getCurrentConfigurationMessage(allConvos)
.then(configMessage => {
@ -128,6 +132,7 @@ export const forceSyncConfigurationNowIfNeeded = async (waitForMessageSent = fal
resolve(false);
});
});
};
const getActiveOpenGroupV2CompleteUrls = async (
convos: Array<ConversationModel>

View File

@ -77,9 +77,9 @@ describe('JobRunner', () => {
const job = getFakeSleepForJob(123);
await runner.addJob(job);
throw new Error('PLOP'); // the line above should throw something else
throw new Error('fake error'); // the line above should throw something else
} catch (e) {
expect(e.message).to.not.eq('PLOP');
expect(e.message).to.not.eq('fake error');
}
});
it('unsorted list is sorted after loading', async () => {

104
ts/util/releaseFeature.ts Normal file
View File

@ -0,0 +1,104 @@
import { GetNetworkTime } from '../session/apis/snode_api/getNetworkTime';
import { assertUnreachable } from '../types/sqlSharedTypes';
import { Storage } from './storage';
let isDisappearingMessageFeatureReleased: boolean | undefined;
let isUserConfigLibsessionFeatureReleased: boolean | undefined;
type FeatureNameTracked = 'disappearing_messages' | 'user_config_libsession';
/**
* This is only intended for testing. Do not call this in production.
*/
export function resetFeatureReleasedCachedValue() {
isDisappearingMessageFeatureReleased = undefined;
isUserConfigLibsessionFeatureReleased = undefined;
}
function getIsFeatureReleasedCached(featureName: FeatureNameTracked) {
switch (featureName) {
case 'disappearing_messages':
return isDisappearingMessageFeatureReleased;
case 'user_config_libsession':
return isUserConfigLibsessionFeatureReleased;
default:
assertUnreachable(featureName, `case not handled for getIsFeatureReleasedCached`);
}
}
function setIsFeatureReleasedCached(featureName: FeatureNameTracked, value: boolean) {
switch (featureName) {
case 'disappearing_messages':
isDisappearingMessageFeatureReleased = value;
break;
case 'user_config_libsession':
isUserConfigLibsessionFeatureReleased = value;
break;
default:
assertUnreachable(featureName, `case not handled for setIsFeatureReleasedCached `);
}
}
function getFeatureReleaseTimestamp(featureName: FeatureNameTracked) {
switch (featureName) {
case 'disappearing_messages':
// TODO update to agreed value between platforms for `disappearing_messages`
return 1706778000000; // unix 01/02/2024 09:00;
// return 1677488400000; // testing: unix 27/02/2023 09:00
case 'user_config_libsession':
// TODO update to agreed value between platforms for `user_config_libsession`
return 1706778000000; // unix 01/02/2024 09:00;
// return 1677488400000; // testing: unix 27/02/2023 09:00
default:
assertUnreachable(featureName, `case not handled for getFeatureReleaseTimestamp `);
}
}
export async function getIsFeatureReleased(featureName: FeatureNameTracked): Promise<boolean> {
if (getIsFeatureReleasedCached(featureName) === undefined) {
// read values from db and cache them as it looks like we did not
const oldIsFeatureReleased = Boolean(Storage.get(`featureReleased-${featureName}`));
// values do not exist in the db yet. Let's store false for now in the db and update our cached value.
if (oldIsFeatureReleased === undefined) {
await Storage.put(`featureReleased-${featureName}`, false);
setIsFeatureReleasedCached(featureName, false);
} else {
setIsFeatureReleasedCached(featureName, oldIsFeatureReleased);
}
}
return Boolean(getIsFeatureReleasedCached(featureName));
}
async function checkIsFeatureReleased(featureName: FeatureNameTracked): Promise<boolean> {
const featureAlreadyReleased = await getIsFeatureReleased(featureName);
// Is it time to release the feature based on the network timestamp?
if (
!featureAlreadyReleased &&
GetNetworkTime.getNowWithNetworkOffset() >= getFeatureReleaseTimestamp(featureName)
) {
window.log.info(`[releaseFeature]: It is time to release ${featureName}. Releasing it now`);
await Storage.put(`featureReleased-${featureName}`, true);
setIsFeatureReleasedCached(featureName, true);
}
const isReleased = Boolean(getIsFeatureReleasedCached(featureName));
window.log.debug(
`[releaseFeature]: "${featureName}" ${isReleased ? 'is released' : 'has not been released yet'}`
);
return isReleased;
}
function checkIsUserConfigFeatureReleased() {
return checkIsFeatureReleased('user_config_libsession');
}
function isUserConfigFeatureReleasedCached(): boolean {
return !!isUserConfigLibsessionFeatureReleased;
}
export const ReleasedFeatures = {
checkIsUserConfigFeatureReleased,
isUserConfigFeatureReleasedCached,
};

View File

@ -3,6 +3,7 @@ import { SessionKeyPair } from '../receiver/keypairs';
import { DEFAULT_RECENT_REACTS } from '../session/constants';
import { deleteSettingsBoolValue, updateSettingsBoolValue } from '../state/ducks/settings';
import { isBoolean } from 'lodash';
import { ReleasedFeatures } from './releaseFeature';
let ready = false;
@ -135,7 +136,7 @@ export function getLastProfileUpdateTimestamp() {
}
export async function setLastProfileUpdateTimestamp(lastUpdateTimestamp: number) {
if (window.sessionFeatureFlags.useSharedUtilForUserConfig) {
if (await ReleasedFeatures.checkIsUserConfigFeatureReleased()) {
return;
}
await put('last_profile_update_timestamp', lastUpdateTimestamp);

1
ts/window.d.ts vendored
View File

@ -37,7 +37,6 @@ declare global {
useOnionRequests: boolean;
useTestNet: boolean;
useClosedGroupV3: boolean;
useSharedUtilForUserConfig: boolean;
debug: {
debugFileServerRequests: boolean;
debugNonSnodeRequests: boolean;