chore: move getExpiries request to class

pull/2963/head
Audric Ackermann 1 year ago
parent d3ed798d0e
commit 6094e725fb

@ -1,19 +1,31 @@
import { GroupPubkeyType, PubkeyType } from 'libsession_util_nodejs';
import { GroupPubkeyType, PubkeyType, WithGroupPubkey } from 'libsession_util_nodejs';
import { from_hex } from 'libsodium-wrappers-sumo';
import { isEmpty } from 'lodash';
import { concatUInt8Array } from '../../crypto';
import { StringUtils, UserUtils } from '../../utils';
import { GetNetworkTime } from './getNetworkTime';
import {
SnodeNamespaces,
SnodeNamespacesGroup,
SnodeNamespacesGroupConfig,
UserConfigNamespaces,
} from './namespaces';
import { SignedGroupHashesParams, SignedHashesParams } from './types';
export type SwarmForSubRequest = { method: 'get_swarm'; params: { pubkey: string } };
import { SnodeGroupSignature } from './signature/groupSignature';
import { SnodeSignature } from './signature/snodeSignatures';
import {
SignedGroupHashesParams,
SignedHashesParams,
WithMessagesHashes,
WithSecretKey,
WithSignature,
WithTimestamp,
} from './types';
type WithRetrieveMethod = { method: 'retrieve' };
type WithMaxCountSize = { max_count?: number; max_size?: number };
type WithPubkeyAsString = { pubkey: string };
type WithPubkeyAsGroupPubkey = { pubkey: GroupPubkeyType };
export type WithShortenOrExtend = { shortenOrExtend: 'shorten' | 'extend' | '' };
type RetrieveAlwaysNeeded = {
namespace: number;
@ -23,12 +35,12 @@ type RetrieveAlwaysNeeded = {
export type RetrievePubkeySubRequestType = WithRetrieveMethod & {
params: {
signature: string;
pubkey_ed25519: string;
namespace: number;
} & RetrieveAlwaysNeeded &
WithMaxCountSize &
WithPubkeyAsString;
WithPubkeyAsString &
WithSignature;
};
/** Those namespaces do not require to be authenticated for storing messages.
@ -49,21 +61,21 @@ export type RetrieveLegacyClosedGroupSubRequestType = WithRetrieveMethod & {
export type RetrieveGroupAdminSubRequestType = WithRetrieveMethod & {
params: {
signature: string;
namespace: SnodeNamespacesGroup;
} & RetrieveAlwaysNeeded &
WithMaxCountSize;
WithMaxCountSize &
WithSignature;
};
export type RetrieveGroupSubAccountSubRequestType = WithRetrieveMethod & {
params: {
namespace: SnodeNamespacesGroup;
signature: string;
subaccount: string;
subaccount_sig: string;
} & RetrieveAlwaysNeeded &
WithMaxCountSize &
WithPubkeyAsGroupPubkey;
WithPubkeyAsGroupPubkey &
WithSignature;
};
export type RetrieveSubRequestType =
@ -74,36 +86,219 @@ export type RetrieveSubRequestType =
| UpdateExpiryOnNodeGroupSubRequest
| RetrieveGroupSubAccountSubRequestType;
/**
* OXEND_REQUESTS
*/
export type OnsResolveSubRequest = {
method: 'oxend_request';
params: {
endpoint: 'ons_resolve';
params: {
type: 0;
name_hash: string; // base64EncodedNameHash
abstract class SnodeAPISubRequest {
public abstract method: string;
}
export class OnsResolveSubRequest extends SnodeAPISubRequest {
public method: string = 'oxend_request';
public readonly base64EncodedNameHash: string;
constructor(base64EncodedNameHash: string) {
super();
this.base64EncodedNameHash = base64EncodedNameHash;
}
public build() {
return {
method: this.method,
params: {
endpoint: 'ons_resolve',
params: {
type: 0,
name_hash: this.base64EncodedNameHash,
},
},
};
};
};
}
}
export type GetServiceNodesSubRequest = {
method: 'oxend_request';
params: {
endpoint: 'get_service_nodes';
params: {
active_only: true;
fields: {
public_ip: true;
storage_port: true;
pubkey_x25519: true;
pubkey_ed25519: true;
};
export class GetServiceNodesSubRequest extends SnodeAPISubRequest {
public method = 'oxend_request' as const;
public build() {
return {
method: this.method,
params: {
endpoint: 'get_service_nodes' as const,
params: {
active_only: true,
fields: {
public_ip: true,
storage_port: true,
pubkey_x25519: true,
pubkey_ed25519: true,
},
},
},
};
};
};
}
}
export class SwarmForSubRequest extends SnodeAPISubRequest {
public method = 'get_swarm' as const;
public readonly pubkey;
constructor(pubkey: PubkeyType | GroupPubkeyType) {
super();
this.pubkey = pubkey;
}
public build() {
return {
method: this.method,
params: {
pubkey: this.pubkey,
params: {
active_only: true,
fields: {
public_ip: true,
storage_port: true,
pubkey_x25519: true,
pubkey_ed25519: true,
},
},
},
} as const;
}
}
export class NetworkTimeSubRequest extends SnodeAPISubRequest {
public method = 'info' as const;
public build() {
return {
method: this.method,
params: {},
} as const;
}
}
abstract class SubaccountRightsSubRequest extends SnodeAPISubRequest {
public readonly groupPk: GroupPubkeyType;
public readonly timestamp: number;
public readonly revokeTokenHex: Array<string>;
protected readonly secretKey: Uint8Array;
constructor({
groupPk,
timestamp,
revokeTokenHex,
secretKey,
}: WithGroupPubkey & WithTimestamp & WithSecretKey & { revokeTokenHex: Array<string> }) {
super();
this.groupPk = groupPk;
this.timestamp = timestamp;
this.revokeTokenHex = revokeTokenHex;
this.secretKey = secretKey;
}
public async sign() {
if (!this.secretKey) {
throw new Error('we need an admin secretkey');
}
const tokensBytes = from_hex(this.revokeTokenHex.join(''));
const prefix = new Uint8Array(StringUtils.encode(`${this.method}${this.timestamp}`, 'utf8'));
const sigResult = await SnodeGroupSignature.signDataWithAdminSecret(
concatUInt8Array(prefix, tokensBytes),
{ secretKey: this.secretKey }
);
return sigResult.signature;
}
}
export class SubaccountRevokeSubRequest extends SubaccountRightsSubRequest {
public method = 'revoke_subaccount' as const;
public async buildAndSignParameters() {
const signature = await this.sign();
return {
method: this.method,
params: {
pubkey: this.groupPk,
signature,
revoke: this.revokeTokenHex,
timestamp: this.timestamp,
},
};
}
}
export class SubaccountUnrevokeSubRequest extends SubaccountRightsSubRequest {
public method = 'unrevoke_subaccount' as const;
/**
* For Revoke/unrevoke, this needs an admin signature
*/
public async buildAndSignParameters() {
const signature = await this.sign();
return {
method: this.method,
params: {
pubkey: this.groupPk,
signature,
unrevoke: this.revokeTokenHex,
timestamp: this.timestamp,
},
};
}
}
/**
* The getExpiriies request can currently only be used for our own pubkey as we use it to fetch
* the expiries updated by another of our devices.
*/
export class GetExpiriesFromNodeSubRequest extends SnodeAPISubRequest {
public method = 'get_expiries' as const;
pubkey: string;
messageHashes: Array<string>;
constructor(args: WithMessagesHashes) {
super();
const ourPubKey = UserUtils.getOurPubKeyStrFromCache();
if (!ourPubKey) {
throw new Error('[GetExpiriesFromNodeSubRequest] No pubkey found');
}
this.pubkey = ourPubKey;
this.messageHashes = args.messagesHashes;
}
/**
* For Revoke/unrevoke, this needs an admin signature
*/
public async buildAndSignParameters() {
const timestamp = GetNetworkTime.now();
const signResult = await SnodeSignature.generateGetExpiriesOurSignature({
timestamp,
messageHashes: this.messageHashes,
});
if (!signResult) {
throw new Error(
`[GetExpiriesFromNodeSubRequest] SnodeSignature.generateUpdateExpirySignature returned an empty result ${this.messageHashes}`
);
}
return {
method: this.method,
params: {
pubkey: this.pubkey,
pubkey_ed25519: signResult.pubkey_ed25519.toUpperCase(),
signature: signResult.signature,
messages: this.messageHashes,
timestamp,
},
};
}
}
/**
* STORE SUBREQUESTS
*/
type StoreOnNodeNormalParams = {
pubkey: string;
ttl: number;
@ -118,13 +313,14 @@ type StoreOnNodeNormalParams = {
type StoreOnNodeSubAccountParams = Pick<
StoreOnNodeNormalParams,
'data' | 'namespace' | 'ttl' | 'timestamp'
> & {
pubkey: GroupPubkeyType;
subaccount: string;
subaccount_sig: string;
namespace: SnodeNamespaces.ClosedGroupMessages; // this can only be this one, subaccounts holder can not post to something else atm
signature: string; // signature is mandatory for subaccount
};
> &
WithSignature & {
pubkey: GroupPubkeyType;
subaccount: string;
subaccount_sig: string;
namespace: SnodeNamespaces.ClosedGroupMessages; // this can only be this one, subaccounts holder can not post to something else atm
// signature is mandatory for subaccount
};
export type StoreOnNodeParams = StoreOnNodeNormalParams | StoreOnNodeSubAccountParams;
@ -133,16 +329,6 @@ export type StoreOnNodeParamsNoSig = Pick<
'pubkey' | 'ttl' | 'timestamp' | 'ttl' | 'namespace'
> & { data64: string };
export type DeleteFromNodeWithTimestampParams = {
timestamp: string | number;
namespace: number | null | 'all';
} & (DeleteSigUserParameters | DeleteSigGroupParameters);
export type DeleteByHashesFromNodeParams = { messages: Array<string> } & (
| DeleteSigUserParameters
| DeleteSigGroupParameters
);
type StoreOnNodeShared = {
networkTimestamp: number;
data: Uint8Array;
@ -173,17 +359,28 @@ export type StoreOnNodeSubRequest = {
method: 'store';
params: StoreOnNodeParams | StoreOnNodeSubAccountParams;
};
export type NetworkTimeSubRequest = { method: 'info'; params: object };
type DeleteSigUserParameters = {
/**
* DELETE SUBREQUESTS
*/
type DeleteFromNodeWithTimestampParams = {
timestamp: string | number;
namespace: number | null | 'all';
} & (DeleteSigUserParameters | DeleteSigGroupParameters);
export type DeleteByHashesFromNodeParams = { messages: Array<string> } & (
| DeleteSigUserParameters
| DeleteSigGroupParameters
);
type DeleteSigUserParameters = WithSignature & {
pubkey: PubkeyType;
pubkey_ed25519: string;
signature: string;
};
type DeleteSigGroupParameters = {
type DeleteSigGroupParameters = WithSignature & {
pubkey: GroupPubkeyType;
signature: string;
};
export type DeleteAllFromNodeSubRequest = {
@ -196,10 +393,9 @@ export type DeleteFromNodeSubRequest = {
params: DeleteByHashesFromNodeParams;
};
type UpdateExpireAlwaysNeeded = {
type UpdateExpireAlwaysNeeded = WithSignature & {
messages: Array<string>;
expiry: number;
signature: string;
extend?: boolean;
shorten?: boolean;
};
@ -225,66 +421,22 @@ type UpdateExpiryOnNodeSubRequest =
| UpdateExpiryOnNodeUserSubRequest
| UpdateExpiryOnNodeGroupSubRequest;
type SignedRevokeSubaccountShared = {
pubkey: GroupPubkeyType;
signature: string;
timestamp: number;
};
export type SignedRevokeSubaccountParams = SignedRevokeSubaccountShared & {
revoke: Array<string>; // the subaccounts token to revoke in hex
};
export type SignedUnrevokeSubaccountParams = SignedRevokeSubaccountShared & {
unrevoke: Array<string>; // the subaccounts token to unrevoke in hex
};
export type RevokeSubaccountParams = Omit<SignedRevokeSubaccountParams, 'timestamp' | 'signature'>;
export type UnrevokeSubaccountParams = Omit<
SignedUnrevokeSubaccountParams,
'timestamp' | 'signature'
>;
export type RevokeSubaccountSubRequest = {
method: 'revoke_subaccount';
params: SignedRevokeSubaccountParams;
};
export type UnrevokeSubaccountSubRequest = {
method: 'unrevoke_subaccount';
params: SignedUnrevokeSubaccountParams;
};
export type GetExpiriesNodeParams = {
pubkey: string;
pubkey_ed25519: string;
messages: Array<string>;
timestamp: number;
signature: string;
};
export type GetExpiriesFromNodeSubRequest = {
method: 'get_expiries';
params: GetExpiriesNodeParams;
};
// Until the next storage server release is released, we need to have at least 2 hashes in the list for the `get_expiries` AND for the `update_expiries`
export const fakeHash = '///////////////////////////////////////////';
export type OxendSubRequest = OnsResolveSubRequest | GetServiceNodesSubRequest;
export type SnodeApiSubRequests =
| RetrieveSubRequestType
| SwarmForSubRequest
| OxendSubRequest
| ReturnType<SwarmForSubRequest['build']>
| ReturnType<OnsResolveSubRequest['build']>
| ReturnType<GetServiceNodesSubRequest['build']>
| StoreOnNodeSubRequest
| NetworkTimeSubRequest
| ReturnType<NetworkTimeSubRequest['build']>
| DeleteFromNodeSubRequest
| DeleteAllFromNodeSubRequest
| UpdateExpiryOnNodeSubRequest
| RevokeSubaccountSubRequest
| UnrevokeSubaccountSubRequest
| GetExpiriesFromNodeSubRequest;
| Awaited<ReturnType<SubaccountRevokeSubRequest['buildAndSignParameters']>>
| Awaited<ReturnType<SubaccountUnrevokeSubRequest['buildAndSignParameters']>>
| Awaited<ReturnType<GetExpiriesFromNodeSubRequest['buildAndSignParameters']>>;
// eslint-disable-next-line @typescript-eslint/array-type
export type NonEmptyArray<T> = [T, ...T[]];
@ -296,28 +448,14 @@ export type BatchResultEntry = {
export type NotEmptyArrayOfBatchResults = NonEmptyArray<BatchResultEntry>;
export type WithShortenOrExtend = { shortenOrExtend: 'shorten' | 'extend' | '' };
export const MAX_SUBREQUESTS_COUNT = 20;
export type BatchStoreWithExtraParams =
| StoreOnNodeParams
| SignedGroupHashesParams
| SignedHashesParams
| RevokeSubaccountSubRequest
| UnrevokeSubaccountSubRequest;
export function isUnrevokeRequest(
request: BatchStoreWithExtraParams
): request is UnrevokeSubaccountSubRequest {
return !isEmpty((request as UnrevokeSubaccountSubRequest)?.params?.unrevoke);
}
export function isRevokeRequest(
request: BatchStoreWithExtraParams
): request is RevokeSubaccountSubRequest {
return !isEmpty((request as RevokeSubaccountSubRequest)?.params?.revoke);
}
| SubaccountRevokeSubRequest
| SubaccountUnrevokeSubRequest;
export function isDeleteByHashesParams(
request: BatchStoreWithExtraParams

@ -1,6 +1,5 @@
import { isArray } from 'lodash';
import { Snode } from '../../../data/data';
import { SnodeNamespace } from './namespaces';
import { processOnionRequestErrorAtDestination, SnodeResponse } from './onions';
import { snodeRpc } from './sessionRpc';
import {
@ -9,12 +8,13 @@ import {
SnodeApiSubRequests,
} from './SnodeRequestTypes';
function logSubRequests(requests: Array<SnodeApiSubRequests>) {
return requests.map(m =>
m.method === 'retrieve' || m.method === 'store'
? `${m.method}-${SnodeNamespace.toRoles(m.params.namespace)}`
: m.method
);
function logSubRequests(_requests: Array<SnodeApiSubRequests>) {
return 'logSubRequests to do';
// return requests.map(m =>
// m.method === 'retrieve' || m.method === 'store'
// ? `${m.method}-${SnodeNamespace.toRoles(m.params.namespace)}`
// : m.method
// );
}
/**
@ -83,6 +83,7 @@ function decodeBatchRequest(snodeResponse: SnodeResponse): NotEmptyArrayOfBatchR
try {
// console.error('decodeBatch: ', snodeResponse);
if (snodeResponse.status !== 200) {
debugger;
throw new Error(`decodeBatchRequest invalid status code: ${snodeResponse.status}`);
}
const parsed = JSON.parse(snodeResponse.body);

@ -1,4 +1,5 @@
/* eslint-disable no-restricted-syntax */
import { PubkeyType } from 'libsession_util_nodejs';
import { isFinite, isNil, isNumber, sample } from 'lodash';
import pRetry from 'p-retry';
import { Snode } from '../../../data/data';
@ -7,10 +8,8 @@ import { EmptySwarmError } from '../../utils/errors';
import { SeedNodeAPI } from '../seed_node_api';
import { GetExpiriesFromNodeSubRequest, fakeHash } from './SnodeRequestTypes';
import { doSnodeBatchRequest } from './batchRequest';
import { GetNetworkTime } from './getNetworkTime';
import { SnodeSignature } from './signature/snodeSignatures';
import { getSwarmFor } from './snodePool';
import { GetExpiriesResultsContent } from './types';
import { GetExpiriesResultsContent, WithMessagesHashes } from './types';
export type GetExpiriesRequestResponseResults = Record<string, number>;
@ -43,16 +42,13 @@ export async function processGetExpiriesRequestResponse(
async function getExpiriesFromNodes(
targetNode: Snode,
expireRequest: GetExpiriesFromNodeSubRequest
messageHashes: Array<string>,
associatedWith: PubkeyType
) {
try {
const result = await doSnodeBatchRequest(
[expireRequest],
targetNode,
4000,
expireRequest.params.pubkey,
'batch'
);
const expireRequest = new GetExpiriesFromNodeSubRequest({ messagesHashes: messageHashes });
const signed = await expireRequest.buildAndSignParameters();
const result = await doSnodeBatchRequest([signed], targetNode, 4000, associatedWith, 'batch');
if (!result || result.length !== 1) {
throw Error(
@ -74,14 +70,14 @@ async function getExpiriesFromNodes(
const expirationResults = await processGetExpiriesRequestResponse(
targetNode,
firstResult.body.expiries as GetExpiriesResultsContent,
expireRequest.params.messages
expireRequest.messageHashes
);
// Note: even if expirationResults is empty we need to process the results.
// The status code is 200, so if the results is empty, it means all those messages already expired.
// Note: a hash which already expired on the server is not going to be returned. So we force it's fetchedExpiry to be now() to make it expire asap
const expiriesWithForcedExpiried = expireRequest.params.messages.map(messageHash => ({
const expiriesWithForcedExpiried = expireRequest.messageHashes.map(messageHash => ({
messageHash,
fetchedExpiry: expirationResults?.[messageHash] || Date.now(),
}));
@ -97,47 +93,6 @@ async function getExpiriesFromNodes(
}
}
export type GetExpiriesFromSnodeProps = {
messageHashes: Array<string>;
};
export async function buildGetExpiriesRequest({
messageHashes,
}: GetExpiriesFromSnodeProps): Promise<GetExpiriesFromNodeSubRequest | null> {
const timestamp = GetNetworkTime.now();
const ourPubKey = UserUtils.getOurPubKeyStrFromCache();
if (!ourPubKey) {
window.log.error('[buildGetExpiriesRequest] No pubkey found', messageHashes);
return null;
}
const signResult = await SnodeSignature.generateGetExpiriesOurSignature({
timestamp,
messageHashes,
});
if (!signResult) {
window.log.error(
`[buildGetExpiriesRequest] SnodeSignature.generateUpdateExpirySignature returned an empty result ${messageHashes}`
);
return null;
}
const getExpiriesParams: GetExpiriesFromNodeSubRequest = {
method: 'get_expiries',
params: {
pubkey: ourPubKey,
pubkey_ed25519: signResult.pubkey_ed25519.toUpperCase(),
messages: messageHashes,
timestamp,
signature: signResult?.signature,
},
};
return getExpiriesParams;
}
/**
* Sends an 'get_expiries' request which retrieves the current expiry timestamps of the given messages.
*
@ -146,26 +101,21 @@ export async function buildGetExpiriesRequest({
* @param timestamp the time (ms) the request was initiated, must be within ±60s of the current time so using the server time is recommended.
* @returns an arrray of the expiry timestamps (TTL) for the given messages
*/
export async function getExpiriesFromSnode({ messageHashes }: GetExpiriesFromSnodeProps) {
export async function getExpiriesFromSnode({ messagesHashes }: WithMessagesHashes) {
// FIXME There is a bug in the snode code that requires at least 2 messages to be requested. Will be fixed in next storage server release
if (messageHashes.length === 1) {
messageHashes.push(fakeHash);
if (messagesHashes.length === 1) {
messagesHashes.push(fakeHash);
}
const ourPubKey = UserUtils.getOurPubKeyStrFromCache();
if (!ourPubKey) {
window.log.error('[getExpiriesFromSnode] No pubkey found', messageHashes);
window.log.error('[getExpiriesFromSnode] No pubkey found', messagesHashes);
return [];
}
let snode: Snode | undefined;
try {
const expireRequestParams = await buildGetExpiriesRequest({ messageHashes });
if (!expireRequestParams) {
throw new Error(`Failed to build get_expiries request ${JSON.stringify({ messageHashes })}`);
}
const fetchedExpiries = await pRetry(
async () => {
const swarm = await getSwarmFor(ourPubKey);
@ -173,7 +123,7 @@ export async function getExpiriesFromSnode({ messageHashes }: GetExpiriesFromSno
if (!snode) {
throw new EmptySwarmError(ourPubKey, 'Ran out of swarm nodes to query');
}
return getExpiriesFromNodes(snode, expireRequestParams);
return getExpiriesFromNodes(snode, messagesHashes, ourPubKey);
},
{
retries: 3,
@ -193,7 +143,7 @@ export async function getExpiriesFromSnode({ messageHashes }: GetExpiriesFromSno
window?.log?.warn(
`[getExpiriesFromSnode] ${e.code ? `${e.code} ` : ''}${
e.message || e
} by ${ourPubKey} for ${messageHashes} via snode:${snodeStr}`
} by ${ourPubKey} for ${messagesHashes} via snode:${snodeStr}`
);
throw e;
}

@ -9,15 +9,10 @@ import { Snode } from '../../../data/data';
import { NetworkTimeSubRequest } from './SnodeRequestTypes';
import { doSnodeBatchRequest } from './batchRequest';
function getNetworkTimeSubRequests(): Array<NetworkTimeSubRequest> {
const request: NetworkTimeSubRequest = { method: 'info', params: {} };
return [request];
}
const getNetworkTime = async (snode: Snode): Promise<string | number> => {
const subRequests = getNetworkTimeSubRequests();
const result = await doSnodeBatchRequest(subRequests, snode, 4000, null);
const subrequest = new NetworkTimeSubRequest();
const result = await doSnodeBatchRequest([subrequest.build()], snode, 4000, null);
if (!result || !result.length) {
window?.log?.warn(`getNetworkTime on ${snode.ip}:${snode.port} returned falsish value`, result);
throw new Error('getNetworkTime: Invalid result');

@ -1,29 +1,10 @@
import _, { intersectionWith, sampleSize } from 'lodash';
import { compact, intersectionWith, sampleSize } from 'lodash';
import { SnodePool } from '.';
import { Snode } from '../../../data/data';
import { GetServiceNodesSubRequest } from './SnodeRequestTypes';
import { doSnodeBatchRequest } from './batchRequest';
import { GetNetworkTime } from './getNetworkTime';
import { minSnodePoolCount, requiredSnodesForAgreement } from './snodePool';
import { GetServiceNodesSubRequest } from './SnodeRequestTypes';
function buildSnodeListRequests(): Array<GetServiceNodesSubRequest> {
const request: GetServiceNodesSubRequest = {
method: 'oxend_request',
params: {
endpoint: 'get_service_nodes',
params: {
active_only: true,
fields: {
public_ip: true,
storage_port: true,
pubkey_x25519: true,
pubkey_ed25519: true,
},
},
},
};
return [request];
}
/**
* Returns a list of unique snodes got from the specified targetNode.
@ -31,8 +12,9 @@ function buildSnodeListRequests(): Array<GetServiceNodesSubRequest> {
* This is exported for testing purpose only.
*/
async function getSnodePoolFromSnode(targetNode: Snode): Promise<Array<Snode>> {
const requests = buildSnodeListRequests();
const results = await doSnodeBatchRequest(requests, targetNode, 4000, null);
const subrequest = new GetServiceNodesSubRequest();
const results = await doSnodeBatchRequest([subrequest.build()], targetNode, 4000, null);
const firstResult = results[0];
@ -60,7 +42,7 @@ async function getSnodePoolFromSnode(targetNode: Snode): Promise<Array<Snode>> {
GetNetworkTime.handleTimestampOffsetFromNetwork('get_service_nodes', json.t);
// we the return list by the snode is already made of uniq snodes
return _.compact(snodes);
return compact(snodes);
} catch (e) {
window?.log?.error('Invalid json response');
return [];

@ -1,14 +1,11 @@
import { isArray } from 'lodash';
import pRetry from 'p-retry';
import { Snode } from '../../../data/data';
import { PubKey } from '../../types';
import { SwarmForSubRequest } from './SnodeRequestTypes';
import { doSnodeBatchRequest } from './batchRequest';
import { GetNetworkTime } from './getNetworkTime';
import { getRandomSnode } from './snodePool';
import { SwarmForSubRequest } from './SnodeRequestTypes';
function buildSwarmForSubRequests(pubkey: string): Array<SwarmForSubRequest> {
return [{ method: 'get_swarm', params: { pubkey } }];
}
/**
* get snodes for pubkey from random snode. Uses an existing snode
@ -17,9 +14,12 @@ async function requestSnodesForPubkeyWithTargetNodeRetryable(
pubkey: string,
targetNode: Snode
): Promise<Array<Snode>> {
const subRequests = buildSwarmForSubRequests(pubkey);
if (!PubKey.is03Pubkey(pubkey) && !PubKey.is05Pubkey(pubkey)) {
throw new Error('invalid pubkey given for swarmFor');
}
const subrequest = new SwarmForSubRequest(pubkey);
const result = await doSnodeBatchRequest(subRequests, targetNode, 4000, pubkey);
const result = await doSnodeBatchRequest([subrequest.build()], targetNode, 4000, pubkey);
if (!result || !result.length) {
window?.log?.warn(

@ -15,17 +15,6 @@ import { getRandomSnode } from './snodePool';
// do not define a regex but rather create it on the fly to avoid https://stackoverflow.com/questions/3891641/regex-test-only-works-every-other-time
const onsNameRegex = '^\\w([\\w-]*[\\w])?$';
function buildOnsResolveRequests(base64EncodedNameHash: string): Array<OnsResolveSubRequest> {
const request: OnsResolveSubRequest = {
method: 'oxend_request',
params: {
endpoint: 'ons_resolve',
params: { type: 0, name_hash: base64EncodedNameHash },
},
};
return [request];
}
async function getSessionIDForOnsName(onsNameCase: string) {
const validationCount = 3;
@ -34,14 +23,13 @@ async function getSessionIDForOnsName(onsNameCase: string) {
const nameAsData = stringToUint8Array(onsNameLowerCase);
const nameHash = sodium.crypto_generichash(sodium.crypto_generichash_BYTES, nameAsData);
const base64EncodedNameHash = fromUInt8ArrayToBase64(nameHash);
const onsResolveRequests = buildOnsResolveRequests(base64EncodedNameHash);
const subRequest = new OnsResolveSubRequest(base64EncodedNameHash);
// we do this request with validationCount snodes
const promises = range(0, validationCount).map(async () => {
const targetNode = await getRandomSnode();
const results = await doSnodeBatchRequest(onsResolveRequests, targetNode, 4000, null);
const results = await doSnodeBatchRequest([subRequest.build()], targetNode, 4000, null);
const firstResult = results[0];
if (!firstResult || firstResult.code !== 200 || !firstResult.body) {
throw new Error('ONSresolve:Failed to resolve ONS');

@ -1,7 +1,8 @@
import { GroupPubkeyType } from 'libsession_util_nodejs';
import { PubKey } from '../../types';
import { RevokeSubaccountParams, UnrevokeSubaccountParams } from './SnodeRequestTypes';
import { SubaccountRevokeSubRequest, SubaccountUnrevokeSubRequest } from './SnodeRequestTypes';
import { GetNetworkTime } from './getNetworkTime';
export type RevokeChanges = Array<{
action: 'revoke_subaccount' | 'unrevoke_subaccount';
@ -10,6 +11,7 @@ export type RevokeChanges = Array<{
async function getRevokeSubaccountParams(
groupPk: GroupPubkeyType,
secretKey: Uint8Array,
{
revokeChanges,
unrevokeChanges,
@ -19,23 +21,26 @@ async function getRevokeSubaccountParams(
throw new Error('revokeSubaccountForGroup: not a 03 group');
}
const revokeParams: RevokeSubaccountParams | null = revokeChanges.length
? {
pubkey: groupPk,
revoke: revokeChanges.map(m => m.tokenToRevokeHex),
}
const revokeSubRequest = revokeChanges
? new SubaccountRevokeSubRequest({
groupPk,
revokeTokenHex: revokeChanges.map(m => m.tokenToRevokeHex),
timestamp: GetNetworkTime.now(),
secretKey,
})
: null;
const unrevokeParams: UnrevokeSubaccountParams | null = unrevokeChanges.length
? {
pubkey: groupPk,
unrevoke: unrevokeChanges.map(m => m.tokenToRevokeHex),
}
const unrevokeSubRequest = unrevokeChanges.length
? new SubaccountUnrevokeSubRequest({
groupPk,
revokeTokenHex: unrevokeChanges.map(m => m.tokenToRevokeHex),
timestamp: GetNetworkTime.now(),
secretKey,
})
: null;
return {
revokeParams,
unrevokeParams,
revokeSubRequest,
unrevokeSubRequest,
};
}

@ -16,14 +16,15 @@ import {
SignedHashesParams,
WithMessagesHashes,
WithShortenOrExtend,
WithSignature,
WithTimestamp,
} from '../types';
export type SnodeSignatureResult = WithTimestamp & {
signature: string;
pubkey_ed25519: string;
pubkey: string; // this is the x25519 key of the pubkey we are doing the request to (ourself for our swarm usually)
};
export type SnodeSignatureResult = WithSignature &
WithTimestamp & {
pubkey_ed25519: string;
pubkey: string; // this is the x25519 key of the pubkey we are doing the request to (ourself for our swarm usually)
};
async function getSnodeSignatureByHashesParams({
messagesHashes,
@ -166,7 +167,7 @@ async function generateUpdateExpirySignature({
WithTimestamp & {
ed25519Privkey: Uint8Array; // len 64
ed25519Pubkey: string;
}): Promise<{ signature: string; pubkey: string }> {
}): Promise<WithSignature & { pubkey: string }> {
// "expire" || ShortenOrExtend || expiry || messages[0] || ... || messages[N]
const verificationString = `expire${shortenOrExtend}${timestamp}${messagesHashes.join('')}`;
const verificationData = StringUtils.encode(verificationString, 'utf8');
@ -217,7 +218,7 @@ async function generateGetExpiriesOurSignature({
}: {
timestamp: number;
messageHashes: Array<string>;
}): Promise<{ signature: string; pubkey_ed25519: string } | null> {
}): Promise<(WithSignature & { pubkey_ed25519: string }) | null> {
const ourEd25519Key = await UserUtils.getUserED25519KeyPair();
if (!ourEd25519Key) {
const err =

@ -3,35 +3,41 @@ import {
BatchStoreWithExtraParams,
NotEmptyArrayOfBatchResults,
SnodeApiSubRequests,
StoreOnNodeSubRequest,
SubaccountRevokeSubRequest,
SubaccountUnrevokeSubRequest,
isDeleteByHashesParams,
isRevokeRequest,
isUnrevokeRequest,
} from './SnodeRequestTypes';
import { doSnodeBatchRequest } from './batchRequest';
import { GetNetworkTime } from './getNetworkTime';
function buildStoreRequests(params: Array<BatchStoreWithExtraParams>): Array<SnodeApiSubRequests> {
return params.map(p => {
if (isDeleteByHashesParams(p)) {
return {
method: 'delete' as const,
async function buildStoreRequests(
params: Array<BatchStoreWithExtraParams>
): Promise<Array<SnodeApiSubRequests>> {
const storeRequests = await Promise.all(
params.map(p => {
if (isDeleteByHashesParams(p)) {
return {
method: 'delete' as const,
params: p,
};
}
// those requests are already fully contained.
if (p instanceof SubaccountRevokeSubRequest || p instanceof SubaccountUnrevokeSubRequest) {
return p.buildAndSignParameters();
}
const storeRequest: StoreOnNodeSubRequest = {
method: 'store',
params: p,
};
}
if (isRevokeRequest(p)) {
return p;
}
if (isUnrevokeRequest(p)) {
return p;
}
return storeRequest;
})
);
return {
method: 'store',
params: p,
};
});
return storeRequests;
}
/**
@ -44,10 +50,10 @@ async function batchStoreOnNode(
method: 'batch' | 'sequence'
): Promise<NotEmptyArrayOfBatchResults> {
try {
const subRequests = buildStoreRequests(params);
const subRequests = await buildStoreRequests(params);
const asssociatedWith = (params[0] as any)?.pubkey as string | undefined;
if (!asssociatedWith) {
// not ideal,
// not ideal
throw new Error('batchStoreOnNode first subrequest pubkey needs to be set');
}
const result = await doSnodeBatchRequest(

@ -1,12 +1,8 @@
import { GroupPubkeyType, PubkeyType } from 'libsession_util_nodejs';
import { PubKey } from '../../types';
import {
RevokeSubaccountParams,
RevokeSubaccountSubRequest,
UnrevokeSubaccountParams,
UnrevokeSubaccountSubRequest,
} from './SnodeRequestTypes';
import { SnodeNamespaces } from './namespaces';
import { SubaccountRevokeSubRequest, SubaccountUnrevokeSubRequest } from './SnodeRequestTypes';
export type RetrieveMessageItem = {
hash: string;
@ -46,15 +42,14 @@ export type DeleteMessageByHashesUserSubRequest = WithMessagesHashes & {
export type RetrieveMessagesResultsBatched = Array<RetrieveRequestResult>;
export type WithTimestamp = { timestamp: number };
export type WithSignature = { signature: string };
export type WithSecretKey = { secretKey: Uint8Array };
export type ShortenOrExtend = 'extend' | 'shorten' | '';
export type WithShortenOrExtend = { shortenOrExtend: ShortenOrExtend };
export type WithSignedRevokeRequests = {
signedRevokeRequests: Array<RevokeSubaccountSubRequest | UnrevokeSubaccountSubRequest> | null;
};
export type WithRevokeParams = {
revokeParams: RevokeSubaccountParams | null;
unrevokeParams: UnrevokeSubaccountParams | null;
export type WithRevokeSubRequest = {
revokeSubRequest: SubaccountRevokeSubRequest | null;
unrevokeSubRequest: SubaccountUnrevokeSubRequest | null;
};
export type WithMessagesToDeleteSubRequest = {
messagesToDelete:
@ -63,15 +58,13 @@ export type WithMessagesToDeleteSubRequest = {
| null;
};
export type SignedHashesParams = {
signature: string;
export type SignedHashesParams = WithSignature & {
pubkey: PubkeyType;
pubkey_ed25519: PubkeyType;
messages: Array<string>;
};
export type SignedGroupHashesParams = {
signature: string;
export type SignedGroupHashesParams = WithSignature & {
pubkey: GroupPubkeyType;
messages: Array<string>;
};
@ -83,7 +76,7 @@ export function isDeleteByHashesGroup(
}
/** inherits from https://api.oxen.io/storage-rpc/#/recursive?id=recursive but we only care about these values */
export type ExpireMessageResultItem = {
export type ExpireMessageResultItem = WithSignature & {
/** the expiry timestamp that was applied (which might be different from the request expiry */
expiry: number;
/** ( PUBKEY_HEX || EXPIRY || RMSGs... || UMSGs... || CMSG_EXPs... )
@ -92,7 +85,6 @@ export type ExpireMessageResultItem = {
CMSG_EXPs are (HASH || EXPIRY) values, ascii-sorted by hash, for the unchanged message hashes included in the "unchanged" field.
The signature uses the node's ed25519 pubkey.
*/
signature: string;
/** Record of <found hashes, current expiries>, but did not get updated due to "shorten"/"extend" in the request. This field is only included when "shorten /extend" is explicitly given. */
unchanged?: Record<string, number>;
/** ascii-sorted list of hashes that had their expiries changed (messages that were not found, and messages excluded by the shorten/extend options, are not included) */

@ -3,7 +3,6 @@
import { AbortController } from 'abort-controller';
import ByteBuffer from 'bytebuffer';
import { GroupPubkeyType, PubkeyType } from 'libsession_util_nodejs';
import { from_hex } from 'libsodium-wrappers-sumo';
import { compact, isEmpty, isNumber, isString, sample } from 'lodash';
import pRetry from 'p-retry';
import { Data } from '../../data/data';
@ -17,13 +16,9 @@ import {
} from '../apis/open_group_api/sogsv3/sogsV3SendMessage';
import {
NotEmptyArrayOfBatchResults,
RevokeSubaccountParams,
RevokeSubaccountSubRequest,
StoreOnNodeData,
StoreOnNodeParams,
StoreOnNodeParamsNoSig,
UnrevokeSubaccountParams,
UnrevokeSubaccountSubRequest,
} from '../apis/snode_api/SnodeRequestTypes';
import { GetNetworkTime } from '../apis/snode_api/getNetworkTime';
import { SnodeNamespace, SnodeNamespaces } from '../apis/snode_api/namespaces';
@ -35,10 +30,10 @@ import {
import { SnodeSignature, SnodeSignatureResult } from '../apis/snode_api/signature/snodeSignatures';
import { getSwarmFor } from '../apis/snode_api/snodePool';
import { SnodeAPIStore } from '../apis/snode_api/storeMessage';
import { WithMessagesHashes, WithRevokeParams } from '../apis/snode_api/types';
import { WithMessagesHashes, WithRevokeSubRequest } from '../apis/snode_api/types';
import { TTL_DEFAULT } from '../constants';
import { ConvoHub } from '../conversations';
import { MessageEncrypter, concatUInt8Array } from '../crypto';
import { MessageEncrypter } from '../crypto';
import { addMessagePadding } from '../crypto/BufferPadding';
import { ContentMessage } from '../messages/outgoing';
import { UnsendMessage } from '../messages/outgoing/controlMessage/UnsendMessage';
@ -47,7 +42,7 @@ import { OpenGroupVisibleMessage } from '../messages/outgoing/visibleMessage/Ope
import { ed25519Str } from '../onions/onionPath';
import { PubKey } from '../types';
import { OutgoingRawMessage } from '../types/RawMessage';
import { StringUtils, UserUtils } from '../utils';
import { UserUtils } from '../utils';
import { fromUInt8ArrayToBase64 } from '../utils/String';
import { EmptySwarmError } from '../utils/errors';
@ -145,7 +140,7 @@ async function send({
},
],
destination,
{ messagesHashes: [], revokeParams: null, unrevokeParams: null },
{ messagesHashes: [], revokeSubRequest: null, unrevokeSubRequest: null },
'batch'
);
@ -265,78 +260,14 @@ async function signDeleteHashesRequest(
return signedRequest || null;
}
async function signedRevokeRequest({
destination,
revokeParams,
unrevokeParams,
}: WithRevokeParams & { destination: PubkeyType | GroupPubkeyType }) {
let revokeSignedRequest: RevokeSubaccountSubRequest | null = null;
let unrevokeSignedRequest: UnrevokeSubaccountSubRequest | null = null;
if (!PubKey.is03Pubkey(destination) || (isEmpty(revokeParams) && isEmpty(unrevokeParams))) {
return { revokeSignedRequest, unrevokeSignedRequest };
}
const group = await UserGroupsWrapperActions.getGroup(destination);
const secretKey = group?.secretKey;
if (!secretKey || isEmpty(secretKey)) {
throw new Error('tried to signedRevokeRequest but we do not have the admin secret key');
}
const timestamp = GetNetworkTime.now();
if (revokeParams) {
const method = 'revoke_subaccount' as const;
const tokensBytes = from_hex(revokeParams.revoke.join(''));
const prefix = new Uint8Array(StringUtils.encode(`${method}${timestamp}`, 'utf8'));
const sigResult = await SnodeGroupSignature.signDataWithAdminSecret(
concatUInt8Array(prefix, tokensBytes),
{ secretKey }
);
revokeSignedRequest = {
method,
params: {
revoke: revokeParams.revoke,
...sigResult,
pubkey: destination,
timestamp,
},
};
}
if (unrevokeParams) {
const method = 'unrevoke_subaccount' as const;
const tokensBytes = from_hex(unrevokeParams.unrevoke.join(''));
const prefix = new Uint8Array(StringUtils.encode(`${method}${timestamp}`, 'utf8'));
const sigResult = await SnodeGroupSignature.signDataWithAdminSecret(
concatUInt8Array(prefix, tokensBytes),
{ secretKey }
);
unrevokeSignedRequest = {
method,
params: {
unrevoke: unrevokeParams.unrevoke,
...sigResult,
pubkey: destination,
timestamp,
},
};
}
return { revokeSignedRequest, unrevokeSignedRequest };
}
async function sendMessagesDataToSnode(
params: Array<StoreOnNodeParamsNoSig>,
destination: PubkeyType | GroupPubkeyType,
{
messagesHashes: messagesToDelete,
revokeParams,
unrevokeParams,
}: WithMessagesHashes & WithRevokeParams,
revokeSubRequest,
unrevokeSubRequest,
}: WithMessagesHashes & WithRevokeSubRequest,
method: 'batch' | 'sequence'
): Promise<NotEmptyArrayOfBatchResults> {
const rightDestination = params.filter(m => m.pubkey === destination);
@ -366,11 +297,6 @@ async function sendMessagesDataToSnode(
}
const signedDeleteHashesRequest = await signDeleteHashesRequest(destination, messagesToDelete);
const signedRevokeRequests = await signedRevokeRequest({
destination,
revokeParams,
unrevokeParams,
});
try {
// No pRetry here as if this is a bad path it will be handled and retried in lokiOnionFetch.
@ -379,8 +305,8 @@ async function sendMessagesDataToSnode(
compact([
...withSigWhenRequired,
signedDeleteHashesRequest,
signedRevokeRequests?.revokeSignedRequest,
signedRevokeRequests?.unrevokeSignedRequest,
revokeSubRequest,
unrevokeSubRequest,
]),
method
@ -541,14 +467,12 @@ async function sendEncryptedDataToSnode({
destination,
encryptedData,
messagesHashesToDelete,
revokeParams,
unrevokeParams,
}: {
revokeSubRequest,
unrevokeSubRequest,
}: WithRevokeSubRequest & {
encryptedData: Array<StoreOnNodeData>;
destination: GroupPubkeyType | PubkeyType;
messagesHashesToDelete: Set<string> | null;
revokeParams: RevokeSubaccountParams | null;
unrevokeParams: UnrevokeSubaccountParams | null;
}): Promise<NotEmptyArrayOfBatchResults | null> {
try {
const batchResults = await pRetry(
@ -562,7 +486,11 @@ async function sendEncryptedDataToSnode({
namespace: content.namespace,
})),
destination,
{ messagesHashes: [...(messagesHashesToDelete || [])], revokeParams, unrevokeParams },
{
messagesHashes: [...(messagesHashesToDelete || [])],
revokeSubRequest,
unrevokeSubRequest,
},
'sequence'
);
},

@ -48,14 +48,14 @@ class FetchMsgExpirySwarmJob extends PersistedJob<FetchMsgExpirySwarmPersistedDa
return RunJobResult.Success;
}
let msgModels = await Data.getMessagesById(this.persistedData.msgIds);
const messageHashes = compact(msgModels.map(m => m.getMessageHash()));
const messagesHashes = compact(msgModels.map(m => m.getMessageHash()));
if (isEmpty(msgModels) || isEmpty(messageHashes)) {
if (isEmpty(msgModels) || isEmpty(messagesHashes)) {
return RunJobResult.Success;
}
const fetchedExpiries = await getExpiriesFromSnode({
messageHashes,
messagesHashes,
});
const updatedMsgModels: Array<MessageModel> = [];

@ -9,7 +9,7 @@ import { MetaGroupWrapperActions } from '../../../../webworker/workers/browser/l
import { StoreOnNodeData } from '../../../apis/snode_api/SnodeRequestTypes';
import { GetNetworkTime } from '../../../apis/snode_api/getNetworkTime';
import { SnodeNamespaces } from '../../../apis/snode_api/namespaces';
import { WithRevokeParams } from '../../../apis/snode_api/types';
import { WithRevokeSubRequest } from '../../../apis/snode_api/types';
import { TTL_DEFAULT } from '../../../constants';
import { ConvoHub } from '../../../conversations';
import { GroupUpdateInfoChangeMessage } from '../../../messages/outgoing/controlMessage/group_v2/to_group/GroupUpdateInfoChangeMessage';
@ -71,13 +71,13 @@ async function confirmPushedAndDump(
}
async function pushChangesToGroupSwarmIfNeeded({
revokeParams,
unrevokeParams,
revokeSubRequest,
unrevokeSubRequest,
updateMessages,
groupPk,
supplementKeys,
}: WithGroupPubkey &
WithRevokeParams & {
WithRevokeSubRequest & {
supplementKeys: Array<Uint8Array>;
updateMessages: Array<GroupUpdateMemberChangeMessage | GroupUpdateInfoChangeMessage>;
}): Promise<RunJobResult> {
@ -147,16 +147,16 @@ async function pushChangesToGroupSwarmIfNeeded({
encryptedData: [...encryptedMessage, ...extraMessagesEncrypted],
destination: groupPk,
messagesHashesToDelete: allOldHashes,
revokeParams,
unrevokeParams,
revokeSubRequest,
unrevokeSubRequest,
});
const expectedReplyLength =
messages.length + // each of those messages are sent as a subrequest
extraMessagesEncrypted.length + // each of those messages are sent as a subrequest
(allOldHashes.size ? 1 : 0) + // we are sending all hashes changes as a single request
(revokeParams?.revoke.length ? 1 : 0) + // we are sending all revoke updates as a single request
(unrevokeParams?.unrevoke.length ? 1 : 0); // we are sending all revoke updates as a single request
(revokeSubRequest?.revokeTokenHex.length ? 1 : 0) + // we are sending all revoke updates as a single request
(unrevokeSubRequest?.revokeTokenHex.length ? 1 : 0); // we are sending all revoke updates as a single request
// we do a sequence call here. If we do not have the right expected number of results, consider it a failure
if (!isArray(result) || result.length !== expectedReplyLength) {
@ -225,8 +225,8 @@ class GroupSyncJob extends PersistedJob<GroupSyncPersistedData> {
// return await so we catch exceptions in here
return await GroupSync.pushChangesToGroupSwarmIfNeeded({
groupPk: thisJobDestination,
revokeParams: null,
unrevokeParams: null,
revokeSubRequest: null,
unrevokeSubRequest: null,
supplementKeys: [],
updateMessages: [],
});

@ -100,8 +100,8 @@ async function pushChangesToUserSwarmIfNeeded() {
encryptedData: msgs,
destination: us,
messagesHashesToDelete: changesToPush.allOldHashes,
revokeParams: null,
unrevokeParams: null,
revokeSubRequest: null,
unrevokeSubRequest: null,
});
const expectedReplyLength =

@ -22,6 +22,7 @@ import { getSwarmPollingInstance } from '../../session/apis/snode_api';
import { GetNetworkTime } from '../../session/apis/snode_api/getNetworkTime';
import { RevokeChanges, SnodeAPIRevoke } from '../../session/apis/snode_api/revokeSubaccount';
import { SnodeGroupSignature } from '../../session/apis/snode_api/signature/groupSignature';
import { WithSecretKey } from '../../session/apis/snode_api/types';
import { ConvoHub } from '../../session/conversations';
import { getSodiumRenderer } from '../../session/crypto';
import { DisappearingMessages } from '../../session/disappearing_messages';
@ -182,8 +183,8 @@ const initNewGroupInWrapper = createAsyncThunk(
const result = await GroupSync.pushChangesToGroupSwarmIfNeeded({
groupPk,
revokeParams: null,
unrevokeParams: null,
revokeSubRequest: null,
unrevokeSubRequest: null,
supplementKeys: [],
updateMessages: [],
});
@ -559,7 +560,12 @@ async function getPendingRevokeParams({
withHistory,
removed,
groupPk,
}: WithGroupPubkey & WithAddWithoutHistoryMembers & WithAddWithHistoryMembers & WithRemoveMembers) {
secretKey,
}: WithGroupPubkey &
WithSecretKey &
WithAddWithoutHistoryMembers &
WithAddWithHistoryMembers &
WithRemoveMembers) {
const revokeChanges: RevokeChanges = [];
const unrevokeChanges: RevokeChanges = [];
@ -579,7 +585,10 @@ async function getPendingRevokeParams({
revokeChanges.push({ action: 'revoke_subaccount', tokenToRevokeHex: token });
}
return SnodeAPIRevoke.getRevokeSubaccountParams(groupPk, { revokeChanges, unrevokeChanges });
return SnodeAPIRevoke.getRevokeSubaccountParams(groupPk, secretKey, {
revokeChanges,
unrevokeChanges,
});
}
function getConvoExpireDetailsForMsg(convo: ConversationModel) {
@ -703,6 +712,7 @@ async function handleMemberAddedFromUIOrNot({
withHistory,
withoutHistory,
removed: [],
secretKey: group.secretKey,
});
// then, handle the addition with history of messages by generating supplement keys.
@ -814,6 +824,7 @@ async function handleMemberRemovedFromUIOrNot({
withHistory: [],
withoutHistory: [],
removed,
secretKey: group.secretKey,
});
// Send the groupUpdateDeleteMessage that can still be decrypted by those removed members to namespace ClosedGroupRevokedRetrievableMessages. (not when handling a MEMBER_LEFT message)
@ -949,8 +960,8 @@ async function handleNameChangeFromUI({
const batchResult = await GroupSync.pushChangesToGroupSwarmIfNeeded({
groupPk,
supplementKeys: [],
revokeParams: null,
unrevokeParams: null,
revokeSubRequest: null,
unrevokeSubRequest: null,
updateMessages,
});

@ -7,6 +7,7 @@ import { GetNetworkTime } from '../../../../session/apis/snode_api/getNetworkTim
import { SnodeNamespaces } from '../../../../session/apis/snode_api/namespaces';
import { SnodeGroupSignature } from '../../../../session/apis/snode_api/signature/groupSignature';
import { SnodeSignature } from '../../../../session/apis/snode_api/signature/snodeSignatures';
import { WithSignature } from '../../../../session/apis/snode_api/types';
import { concatUInt8Array } from '../../../../session/crypto';
import { UserUtils } from '../../../../session/utils';
import { fromBase64ToArray, fromHexToArray } from '../../../../session/utils/String';
@ -27,7 +28,7 @@ const userEd25519Keypair = {
const hardcodedTimestamp = 1234;
async function verifySig(ret: { pubkey: string; signature: string }, verificationData: string) {
async function verifySig(ret: WithSignature & { pubkey: string }, verificationData: string) {
const without03 =
ret.pubkey.startsWith('03') || ret.pubkey.startsWith('05') ? ret.pubkey.slice(2) : ret.pubkey; //
const pk = HexString.fromHexString(without03);

@ -7,13 +7,12 @@ import {
fakeHash,
} from '../../../../session/apis/snode_api/SnodeRequestTypes';
import {
GetExpiriesFromSnodeProps,
GetExpiriesRequestResponseResults,
buildGetExpiriesRequest,
processGetExpiriesRequestResponse,
} from '../../../../session/apis/snode_api/getExpiriesRequest';
import { GetNetworkTime } from '../../../../session/apis/snode_api/getNetworkTime';
import { SnodeSignature } from '../../../../session/apis/snode_api/signature/snodeSignatures';
import { WithMessagesHashes } from '../../../../session/apis/snode_api/types';
import { UserUtils } from '../../../../session/utils';
import { isValidUnixTimestamp } from '../../../../session/utils/Timestamps';
import { TypedStub, generateFakeSnode, stubWindowLog } from '../../../test-utils/utils';
@ -47,12 +46,13 @@ describe('GetExpiriesRequest', () => {
});
describe('buildGetExpiriesRequest', () => {
const props: GetExpiriesFromSnodeProps = {
messageHashes: ['messageHash'],
const props: WithMessagesHashes = {
messagesHashes: ['messageHash'],
};
it('builds a valid request given the messageHashes and valid timestamp for now', async () => {
const request: GetExpiriesFromNodeSubRequest | null = await buildGetExpiriesRequest(props);
const unsigned = new GetExpiriesFromNodeSubRequest(props);
const request = await unsigned.buildAndSignParameters();
expect(request, 'should not return null').to.not.be.null;
expect(request, 'should not return undefined').to.not.be.undefined;
@ -63,7 +63,7 @@ describe('GetExpiriesRequest', () => {
expect(request, "method should be 'get_expiries'").to.have.property('method', 'get_expiries');
expect(request.params.pubkey, 'should have a matching pubkey').to.equal(ourNumber);
expect(request.params.messages, 'messageHashes should match our input').to.deep.equal(
props.messageHashes
props.messagesHashes
);
expect(
request.params.timestamp && isValidUnixTimestamp(request?.params.timestamp),
@ -74,16 +74,17 @@ describe('GetExpiriesRequest', () => {
it('fails to build a request if our pubkey is missing', async () => {
// Modify the stub behavior for this test only we need to return an unsupported type to simulate a missing pubkey
(getOurPubKeyStrFromCacheStub as any).returns(undefined);
const request: GetExpiriesFromNodeSubRequest | null = await buildGetExpiriesRequest(props);
const unsigned = new GetExpiriesFromNodeSubRequest(props);
const request = await unsigned.buildAndSignParameters();
expect(request, 'should return null').to.be.null;
});
it('fails to build a request if our signature is missing', async () => {
// Modify the stub behavior for this test only we need to return an unsupported type to simulate a missing pubkey
Sinon.stub(SnodeSignature, 'generateGetExpiriesOurSignature').resolves(null);
// TODO audric this should throw
const request: GetExpiriesFromNodeSubRequest | null = await buildGetExpiriesRequest(props);
// TODO audric this should throw debugger
const unsigned = new GetExpiriesFromNodeSubRequest(props);
const request = await unsigned.buildAndSignParameters();
expect(request, 'should return null').to.be.null;
});

@ -273,8 +273,8 @@ describe('GroupSyncJob pushChangesToGroupSwarmIfNeeded', () => {
it('call savesDumpToDb even if no changes are required on the serverside', async () => {
const result = await GroupSync.pushChangesToGroupSwarmIfNeeded({
groupPk,
revokeParams: null,
unrevokeParams: null,
revokeSubRequest: null,
unrevokeSubRequest: null,
supplementKeys: [],
updateMessages: [],
});
@ -298,8 +298,8 @@ describe('GroupSyncJob pushChangesToGroupSwarmIfNeeded', () => {
});
const result = await GroupSync.pushChangesToGroupSwarmIfNeeded({
groupPk,
revokeParams: null,
unrevokeParams: null,
revokeSubRequest: null,
unrevokeSubRequest: null,
supplementKeys: [],
updateMessages: [],
});
@ -363,8 +363,8 @@ describe('GroupSyncJob pushChangesToGroupSwarmIfNeeded', () => {
]);
const result = await GroupSync.pushChangesToGroupSwarmIfNeeded({
groupPk,
revokeParams: null,
unrevokeParams: null,
revokeSubRequest: null,
unrevokeSubRequest: null,
supplementKeys: [],
updateMessages: [],
});

Loading…
Cancel
Save