diff --git a/_locales/en/messages.json b/_locales/en/messages.json index d498763cd..36ebc5239 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -578,6 +578,8 @@ "respondingToGroupRequestWarning": "Sending a message to this group will automatically accept the group invite.", "userInvitedYouToGroup": "$name$ invited you to join $groupName$.", "youWereInvitedToGroup": "You were invited to join $groupName$.", + "userInvitedYouToGroupAsAdmin": "$name$ invited you to join $groupName$, where you are an Admin.", + "youWereInvitedToGroupAsAdmin": "You were invited to join $groupName$, where you are an Admin.", "hideRequestBanner": "Hide Message Request Banner", "openMessageRequestInbox": "Message Requests", "noMessageRequestsPending": "No pending message requests", diff --git a/package.json b/package.json index 5823f6b4e..0f481a6a5 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "fs-extra": "9.0.0", "glob": "10.3.10", "image-type": "^4.1.0", - "libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.19/libsession_util_nodejs-v0.3.19.tar.gz", + "libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.21/libsession_util_nodejs-v0.3.21.tar.gz", "libsodium-wrappers-sumo": "^0.7.9", "linkify-it": "^4.0.1", "lodash": "^4.17.21", diff --git a/ts/components/conversation/SessionConversation.tsx b/ts/components/conversation/SessionConversation.tsx index 73b789059..1f58320fe 100644 --- a/ts/components/conversation/SessionConversation.tsx +++ b/ts/components/conversation/SessionConversation.tsx @@ -6,6 +6,7 @@ import { blobToArrayBuffer } from 'blob-util'; import loadImage from 'blueimp-load-image'; import classNames from 'classnames'; import styled from 'styled-components'; +import { useDispatch } from 'react-redux'; import { CompositionBox, SendMessageType, @@ -60,7 +61,6 @@ import { SessionSpinner } from '../basic/SessionSpinner'; import { ConversationMessageRequestButtons } from './MessageRequestButtons'; import { RightPanel, StyledRightPanelContainer } from './right-panel/RightPanel'; import { showLinkVisitWarningDialog } from '../dialog/SessionConfirm'; -import { useDispatch } from 'react-redux'; const DEFAULT_JPEG_QUALITY = 0.85; interface State { diff --git a/ts/components/conversation/SubtleNotification.tsx b/ts/components/conversation/SubtleNotification.tsx index be4e50bf8..02840cb98 100644 --- a/ts/components/conversation/SubtleNotification.tsx +++ b/ts/components/conversation/SubtleNotification.tsx @@ -25,6 +25,7 @@ import { useLibGroupInviteGroupName, useLibGroupInvitePending, useLibGroupKicked, + useLibGroupWeHaveSecretKey, } from '../../state/selectors/userGroups'; import { LocalizerKeys } from '../../types/LocalizerKeys'; import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer'; @@ -119,6 +120,8 @@ export const InvitedToGroupControlMessage = () => { const adminNameInvitedUs = useNicknameOrProfileNameOrShortenedPubkey(conversationOrigin) || window.i18n('unknown'); const isGroupPendingInvite = useLibGroupInvitePending(selectedConversation); + const weHaveSecretKey = useLibGroupWeHaveSecretKey(selectedConversation); + if ( !selectedConversation || isApproved || @@ -131,8 +134,12 @@ export const InvitedToGroupControlMessage = () => { } // when restoring from seed we might not have the pubkey of who invited us, in that case, we just use a fallback const html = conversationOrigin - ? window.i18n('userInvitedYouToGroup', [adminNameInvitedUs, groupName]) - : window.i18n('youWereInvitedToGroup', [groupName]); + ? weHaveSecretKey + ? window.i18n('userInvitedYouToGroupAsAdmin', [adminNameInvitedUs, groupName]) + : window.i18n('userInvitedYouToGroup', [adminNameInvitedUs, groupName]) + : weHaveSecretKey + ? window.i18n('youWereInvitedToGroupAsAdmin', [groupName]) + : window.i18n('youWereInvitedToGroup', [groupName]); return ( { data-testid="message-input-text-area" style={style} suggestionsPortalHost={this.container as any} - forceSuggestionsAboveCursor={true} // force mentions to be rendered on top of the cursor, this is working with a fork of react-mentions for now + forceSuggestionsAboveCursor={true} > { {hasContacts && isGroupV2 && } - {isGroupV2 && ( + + {/* TODO: localize those strings once out releasing those buttons for real */} + {isGroupV2 && isDevProd() && ( <> Share History?{' '} diff --git a/ts/components/dialog/SessionConfirm.tsx b/ts/components/dialog/SessionConfirm.tsx index 32e0d8a2b..c171dfd55 100644 --- a/ts/components/dialog/SessionConfirm.tsx +++ b/ts/components/dialog/SessionConfirm.tsx @@ -219,17 +219,11 @@ export const SessionConfirm = (props: SessionConfirmDialogProps) => { ); }; - - - - export const showLinkVisitWarningDialog = (urlToOpen: string, dispatch: Dispatch) => { function onClickOk() { void shell.openExternal(urlToOpen); } - - dispatch( updateConfirmModal({ title: window.i18n('linkVisitWarningTitle'), diff --git a/ts/components/leftpane/ActionsPanel.tsx b/ts/components/leftpane/ActionsPanel.tsx index cbd1f3fcb..fef26e3b5 100644 --- a/ts/components/leftpane/ActionsPanel.tsx +++ b/ts/components/leftpane/ActionsPanel.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-await-in-loop */ import { ipcRenderer } from 'electron'; import React, { useEffect, useState } from 'react'; diff --git a/ts/components/leftpane/overlay/OverlayClosedGroup.tsx b/ts/components/leftpane/overlay/OverlayClosedGroup.tsx index fe8db6d1b..03569a6dd 100644 --- a/ts/components/leftpane/overlay/OverlayClosedGroup.tsx +++ b/ts/components/leftpane/overlay/OverlayClosedGroup.tsx @@ -5,6 +5,7 @@ import useKey from 'react-use/lib/useKey'; import styled from 'styled-components'; import { concat } from 'lodash'; +import useUpdate from 'react-use/lib/useUpdate'; import { MemberListItem } from '../../MemberListItem'; import { SessionButton } from '../../basic/SessionButton'; import { SessionIdEditable } from '../../basic/SessionIdEditable'; @@ -24,6 +25,8 @@ import { useOurPkStr } from '../../../state/selectors/user'; import { SessionSearchInput } from '../../SessionSearchInput'; import { SpacerLG } from '../../basic/Text'; import { GroupInviteRequiredVersionBanner } from '../../NoticeBanner'; +import { isDevProd } from '../../../shared/env_vars'; +import { SessionToggle } from '../../basic/SessionToggle'; const StyledMemberListNoContacts = styled.div` font-family: var(--font-mono), var(--font-default); @@ -100,6 +103,7 @@ export const OverlayClosedGroupV2 = () => { const privateContactsPubkeys = useContactsToInviteToGroup(); const isCreatingGroup = useIsCreatingGroupFromUIPending(); const [groupName, setGroupName] = useState(''); + const forceUpdate = useUpdate(); const { uniqueValues: members, addTo: addToSelected, @@ -177,6 +181,22 @@ export const OverlayClosedGroupV2 = () => { /> + {/* TODO: localize those strings once out releasing those buttons for real */} + {isDevProd() && ( + <> + + Invite as admin?{' '} + { + window.sessionFeatureFlags.useGroupV2InviteAsAdmin = + !window.sessionFeatureFlags.useGroupV2InviteAsAdmin; + forceUpdate(); + }} + /> + + + )} {!noContactsForClosedGroup && window.sessionFeatureFlags.useClosedGroupV2 && ( diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 7e86e7387..4860e1c61 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -1101,7 +1101,7 @@ export class ConversationModel extends Backbone.Model { if (this.isClosedGroup()) { if (this.isAdmin(UserUtils.getOurPubKeyStrFromCache())) { if (this.isClosedGroupV2()) { - if (!PubKey.is03Pubkey(this.id)) { + if (!PubKey.is03Pubkey(this.id)) { throw new Error('updateExpireTimer v2 group requires a 03 key'); } const group = await UserGroupsWrapperActions.getGroup(this.id); diff --git a/ts/receiver/groupv2/handleGroupV2Message.ts b/ts/receiver/groupv2/handleGroupV2Message.ts index 38d2c5a7d..88f45c170 100644 --- a/ts/receiver/groupv2/handleGroupV2Message.ts +++ b/ts/receiver/groupv2/handleGroupV2Message.ts @@ -1,6 +1,7 @@ import { GroupPubkeyType, PubkeyType, WithGroupPubkey } from 'libsession_util_nodejs'; import { compact, isEmpty, isFinite, isNumber } from 'lodash'; import { Data } from '../../data/data'; +import { deleteAllMessagesByConvoIdNoConfirmation } from '../../interactions/conversationInteractions'; import { deleteMessagesFromSwarmOnly } from '../../interactions/conversations/unsendingInteractions'; import { ConversationTypeEnum } from '../../models/conversationAttributes'; import { HexString } from '../../node/hexStrings'; @@ -14,6 +15,7 @@ import { WithDisappearingMessageUpdate } from '../../session/disappearing_messag import { ClosedGroup } from '../../session/group/closed-group'; import { GroupUpdateInviteResponseMessage } from '../../session/messages/outgoing/controlMessage/group_v2/to_group/GroupUpdateInviteResponseMessage'; import { PubKey } from '../../session/types'; +import { WithMessageHash } from '../../session/types/with'; import { UserUtils } from '../../session/utils'; import { sleepFor } from '../../session/utils/Promise'; import { ed25519Str, stringToUint8Array } from '../../session/utils/String'; @@ -29,8 +31,6 @@ import { MetaGroupWrapperActions, UserGroupsWrapperActions, } from '../../webworker/workers/browser/libsession_worker_interface'; -import { WithMessageHash } from '../../session/types/with'; -import { deleteAllMessagesByConvoIdNoConfirmation } from '../../interactions/conversationInteractions'; type WithSignatureTimestamp = { signatureTimestamp: number }; type WithAuthor = { author: PubkeyType }; @@ -503,41 +503,109 @@ async function handleGroupUpdateInviteResponseMessage({ async function handleGroupUpdatePromoteMessage({ change, + author, + signatureTimestamp, }: Omit, 'groupPk'>) { const seed = change.groupIdentitySeed; const sodium = await getSodiumRenderer(); const groupKeypair = sodium.crypto_sign_seed_keypair(seed); const groupPk = `03${HexString.toHexString(groupKeypair.publicKey)}` as GroupPubkeyType; + // we can be invited via a GroupUpdatePromoteMessage as an admin right away, + // so we potentially need to deal with part of the invite process here too. - const convo = ConvoHub.use().get(groupPk); - if (!convo) { + if (BlockedNumberController.isBlocked(author)) { + window.log.info( + `received promote to group ${ed25519Str(groupPk)} by blocked user:${ed25519Str( + author + )}... dropping it` + ); return; } - window.log.info(`handleGroupUpdatePromoteMessage for ${ed25519Str(groupPk)}`); - // no group update message here, another message is sent to the group's swarm for the update message. - // this message is just about the keys that we need to save, and accepting the promotion. + const authorIsApproved = ConvoHub.use().get(author)?.isApproved() || false; + window.log.info( + `received promote to group ${ed25519Str(groupPk)} by author:${ed25519Str(author)}. authorIsApproved:${authorIsApproved} ` + ); - const found = await UserGroupsWrapperActions.getGroup(groupPk); + const convo = await ConvoHub.use().getOrCreateAndWait(groupPk, ConversationTypeEnum.GROUPV2); + convo.set({ + active_at: signatureTimestamp, + didApproveMe: true, + conversationIdOrigin: author, + }); + + if (change.name && isEmpty(convo.getRealSessionUsername())) { + convo.set({ + displayNameInProfile: change.name, + }); + } + const userEd25519Secretkey = (await UserUtils.getUserED25519KeyPairBytes()).privKeyBytes; + + let found = await UserGroupsWrapperActions.getGroup(groupPk); + const wasKicked = found?.kicked || false; if (!found) { - // could have been removed by the user already so let's not force create it - window.log.info( - 'received group promote message but that group is not in the usergroups wrapper' - ); - return; + found = { + authData: null, + joinedAtSeconds: Date.now(), + name: change.name, + priority: 0, + pubkeyHex: groupPk, + secretKey: groupKeypair.privateKey, + kicked: false, + invitePending: true, + }; + } else { + found.kicked = false; + found.name = change.name; + found.secretKey = groupKeypair.privateKey; + } + if (authorIsApproved) { + // pre approve invite to groups when we've already approved the person who invited us + found.invitePending = false; } - found.secretKey = groupKeypair.privateKey; + await UserGroupsWrapperActions.setGroup(found); - await UserSync.queueNewJobIfNeeded(); + // force markedAsUnread to be true so it shows the unread banner (we only show the banner if there are unread messages on at least one msg/group request) + await convo.markAsUnread(true, false); + await convo.commit(); - window.inboxStore.dispatch( - groupInfoActions.markUsAsAdmin({ - groupPk, - secret: groupKeypair.privateKey, - }) - ); + await SessionUtilConvoInfoVolatile.insertConvoFromDBIntoWrapperAndRefresh(convo.id); + + if (wasKicked) { + // we have been reinvited to a group which we had been kicked from. + // Let's empty the conversation again to remove any "you were removed from the group" control message + await deleteAllMessagesByConvoIdNoConfirmation(groupPk); + } + try { + await MetaGroupWrapperActions.init(groupPk, { + metaDumped: null, + groupEd25519Secretkey: groupKeypair.privateKey, + userEd25519Secretkey: toFixedUint8ArrayOfLength(userEd25519Secretkey, 64).buffer, + groupEd25519Pubkey: toFixedUint8ArrayOfLength(HexString.fromHexStringNoPrefix(groupPk), 32) + .buffer, + }); + } catch (e) { + window.log.warn( + `handleGroupUpdatePromoteMessage: init of ${ed25519Str(groupPk)} failed with ${e.message}. Trying to just load admin keys` + ); + try { + await MetaGroupWrapperActions.loadAdminKeys(groupPk, groupKeypair.privateKey); + } catch (e2) { + window.log.warn( + `handleGroupUpdatePromoteMessage: loadAdminKeys of ${ed25519Str(groupPk)} failed with ${e.message}` + ); + } + } + + await LibSessionUtil.saveDumpsToDb(UserUtils.getOurPubKeyStrFromCache()); + await UserSync.queueNewJobIfNeeded(); + if (!found.invitePending) { + // This group should already be polling based on if that author is pre-approved or we've already approved that group from another device. + // Start polling from it, we will mark ourselves as admin once we get the first merge result, if needed. + getSwarmPollingInstance().addGroupId(groupPk); + } } async function handle1o1GroupUpdateMessage( diff --git a/ts/session/apis/snode_api/signature/groupSignature.ts b/ts/session/apis/snode_api/signature/groupSignature.ts index 3264dafd5..30ac61e71 100644 --- a/ts/session/apis/snode_api/signature/groupSignature.ts +++ b/ts/session/apis/snode_api/signature/groupSignature.ts @@ -61,12 +61,12 @@ async function getGroupPromoteMessage({ member, secretKey, groupPk, - name, + groupName, }: { member: PubkeyType; secretKey: Uint8ArrayLen64; // len 64 groupPk: GroupPubkeyType; - name: string; + groupName: string; }) { const createAtNetworkTimestamp = GetNetworkTime.now(); @@ -80,7 +80,7 @@ async function getGroupPromoteMessage({ groupIdentitySeed: secretKey.slice(0, 32), // the seed is the first 32 bytes of the secretkey expirationType: 'unknown', // a promote message is not expiring expireTimer: 0, - name, + groupName, }); return msg; } diff --git a/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts b/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts index 0d2e9c220..6911bf2ee 100644 --- a/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts +++ b/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts @@ -15,6 +15,8 @@ import { SnodeNamespaces } from '../namespaces'; import { RetrieveMessageItemWithNamespace } from '../types'; import { ConvoHub } from '../../../conversations'; import { ProfileManager } from '../../../profile_manager/ProfileManager'; +import { UserUtils } from '../../../utils'; +import { GroupSync } from '../../../utils/job_runners/jobs/GroupSyncJob'; /** * This is a basic optimization to avoid running the logic when the `deleteBeforeSeconds` @@ -36,74 +38,105 @@ async function handleMetaMergeResults(groupPk: GroupPubkeyType) { to_hex(dumps) ); } - if (infos) { - if (infos.isDestroyed) { - window.log.info(`${ed25519Str(groupPk)} is marked as destroyed after merge. Removing it.`); - await ConvoHub.use().deleteGroup(groupPk, { - sendLeaveMessage: false, - fromSyncMessage: false, - emptyGroupButKeepAsKicked: true, // we just got something from the group's swarm, so it is not pendingInvite - deleteAllMessagesOnSwarm: false, - forceDestroyForAllMembers: false, + if (infos.isDestroyed) { + window.log.info(`${ed25519Str(groupPk)} is marked as destroyed after merge. Removing it.`); + await ConvoHub.use().deleteGroup(groupPk, { + sendLeaveMessage: false, + fromSyncMessage: false, + emptyGroupButKeepAsKicked: true, // we just got something from the group's swarm, so it is not pendingInvite + deleteAllMessagesOnSwarm: false, + forceDestroyForAllMembers: false, + }); + } else { + if ( + isNumber(infos.deleteBeforeSeconds) && + isFinite(infos.deleteBeforeSeconds) && + infos.deleteBeforeSeconds > 0 && + (lastAppliedRemoveMsgSentBeforeSeconds.get(groupPk) || Number.MAX_SAFE_INTEGER) > + infos.deleteBeforeSeconds + ) { + // delete any messages in this conversation sent before that timestamp (in seconds) + const deletedMsgIds = await Data.removeAllMessagesInConversationSentBefore({ + deleteBeforeSeconds: infos.deleteBeforeSeconds, + conversationId: groupPk, }); - } else { - if ( - isNumber(infos.deleteBeforeSeconds) && - isFinite(infos.deleteBeforeSeconds) && - infos.deleteBeforeSeconds > 0 && - (lastAppliedRemoveMsgSentBeforeSeconds.get(groupPk) || Number.MAX_SAFE_INTEGER) > - infos.deleteBeforeSeconds - ) { - // delete any messages in this conversation sent before that timestamp (in seconds) - const deletedMsgIds = await Data.removeAllMessagesInConversationSentBefore({ - deleteBeforeSeconds: infos.deleteBeforeSeconds, - conversationId: groupPk, - }); - window.log.info( - `removeAllMessagesInConversationSentBefore of ${ed25519Str(groupPk)} before ${infos.deleteBeforeSeconds}: `, - deletedMsgIds - ); - window.inboxStore.dispatch( - messagesExpired(deletedMsgIds.map(messageId => ({ conversationKey: groupPk, messageId }))) - ); - lastAppliedRemoveMsgSentBeforeSeconds.set(groupPk, infos.deleteBeforeSeconds); - } + window.log.info( + `removeAllMessagesInConversationSentBefore of ${ed25519Str(groupPk)} before ${infos.deleteBeforeSeconds}: `, + deletedMsgIds + ); + window.inboxStore.dispatch( + messagesExpired(deletedMsgIds.map(messageId => ({ conversationKey: groupPk, messageId }))) + ); + lastAppliedRemoveMsgSentBeforeSeconds.set(groupPk, infos.deleteBeforeSeconds); + } + + if ( + isNumber(infos.deleteAttachBeforeSeconds) && + isFinite(infos.deleteAttachBeforeSeconds) && + infos.deleteAttachBeforeSeconds > 0 && + (lastAppliedRemoveAttachmentSentBeforeSeconds.get(groupPk) || Number.MAX_SAFE_INTEGER) > + infos.deleteAttachBeforeSeconds + ) { + // delete any attachments in this conversation sent before that timestamp (in seconds) + const impactedMsgModels = await Data.getAllMessagesWithAttachmentsInConversationSentBefore({ + deleteAttachBeforeSeconds: infos.deleteAttachBeforeSeconds, + conversationId: groupPk, + }); + window.log.info( + `getAllMessagesWithAttachmentsInConversationSentBefore of ${ed25519Str(groupPk)} before ${infos.deleteAttachBeforeSeconds}: impactedMsgModelsIds `, + impactedMsgModels.map(m => m.id) + ); - if ( - isNumber(infos.deleteAttachBeforeSeconds) && - isFinite(infos.deleteAttachBeforeSeconds) && - infos.deleteAttachBeforeSeconds > 0 && - (lastAppliedRemoveAttachmentSentBeforeSeconds.get(groupPk) || Number.MAX_SAFE_INTEGER) > - infos.deleteAttachBeforeSeconds - ) { - // delete any attachments in this conversation sent before that timestamp (in seconds) - const impactedMsgModels = await Data.getAllMessagesWithAttachmentsInConversationSentBefore({ - deleteAttachBeforeSeconds: infos.deleteAttachBeforeSeconds, - conversationId: groupPk, - }); - window.log.info( - `getAllMessagesWithAttachmentsInConversationSentBefore of ${ed25519Str(groupPk)} before ${infos.deleteAttachBeforeSeconds}: impactedMsgModelsIds `, - impactedMsgModels.map(m => m.id) - ); - - for (let index = 0; index < impactedMsgModels.length; index++) { - const msg = impactedMsgModels[index]; - - // eslint-disable-next-line no-await-in-loop - await msg?.cleanup(); - } - lastAppliedRemoveAttachmentSentBeforeSeconds.set(groupPk, infos.deleteAttachBeforeSeconds); + for (let index = 0; index < impactedMsgModels.length; index++) { + const msg = impactedMsgModels[index]; + + // eslint-disable-next-line no-await-in-loop + await msg?.cleanup(); } + lastAppliedRemoveAttachmentSentBeforeSeconds.set(groupPk, infos.deleteAttachBeforeSeconds); } - const membersWithPendingRemovals = - await MetaGroupWrapperActions.memberGetAllPendingRemovals(groupPk); - if (membersWithPendingRemovals.length) { - const group = await UserGroupsWrapperActions.getGroup(groupPk); - if (group && group.secretKey && !isEmpty(group.secretKey)) { - await GroupPendingRemovals.addJob({ groupPk }); - } + } + const membersWithPendingRemovals = + await MetaGroupWrapperActions.memberGetAllPendingRemovals(groupPk); + if (membersWithPendingRemovals.length) { + const group = await UserGroupsWrapperActions.getGroup(groupPk); + if (group && group.secretKey && !isEmpty(group.secretKey)) { + await GroupPendingRemovals.addJob({ groupPk }); } } + + const us = UserUtils.getOurPubKeyStrFromCache(); + const usMember = await MetaGroupWrapperActions.memberGet(groupPk, us); + let keysAlreadyHaveAdmin = await MetaGroupWrapperActions.keysAdmin(groupPk); + const secretKeyInUserWrapper = (await UserGroupsWrapperActions.getGroup(groupPk))?.secretKey; + + // load admin keys if needed + if ( + usMember && + secretKeyInUserWrapper && + !isEmpty(secretKeyInUserWrapper) && + !keysAlreadyHaveAdmin + ) { + try { + await MetaGroupWrapperActions.loadAdminKeys(groupPk, secretKeyInUserWrapper); + keysAlreadyHaveAdmin = await MetaGroupWrapperActions.keysAdmin(groupPk); + } catch (e) { + window.log.warn( + `tried to update our adminKeys/state for group ${ed25519Str(groupPk)} but failed with, ${e.message}` + ); + } + } + // mark ourselves as accepting the invite if needed + if (usMember?.invitePending && keysAlreadyHaveAdmin) { + await MetaGroupWrapperActions.memberSetAccepted(groupPk, us); + } + // mark ourselves as accepting the promotion if needed + if (usMember?.promotionPending && keysAlreadyHaveAdmin) { + await MetaGroupWrapperActions.memberSetPromotionAccepted(groupPk, us); + } + // this won't do anything if there is no need for a sync, so we can safely plan one + await GroupSync.queueNewJobIfNeeded(groupPk); + const convo = ConvoHub.use().get(groupPk); const refreshedInfos = await MetaGroupWrapperActions.infoGet(groupPk); diff --git a/ts/session/conversations/ConversationController.ts b/ts/session/conversations/ConversationController.ts index b2c15c5b2..9892cdd94 100644 --- a/ts/session/conversations/ConversationController.ts +++ b/ts/session/conversations/ConversationController.ts @@ -319,7 +319,7 @@ class ConvoController { const us = UserUtils.getOurPubKeyStrFromCache(); const allMembers = await MetaGroupWrapperActions.memberGetAll(groupPk); const otherAdminsCount = allMembers - .filter(m => m.admin || m.promoted) + .filter(m => m.promoted) .filter(m => m.pubkeyHex !== us).length; const weAreLastAdmin = otherAdminsCount === 0; const infos = await MetaGroupWrapperActions.infoGet(groupPk); diff --git a/ts/session/messages/outgoing/controlMessage/group_v2/to_user/GroupUpdatePromoteMessage.ts b/ts/session/messages/outgoing/controlMessage/group_v2/to_user/GroupUpdatePromoteMessage.ts index 5c5c20283..312af61a3 100644 --- a/ts/session/messages/outgoing/controlMessage/group_v2/to_user/GroupUpdatePromoteMessage.ts +++ b/ts/session/messages/outgoing/controlMessage/group_v2/to_user/GroupUpdatePromoteMessage.ts @@ -5,7 +5,7 @@ import { GroupUpdateMessage, GroupUpdateMessageParams } from '../GroupUpdateMess interface Params extends GroupUpdateMessageParams { groupPk: GroupPubkeyType; groupIdentitySeed: Uint8Array; - name: string; + groupName: string; } /** @@ -13,17 +13,17 @@ interface Params extends GroupUpdateMessageParams { */ export class GroupUpdatePromoteMessage extends GroupUpdateMessage { public readonly groupIdentitySeed: Params['groupIdentitySeed']; - public readonly name: Params['name']; + public readonly groupName: Params['groupName']; constructor(params: Params) { super(params); this.groupIdentitySeed = params.groupIdentitySeed; - this.name = params.name; + this.groupName = params.groupName; if (!this.groupIdentitySeed || this.groupIdentitySeed.length !== 32) { throw new Error('groupIdentitySeed must be set'); } - if (!this.name) { + if (!this.groupName) { throw new Error('name must be set and not empty'); } } @@ -31,7 +31,7 @@ export class GroupUpdatePromoteMessage extends GroupUpdateMessage { public dataProto(): SignalService.DataMessage { const promoteMessage = new SignalService.GroupUpdatePromoteMessage({ groupIdentitySeed: this.groupIdentitySeed, - name: this.name, + name: this.groupName, }); return new SignalService.DataMessage({ diff --git a/ts/session/onions/onionSend.ts b/ts/session/onions/onionSend.ts index ccef16b06..20c57e73a 100644 --- a/ts/session/onions/onionSend.ts +++ b/ts/session/onions/onionSend.ts @@ -17,7 +17,6 @@ import { FinalRelayOptions, Onions, STATUS_NO_STATUS, - SnodeResponse, buildErrorMessageWithFailedCode, } from '../apis/snode_api/onions'; import { PROTOCOLS } from '../constants'; @@ -75,12 +74,6 @@ const getOnionPathForSending = async () => { return pathNodes; }; -export type OnionSnodeResponse = { - result: SnodeResponse; - txtResponse: string; - response: string; -}; - export type OnionV4SnodeResponse = { body: string | object | null; // if the content can be decoded as string bodyBinary: Uint8Array | null; // otherwise we return the raw content (could be an image data or file from sogs/fileserver) diff --git a/ts/session/utils/job_runners/jobs/GroupInviteJob.ts b/ts/session/utils/job_runners/jobs/GroupInviteJob.ts index 9fb8aba10..f26de93a3 100644 --- a/ts/session/utils/job_runners/jobs/GroupInviteJob.ts +++ b/ts/session/utils/job_runners/jobs/GroupInviteJob.ts @@ -153,12 +153,19 @@ class GroupInviteJob extends PersistedJob { } let failed = true; try { - const inviteDetails = await SnodeGroupSignature.getGroupInviteMessage({ - groupName: group.name, - member, - secretKey: group.secretKey, - groupPk, - }); + const inviteDetails = window.sessionFeatureFlags.useGroupV2InviteAsAdmin + ? await SnodeGroupSignature.getGroupPromoteMessage({ + groupName: group.name, + member, + secretKey: group.secretKey, + groupPk, + }) + : await SnodeGroupSignature.getGroupInviteMessage({ + groupName: group.name, + member, + secretKey: group.secretKey, + groupPk, + }); const storedAt = await getMessageQueue().sendTo1o1NonDurably({ message: inviteDetails, diff --git a/ts/session/utils/job_runners/jobs/GroupPendingRemovalsJob.ts b/ts/session/utils/job_runners/jobs/GroupPendingRemovalsJob.ts index b8cb17aa8..b40d6d42e 100644 --- a/ts/session/utils/job_runners/jobs/GroupPendingRemovalsJob.ts +++ b/ts/session/utils/job_runners/jobs/GroupPendingRemovalsJob.ts @@ -134,7 +134,7 @@ class GroupPendingRemovalsJob extends PersistedJob m.removedStatus === 2) + .filter(m => m.shouldRemoveMessages) .map(m => m.pubkeyHex); const sessionIdsHex = pendingRemovals.map(m => m.pubkeyHex); diff --git a/ts/session/utils/job_runners/jobs/GroupPromoteJob.ts b/ts/session/utils/job_runners/jobs/GroupPromoteJob.ts index 9ae1675d2..0c8ccd4e7 100644 --- a/ts/session/utils/job_runners/jobs/GroupPromoteJob.ts +++ b/ts/session/utils/job_runners/jobs/GroupPromoteJob.ts @@ -102,7 +102,7 @@ class GroupPromoteJob extends PersistedJob { member, secretKey: group.secretKey, groupPk, - name: group.name, + groupName: group.name, }); const storedAt = await getMessageQueue().sendTo1o1NonDurably({ @@ -118,7 +118,11 @@ class GroupPromoteJob extends PersistedJob { groupInfoActions.setPromotionPending({ groupPk, pubkey: member, sending: false }) ); try { - await MetaGroupWrapperActions.memberSetPromoted(groupPk, member, failed); + if (failed) { + await MetaGroupWrapperActions.memberSetPromotionFailed(groupPk, member); + } else { + await MetaGroupWrapperActions.memberSetPromotionSent(groupPk, member); + } } catch (e) { window.log.warn('GroupPromoteJob memberSetPromoted failed with', e.message); } diff --git a/ts/session/utils/libsession/libsession_utils.ts b/ts/session/utils/libsession/libsession_utils.ts index f649e94d6..024470596 100644 --- a/ts/session/utils/libsession/libsession_utils.ts +++ b/ts/session/utils/libsession/libsession_utils.ts @@ -410,7 +410,7 @@ async function createMemberAndSetDetails({ await MetaGroupWrapperActions.memberConstructAndSet(groupPk, memberPubkey); if (displayName) { - await MetaGroupWrapperActions.memberSetName(groupPk, memberPubkey, displayName); + await MetaGroupWrapperActions.memberSetNameTruncated(groupPk, memberPubkey, displayName); } if (profileKeyHex && avatarUrl) { await MetaGroupWrapperActions.memberSetProfilePicture(groupPk, memberPubkey, { diff --git a/ts/state/ducks/metaGroups.ts b/ts/state/ducks/metaGroups.ts index aaee96d39..bb3259c6e 100644 --- a/ts/state/ducks/metaGroups.ts +++ b/ts/state/ducks/metaGroups.ts @@ -156,6 +156,8 @@ const initNewGroupInWrapper = createAsyncThunk( const profileKeyHex = convoMember?.getProfileKey() || null; const avatarUrl = convoMember?.getAvatarPointer() || null; + // we just create the members in the state. Their invite state defaults to NOT_SENT, + // which will make our logic kick in to send them an invite in the `GroupInviteJob` await LibSessionUtil.createMemberAndSetDetails({ avatarUrl, displayName, @@ -165,9 +167,8 @@ const initNewGroupInWrapper = createAsyncThunk( }); if (member === us) { - await MetaGroupWrapperActions.memberSetAdmin(groupPk, member); - } else { - await MetaGroupWrapperActions.memberSetInvited(groupPk, member, false); + // we need to excplicitely mark us as having accepted the promotion + await MetaGroupWrapperActions.memberSetPromotionAccepted(groupPk, member); } } @@ -1122,48 +1123,6 @@ const handleMemberLeftMessage = createAsyncThunk( } ); -const markUsAsAdmin = createAsyncThunk( - 'group/markUsAsAdmin', - async ( - { - groupPk, - secret, - }: { - groupPk: GroupPubkeyType; - secret: Uint8ArrayLen64; - }, - payloadCreator - ): Promise => { - const state = payloadCreator.getState() as StateType; - if (!state.groups.infos[groupPk] || !state.groups.members[groupPk]) { - throw new PreConditionFailed('markUsAsAdmin group not present in redux slice'); - } - if (secret.length !== 64) { - throw new PreConditionFailed('markUsAsAdmin secret needs to be 64'); - } - await MetaGroupWrapperActions.loadAdminKeys(groupPk, secret); - const us = UserUtils.getOurPubKeyStrFromCache(); - - if (state.groups.members[groupPk].find(m => m.pubkeyHex === us)?.admin) { - // we are already an admin, nothing to do - return { - groupPk, - infos: await MetaGroupWrapperActions.infoGet(groupPk), - members: await MetaGroupWrapperActions.memberGetAll(groupPk), - }; - } - await MetaGroupWrapperActions.memberSetAdmin(groupPk, us); - - await GroupSync.queueNewJobIfNeeded(groupPk); - - return { - groupPk, - infos: await MetaGroupWrapperActions.infoGet(groupPk), - members: await MetaGroupWrapperActions.memberGetAll(groupPk), - }; - } -); - const inviteResponseReceived = createAsyncThunk( 'group/inviteResponseReceived', async ( @@ -1443,21 +1402,6 @@ const metaGroupSlice = createSlice({ window.log.error('a handleMemberLeftMessage was rejected', action.error); }); - /** markUsAsAdmin */ - builder.addCase(markUsAsAdmin.fulfilled, (state, action) => { - const { infos, members, groupPk } = action.payload; - state.infos[groupPk] = infos; - state.members[groupPk] = members; - refreshConvosModelProps([groupPk]); - if (window.sessionFeatureFlags.debug.debugLibsessionDumps) { - window.log.info(`groupInfo after markUsAsAdmin: ${stringify(infos)}`); - window.log.info(`groupMembers after markUsAsAdmin: ${stringify(members)}`); - } - }); - builder.addCase(markUsAsAdmin.rejected, (_state, action) => { - window.log.error('a markUsAsAdmin was rejected', action.error); - }); - builder.addCase(inviteResponseReceived.fulfilled, (state, action) => { const { infos, members, groupPk } = action.payload; state.infos[groupPk] = infos; @@ -1488,7 +1432,6 @@ export const groupInfoActions = { refreshGroupDetailsFromWrapper, handleUserGroupUpdate, currentDeviceGroupMembersChange, - markUsAsAdmin, inviteResponseReceived, handleMemberLeftMessage, currentDeviceGroupNameChange, diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 7f70a6fd6..a747f12e9 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -1,4 +1,5 @@ /* eslint-disable no-restricted-syntax */ + import { createSelector } from '@reduxjs/toolkit'; import { filter, isEmpty, isFinite, isNumber, pick, sortBy, toNumber } from 'lodash'; diff --git a/ts/state/selectors/userGroups.ts b/ts/state/selectors/userGroups.ts index 42c3b59a7..1c2b33704 100644 --- a/ts/state/selectors/userGroups.ts +++ b/ts/state/selectors/userGroups.ts @@ -1,4 +1,5 @@ import { useSelector } from 'react-redux'; +import { isEmpty } from 'lodash'; import { PubKey } from '../../session/types'; import { UserGroupState } from '../ducks/userGroups'; import { StateType } from '../reducer'; @@ -11,6 +12,12 @@ const getGroupById = (state: StateType, convoId?: string) => { : undefined; }; +export function useLibGroupWeHaveSecretKey(convoId?: string) { + return useSelector((state: StateType) => { + return !isEmpty(getGroupById(state, convoId)?.secretKey); + }); +} + export function useLibGroupInvitePending(convoId?: string) { return useSelector((state: StateType) => getGroupById(state, convoId)?.invitePending); } 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 52bab12b2..6e756ee9c 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 @@ -28,8 +28,11 @@ function emptyMember(pubkeyHex: PubkeyType): GroupMemberGet { promoted: false, promotionFailed: false, promotionPending: false, - admin: false, - removedStatus: 0, + inviteAccepted: false, + inviteNotSent: false, + isRemoved: false, + promotionNotSent: false, + shouldRemoveMessages: false, pubkeyHex, }; } @@ -166,7 +169,7 @@ describe('libsession_metagroup', () => { }); it('can add member by setting its promoted state, both ok and nok', () => { - metaGroupWrapper.memberSetPromoted(member, false); + metaGroupWrapper.memberSetPromotionSent(member); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq({ ...emptyMember(member), @@ -176,7 +179,7 @@ describe('libsession_metagroup', () => { admin: false, }); - metaGroupWrapper.memberSetPromoted(member2, true); + metaGroupWrapper.memberSetPromotionFailed(member2); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(2); // the list is sorted by member pk, which means that index based test do not work expect(metaGroupWrapper.memberGet(member2)).to.be.deep.eq({ @@ -224,7 +227,7 @@ describe('libsession_metagroup', () => { it('can erase member', () => { metaGroupWrapper.memberSetAccepted(member); - metaGroupWrapper.memberSetPromoted(member2, false); + metaGroupWrapper.memberSetPromoted(member2); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(2); expect(metaGroupWrapper.memberGet(member)).to.be.deep.eq({ @@ -245,7 +248,7 @@ describe('libsession_metagroup', () => { }); it('can add via name set', () => { - metaGroupWrapper.memberSetName(member, 'member name'); + metaGroupWrapper.memberSetNameTruncated(member, 'member name'); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq({ ...emptyMember(member), @@ -263,14 +266,14 @@ describe('libsession_metagroup', () => { }); it('can add via admin set', () => { - metaGroupWrapper.memberSetAdmin(member); + metaGroupWrapper.memberSetPromotionAccepted(member); expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); const expected: GroupMemberGet = { ...emptyMember(member), - admin: true, promoted: true, promotionFailed: false, promotionPending: false, + promotionNotSent: false, }; expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq(expected); @@ -281,7 +284,8 @@ describe('libsession_metagroup', () => { expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); const expected: GroupMemberGet = { ...emptyMember(member), - removedStatus: 2, + shouldRemoveMessages: true, + isRemoved: true, }; expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq(expected); @@ -292,7 +296,8 @@ describe('libsession_metagroup', () => { expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); const expected: GroupMemberGet = { ...emptyMember(member), - removedStatus: 1, + shouldRemoveMessages: false, + isRemoved: true, }; expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1); expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq(expected); @@ -319,8 +324,8 @@ describe('libsession_metagroup', () => { }); // mark current user as admin - metaGroupWrapper.memberSetPromoted(us.x25519KeyPair.pubkeyHex, false); - metaGroupWrapper2.memberSetPromoted(us.x25519KeyPair.pubkeyHex, false); + metaGroupWrapper.memberSetPromotionAccepted(us.x25519KeyPair.pubkeyHex); + metaGroupWrapper2.memberSetPromotionAccepted(us.x25519KeyPair.pubkeyHex); // add 2 normal members to each of those wrappers const m1 = TestUtils.generateFakePubKeyStr(); diff --git a/ts/types/LocalizerKeys.ts b/ts/types/LocalizerKeys.ts index b27eea432..653dfe0c0 100644 --- a/ts/types/LocalizerKeys.ts +++ b/ts/types/LocalizerKeys.ts @@ -582,6 +582,7 @@ export type LocalizerKeys = | 'userBanFailed' | 'userBanned' | 'userInvitedYouToGroup' + | 'userInvitedYouToGroupAsAdmin' | 'userRemovedFromModerators' | 'userUnbanFailed' | 'userUnbanned' @@ -609,6 +610,7 @@ export type LocalizerKeys = | 'youLeftTheGroup' | 'youSetYourDisappearingMessages' | 'youWereInvitedToGroup' + | 'youWereInvitedToGroupAsAdmin' | 'youWereRemovedFrom' | 'yourSessionID' | 'yourUniqueSessionID' diff --git a/ts/webworker/workers/browser/libsession_worker_interface.ts b/ts/webworker/workers/browser/libsession_worker_interface.ts index 886c27835..0cfe2e536 100644 --- a/ts/webworker/workers/browser/libsession_worker_interface.ts +++ b/ts/webworker/workers/browser/libsession_worker_interface.ts @@ -548,17 +548,29 @@ export const MetaGroupWrapperActions: MetaGroupWrapperActionsCalls = { callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberSetAccepted', pubkeyHex]) as Promise< ReturnType >, - memberSetPromoted: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType, failed: boolean) => + memberSetPromoted: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberSetPromoted', pubkeyHex]) as Promise< + ReturnType + >, + memberSetPromotionAccepted: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType) => callLibSessionWorker([ `MetaGroupConfig-${groupPk}`, - 'memberSetPromoted', + 'memberSetPromotionAccepted', pubkeyHex, - failed, - ]) as Promise>, - memberSetAdmin: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType) => - callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberSetAdmin', pubkeyHex]) as Promise< - ReturnType - >, + ]) as Promise>, + memberSetPromotionFailed: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType) => + callLibSessionWorker([ + `MetaGroupConfig-${groupPk}`, + 'memberSetPromotionFailed', + pubkeyHex, + ]) as Promise>, + memberSetPromotionSent: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType) => + callLibSessionWorker([ + `MetaGroupConfig-${groupPk}`, + 'memberSetPromotionSent', + pubkeyHex, + ]) as Promise>, + memberSetInvited: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType, failed: boolean) => callLibSessionWorker([ `MetaGroupConfig-${groupPk}`, @@ -566,13 +578,13 @@ export const MetaGroupWrapperActions: MetaGroupWrapperActionsCalls = { pubkeyHex, failed, ]) as Promise>, - memberSetName: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType, name: string) => + memberSetNameTruncated: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType, name: string) => callLibSessionWorker([ `MetaGroupConfig-${groupPk}`, - 'memberSetName', + 'memberSetNameTruncated', pubkeyHex, name, - ]) as Promise>, + ]) as Promise>, memberSetProfilePicture: async ( groupPk: GroupPubkeyType, pubkeyHex: PubkeyType, @@ -617,6 +629,10 @@ export const MetaGroupWrapperActions: MetaGroupWrapperActionsCalls = { data, timestampMs, ]) as Promise>, + keysAdmin: async (groupPk: GroupPubkeyType) => + callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'keysAdmin']) as Promise< + ReturnType + >, keyGetCurrentGen: async (groupPk: GroupPubkeyType) => callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'keyGetCurrentGen']) as Promise< ReturnType diff --git a/ts/window.d.ts b/ts/window.d.ts index 9a7dc27d9..7dd755810 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -31,6 +31,7 @@ declare global { useTestNet: boolean; useClosedGroupV2: boolean; useClosedGroupV2QAButtons: boolean; + useGroupV2InviteAsAdmin: boolean; debug: { debugLogging: boolean; debugLibsessionDumps: boolean; diff --git a/yarn.lock b/yarn.lock index 518c90def..28a346a7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2906,7 +2906,7 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -axios@^1.3.2: +axios@^1.6.5: version "1.7.2" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== @@ -3527,24 +3527,24 @@ clsx@^1.0.4, clsx@^1.1.1, clsx@^1.2.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== -cmake-js@7.2.1: - version "7.2.1" - resolved "https://registry.yarnpkg.com/cmake-js/-/cmake-js-7.2.1.tgz#757c0d39994121b084bab96290baf115ee7712cd" - integrity sha512-AdPSz9cSIJWdKvm0aJgVu3X8i0U3mNTswJkSHzZISqmYVjZk7Td4oDFg0mCBA383wO+9pG5Ix7pEP1CZH9x2BA== +cmake-js@^7.2.1: + version "7.3.0" + resolved "https://registry.yarnpkg.com/cmake-js/-/cmake-js-7.3.0.tgz#6fd6234b7aeec4545c1c806f9e3f7ffacd9798b2" + integrity sha512-dXs2zq9WxrV87bpJ+WbnGKv8WUBXDw8blNiwNHoRe/it+ptscxhQHKB1SJXa1w+kocLMeP28Tk4/eTCezg4o+w== dependencies: - axios "^1.3.2" + axios "^1.6.5" debug "^4" - fs-extra "^10.1.0" + fs-extra "^11.2.0" lodash.isplainobject "^4.0.6" memory-stream "^1.0.0" - node-api-headers "^0.0.2" + node-api-headers "^1.1.0" npmlog "^6.0.2" rc "^1.2.7" - semver "^7.3.8" - tar "^6.1.11" + semver "^7.5.4" + tar "^6.2.0" url-join "^4.0.1" which "^2.0.2" - yargs "^17.6.0" + yargs "^17.7.2" color-convert@^1.9.0: version "1.9.3" @@ -3966,7 +3966,14 @@ debug@2.6.9, debug@^2.2.0, debug@^2.6.8, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +debug@4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -3981,9 +3988,9 @@ debug@^3.2.7: ms "^2.1.1" debug@^4: - version "4.3.5" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" - integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== dependencies: ms "2.1.2" @@ -5177,7 +5184,7 @@ fs-extra@^10.0.0, fs-extra@^10.1.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^11.0.0: +fs-extra@^11.0.0, fs-extra@^11.2.0: version "11.2.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== @@ -6599,9 +6606,12 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -"libsession_util_nodejs@link:../libsession-util-nodejs": - version "0.0.0" - uid "" +"libsession_util_nodejs@https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.21/libsession_util_nodejs-v0.3.21.tar.gz": + version "0.3.21" + resolved "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.21/libsession_util_nodejs-v0.3.21.tar.gz#64705b1f7c934ca32f929ea8127370cc82bab97a" + dependencies: + cmake-js "^7.2.1" + node-addon-api "^6.1.0" libsodium-sumo@^0.7.13: version "0.7.13" @@ -7483,10 +7493,10 @@ node-addon-api@^6.1.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== -node-api-headers@^0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/node-api-headers/-/node-api-headers-0.0.2.tgz#31f4c6c2750b63e598128e76a60aefca6d76ac5d" - integrity sha512-YsjmaKGPDkmhoNKIpkChtCsPVaRE0a274IdERKnuc/E8K1UJdBZ4/mvI006OijlQZHCfpRNOH3dfHQs92se8gg== +node-api-headers@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/node-api-headers/-/node-api-headers-1.2.0.tgz#b717cd420aec79031f8dc83a50eb0a8bdf24c70d" + integrity sha512-L9AiEkBfgupC0D/LsudLPOhzy/EdObsp+FHyL1zSK0kKv5FDA9rJMoRz8xd+ojxzlqfg0tTZm2h8ot2nS7bgRA== node-dir@^0.1.17: version "0.1.17" @@ -9112,7 +9122,7 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" 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.2, semver@^7.5.4: +semver@^7.1.2, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2, semver@^7.5.4: version "7.6.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== @@ -9707,7 +9717,7 @@ tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -tar@^6.1.0, tar@^6.1.11: +tar@^6.1.0, tar@^6.1.11, tar@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== @@ -10695,7 +10705,7 @@ yargs@^15.1.0: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^17.0.0, yargs@^17.0.1, yargs@^17.6.0, yargs@^17.6.2: +yargs@^17.0.0, yargs@^17.0.1, yargs@^17.6.2, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==