feat: attempting to fetch the display name separate from the usual pipeline

pull/3056/head
William Grant 1 year ago
parent fd4e1525cb
commit a3262d7af9

@ -1,6 +1,5 @@
import { useState } from 'react';
import { useDispatch } from 'react-redux';
import { getSwarmPollingInstance } from '../../../session/apis/snode_api';
import { ONBOARDING_TIMES } from '../../../session/constants';
import { InvalidWordsError, NotEnoughWordsError } from '../../../session/crypto/mnemonic';
import { PromiseUtils, ToastUtils } from '../../../session/utils';
@ -76,10 +75,11 @@ async function signInAndFetchDisplayName(
await resetRegistration();
await signInByLinkingDevice(recoveryPassword, 'english', loadingAnimationCallback);
await getSwarmPollingInstance().start();
await PromiseUtils.waitForTask(done => {
window.Whisper.events.on('configurationMessageReceived', async (displayName: string) => {
window.log.debug(
`WIP: [signInAndFetchDisplayName] waitForTask done with displayName: "${displayName}"`
);
window.Whisper.events.off('configurationMessageReceived');
await setSignInByLinking(false);
await setSignWithRecoveryPhrase(true);

@ -103,7 +103,7 @@ async function mergeConfigsWithIncomingUpdates(
}));
if (window.sessionFeatureFlags.debug.debugLibsessionDumps) {
window.log.info(
`printDumpsForDebugging: before merge of ${variant}:`,
`WIP: printDumpsForDebugging: before merge of ${variant}:`,
StringUtils.toHex(await GenericWrapperActions.dump(variant))
);
@ -132,7 +132,7 @@ async function mergeConfigsWithIncomingUpdates(
if (window.sessionFeatureFlags.debug.debugLibsessionDumps) {
window.log.info(
`printDumpsForDebugging: after merge of ${variant}:`,
`WIP: printDumpsForDebugging: after merge of ${variant}:`,
StringUtils.toHex(await GenericWrapperActions.dump(variant))
);
}
@ -821,9 +821,11 @@ async function handleConvoInfoVolatileUpdate(
return result;
}
async function processMergingResults(results: Map<ConfigWrapperObjectTypes, IncomingConfResult>) {
async function processMergingResults(
results: Map<ConfigWrapperObjectTypes, IncomingConfResult>
): Promise<IncomingConfResult | undefined> {
if (!results || !results.size) {
return;
return undefined;
}
const keys = [...results.keys()];
@ -858,6 +860,7 @@ async function processMergingResults(results: Map<ConfigWrapperObjectTypes, Inco
window.log.warn('assertUnreachable failed', e.message);
}
}
const variant = LibSessionUtil.kindToVariant(kind);
try {
await updateLibsessionLatestProcessedUserTimestamp(
@ -882,9 +885,11 @@ async function processMergingResults(results: Map<ConfigWrapperObjectTypes, Inco
if (incomingResult.needsPush) {
anyNeedsPush = true;
}
return incomingResult;
} catch (e) {
window.log.error(`processMergingResults failed with ${e.message}`);
return;
return undefined;
}
}
// Now that the local state has been updated, trigger a config sync (this will push any
@ -892,19 +897,21 @@ async function processMergingResults(results: Map<ConfigWrapperObjectTypes, Inco
if (anyNeedsPush) {
await ConfigurationSync.queueNewJobIfNeeded();
}
return undefined;
}
async function handleConfigMessagesViaLibSession(
configMessages: Array<IncomingMessage<SignalService.ISharedConfigMessage>>
) {
configMessages: Array<IncomingMessage<SignalService.ISharedConfigMessage>>,
returnAndKeepInMemory?: boolean
): Promise<IncomingConfResult | undefined> {
const userConfigLibsession = await ReleasedFeatures.checkIsUserConfigFeatureReleased();
if (!userConfigLibsession) {
return;
return undefined;
}
if (isEmpty(configMessages)) {
return;
return undefined;
}
window?.log?.debug(
@ -918,7 +925,20 @@ async function handleConfigMessagesViaLibSession(
);
const incomingMergeResult = await mergeConfigsWithIncomingUpdates(configMessages);
await processMergingResults(incomingMergeResult);
window.log.debug(
`WIP: [handleConfigMessagesViaLibSession] incomingMergeResult:`,
incomingMergeResult
);
if (returnAndKeepInMemory) {
// TODO[epic=899] we should return the display name and keep it in memory
return incomingMergeResult.get('UserConfig');
// const dump = await GenericWrapperActions.dump('UserConfig');
// return dump;
}
const result = await processMergingResults(incomingMergeResult);
return result;
}
async function updateOurProfileLegacyOrViaLibSession({

@ -111,7 +111,7 @@ async function retrieveNextMessages(
configHashesToBump: Array<string> | null
): Promise<RetrieveMessagesResultsBatched> {
if (namespaces.length !== lastHashes.length) {
throw new Error('namespaces and lasthashes does not match');
throw new Error('namespaces and last hashes does not match');
}
const retrieveRequestsParams = await buildRetrieveRequest(
@ -182,4 +182,75 @@ async function retrieveNextMessages(
}
}
export const SnodeAPIRetrieve = { retrieveNextMessages };
async function retrieveDisplayName(
targetNode: Snode,
ourPubkey: string
): Promise<RetrieveMessagesResultsBatched> {
const retrieveRequestsParams = await buildRetrieveRequest(
[],
ourPubkey,
[SnodeNamespaces.UserProfile],
ourPubkey,
[]
);
// let exceptions bubble up
// no retry for this one as this a call we do every few seconds through polling
const results = await doSnodeBatchRequest(retrieveRequestsParams, targetNode, 4000, ourPubkey);
if (!results || !results.length) {
window?.log?.warn(
`retrieveDisplayName - sessionRpc could not talk to ${targetNode.ip}:${targetNode.port}`
);
throw new Error(
`retrieveDisplayName - sessionRpc could not talk to ${targetNode.ip}:${targetNode.port}`
);
}
// the +1 is to take care of the extra `expire` method added once user config is released
if (results.length !== 1 && results.length !== 2) {
throw new Error(
`We asked for updates about a message but got results of length ${results.length}`
);
}
// do a basic check to know if we have something kind of looking right (status 200 should always be there for a retrieve)
const firstResult = results[0];
if (firstResult.code !== 200) {
window?.log?.warn(`retrieveDisplayName result is not 200 but ${firstResult.code}`);
throw new Error(
`retrieveDisplayName - retrieve result is not 200 with ${targetNode.ip}:${targetNode.port} but ${firstResult.code}`
);
}
try {
// we rely on the code of the first one to check for online status
const bodyFirstResult = firstResult.body;
if (!window.inboxStore?.getState().onionPaths.isOnline) {
window.inboxStore?.dispatch(updateIsOnline(true));
}
GetNetworkTime.handleTimestampOffsetFromNetwork('retrieve', bodyFirstResult.t);
// merge results with their corresponding namespaces
const resultsWithNamespaces = results.map(result => ({
code: result.code,
messages: result.body as RetrieveMessagesResultsContent,
namespace: SnodeNamespaces.UserProfile,
}));
return resultsWithNamespaces;
} catch (e) {
window?.log?.warn('retrieveDisplayName:', e);
if (!window.inboxStore?.getState().onionPaths.isOnline) {
window.inboxStore?.dispatch(updateIsOnline(true));
}
throw new Error(
`retrieveDisplayName - exception while parsing json of nextMessage ${targetNode.ip}:${targetNode.port}: ${e?.message}`
);
}
}
export const SnodeAPIRetrieve = { retrieveNextMessages, retrieveDisplayName };

@ -346,7 +346,10 @@ export class SwarmPolling {
}
}
private async handleSharedConfigMessages(userConfigMessagesMerged: Array<RetrieveMessageItem>) {
private async handleSharedConfigMessages(
userConfigMessagesMerged: Array<RetrieveMessageItem>,
returnAndKeepInMemory?: boolean
): Promise<string> {
const extractedUserConfigMessage = compact(
userConfigMessagesMerged.map((m: RetrieveMessageItem) => {
return extractWebSocketContent(m.data, m.hash);
@ -391,7 +394,36 @@ export class SwarmPolling {
window.log.info(
`handleConfigMessagesViaLibSession of "${allDecryptedConfigMessages.length}" messages with libsession`
);
await ConfigMessageHandler.handleConfigMessagesViaLibSession(allDecryptedConfigMessages);
if (returnAndKeepInMemory) {
try {
// TODO[epic=899] trying to create a dump in memory for the userconfig
const ourKeyPair = await UserUtils.getIdentityKeyPair();
if (ourKeyPair) {
await GenericWrapperActions.init(
'UserConfig',
new Uint8Array(ourKeyPair.privKey),
null
);
// save the newly created dump to the database even if it is empty, just so we do not need to recreate one next run
// const dump = await GenericWrapperActions.dump('UserConfig');
}
// await LibSessionUtil.initializeLibSessionUtilWrappers();
} catch (e) {
window.log.warn(
'[SwarmPolling] LibSessionUtil.initializeLibSessionUtilWrappers failed with',
e.message
);
}
}
const result = await ConfigMessageHandler.handleConfigMessagesViaLibSession(
allDecryptedConfigMessages,
returnAndKeepInMemory
);
window.log.debug(`WIP: [handleSharedConfigMessages] result ${JSON.stringify(result)} `);
return String(result);
} catch (e) {
const allMessageHases = allDecryptedConfigMessages.map(m => m.messageHash).join(',');
window.log.warn(
@ -399,6 +431,7 @@ export class SwarmPolling {
);
}
}
return '';
}
// Fetches messages for `pubkey` from `node` potentially updating
@ -605,4 +638,92 @@ export class SwarmPolling {
// return the cached value
return this.lastHashes[nodeEdKey][pubkey][namespace];
}
/**
* Only exposed as public for testing
*/
public async pollOnceForDisplayName(pubkey: PubKey) {
const polledPubkey = pubkey.key;
const swarmSnodes = await snodePool.getSwarmFor(polledPubkey);
// Select nodes for which we already have lastHashes
const alreadyPolled = swarmSnodes.filter((n: Snode) => this.lastHashes[n.pubkey_ed25519]);
let toPollFrom = alreadyPolled.length ? alreadyPolled[0] : null;
// If we need more nodes, select randomly from the remaining nodes:
if (!toPollFrom) {
const notPolled = difference(swarmSnodes, alreadyPolled);
toPollFrom = sample(notPolled) as Snode;
}
let resultsFromUserProfile: RetrieveMessagesResultsBatched | null;
try {
resultsFromUserProfile = await SnodeAPIRetrieve.retrieveDisplayName(
toPollFrom,
UserUtils.getOurPubKeyStrFromCache()
);
window.log.debug(
`WIP: [resultsFromUserProfile] resultsFromUserProfile: ${JSON.stringify(resultsFromUserProfile)}`
);
} catch (e) {
if (e.message === ERROR_CODE_NO_CONNECT) {
if (window.inboxStore?.getState().onionPaths.isOnline) {
window.inboxStore?.dispatch(updateIsOnline(false));
}
} else if (!window.inboxStore?.getState().onionPaths.isOnline) {
window.inboxStore?.dispatch(updateIsOnline(true));
}
window.log.warn(
`pollOnceForDisplayName of ${pubkey} namespace: SnodeNamespaces.UserProfile failed with: ${e.message}`
);
resultsFromUserProfile = null;
}
// 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 (resultsFromUserProfile?.length) {
const userConfigMessages = resultsFromUserProfile
.filter(m => SnodeNamespace.isUserConfigNamespace(m.namespace))
.map(r => r.messages.messages);
const userConfigMessagesMerged = flatten(compact(userConfigMessages));
if (userConfigMessagesMerged.length) {
window.log.info(
`[pollOnceForDisplayName] received userConfigMessages count: ${userConfigMessagesMerged.length} for key ${pubkey.key}`
);
try {
const displayName = await this.handleSharedConfigMessages(userConfigMessagesMerged, true);
window.log.debug(`WIP: [pollForOurDisplayName] displayName ${displayName}`);
return displayName;
} catch (e) {
window.log.warn(
`handleSharedConfigMessages of ${userConfigMessagesMerged.length} failed with ${e.message}`
);
// not rethrowing
}
}
}
return '';
}
// TODO[epic=ses-899] add a function that only polls for the display name?
public async pollForOurDisplayName(): Promise<string> {
if (!window.getGlobalOnlineStatus()) {
window?.log?.error('pollForOurDisplayName: offline');
// Very important to set up a new polling call so we do retry at some point
setTimeout(this.pollForOurDisplayName.bind(this), SWARM_POLLING_TIMEOUT.ACTIVE);
return '';
}
try {
const displayName = await this.pollOnceForDisplayName(UserUtils.getOurPubKeyFromCache());
return displayName;
} catch (e) {
window?.log?.warn('pollForOurDisplayName exception: ', e);
return '';
}
}
}

@ -3,7 +3,7 @@ import { SnodeNamespaces } from './namespaces';
export type RetrieveMessageItem = {
hash: string;
expiration: number;
data: string; // base64 encrypted content of the emssage
data: string; // base64 encrypted content of the message
timestamp: number;
};

@ -7,6 +7,7 @@ import { trigger } from '../shims/events';
import { SettingsKey } from '../data/settings-key';
import { ConversationTypeEnum } from '../models/conversationAttributes';
import { SessionKeyPair } from '../receiver/keypairs';
import { getSwarmPollingInstance } from '../session/apis/snode_api';
import { mnDecode, mnEncode } from '../session/crypto/mnemonic';
import { LibSessionUtil } from '../session/utils/libsession/libsession_utils';
import { actions as userActions } from '../state/ducks/user';
@ -98,8 +99,10 @@ export async function signInByLinkingDevice(
await saveRecoveryPhrase(mnemonic);
const pubKeyString = toHex(identityKeyPair.pubKey);
const displayName = await getSwarmPollingInstance().pollForOurDisplayName();
// await for the first configuration message to come in.
await registrationDone(pubKeyString, '');
await registrationDone(pubKeyString, displayName);
return pubKeyString;
}
/**
@ -197,7 +200,10 @@ async function registrationDone(ourPubkey: string, displayName: string) {
try {
await LibSessionUtil.initializeLibSessionUtilWrappers();
} catch (e) {
window.log.warn('LibSessionUtil.initializeLibSessionUtilWrappers failed with', e.message);
window.log.warn(
'[registrationDone] LibSessionUtil.initializeLibSessionUtilWrappers failed with',
e.message
);
}
// Ensure that we always have a conversation for ourself
const conversation = await getConversationController().getOrCreateAndWait(

Loading…
Cancel
Save