chore: commit before merge unstable

pull/3052/head
Audric Ackermann 10 months ago committed by Audric Ackermann
parent 4d7a0e7a04
commit 74aa448631
No known key found for this signature in database

@ -578,6 +578,8 @@
"respondingToGroupRequestWarning": "Sending a message to this group will automatically accept the group invite.",
"userInvitedYouToGroup": "<b>$name$</b> invited you to join <b>$groupName$</b>.",
"youWereInvitedToGroup": "<b>You</b> were invited to join <b>$groupName$</b>.",
"userInvitedYouToGroupAsAdmin": "<b>$name$</b> invited you to join <b>$groupName$</b>, where you are an Admin.",
"youWereInvitedToGroupAsAdmin": "<b>You</b> were invited to join <b>$groupName$</b>, where you are an Admin.",
"hideRequestBanner": "Hide Message Request Banner",
"openMessageRequestInbox": "Message Requests",
"noMessageRequestsPending": "No pending message requests",

@ -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",

@ -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 {

@ -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 (
<TextNotification

@ -517,7 +517,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
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}
>
<Mention
appendSpaceOnAdd={true}

@ -32,6 +32,7 @@ import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/S
import { SessionSpinner } from '../basic/SessionSpinner';
import { SessionToggle } from '../basic/SessionToggle';
import { GroupInviteRequiredVersionBanner } from '../NoticeBanner';
import { isDevProd } from '../../shared/env_vars';
type Props = {
conversationId: string;
@ -190,7 +191,9 @@ const InviteContactsDialogInner = (props: Props) => {
{hasContacts && isGroupV2 && <GroupInviteRequiredVersionBanner />}
<SpacerLG />
{isGroupV2 && (
{/* TODO: localize those strings once out releasing those buttons for real */}
{isGroupV2 && isDevProd() && (
<>
<span style={{ display: 'flex', alignItems: 'center' }}>
Share History?{' '}

@ -219,17 +219,11 @@ export const SessionConfirm = (props: SessionConfirmDialogProps) => {
);
};
export const showLinkVisitWarningDialog = (urlToOpen: string, dispatch: Dispatch<any>) => {
function onClickOk() {
void shell.openExternal(urlToOpen);
}
dispatch(
updateConfirmModal({
title: window.i18n('linkVisitWarningTitle'),

@ -1,3 +1,4 @@
/* eslint-disable no-await-in-loop */
import { ipcRenderer } from 'electron';
import React, { useEffect, useState } from 'react';

@ -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 = () => {
/>
</div>
<SessionSpinner loading={isCreatingGroup} />
{/* TODO: localize those strings once out releasing those buttons for real */}
{isDevProd() && (
<>
<span style={{ display: 'flex', alignItems: 'center' }}>
Invite as admin?{' '}
<SessionToggle
active={window.sessionFeatureFlags.useGroupV2InviteAsAdmin}
onClick={() => {
window.sessionFeatureFlags.useGroupV2InviteAsAdmin =
!window.sessionFeatureFlags.useGroupV2InviteAsAdmin;
forceUpdate();
}}
/>
</span>
</>
)}
<SpacerLG />
<SessionSearchInput />
{!noContactsForClosedGroup && window.sessionFeatureFlags.useClosedGroupV2 && (

@ -1101,7 +1101,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
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);

@ -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<GroupUpdateGeneric<SignalService.GroupUpdatePromoteMessage>, '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(

@ -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;
}

@ -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);

@ -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);

@ -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({

@ -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)

@ -153,12 +153,19 @@ class GroupInviteJob extends PersistedJob<GroupInvitePersistedData> {
}
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,

@ -134,7 +134,7 @@ class GroupPendingRemovalsJob extends PersistedJob<GroupPendingRemovalsPersisted
return RunJobResult.Success;
}
const deleteMessagesOfMembers = pendingRemovals
.filter(m => m.removedStatus === 2)
.filter(m => m.shouldRemoveMessages)
.map(m => m.pubkeyHex);
const sessionIdsHex = pendingRemovals.map(m => m.pubkeyHex);

@ -102,7 +102,7 @@ class GroupPromoteJob extends PersistedJob<GroupPromotePersistedData> {
member,
secretKey: group.secretKey,
groupPk,
name: group.name,
groupName: group.name,
});
const storedAt = await getMessageQueue().sendTo1o1NonDurably({
@ -118,7 +118,11 @@ class GroupPromoteJob extends PersistedJob<GroupPromotePersistedData> {
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);
}

@ -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, {

@ -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<GroupDetailsUpdate> => {
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,

@ -1,4 +1,5 @@
/* eslint-disable no-restricted-syntax */
import { createSelector } from '@reduxjs/toolkit';
import { filter, isEmpty, isFinite, isNumber, pick, sortBy, toNumber } from 'lodash';

@ -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);
}

@ -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();

@ -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'

@ -548,17 +548,29 @@ export const MetaGroupWrapperActions: MetaGroupWrapperActionsCalls = {
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberSetAccepted', pubkeyHex]) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['memberSetAccepted']>
>,
memberSetPromoted: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType, failed: boolean) =>
memberSetPromoted: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberSetPromoted', pubkeyHex]) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['memberSetPromoted']>
>,
memberSetPromotionAccepted: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType) =>
callLibSessionWorker([
`MetaGroupConfig-${groupPk}`,
'memberSetPromoted',
'memberSetPromotionAccepted',
pubkeyHex,
failed,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberSetPromoted']>>,
memberSetAdmin: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'memberSetAdmin', pubkeyHex]) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['memberSetAdmin']>
>,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberSetPromotionAccepted']>>,
memberSetPromotionFailed: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType) =>
callLibSessionWorker([
`MetaGroupConfig-${groupPk}`,
'memberSetPromotionFailed',
pubkeyHex,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberSetPromotionFailed']>>,
memberSetPromotionSent: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType) =>
callLibSessionWorker([
`MetaGroupConfig-${groupPk}`,
'memberSetPromotionSent',
pubkeyHex,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberSetPromotionSent']>>,
memberSetInvited: async (groupPk: GroupPubkeyType, pubkeyHex: PubkeyType, failed: boolean) =>
callLibSessionWorker([
`MetaGroupConfig-${groupPk}`,
@ -566,13 +578,13 @@ export const MetaGroupWrapperActions: MetaGroupWrapperActionsCalls = {
pubkeyHex,
failed,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberSetInvited']>>,
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<ReturnType<MetaGroupWrapperActionsCalls['memberSetName']>>,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['memberSetNameTruncated']>>,
memberSetProfilePicture: async (
groupPk: GroupPubkeyType,
pubkeyHex: PubkeyType,
@ -617,6 +629,10 @@ export const MetaGroupWrapperActions: MetaGroupWrapperActionsCalls = {
data,
timestampMs,
]) as Promise<ReturnType<MetaGroupWrapperActionsCalls['loadKeyMessage']>>,
keysAdmin: async (groupPk: GroupPubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'keysAdmin']) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['keysAdmin']>
>,
keyGetCurrentGen: async (groupPk: GroupPubkeyType) =>
callLibSessionWorker([`MetaGroupConfig-${groupPk}`, 'keyGetCurrentGen']) as Promise<
ReturnType<MetaGroupWrapperActionsCalls['keyGetCurrentGen']>

1
ts/window.d.ts vendored

@ -31,6 +31,7 @@ declare global {
useTestNet: boolean;
useClosedGroupV2: boolean;
useClosedGroupV2QAButtons: boolean;
useGroupV2InviteAsAdmin: boolean;
debug: {
debugLogging: boolean;
debugLibsessionDumps: boolean;

@ -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==

Loading…
Cancel
Save