From 99529847e09123c590835ff45a8594a47074220c Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 6 Feb 2025 13:37:56 +1100 Subject: [PATCH 1/2] fix: invite contacts to groups is not blocking --- ts/components/dialog/InviteContactsDialog.tsx | 16 ++++++------ .../utils/job_runners/jobs/GroupInviteJob.ts | 5 ++-- ts/state/ducks/metaGroups.ts | 25 +++++++++++++++++-- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/ts/components/dialog/InviteContactsDialog.tsx b/ts/components/dialog/InviteContactsDialog.tsx index e4250b745..87f142a03 100644 --- a/ts/components/dialog/InviteContactsDialog.tsx +++ b/ts/components/dialog/InviteContactsDialog.tsx @@ -7,7 +7,7 @@ import { useDispatch } from 'react-redux'; import { VALIDATION } from '../../session/constants'; import { ConvoHub } from '../../session/conversations'; import { ToastUtils, UserUtils } from '../../session/utils'; -import { updateInviteContactModal } from '../../state/ducks/modalDialog'; +import { updateGroupMembersModal, updateInviteContactModal } from '../../state/ducks/modalDialog'; import { SpacerLG } from '../basic/Text'; import { @@ -22,12 +22,10 @@ import { PubKey } from '../../session/types'; import { SessionUtilUserGroups } from '../../session/utils/libsession/libsession_utils_user_groups'; import { groupInfoActions } from '../../state/ducks/metaGroups'; import { useContactsToInviteToGroup } from '../../state/selectors/conversations'; -import { useMemberGroupChangePending } from '../../state/selectors/groups'; import { useSelectedIsGroupV2 } from '../../state/selectors/selectedConversation'; import { MemberListItem } from '../MemberListItem'; import { SessionWrapperModal } from '../SessionWrapperModal'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton'; -import { SessionSpinner } from '../loading'; import { SessionToggle } from '../basic/SessionToggle'; import { GroupInviteRequiredVersionBanner } from '../NoticeBanner'; import { hasClosedGroupV2QAButtons } from '../../shared/env_vars'; @@ -117,9 +115,6 @@ const InviteContactsDialogInner = (props: Props) => { const dispatch = useDispatch(); const privateContactPubkeys = useContactsToInviteToGroup() as Array; - - const isProcessingUIChange = useMemberGroupChangePending(); - const isPrivate = useIsPrivate(conversationId); const isPublic = useIsPublic(conversationId); const membersFromRedux = useSortedGroupMembers(conversationId) || []; @@ -163,6 +158,11 @@ const InviteContactsDialogInner = (props: Props) => { }); dispatch(action as any); empty(); + // We want to show the dialog where "invite sending" is visible (i.e. the current group members) instead of this one + // once we hit "invite" + closeDialog(); + dispatch(updateGroupMembersModal({ conversationId })); + return; } void submitForClosedGroup(conversationId, selectedContacts); @@ -221,13 +221,12 @@ const InviteContactsDialogInner = (props: Props) => { )} -
@@ -236,7 +235,6 @@ const InviteContactsDialogInner = (props: Props) => { buttonColor={SessionButtonColor.Danger} buttonType={SessionButtonType.Simple} onClick={closeDialog} - disabled={isProcessingUIChange} dataTestId="session-confirm-cancel-button" />
diff --git a/ts/session/utils/job_runners/jobs/GroupInviteJob.ts b/ts/session/utils/job_runners/jobs/GroupInviteJob.ts index 0eff4695c..4a7029592 100644 --- a/ts/session/utils/job_runners/jobs/GroupInviteJob.ts +++ b/ts/session/utils/job_runners/jobs/GroupInviteJob.ts @@ -285,7 +285,7 @@ class GroupInviteJob extends PersistedJob { ); } - updateFailedStateForMember(groupPk, member, failed); + debounceFailedStateForMember(groupPk, member, failed); window?.inboxStore?.dispatch( groupInfoActions.refreshGroupDetailsFromWrapper({ groupPk }) as any @@ -325,9 +325,10 @@ class GroupInviteJob extends PersistedJob { export const GroupInvite = { GroupInviteJob, addJob, + debounceFailedStateForMember, }; -function updateFailedStateForMember(groupPk: GroupPubkeyType, member: PubkeyType, failed: boolean) { +function debounceFailedStateForMember(groupPk: GroupPubkeyType, member: PubkeyType, failed: boolean) { let thisGroupFailure = invitesFailed.get(groupPk); if (!failed) { diff --git a/ts/state/ducks/metaGroups.ts b/ts/state/ducks/metaGroups.ts index 95151b9ee..6257a2c62 100644 --- a/ts/state/ducks/metaGroups.ts +++ b/ts/state/ducks/metaGroups.ts @@ -546,6 +546,8 @@ async function handleWithHistoryMembers({ }); // a group invite job will be added to the queue await MetaGroupWrapperActions.memberSetInviteNotSent(groupPk, member); + // update the in-memory failed state, so that if we fail again to send that invite, the toast is shown again + GroupInvite.debounceFailedStateForMember(groupPk, member, false); } const encryptedSupplementKeys = withHistory.length ? await MetaGroupWrapperActions.generateSupplementKeys(groupPk, withHistory) @@ -695,9 +697,12 @@ async function handleMemberAddedFromUI({ }); if (sequenceResult !== RunJobResult.Success) { await LibSessionUtil.saveDumpsToDb(groupPk); - + window.log.warn( + `handleMemberAddedFromUI: pushChangesToGroupSwarmIfNeeded for ${ed25519Str(groupPk)} did not return success` + ); + // throwing so we handle the reset state in the catch below throw new Error( - 'handleMemberAddedFromUI: pushChangesToGroupSwarmIfNeeded did not return success' + `handleMemberAddedFromUI: pushChangesToGroupSwarmIfNeeded for ${ed25519Str(groupPk)} did not return success` ); } } catch (e) { @@ -705,6 +710,21 @@ async function handleMemberAddedFromUI({ 'handleMemberAddedFromUI: pushChangesToGroupSwarmIfNeeded failed with:', e.message ); + + try { + const merged = withHistory.concat(withoutHistory); + for (let index = 0; index < merged.length; index++) { + await MetaGroupWrapperActions.memberSetInviteFailed(groupPk, merged[index]); + // this gets reset once we do send an invite to that user + GroupInvite.debounceFailedStateForMember(groupPk, merged[index], true); + } + } catch (e2) { + window.log.warn( + 'handleMemberAddedFromUI: marking members invite failed, failed with:', + e2.message + ); + } + return false; } // schedule send invite details, auth signature, etc. to the new users @@ -716,6 +736,7 @@ async function handleMemberAddedFromUI({ }); await convo.commit(); + return true; } /** From b2c34cb805297e1a5265f8f881f7cef8dc7cc06e Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Thu, 6 Feb 2025 17:07:12 +1100 Subject: [PATCH 2/2] fix: resend invite checks for supplement key --- package.json | 2 +- ts/components/MemberListItem.tsx | 1 - ts/interactions/conversationInteractions.ts | 2 +- .../apis/snode_api/SnodeRequestTypes.ts | 18 ++-- ts/session/apis/snode_api/revokeSubaccount.ts | 4 +- ts/session/utils/job_runners/PersistedJob.ts | 1 - .../utils/job_runners/jobs/GroupInviteJob.ts | 90 +++++++++++-------- ts/state/ducks/metaGroups.ts | 5 +- .../libsession_wrapper_metagroup_test.ts | 23 ++++- .../browser/libsession_worker_interface.ts | 6 ++ yarn.lock | 12 +-- 11 files changed, 100 insertions(+), 64 deletions(-) diff --git a/package.json b/package.json index 8448927cd..0a1e21efd 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "fs-extra": "9.0.0", "glob": "10.3.10", "image-type": "^4.1.0", - "libsession_util_nodejs": "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.12/libsession_util_nodejs-v0.4.12.tar.gz", + "libsession_util_nodejs": "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.15/libsession_util_nodejs-v0.4.15.tar.gz", "libsodium-wrappers-sumo": "^0.7.9", "linkify-it": "^4.0.1", "lodash": "^4.17.21", diff --git a/ts/components/MemberListItem.tsx b/ts/components/MemberListItem.tsx index 972fbe19f..f911708dc 100644 --- a/ts/components/MemberListItem.tsx +++ b/ts/components/MemberListItem.tsx @@ -277,7 +277,6 @@ const ResendButton = ({ groupPk, pubkey }: { pubkey: PubkeyType; groupPk: GroupP groupPk, member: pubkey, inviteAsAdmin: member.nominatedAdmin, - forceUnrevoke: true, }); }} /> diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index 68643a2e0..2b0892926 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -1053,6 +1053,6 @@ export async function promoteUsersInGroup({ for (let index = 0; index < membersHex.length; index++) { const member = membersHex[index]; // eslint-disable-next-line no-await-in-loop - await GroupInvite.addJob({ groupPk, member, inviteAsAdmin: true, forceUnrevoke: true }); + await GroupInvite.addJob({ groupPk, member, inviteAsAdmin: true }); } } diff --git a/ts/session/apis/snode_api/SnodeRequestTypes.ts b/ts/session/apis/snode_api/SnodeRequestTypes.ts index dd3bc23f9..ab0e86c19 100644 --- a/ts/session/apis/snode_api/SnodeRequestTypes.ts +++ b/ts/session/apis/snode_api/SnodeRequestTypes.ts @@ -403,24 +403,22 @@ abstract class AbstractRevokeSubRequest< > extends SnodeAPISubRequest { public readonly destination: GroupPubkeyType; public readonly timestamp: number; - public readonly revokeTokenHex: Array; + public readonly tokensHex: Array; protected readonly adminSecretKey: Uint8Array; constructor({ groupPk, timestamp, - revokeTokenHex, + tokensHex, secretKey, method, - }: WithGroupPubkey & - WithTimestamp & - WithSecretKey & { revokeTokenHex: Array; method: T }) { + }: WithGroupPubkey & WithTimestamp & WithSecretKey & { tokensHex: Array; method: T }) { super({ method }); this.destination = groupPk; this.timestamp = timestamp; - this.revokeTokenHex = revokeTokenHex; + this.tokensHex = tokensHex; this.adminSecretKey = secretKey; - if (this.revokeTokenHex.length === 0) { + if (this.tokensHex.length === 0) { throw new Error('AbstractRevokeSubRequest needs at least one token to do a change'); } } @@ -429,7 +427,7 @@ abstract class AbstractRevokeSubRequest< if (!this.adminSecretKey) { throw new Error('we need an admin secretKey'); } - const tokensBytes = from_hex(this.revokeTokenHex.join('')); + const tokensBytes = from_hex(this.tokensHex.join('')); const prefix = new Uint8Array(StringUtils.encode(`${this.method}${this.timestamp}`, 'utf8')); const sigResult = await SnodeGroupSignature.signDataWithAdminSecret( @@ -461,7 +459,7 @@ export class SubaccountRevokeSubRequest extends AbstractRevokeSubRequest<'revoke params: { pubkey: this.destination, signature, - revoke: this.revokeTokenHex, + revoke: this.tokensHex, timestamp: this.timestamp, }, }; @@ -483,7 +481,7 @@ export class SubaccountUnrevokeSubRequest extends AbstractRevokeSubRequest<'unre params: { pubkey: this.destination, signature, - unrevoke: this.revokeTokenHex, + unrevoke: this.tokensHex, timestamp: this.timestamp, }, }; diff --git a/ts/session/apis/snode_api/revokeSubaccount.ts b/ts/session/apis/snode_api/revokeSubaccount.ts index 5a296a66e..204604a1d 100644 --- a/ts/session/apis/snode_api/revokeSubaccount.ts +++ b/ts/session/apis/snode_api/revokeSubaccount.ts @@ -24,7 +24,7 @@ async function getRevokeSubaccountParams( const revokeSubRequest = revokeChanges.length ? new SubaccountRevokeSubRequest({ groupPk, - revokeTokenHex: revokeChanges.map(m => m.tokenToRevokeHex), + tokensHex: revokeChanges.map(m => m.tokenToRevokeHex), timestamp: NetworkTime.now(), secretKey, }) @@ -32,7 +32,7 @@ async function getRevokeSubaccountParams( const unrevokeSubRequest = unrevokeChanges.length ? new SubaccountUnrevokeSubRequest({ groupPk, - revokeTokenHex: unrevokeChanges.map(m => m.tokenToRevokeHex), + tokensHex: unrevokeChanges.map(m => m.tokenToRevokeHex), timestamp: NetworkTime.now(), secretKey, }) diff --git a/ts/session/utils/job_runners/PersistedJob.ts b/ts/session/utils/job_runners/PersistedJob.ts index c52a4ccc5..290f285d4 100644 --- a/ts/session/utils/job_runners/PersistedJob.ts +++ b/ts/session/utils/job_runners/PersistedJob.ts @@ -43,7 +43,6 @@ export interface GroupInvitePersistedData extends PersistedJobData { groupPk: GroupPubkeyType; member: PubkeyType; inviteAsAdmin: boolean; - forceUnrevoke: boolean; } export interface GroupPromotePersistedData extends PersistedJobData { diff --git a/ts/session/utils/job_runners/jobs/GroupInviteJob.ts b/ts/session/utils/job_runners/jobs/GroupInviteJob.ts index 4a7029592..4a9a62bc6 100644 --- a/ts/session/utils/job_runners/jobs/GroupInviteJob.ts +++ b/ts/session/utils/job_runners/jobs/GroupInviteJob.ts @@ -23,10 +23,14 @@ import { showUpdateGroupMembersByConvoId } from '../../../../interactions/conver import { ConvoHub } from '../../../conversations'; import { MessageSender } from '../../../sending'; import { NetworkTime } from '../../../../util/NetworkTime'; -import { SubaccountUnrevokeSubRequest } from '../../../apis/snode_api/SnodeRequestTypes'; +import { + SubaccountUnrevokeSubRequest, + type StoreGroupKeysSubRequest, +} from '../../../apis/snode_api/SnodeRequestTypes'; import { GroupSync } from './GroupSyncJob'; import { DURATION } from '../../../constants'; import { timeoutWithAbort } from '../../Promise'; +import { StoreGroupRequestFactory } from '../../../apis/snode_api/factories/StoreGroupRequestFactory'; const defaultMsBetweenRetries = 10000; const defaultMaxAttempts = 1; @@ -35,12 +39,6 @@ type JobExtraArgs = { groupPk: GroupPubkeyType; member: PubkeyType; inviteAsAdmin: boolean; - /** - * When inviting a member, we usually only want to sent a message to his swarm. - * In the case of an invitation resend process though, we also want to make sure his token is unrevoked from the group's swarm. - * - */ - forceUnrevoke: boolean; }; export function shouldAddJob(args: JobExtraArgs) { @@ -59,13 +57,12 @@ const invitesFailed = new Map< } >(); -async function addJob({ groupPk, member, inviteAsAdmin, forceUnrevoke }: JobExtraArgs) { - if (shouldAddJob({ groupPk, member, inviteAsAdmin, forceUnrevoke })) { +async function addJob({ groupPk, member, inviteAsAdmin }: JobExtraArgs) { + if (shouldAddJob({ groupPk, member, inviteAsAdmin })) { const groupInviteJob = new GroupInviteJob({ groupPk, member, inviteAsAdmin, - forceUnrevoke, nextAttemptTimestamp: Date.now(), }); window.log.debug( @@ -146,9 +143,8 @@ class GroupInviteJob extends PersistedJob { nextAttemptTimestamp, maxAttempts, currentRetry, - forceUnrevoke, identifier, - }: Pick & + }: Pick & Partial< Pick< GroupInvitePersistedData, @@ -161,7 +157,6 @@ class GroupInviteJob extends PersistedJob { member, groupPk, inviteAsAdmin, - forceUnrevoke, delayBetweenRetries: defaultMsBetweenRetries, maxAttempts: isNumber(maxAttempts) ? maxAttempts : defaultMaxAttempts, nextAttemptTimestamp: nextAttemptTimestamp || Date.now() + defaultMsBetweenRetries, @@ -187,36 +182,51 @@ class GroupInviteJob extends PersistedJob { let failed = true; try { let start = Date.now(); + const memberObj = await MetaGroupWrapperActions.memberGet(groupPk, member); + if (!memberObj) { + throw new Error('Member should have been added before GroupInviteJob was run()'); + } + let supplementalKeysSubRequest: StoreGroupKeysSubRequest | undefined; - if (this.persistedData.forceUnrevoke) { - const token = await MetaGroupWrapperActions.swarmSubAccountToken(groupPk, member); - const unrevokeSubRequest = new SubaccountUnrevokeSubRequest({ - groupPk, - revokeTokenHex: [token], - timestamp: NetworkTime.now(), - secretKey: group.secretKey, - }); - const sequenceResult = await GroupSync.pushChangesToGroupSwarmIfNeeded({ + if (memberObj.supplement) { + const encryptedSupplementKeys = await MetaGroupWrapperActions.generateSupplementKeys( groupPk, - unrevokeSubRequest, - extraStoreRequests: [], - allow401s: false, - timeoutMs: 10 * DURATION.SECONDS, + [member] + ); + supplementalKeysSubRequest = StoreGroupRequestFactory.makeStoreGroupKeysSubRequest({ + group, + encryptedSupplementKeys, }); - window?.inboxStore?.dispatch( - groupInfoActions.refreshGroupDetailsFromWrapper({ groupPk }) as any + } + + const token = await MetaGroupWrapperActions.swarmSubAccountToken(groupPk, member); + const unrevokeSubRequest = new SubaccountUnrevokeSubRequest({ + groupPk, + tokensHex: [token], + timestamp: NetworkTime.now(), + secretKey: group.secretKey, + }); + const sequenceResult = await GroupSync.pushChangesToGroupSwarmIfNeeded({ + groupPk, + unrevokeSubRequest, + supplementalKeysSubRequest, + extraStoreRequests: [], + allow401s: false, + timeoutMs: 10 * DURATION.SECONDS, + }); + window?.inboxStore?.dispatch( + groupInfoActions.refreshGroupDetailsFromWrapper({ groupPk }) as any + ); + if (sequenceResult !== RunJobResult.Success) { + window.log.warn( + `GroupInvite: GroupSync.pushChangesToGroupSwarmIfNeeded failed after ${Date.now() - start}ms` ); - if (sequenceResult !== RunJobResult.Success) { - window.log.warn( - `GroupInvite: GroupSync.pushChangesToGroupSwarmIfNeeded failed after ${Date.now() - start}ms` - ); - await LibSessionUtil.saveDumpsToDb(groupPk); + await LibSessionUtil.saveDumpsToDb(groupPk); - throw new Error( - 'GroupInviteJob: SubaccountUnrevokeSubRequest push() did not return success' - ); - } + throw new Error( + 'GroupInviteJob: SubaccountUnrevokeSubRequest push() did not return success' + ); } const inviteDetails = inviteAsAdmin @@ -328,7 +338,11 @@ export const GroupInvite = { debounceFailedStateForMember, }; -function debounceFailedStateForMember(groupPk: GroupPubkeyType, member: PubkeyType, failed: boolean) { +function debounceFailedStateForMember( + groupPk: GroupPubkeyType, + member: PubkeyType, + failed: boolean +) { let thisGroupFailure = invitesFailed.get(groupPk); if (!failed) { diff --git a/ts/state/ducks/metaGroups.ts b/ts/state/ducks/metaGroups.ts index 6257a2c62..ff8d3a482 100644 --- a/ts/state/ducks/metaGroups.ts +++ b/ts/state/ducks/metaGroups.ts @@ -546,6 +546,7 @@ async function handleWithHistoryMembers({ }); // a group invite job will be added to the queue await MetaGroupWrapperActions.memberSetInviteNotSent(groupPk, member); + await MetaGroupWrapperActions.memberSetSupplement(groupPk, member); // update the in-memory failed state, so that if we fail again to send that invite, the toast is shown again GroupInvite.debounceFailedStateForMember(groupPk, member, false); } @@ -1505,8 +1506,6 @@ async function scheduleGroupInviteJobs( const merged = uniq(concat(withHistory, withoutHistory)); for (let index = 0; index < merged.length; index++) { const member = merged[index]; - // Note: forceUnrevoke is false, because `scheduleGroupInviteJobs` is always called after we've done - // a batch unrevoke of all the members' pk - await GroupInvite.addJob({ groupPk, member, inviteAsAdmin, forceUnrevoke: false }); + await GroupInvite.addJob({ groupPk, member, inviteAsAdmin }); } } diff --git a/ts/test/session/unit/libsession_wrapper/libsession_wrapper_metagroup_test.ts b/ts/test/session/unit/libsession_wrapper/libsession_wrapper_metagroup_test.ts index 410371aa4..1ddac14ea 100644 --- a/ts/test/session/unit/libsession_wrapper/libsession_wrapper_metagroup_test.ts +++ b/ts/test/session/unit/libsession_wrapper/libsession_wrapper_metagroup_test.ts @@ -26,6 +26,7 @@ function emptyMember(pubkeyHex: PubkeyType): GroupMemberGet { }, nominatedAdmin: false, pubkeyHex, + supplement: false, }; } @@ -155,7 +156,14 @@ describe('libsession_metagroup', () => { const memberCreated = metaGroupWrapper.memberGetOrConstruct(member); console.info('Object.keys(memberCreated) ', JSON.stringify(Object.keys(memberCreated))); expect(Object.keys(memberCreated).sort()).to.be.deep.eq( - ['pubkeyHex', 'name', 'profilePicture', 'memberStatus', 'nominatedAdmin'].sort(), // if you change this value, also make sure you add a test, testing that new field, below + [ + 'pubkeyHex', + 'name', + 'profilePicture', + 'memberStatus', + 'nominatedAdmin', + 'supplement', + ].sort(), // if you change this value, also make sure you add a test, testing that new field, below 'this test is designed to fail if you need to add tests to test a new field of libsession' ); }); @@ -278,6 +286,19 @@ describe('libsession_metagroup', () => { expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq(expected); }); + it('can add via supplement set', () => { + metaGroupWrapper.memberConstructAndSet(member); + metaGroupWrapper.memberSetSupplement(member); + expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); + const expected = { + ...emptyMember(member), + memberStatus: 'INVITE_SENDING', // invite_sending is the default state + supplement: true, + }; + + expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq(expected); + }); + it('can add via admin set', () => { metaGroupWrapper.memberConstructAndSet(member); metaGroupWrapper.memberSetPromotionAccepted(member); diff --git a/ts/webworker/workers/browser/libsession_worker_interface.ts b/ts/webworker/workers/browser/libsession_worker_interface.ts index 896b9ad82..b178ffc5c 100644 --- a/ts/webworker/workers/browser/libsession_worker_interface.ts +++ b/ts/webworker/workers/browser/libsession_worker_interface.ts @@ -657,6 +657,12 @@ export const MetaGroupWrapperActions: MetaGroupWrapperActionsCalls = { pubkeyHex, name, ]) as Promise>, + memberSetSupplement: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType) => + callLibSessionWorker([ + `MetaGroupConfig-${groupPk}`, + 'memberSetSupplement', + pubkeyHex, + ]) as Promise>, memberSetProfilePicture: async ( groupPk: GroupPubkeyType, pubkeyHex: PubkeyType, diff --git a/yarn.lock b/yarn.lock index 73bc16ebd..10e898954 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4944,9 +4944,9 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -"libsession_util_nodejs@https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.12/libsession_util_nodejs-v0.4.12.tar.gz": - version "0.4.12" - resolved "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.12/libsession_util_nodejs-v0.4.12.tar.gz#6f0eae5c81f9a3e5101e038dbb7c82a9d50bfb7a" +"libsession_util_nodejs@https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.15/libsession_util_nodejs-v0.4.15.tar.gz": + version "0.4.15" + resolved "https://github.com/session-foundation/libsession-util-nodejs/releases/download/v0.4.15/libsession_util_nodejs-v0.4.15.tar.gz#de0e90e14327e60d81d2a6941bcd0af33fcfed82" dependencies: cmake-js "7.2.1" node-addon-api "^6.1.0" @@ -6859,9 +6859,9 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.1.2, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.4: - version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== serialize-error@^7.0.1: version "7.0.1"