fix: add currentHashes and update expiry on every poll

pull/2620/head
Audric Ackermann 2 years ago
parent 44483b7d23
commit 3cd52d5b1a

@ -98,7 +98,7 @@
"glob": "7.1.2", "glob": "7.1.2",
"image-type": "^4.1.0", "image-type": "^4.1.0",
"ip2country": "1.0.1", "ip2country": "1.0.1",
"libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.1.5/libsession_util_nodejs-v0.1.5.tar.gz", "libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.1.8/libsession_util_nodejs-v0.1.8.tar.gz",
"libsodium-wrappers-sumo": "^0.7.9", "libsodium-wrappers-sumo": "^0.7.9",
"linkify-it": "3.0.2", "linkify-it": "3.0.2",
"lodash": "^4.17.20", "lodash": "^4.17.20",

@ -192,7 +192,11 @@ export class OpenGroupManagerV2 {
); );
// here, the convo does not exist. Make sure the db & wrappers are clean too // here, the convo does not exist. Make sure the db & wrappers are clean too
await OpenGroupData.removeV2OpenGroupRoom(conversationId); await OpenGroupData.removeV2OpenGroupRoom(conversationId);
await SessionUtilUserGroups.removeCommunityFromWrapper(conversationId, fullUrl); try {
await SessionUtilUserGroups.removeCommunityFromWrapper(conversationId, fullUrl);
} catch (e) {
window.log.warn('failed to removeCommunityFromWrapper', conversationId);
}
const room: OpenGroupV2Room = { const room: OpenGroupV2Room = {
serverUrl, serverUrl,

@ -50,7 +50,8 @@ export type RetrieveSubKeySubRequestType = {
export type RetrieveSubRequestType = export type RetrieveSubRequestType =
| RetrieveLegacyClosedGroupSubRequestType | RetrieveLegacyClosedGroupSubRequestType
| RetrievePubkeySubRequestType | RetrievePubkeySubRequestType
| RetrieveSubKeySubRequestType; | RetrieveSubKeySubRequestType
| UpdateExpiryOnNodeSubRequest;
/** /**
* OXEND_REQUESTS * OXEND_REQUESTS
@ -129,6 +130,21 @@ export type DeleteFromNodeSubRequest = {
params: DeleteByHashesFromNodeParams; params: DeleteByHashesFromNodeParams;
}; };
export type UpdateExpireNodeParams = {
pubkey: string;
pubkey_ed25519: string;
messages: Array<string>;
expiry: number;
signature: string;
extend?: boolean;
shorten?: boolean;
};
export type UpdateExpiryOnNodeSubRequest = {
method: 'expire';
params: UpdateExpireNodeParams;
};
export type OxendSubRequest = OnsResolveSubRequest | GetServiceNodesSubRequest; export type OxendSubRequest = OnsResolveSubRequest | GetServiceNodesSubRequest;
export type SnodeApiSubRequests = export type SnodeApiSubRequests =
@ -138,7 +154,8 @@ export type SnodeApiSubRequests =
| StoreOnNodeSubRequest | StoreOnNodeSubRequest
| NetworkTimeSubRequest | NetworkTimeSubRequest
| DeleteFromNodeSubRequest | DeleteFromNodeSubRequest
| DeleteAllFromNodeSubRequest; | DeleteAllFromNodeSubRequest
| UpdateExpiryOnNodeSubRequest;
// tslint:disable: array-type // tslint:disable: array-type
export type NonEmptyArray<T> = [T, ...T[]]; export type NonEmptyArray<T> = [T, ...T[]];

@ -0,0 +1,237 @@
import { isEmpty, sample } from 'lodash';
import { Snode } from '../../../data/data';
import { getSodiumRenderer } from '../../crypto';
import { StringUtils, UserUtils } from '../../utils';
import { fromBase64ToArray, fromHexToArray } from '../../utils/String';
import { EmptySwarmError } from '../../utils/errors';
import { UpdateExpireNodeParams } from './SnodeRequestTypes';
import { doSnodeBatchRequest } from './batchRequest';
import { GetNetworkTime } from './getNetworkTime';
import { getSwarmFor } from './snodePool';
import { SnodeSignature } from './snodeSignatures';
async function verifySignature({
pubkey,
snodePubkey,
expiryApplied,
signature,
messageHashes,
updatedHashes,
unchangedHashes,
}: {
pubkey: string;
snodePubkey: any;
expiryApplied: number;
signature: string;
messageHashes: Array<string>;
updatedHashes: Array<string>;
// only used when shorten or extend is in the request
unchangedHashes?: Record<string, string>;
}): Promise<boolean> {
if (!expiryApplied || isEmpty(messageHashes) || isEmpty(signature)) {
window.log.warn('verifySignature missing argument');
return false;
}
const edKeyPrivBytes = fromHexToArray(snodePubkey);
/* PUBKEY_HEX || EXPIRY || RMSGs... || UMSGs... || CMSG_EXPs...
where RMSGs are the requested expiry hashes,
UMSGs are the actual updated hashes, and
CMSG_EXPs are (HASH || EXPIRY) values, ascii-sorted by hash, for the unchanged message hashes included in the "unchanged" field.
*/
const hashes = [...messageHashes, ...updatedHashes];
if (unchangedHashes && Object.keys(unchangedHashes).length > 0) {
hashes.push(
...Object.entries(unchangedHashes)
.map(([key, value]: [string, string]) => {
return `${key}${value}`;
})
.sort()
);
}
const verificationString = `${pubkey}${expiryApplied}${hashes.join('')}`;
const verificationData = StringUtils.encode(verificationString, 'utf8');
window.log.debug('verifySignature verificationString', verificationString);
const sodium = await getSodiumRenderer();
try {
const isValid = sodium.crypto_sign_verify_detached(
fromBase64ToArray(signature),
new Uint8Array(verificationData),
edKeyPrivBytes
);
return isValid;
} catch (e) {
window.log.warn('verifySignature failed with: ', e.message);
return false;
}
}
async function processExpirationResults(
pubkey: string,
targetNode: Snode,
swarm: Record<string, any>,
messageHashes: Array<string>
) {
if (isEmpty(swarm)) {
throw Error(`expireOnNodes failed! ${messageHashes}`);
}
// TODO need proper typing for swarm and results
const results: Record<string, { hashes: Array<string>; expiry: number }> = {};
// window.log.debug(`processExpirationResults start`, swarm, messageHashes);
for (const nodeKey of Object.keys(swarm)) {
if (!isEmpty(swarm[nodeKey].failed)) {
const reason = 'Unknown';
const statusCode = '404';
window?.log?.warn(
`loki_message:::expireMessage - Couldn't delete data from: ${
targetNode.pubkey_ed25519
}${reason && statusCode && ` due to an error ${reason} (${statusCode})`}`
);
// TODO This might be a redundant step
results[nodeKey] = { hashes: [], expiry: 0 };
}
const updatedHashes = swarm[nodeKey].updated;
const unchangedHashes = swarm[nodeKey].unchanged;
const expiryApplied = swarm[nodeKey].expiry;
const signature = swarm[nodeKey].signature;
const isValid = await verifySignature({
pubkey,
snodePubkey: nodeKey,
expiryApplied,
signature,
messageHashes,
updatedHashes,
unchangedHashes,
});
if (!isValid) {
window.log.warn(
'loki_message:::expireMessage - Signature verification failed!',
messageHashes
);
}
results[nodeKey] = { hashes: updatedHashes, expiry: expiryApplied };
}
return results;
}
async function expireOnNodes(targetNode: Snode, params: UpdateExpireNodeParams) {
try {
const result = await doSnodeBatchRequest(
[
{
method: 'expire',
params,
},
],
targetNode,
4000,
params.pubkey,
'batch'
);
if (!result || result.length !== 1 || result[0]?.code !== 200 || !result[0]?.body) {
return false;
}
try {
// TODOLATER make sure that this code still works once disappearing messages is merged
const parsed = result[0].body;
const expirationResults = await processExpirationResults(
params.pubkey,
targetNode,
parsed.swarm,
params.messages
);
window.log.debug('expireOnNodes attempt complete. Here are the results', expirationResults);
return true;
} catch (e) {
window?.log?.warn('expireOnNodes Failed to parse "swarm" result: ', e.msg);
}
return false;
} catch (e) {
window?.log?.warn('expire - send error:', e, `destination ${targetNode.ip}:${targetNode.port}`);
throw e;
}
}
type ExpireMessageOnSnodeProps = {
messageHash: string;
expireTimer: number;
extend?: boolean;
shorten?: boolean;
};
// TODO make this retry in case of updated swarm
export async function expireMessageOnSnode(props: ExpireMessageOnSnodeProps) {
const { messageHash, expireTimer, extend, shorten } = props;
if (extend && shorten) {
window.log.error(
'[expireMessageOnSnode] We cannot extend and shorten a message at the same time',
messageHash
);
return;
}
const shortenOrExtend = shorten ? 'shorten' : extend ? 'extend' : ('' as const);
const ourPubKey = UserUtils.getOurPubKeyStrFromCache();
if (!ourPubKey) {
window.log.eror('[expireMessageOnSnode] No pubkey found', messageHash);
return;
}
const swarm = await getSwarmFor(ourPubKey);
const expiry = GetNetworkTime.getNowWithNetworkOffset() + expireTimer;
const signResult = await SnodeSignature.generateUpdateExpirySignature({
shortenOrExtend,
timestamp: expiry,
messageHashes: [messageHash],
});
if (!signResult) {
window.log.error('[expireMessageOnSnode] Signing message expiry on swarm failed', messageHash);
return;
}
const params: UpdateExpireNodeParams = {
pubkey: ourPubKey,
pubkey_ed25519: signResult.pubkey_ed25519.toUpperCase(),
// TODO better testing for failed case
messages: [messageHash],
expiry,
extend: extend || undefined,
shorten: shorten || undefined,
signature: signResult?.signature,
};
const snode = sample(swarm);
if (!snode) {
throw new EmptySwarmError(ourPubKey, 'Ran out of swarm nodes to query');
}
try {
// TODO make this whole function `expireMessageOnSnode` retry
const successfulSend = await expireOnNodes(snode, params);
} catch (e) {
const snodeStr = snode ? `${snode.ip}:${snode.port}` : 'null';
window?.log?.warn(
`loki_message:::expireMessage - ${e.code ? `${e.code} ` : ''}${
e.message
} by ${ourPubKey} for ${messageHash} via snode:${snodeStr}`
);
throw e;
}
}

@ -67,7 +67,12 @@ function isUserConfigNamespace(namespace: SnodeNamespaces) {
return false; return false;
default: default:
assertUnreachable(namespace, `isUserConfigNamespace case not handled: ${namespace}`); try {
assertUnreachable(namespace, `isUserConfigNamespace case not handled: ${namespace}`);
} catch (e) {
window.log.warn(`isUserConfigNamespace case not handled: ${namespace}: ${e.message}`);
return false;
}
} }
} }

