feat: lookup for shared config message on link device

pull/2620/head
Audric Ackermann 2 years ago
parent 0050352470
commit 554b445a3e

@ -1,9 +1,10 @@
import React, { useCallback, useState } from 'react'; import React from 'react';
import moment from 'moment'; import moment from 'moment';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useInterval from 'react-use/lib/useInterval'; import useInterval from 'react-use/lib/useInterval';
import styled from 'styled-components'; import styled from 'styled-components';
import useUpdate from 'react-use/lib/useUpdate';
type Props = { type Props = {
timestamp?: number; timestamp?: number;
@ -31,13 +32,7 @@ const TimestampContainerListItem = styled(TimestampContainerNotListItem)`
`; `;
export const Timestamp = (props: Props) => { export const Timestamp = (props: Props) => {
const [_lastUpdated, setLastUpdated] = useState(Date.now()); const update = useUpdate();
// this is kind of a hack, but we use lastUpdated just to trigger a refresh.
// formatRelativeTime() will print the correct moment.
const update = useCallback(() => {
setLastUpdated(Date.now());
}, [setLastUpdated]);
useInterval(update, UPDATE_FREQUENCY); useInterval(update, UPDATE_FREQUENCY);
const { timestamp, momentFromNow } = props; const { timestamp, momentFromNow } = props;
@ -47,14 +42,14 @@ export const Timestamp = (props: Props) => {
} }
const momentValue = moment(timestamp); const momentValue = moment(timestamp);
let dateString: string = ''; // this is a hack to make the date string shorter, looks like moment does not have a localized way of doing this for now.
if (momentFromNow) {
dateString = momentValue.fromNow(); const dateString = momentFromNow
} else { ? momentValue
dateString = momentValue.format('lll'); .fromNow()
} .replace('minutes', 'mins')
.replace('minute', 'min')
dateString = dateString.replace('minutes', 'mins').replace('minute', 'min'); : momentValue.format('lll');
const title = moment(timestamp).format('llll'); const title = moment(timestamp).format('llll');
if (props.isConversationListItem) { if (props.isConversationListItem) {

@ -1162,9 +1162,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
} }
public async commit() { public async commit() {
perfStart(`conversationCommit-${this.attributes.id}`); perfStart(`conversationCommit-${this.id}`);
await commitConversationAndRefreshWrapper(this.id); await commitConversationAndRefreshWrapper(this.id);
perfEnd(`conversationCommit-${this.attributes.id}`, 'conversationCommit'); perfEnd(`conversationCommit-${this.id}`, 'conversationCommit');
} }
public async addSingleOutgoingMessage( public async addSingleOutgoingMessage(

@ -118,10 +118,10 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
const filledAttrs = fillMessageAttributesWithDefaults(attributes); const filledAttrs = fillMessageAttributesWithDefaults(attributes);
super(filledAttrs); super(filledAttrs);
if (!this.attributes.id) { if (!this.id) {
throw new Error('A message always needs to have an id.'); throw new Error('A message always needs to have an id.');
} }
if (!this.attributes.conversationId) { if (!this.get('conversationId')) {
throw new Error('A message always needs to have an conversationId.'); throw new Error('A message always needs to have an conversationId.');
} }
@ -1084,17 +1084,17 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
} }
public async commit(triggerUIUpdate = true) { public async commit(triggerUIUpdate = true) {
if (!this.attributes.id) { if (!this.id) {
throw new Error('A message always needs an id'); throw new Error('A message always needs an id');
} }
perfStart(`messageCommit-${this.attributes.id}`); perfStart(`messageCommit-${this.id}`);
// because the saving to db calls _cleanData which mutates the field for cleaning, we need to save a copy // because the saving to db calls _cleanData which mutates the field for cleaning, we need to save a copy
const id = await Data.saveMessage(cloneDeep(this.attributes)); const id = await Data.saveMessage(cloneDeep(this.attributes));
if (triggerUIUpdate) { if (triggerUIUpdate) {
this.dispatchMessageUpdate(); this.dispatchMessageUpdate();
} }
perfEnd(`messageCommit-${this.attributes.id}`, 'messageCommit'); perfEnd(`messageCommit-${this.id}`, 'messageCommit');
return id; return id;
} }

@ -1368,7 +1368,6 @@ function getFirstUnreadMessageIdInConversation(conversationId: string) {
} }
/** /**
*
* Returns the last read message timestamp in the specific conversation (the columns `serverTimestamp` || `sent_at`) * Returns the last read message timestamp in the specific conversation (the columns `serverTimestamp` || `sent_at`)
*/ */
function getLastMessageReadInConversation(conversationId: string): number | null { function getLastMessageReadInConversation(conversationId: string): number | null {

@ -1,4 +1,4 @@
import _, { compact, isEmpty } from 'lodash'; import _, { compact, isEmpty, toNumber } from 'lodash';
import { ConfigDumpData } from '../data/configDump/configDump'; import { ConfigDumpData } from '../data/configDump/configDump';
import { Data, hasSyncedInitialConfigurationItem } from '../data/data'; import { Data, hasSyncedInitialConfigurationItem } from '../data/data';
import { ConversationInteraction } from '../interactions'; import { ConversationInteraction } from '../interactions';
@ -118,7 +118,9 @@ async function handleUserProfileUpdate(result: IncomingConfResult): Promise<Inco
const updatedProfilePicture = await UserConfigWrapperActions.getProfilePicture(); const updatedProfilePicture = await UserConfigWrapperActions.getProfilePicture();
const picUpdate = !isEmpty(updatedProfilePicture.key) && !isEmpty(updatedProfilePicture.url); const picUpdate = !isEmpty(updatedProfilePicture.key) && !isEmpty(updatedProfilePicture.url);
await ProfileManager.updateOurProfileSync(
await updateOurProfileLegacyOrViaLibSession(
result.latestEnvelopeTimestamp,
updatedUserName, updatedUserName,
picUpdate ? updatedProfilePicture.url : null, picUpdate ? updatedProfilePicture.url : null,
picUpdate ? updatedProfilePicture.key : null picUpdate ? updatedProfilePicture.key : null
@ -156,8 +158,8 @@ async function handleContactsUpdate(result: IncomingConfResult): Promise<Incomin
changes = true; changes = true;
} }
if (!wrapperConvo.hidden && !contactConvo.isHidden()) { if (Boolean(wrapperConvo.hidden) !== Boolean(contactConvo.isHidden())) {
contactConvo.set({ hidden: false }); contactConvo.set({ hidden: !!wrapperConvo.hidden });
changes = true; changes = true;
} }
@ -635,6 +637,23 @@ async function handleConfigMessagesViaLibSession(
await processMergingResults(incomingMergeResult); await processMergingResults(incomingMergeResult);
} }
async function updateOurProfileLegacyOrViaLibSession(
sentAt: number,
displayName: string,
profileUrl: string | null,
profileKey: Uint8Array | null
) {
await ProfileManager.updateOurProfileSync(displayName, profileUrl, profileKey);
await setLastProfileUpdateTimestamp(toNumber(sentAt));
// do not trigger a signin by linking if the display name is empty
if (!isEmpty(displayName)) {
trigger(configurationMessageReceived, displayName);
} else {
window?.log?.warn('Got a configuration message but the display name is empty');
}
}
async function handleOurProfileUpdateLegacy( async function handleOurProfileUpdateLegacy(
sentAt: number | Long, sentAt: number | Long,
configMessage: SignalService.ConfigurationMessage configMessage: SignalService.ConfigurationMessage
@ -650,15 +669,12 @@ async function handleOurProfileUpdateLegacy(
); );
const { profileKey, profilePicture, displayName } = configMessage; const { profileKey, profilePicture, displayName } = configMessage;
await ProfileManager.updateOurProfileSync(displayName, profilePicture, profileKey); await updateOurProfileLegacyOrViaLibSession(
toNumber(sentAt),
await setLastProfileUpdateTimestamp(_.toNumber(sentAt)); displayName,
// do not trigger a signin by linking if the display name is empty profilePicture,
if (displayName) { profileKey
trigger(configurationMessageReceived, displayName); );
} else {
window?.log?.warn('Got a configuration message but the display name is empty');
}
} }
} }
@ -669,7 +685,7 @@ async function handleGroupsAndContactsFromConfigMessageLegacy(
if (window.sessionFeatureFlags.useSharedUtilForUserConfig) { if (window.sessionFeatureFlags.useSharedUtilForUserConfig) {
return; return;
} }
const envelopeTimestamp = _.toNumber(envelope.timestamp); const envelopeTimestamp = toNumber(envelope.timestamp);
const lastConfigUpdate = await Data.getItemById(hasSyncedInitialConfigurationItem); const lastConfigUpdate = await Data.getItemById(hasSyncedInitialConfigurationItem);
const lastConfigTimestamp = lastConfigUpdate?.timestamp; const lastConfigTimestamp = lastConfigUpdate?.timestamp;
const isNewerConfig = const isNewerConfig =
@ -789,14 +805,14 @@ const handleContactFromConfigLegacy = async (
const existingActiveAt = contactConvo.get('active_at'); const existingActiveAt = contactConvo.get('active_at');
if (!existingActiveAt || existingActiveAt === 0) { if (!existingActiveAt || existingActiveAt === 0) {
contactConvo.set('active_at', _.toNumber(envelope.timestamp)); contactConvo.set('active_at', toNumber(envelope.timestamp));
} }
// checking for existence of field on protobuf // checking for existence of field on protobuf
if (contactReceived.isApproved === true) { if (contactReceived.isApproved === true) {
if (!contactConvo.isApproved()) { if (!contactConvo.isApproved()) {
await contactConvo.setIsApproved(Boolean(contactReceived.isApproved)); await contactConvo.setIsApproved(Boolean(contactReceived.isApproved));
await contactConvo.addOutgoingApprovalMessage(_.toNumber(envelope.timestamp)); await contactConvo.addOutgoingApprovalMessage(toNumber(envelope.timestamp));
} }
if (contactReceived.didApproveMe === true) { if (contactReceived.didApproveMe === true) {

@ -145,7 +145,7 @@ export class SwarmPolling {
public async pollForAllKeys() { public async pollForAllKeys() {
if (!window.getGlobalOnlineStatus()) { if (!window.getGlobalOnlineStatus()) {
window?.log?.error('pollForAllKeys: offline'); window?.log?.error('pollForAllKeys: offline');
// Important to set up a new polling // Very important to set up a new polling call so we do retry at some point
setTimeout(this.pollForAllKeys.bind(this), SWARM_POLLING_TIMEOUT.ACTIVE); setTimeout(this.pollForAllKeys.bind(this), SWARM_POLLING_TIMEOUT.ACTIVE);
return; return;
} }

@ -305,20 +305,21 @@ export class ConversationController {
const load = async () => { const load = async () => {
try { try {
const start = Date.now(); const startLoad = Date.now();
const convoModels = await Data.getAllConversations(); const convoModels = await Data.getAllConversations();
this.conversations.add(convoModels); this.conversations.add(convoModels);
console.time('refreshAllWrapperContactsData'); const start = Date.now();
// TODO make this a switch so we take care of all wrappers and have compilation errors if we forgot to add one. // TODO make this a switch so we take care of all wrappers and have compilation errors if we forgot to add one.
// also keep in mind that the convo volatile one need to run for each convo.
for (let index = 0; index < convoModels.length; index++) { for (let index = 0; index < convoModels.length; index++) {
const convo = convoModels[index]; const convo = convoModels[index];
if (SessionUtilContact.isContactToStoreInContactsWrapper(convo)) { if (SessionUtilContact.isContactToStoreInContactsWrapper(convo)) {
await SessionUtilContact.refreshMappedValue(convo.id, true); await SessionUtilContact.refreshMappedValue(convo.id, true);
} } else if (SessionUtilUserGroups.isUserGroupToStoreInWrapper(convo)) {
if (SessionUtilUserGroups.isUserGroupToStoreInWrapper(convo)) {
await SessionUtilUserGroups.refreshCachedUserGroup(convo.id, true); await SessionUtilUserGroups.refreshCachedUserGroup(convo.id, true);
} }
// we actually want to run the convo volatile check and fetch even if one of the cases above is already true
if (SessionUtilConvoInfoVolatile.isConvoToStoreInWrapper(convo)) { if (SessionUtilConvoInfoVolatile.isConvoToStoreInWrapper(convo)) {
await SessionUtilConvoInfoVolatile.refreshConvoVolatileCached( await SessionUtilConvoInfoVolatile.refreshConvoVolatileCached(
convo.id, convo.id,
@ -329,21 +330,22 @@ export class ConversationController {
await convo.refreshInMemoryDetails(); await convo.refreshInMemoryDetails();
} }
} }
console.timeEnd('refreshAllWrapperContactsData'); console.info(`refreshAllWrappersMappedValues took ${Date.now() - start}ms`);
this._initialFetchComplete = true; this._initialFetchComplete = true;
this.conversations.forEach((conversation: ConversationModel) => { // TODO do we really need to do this?
if ( // this.conversations.forEach((conversation: ConversationModel) => {
conversation.isActive() && // if (
!conversation.isHidden() && // conversation.isActive() &&
!conversation.get('lastMessage') // !conversation.isHidden() &&
) { // !conversation.get('lastMessage')
conversation.updateLastMessage(); // ) {
} // conversation.updateLastMessage();
}); // }
// });
window?.log?.info( window?.log?.info(
`ConversationController: done with initial fetch in ${Date.now() - start}ms.` `ConversationController: done with initial fetch in ${Date.now() - startLoad}ms.`
); );
} catch (error) { } catch (error) {
window?.log?.error( window?.log?.error(

@ -65,6 +65,7 @@ function isConvoToStoreInWrapper(convo: ConversationModel): boolean {
SessionUtilUserProfile.isUserProfileToStoreInContactsWrapper(convo.id) // this checks for out own pubkey, as we want to keep track of the read state for the Note To Self SessionUtilUserProfile.isUserProfileToStoreInContactsWrapper(convo.id) // this checks for out own pubkey, as we want to keep track of the read state for the Note To Self
); );
} }
function getConvoType(convo: ConversationModel): ConvoVolatileType { function getConvoType(convo: ConversationModel): ConvoVolatileType {
const convoType: ConvoVolatileType = const convoType: ConvoVolatileType =
SessionUtilContact.isContactToStoreInContactsWrapper(convo) || SessionUtilContact.isContactToStoreInContactsWrapper(convo) ||
@ -88,8 +89,13 @@ async function insertConvoFromDBIntoWrapperAndRefresh(convoId: string): Promise<
return; return;
} }
const isForcedUnread = foundConvo.isMarkedUnread(); const isForcedUnread = foundConvo.isMarkedUnread();
const timestampFromDbMs = (await Data.fetchConvoMemoryDetails(convoId))?.lastReadTimestampMessage;
// TODO not having a last read timestamp fallsback to 0, which keeps the existing value in the wrapper if it is already set (as done in src/convo_info_volatile_config.cpp)
const lastReadMessageTimestamp = const lastReadMessageTimestamp =
(await Data.fetchConvoMemoryDetails(convoId))?.lastReadTimestampMessage || 0; !!timestampFromDbMs && isFinite(timestampFromDbMs) && timestampFromDbMs > 0
? timestampFromDbMs
: 0;
console.info( console.info(
`convoInfoVolatile:insert "${convoId}";lastMessageReadTimestamp:${lastReadMessageTimestamp};forcedUnread:${isForcedUnread}...` `convoInfoVolatile:insert "${convoId}";lastMessageReadTimestamp:${lastReadMessageTimestamp};forcedUnread:${isForcedUnread}...`

@ -45,7 +45,7 @@ async function destroyExpiredMessages() {
conversationKey: string; conversationKey: string;
messageId: string; messageId: string;
}> = messages.map(m => ({ }> = messages.map(m => ({
conversationKey: m.attributes.conversationId, conversationKey: m.get('conversationId'),
messageId: m.id, messageId: m.id,
})); }));

@ -126,6 +126,7 @@ export function getLastProfileUpdateTimestamp() {
} }
export async function setLastProfileUpdateTimestamp(lastUpdateTimestamp: number) { export async function setLastProfileUpdateTimestamp(lastUpdateTimestamp: number) {
// TODO get rid of this once we remove the feature flag altogether
if (window.sessionFeatureFlags.useSharedUtilForUserConfig) { if (window.sessionFeatureFlags.useSharedUtilForUserConfig) {
return; return;
} }

Loading…
Cancel
Save