@ -5,9 +5,12 @@ import { doSnodeBatchRequest } from './batchRequest';
import { GetNetworkTime } from './getNetworkTime'; import { GetNetworkTime } from './getNetworkTime';
import { SnodeNamespace, SnodeNamespaces } from './namespaces'; import { SnodeNamespace, SnodeNamespaces } from './namespaces';
import { DURATION } from '../../constants';
import { UserUtils } from '../../utils';
import { import {
RetrieveLegacyClosedGroupSubRequestType, RetrieveLegacyClosedGroupSubRequestType,
RetrieveSubRequestType, RetrieveSubRequestType,
UpdateExpiryOnNodeSubRequest,
} from './SnodeRequestTypes'; } from './SnodeRequestTypes';
import { SnodeSignature } from './snodeSignatures'; import { SnodeSignature } from './snodeSignatures';
import { RetrieveMessagesResultsBatched, RetrieveMessagesResultsContent } from './types'; import { RetrieveMessagesResultsBatched, RetrieveMessagesResultsContent } from './types';
@ -16,9 +19,10 @@ async function buildRetrieveRequest(
lastHashes: Array<string>, lastHashes: Array<string>,
pubkey: string, pubkey: string,
namespaces: Array<SnodeNamespaces>, namespaces: Array<SnodeNamespaces>,
ourPubkey: string ourPubkey: string,
configHashesToBump: Array<string> | null
): Promise<Array<RetrieveSubRequestType>> { ): Promise<Array<RetrieveSubRequestType>> {
const retrieveRequestsParams = await Promise.all( const retrieveRequestsParams: Array<RetrieveSubRequestType> = await Promise.all(
namespaces.map(async (namespace, index) => { namespaces.map(async (namespace, index) => {
const retrieveParam = { const retrieveParam = {
pubkey, pubkey,
@ -65,6 +69,33 @@ async function buildRetrieveRequest(
return retrieve; return retrieve;
}) })
); );
if (configHashesToBump?.length) {
const expiry = GetNetworkTime.getNowWithNetworkOffset() + DURATION.DAYS * 30;
const signResult = await SnodeSignature.generateUpdateExpirySignature({
shortenOrExtend: '',
timestamp: expiry,
messageHashes: configHashesToBump,
});
if (!signResult) {
window.log.warn(
`SnodeSignature.generateUpdateExpirySignature returned result empty for hashes ${configHashesToBump}`
);
} else {
const expireParams: UpdateExpiryOnNodeSubRequest = {
method: 'expire',
params: {
messages: configHashesToBump,
pubkey: UserUtils.getOurPubKeyStrFromCache(),
expiry,
signature: signResult.signature,
pubkey_ed25519: signResult.pubkey_ed25519,
},
};
retrieveRequestsParams.push(expireParams);
}
}
return retrieveRequestsParams; return retrieveRequestsParams;
} }
@ -73,7 +104,8 @@ async function retrieveNextMessages(
lastHashes: Array<string>, lastHashes: Array<string>,
associatedWith: string, associatedWith: string,
namespaces: Array<SnodeNamespaces>, namespaces: Array<SnodeNamespaces>,
ourPubkey: string ourPubkey: string,
configHashesToBump: Array<string> | null
): Promise<RetrieveMessagesResultsBatched> { ): Promise<RetrieveMessagesResultsBatched> {
if (namespaces.length !== lastHashes.length) { if (namespaces.length !== lastHashes.length) {
throw new Error('namespaces and lasthashes does not match'); throw new Error('namespaces and lasthashes does not match');
@ -83,7 +115,8 @@ async function retrieveNextMessages(
lastHashes, lastHashes,
associatedWith, associatedWith,
namespaces, namespaces,
ourPubkey ourPubkey,
configHashesToBump
); );
// let exceptions bubble up // let exceptions bubble up
// no retry for this one as this a call we do every few seconds while polling for messages // no retry for this one as this a call we do every few seconds while polling for messages
@ -99,7 +132,8 @@ async function retrieveNextMessages(
); );
} }
if (results.length !== namespaces.length) { // the +1 is to take care of the extra `expire` method added once user config is released
if (results.length !== namespaces.length && results.length !== namespaces.length + 1) {
throw new Error( throw new Error(
`We asked for updates about ${namespaces.length} messages but got results of length ${results.length}` `We asked for updates about ${namespaces.length} messages but got results of length ${results.length}`
); );

@ -94,4 +94,47 @@ async function getSnodeSignatureParams(params: {
} }
} }
export const SnodeSignature = { getSnodeSignatureParams, getSnodeSignatureByHashesParams }; async function generateUpdateExpirySignature({
shortenOrExtend,
timestamp,
messageHashes,
}: {
shortenOrExtend: 'extend' | 'shorten' | '';
timestamp: number;
messageHashes: Array<string>;
}): Promise<{ signature: string; pubkey_ed25519: string } | null> {
const ourEd25519Key = await UserUtils.getUserED25519KeyPair();
if (!ourEd25519Key) {
const err = `getSnodeSignatureParams "expiry": User has no getUserED25519KeyPair()`;
window.log.warn(err);
throw new Error(err);
}
const edKeyPrivBytes = fromHexToArray(ourEd25519Key?.privKey);
// "expire" || ShortenOrExtend || expiry || messages[0] || ... || messages[N]
const verificationString = `expire${shortenOrExtend}${timestamp}${messageHashes.join('')}`;
const verificationData = StringUtils.encode(verificationString, 'utf8');
const message = new Uint8Array(verificationData);
const sodium = await getSodiumRenderer();
try {
const signature = sodium.crypto_sign_detached(message, edKeyPrivBytes);
const signatureBase64 = fromUInt8ArrayToBase64(signature);
return {
signature: signatureBase64,
pubkey_ed25519: ourEd25519Key.pubKey,
};
} catch (e) {
window.log.warn('generateSignature failed with: ', e.message);
return null;
}
}
export const SnodeSignature = {
getSnodeSignatureParams,
getSnodeSignatureByHashesParams,
generateUpdateExpirySignature,
};

@ -22,6 +22,8 @@ import { SnodeNamespace, SnodeNamespaces } from './namespaces';
import { SnodeAPIRetrieve } from './retrieveRequest'; import { SnodeAPIRetrieve } from './retrieveRequest';
import { RetrieveMessageItem, RetrieveMessagesResultsBatched } from './types'; import { RetrieveMessageItem, RetrieveMessagesResultsBatched } from './types';
import { ReleasedFeatures } from '../../../util/releaseFeature'; import { ReleasedFeatures } from '../../../util/releaseFeature';
import { LibSessionUtil } from '../../utils/libsession/libsession_utils';
import { GenericWrapperActions } from '../../../webworker/workers/browser/libsession_worker_interface';
export function extractWebSocketContent( export function extractWebSocketContent(
message: string, message: string,
@ -215,7 +217,12 @@ export class SwarmPolling {
let resultsFromAllNamespaces: RetrieveMessagesResultsBatched | null; let resultsFromAllNamespaces: RetrieveMessagesResultsBatched | null;
try { try {
resultsFromAllNamespaces = await this.pollNodeForKey(toPollFrom, pubkey, namespaces); resultsFromAllNamespaces = await this.pollNodeForKey(
toPollFrom,
pubkey,
namespaces,
!isGroup
);
} catch (e) { } catch (e) {
window.log.warn( window.log.warn(
`pollNodeForKey of ${pubkey} namespaces: ${namespaces} failed with: ${e.message}` `pollNodeForKey of ${pubkey} namespaces: ${namespaces} failed with: ${e.message}`
@ -371,39 +378,69 @@ export class SwarmPolling {
private async pollNodeForKey( private async pollNodeForKey(
node: Snode, node: Snode,
pubkey: PubKey, pubkey: PubKey,
namespaces: Array<SnodeNamespaces> namespaces: Array<SnodeNamespaces>,
isUs: boolean
): Promise<RetrieveMessagesResultsBatched | null> { ): Promise<RetrieveMessagesResultsBatched | null> {
const namespaceLength = namespaces.length; const namespaceLength = namespaces.length;
if (namespaceLength <= 0) { if (namespaceLength <= 0) {
throw new Error(`invalid number of retrieve namespace provided: ${namespaceLength}`); throw new Error(`invalid number of retrieve namespace provided: ${namespaceLength}`);
} }
const edkey = node.pubkey_ed25519; const snodeEdkey = node.pubkey_ed25519;
const pkStr = pubkey.key; const pkStr = pubkey.key;
try { try {
return await pRetry( return await pRetry(
async () => { async () => {
const prevHashes = await Promise.all( const prevHashes = await Promise.all(
namespaces.map(namespace => this.getLastHash(edkey, pkStr, namespace)) namespaces.map(namespace => this.getLastHash(snodeEdkey, pkStr, namespace))
); );
const results = await SnodeAPIRetrieve.retrieveNextMessages( const configHashesToBump: Array<string> = [];
if (await ReleasedFeatures.checkIsUserConfigFeatureReleased()) {
// TODOLATER add the logic to take care of the closed groups too once we have a way to do it with the wrappers
if (isUs) {
for (let index = 0; index < LibSessionUtil.requiredUserVariants.length; index++) {
const variant = LibSessionUtil.requiredUserVariants[index];
try {
const toBump = await GenericWrapperActions.currentHashes(variant);
if (toBump?.length) {
configHashesToBump.push(...toBump);
}
} catch (e) {
window.log.warn(`failed to get currentHashes for user variant ${variant}`);
}
}
window.log.debug(`configHashesToBump: ${configHashesToBump}`);
}
}
let results = await SnodeAPIRetrieve.retrieveNextMessages(
node, node,
prevHashes, prevHashes,
pkStr, pkStr,
namespaces, namespaces,
UserUtils.getOurPubKeyStrFromCache() UserUtils.getOurPubKeyStrFromCache(),
configHashesToBump
); );
if (!results.length) { if (!results.length) {
return []; return [];
} }
// when we asked to extend the expiry of the config messages, exclude it from the list of results as we do not want to mess up the last hash tracking logic
if (results.length !== namespaceLength) { if (configHashesToBump.length) {
window.log.error( try {
`pollNodeForKey asked for ${namespaceLength} namespaces but received only messages about ${results.length} namespaces` const lastResult = results[results.length - 1];
); if (lastResult?.code !== 200) {
throw new Error( // the update expiry of our config messages didn't work.
`pollNodeForKey asked for ${namespaceLength} namespaces but received only messages about ${results.length} namespaces` window.log.warn(
); `the update expiry of our tracked config hashes didn't work: ${JSON.stringify(
lastResult
)}`
);
}
} catch (e) {}
results = results.slice(0, results.length - 1);
} }
const lastMessages = results.map(r => { const lastMessages = results.map(r => {
@ -416,7 +453,7 @@ export class SwarmPolling {
return; return;
} }
return this.updateLastHash({ return this.updateLastHash({
edkey: edkey, edkey: snodeEdkey,
pubkey, pubkey,
namespace: namespaces[index], namespace: namespaces[index],
hash: lastMessage.hash, hash: lastMessage.hash,

@ -238,13 +238,17 @@ export class ConversationController {
try { try {
const fromWrapper = await UserGroupsWrapperActions.getCommunityByFullUrl(conversation.id); const fromWrapper = await UserGroupsWrapperActions.getCommunityByFullUrl(conversation.id);
if (fromWrapper?.fullUrl) {
await SessionUtilConvoInfoVolatile.removeCommunityFromWrapper( await SessionUtilConvoInfoVolatile.removeCommunityFromWrapper(
conversation.id, conversation.id,
fromWrapper?.fullUrl || '' fromWrapper.fullUrl
); );
}
} catch (e) { } catch (e) {
window?.log?.info('SessionUtilConvoInfoVolatile.removeCommunityFromWrapper failed:', e); window?.log?.info(
'SessionUtilConvoInfoVolatile.removeCommunityFromWrapper failed:',
e.message
);
} }
// remove from the wrapper the entries before we remove the roomInfos, as we won't have the required community pubkey afterwards // remove from the wrapper the entries before we remove the roomInfos, as we won't have the required community pubkey afterwards

@ -84,9 +84,9 @@ async function checkIsFeatureReleased(featureName: FeatureNameTracked): Promise<
} }
const isReleased = Boolean(getIsFeatureReleasedCached(featureName)); const isReleased = Boolean(getIsFeatureReleasedCached(featureName));
window.log.debug( // window.log.debug(
`[releaseFeature]: "${featureName}" ${isReleased ? 'is released' : 'has not been released yet'}` // `[releaseFeature]: "${featureName}" ${isReleased ? 'is released' : 'has not been released yet'}`
); // );
return isReleased; return isReleased;
} }

@ -75,6 +75,10 @@ export const GenericWrapperActions = {
callLibSessionWorker([wrapperId, 'storageNamespace']) as Promise< callLibSessionWorker([wrapperId, 'storageNamespace']) as Promise<
ReturnType<BaseWrapperActionsCalls['storageNamespace']> ReturnType<BaseWrapperActionsCalls['storageNamespace']>
>, >,
currentHashes: async (wrapperId: ConfigWrapperObjectTypes) =>
callLibSessionWorker([wrapperId, 'currentHashes']) as Promise<
ReturnType<BaseWrapperActionsCalls['currentHashes']>
>,
}; };
export const UserConfigWrapperActions: UserConfigWrapperActionsCalls = { export const UserConfigWrapperActions: UserConfigWrapperActionsCalls = {
@ -90,6 +94,7 @@ export const UserConfigWrapperActions: UserConfigWrapperActionsCalls = {
needsPush: async () => GenericWrapperActions.needsPush('UserConfig'), needsPush: async () => GenericWrapperActions.needsPush('UserConfig'),
push: async () => GenericWrapperActions.push('UserConfig'), push: async () => GenericWrapperActions.push('UserConfig'),
storageNamespace: async () => GenericWrapperActions.storageNamespace('UserConfig'), storageNamespace: async () => GenericWrapperActions.storageNamespace('UserConfig'),
currentHashes: async () => GenericWrapperActions.currentHashes('UserConfig'),
/** UserConfig wrapper specific actions */ /** UserConfig wrapper specific actions */
getUserInfo: async () => getUserInfo: async () =>
@ -119,6 +124,7 @@ export const ContactsWrapperActions: ContactsWrapperActionsCalls = {
needsPush: async () => GenericWrapperActions.needsPush('ContactsConfig'), needsPush: async () => GenericWrapperActions.needsPush('ContactsConfig'),
push: async () => GenericWrapperActions.push('ContactsConfig'), push: async () => GenericWrapperActions.push('ContactsConfig'),
storageNamespace: async () => GenericWrapperActions.storageNamespace('ContactsConfig'), storageNamespace: async () => GenericWrapperActions.storageNamespace('ContactsConfig'),
currentHashes: async () => GenericWrapperActions.currentHashes('ContactsConfig'),
/** ContactsConfig wrapper specific actions */ /** ContactsConfig wrapper specific actions */
get: async (pubkeyHex: string) => get: async (pubkeyHex: string) =>
@ -154,6 +160,7 @@ export const UserGroupsWrapperActions: UserGroupsWrapperActionsCalls = {
needsPush: async () => GenericWrapperActions.needsPush('UserGroupsConfig'), needsPush: async () => GenericWrapperActions.needsPush('UserGroupsConfig'),
push: async () => GenericWrapperActions.push('UserGroupsConfig'), push: async () => GenericWrapperActions.push('UserGroupsConfig'),
storageNamespace: async () => GenericWrapperActions.storageNamespace('UserGroupsConfig'), storageNamespace: async () => GenericWrapperActions.storageNamespace('UserGroupsConfig'),
currentHashes: async () => GenericWrapperActions.currentHashes('UserGroupsConfig'),
/** UserGroups wrapper specific actions */ /** UserGroups wrapper specific actions */
@ -226,6 +233,7 @@ export const ConvoInfoVolatileWrapperActions: ConvoInfoVolatileWrapperActionsCal
needsPush: async () => GenericWrapperActions.needsPush('ConvoInfoVolatileConfig'), needsPush: async () => GenericWrapperActions.needsPush('ConvoInfoVolatileConfig'),
push: async () => GenericWrapperActions.push('ConvoInfoVolatileConfig'), push: async () => GenericWrapperActions.push('ConvoInfoVolatileConfig'),
storageNamespace: async () => GenericWrapperActions.storageNamespace('ConvoInfoVolatileConfig'), storageNamespace: async () => GenericWrapperActions.storageNamespace('ConvoInfoVolatileConfig'),
currentHashes: async () => GenericWrapperActions.currentHashes('ConvoInfoVolatileConfig'),
/** ConvoInfoVolatile wrapper specific actions */ /** ConvoInfoVolatile wrapper specific actions */
// 1o1 // 1o1

@ -5148,9 +5148,9 @@ levn@~0.3.0:
prelude-ls "~1.1.2" prelude-ls "~1.1.2"
type-check "~0.3.2" type-check "~0.3.2"
"libsession_util_nodejs@https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.1.5/libsession_util_nodejs-v0.1.5.tar.gz": "libsession_util_nodejs@https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.1.8/libsession_util_nodejs-v0.1.8.tar.gz":
version "0.1.5" version "0.1.8"
resolved "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.1.5/libsession_util_nodejs-v0.1.5.tar.gz#4f9dbac0c3011fc3edd29ce0625cba1044db6990" resolved "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.1.8/libsession_util_nodejs-v0.1.8.tar.gz#49a296dc1d81db5ec8104ef1e6ce5ed2cecff6e9"
dependencies: dependencies:
cmake-js "^7.2.1" cmake-js "^7.2.1"
node-addon-api "^6.1.0" node-addon-api "^6.1.0"

Loading…
Cancel
Save