fix: allow non admin mods to delete message for deletion

pull/2407/head
Audric Ackermann 2 years ago
parent e8a1e07b68
commit e6cd277bd2

@ -29,6 +29,11 @@ window.sessionFeatureFlags = {
useTestNet: Boolean(
process.env.NODE_APP_INSTANCE && process.env.NODE_APP_INSTANCE.includes('testnet')
),
debug: {
debugFileServerRequests: false,
debugNonSnodeRequests: false,
debugOnionRequests: false,
},
};
window.versionInfo = {

@ -61,7 +61,6 @@ export const MessageContextMenu = (props: Props) => {
isDeletable,
isDeletableForEveryone,
isPublic,
isOpenGroupV2,
weAreAdmin,
isSenderAdmin,
text,
@ -208,9 +207,7 @@ export const MessageContextMenu = (props: Props) => {
</>
) : null}
{weAreAdmin && isPublic ? <Item onClick={onBan}>{window.i18n('banUser')}</Item> : null}
{weAreAdmin && isOpenGroupV2 ? (
<Item onClick={onUnban}>{window.i18n('unbanUser')}</Item>
) : null}
{weAreAdmin && isPublic ? <Item onClick={onUnban}>{window.i18n('unbanUser')}</Item> : null}
{weAreAdmin && isPublic && !isSenderAdmin ? (
<Item onClick={addModerator}>{window.i18n('addAsModerator')}</Item>
) : null}

@ -124,6 +124,11 @@ export function useWeAreAdmin(convoId?: string) {
return Boolean(convoProps && convoProps.weAreAdmin);
}
export function useWeAreModerator(convoId?: string) {
const convoProps = useConversationPropsById(convoId);
return Boolean(convoProps && (convoProps.weAreAdmin || convoProps.weAreModerator));
}
export function useExpireTimer(convoId?: string) {
const convoProps = useConversationPropsById(convoId);
return convoProps && convoProps.expireTimer;

@ -245,8 +245,9 @@ const doDeleteSelectedMessagesInSOGS = async (
//#region open group v2 deletion
// Get our Moderator status
const isAdmin = conversation.isAdmin(ourDevicePubkey);
const isModerator = conversation.isModerator(ourDevicePubkey);
if (!isAllOurs && !isAdmin) {
if (!isAllOurs && !(isAdmin || isModerator)) {
ToastUtils.pushMessageDeleteForbidden();
window.inboxStore?.dispatch(resetSelectedMessageIds());
return;

@ -1,5 +1,19 @@
import Backbone from 'backbone';
import _, { isArray, isEmpty, isNumber, isString } from 'lodash';
import {
debounce,
defaults,
filter,
includes,
isArray,
isEmpty,
isEqual,
isNumber,
isString,
map,
sortBy,
throttle,
uniq,
} from 'lodash';
import { getMessageQueue } from '../session';
import { getConversationController } from '../session/conversations';
import { ClosedGroupVisibleMessage } from '../session/messages/outgoing/visibleMessage/ClosedGroupVisibleMessage';
@ -99,15 +113,15 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
this.initialPromise = Promise.resolve();
autoBind(this);
this.throttledBumpTyping = _.throttle(this.bumpTyping, 300);
this.updateLastMessage = _.throttle(this.bouncyUpdateLastMessage.bind(this), 1000, {
this.throttledBumpTyping = throttle(this.bumpTyping, 300);
this.updateLastMessage = throttle(this.bouncyUpdateLastMessage.bind(this), 1000, {
trailing: true,
leading: true,
});
this.throttledNotify = _.debounce(this.notify, 2000, { maxWait: 2000, trailing: true });
this.throttledNotify = debounce(this.notify, 2000, { maxWait: 2000, trailing: true });
//start right away the function is called, and wait 1sec before calling it again
const markReadDebounced = _.debounce(this.markReadBouncy, 1000, {
const markReadDebounced = debounce(this.markReadBouncy, 1000, {
leading: true,
trailing: true,
});
@ -240,9 +254,22 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return groupAdmins && groupAdmins?.length > 0 ? groupAdmins : [];
}
/**
* Get the list of moderators in that room, or an empty array
* Only to be called for opengroup conversations.
* This makes no sense for a private chat or an closed group, as closed group admins must be stored with getGroupAdmins
* @returns the list of moderators for the conversation if the conversation is public, or []
*/
public getGroupModerators(): Array<string> {
const groupModerators = this.get('groupModerators') as Array<string> | undefined;
return this.isPublic() && groupModerators && groupModerators?.length > 0 ? groupModerators : [];
}
// tslint:disable-next-line: cyclomatic-complexity max-func-body-length
public getConversationModelProps(): ReduxConversationType {
const groupAdmins = this.getGroupAdmins();
const groupModerators = this.getGroupModerators();
// tslint:disable-next-line: cyclomatic-complexity
const isPublic = this.isPublic();
@ -253,6 +280,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const isPrivate = this.isPrivate();
const isGroup = !isPrivate;
const weAreAdmin = this.isAdmin(ourNumber);
const weAreModerator = this.isModerator(ourNumber); // only used for sogs
const isMe = this.isMe();
const isTyping = !!this.typingTimer;
const unreadCount = this.get('unreadCount') || undefined;
@ -290,6 +318,10 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
toRet.weAreAdmin = true;
}
if (weAreModerator) {
toRet.weAreModerator = true;
}
if (isMe) {
toRet.isMe = true;
}
@ -343,21 +375,29 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
if (didApproveMe) {
toRet.didApproveMe = didApproveMe;
}
if (isApproved) {
toRet.isApproved = isApproved;
}
if (subscriberCount) {
toRet.subscriberCount = subscriberCount;
}
if (groupAdmins && groupAdmins.length) {
toRet.groupAdmins = _.uniq(groupAdmins);
toRet.groupAdmins = uniq(groupAdmins);
}
if (groupModerators && groupModerators.length) {
toRet.groupModerators = uniq(groupModerators);
}
if (members && members.length) {
toRet.members = _.uniq(members);
toRet.members = uniq(members);
}
if (zombies && zombies.length) {
toRet.zombies = _.uniq(zombies);
toRet.zombies = uniq(zombies);
}
if (expireTimer) {
@ -384,10 +424,10 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
public async updateGroupAdmins(groupAdmins: Array<string>, shouldCommit: boolean) {
const existingAdmins = _.uniq(_.sortBy(this.getGroupAdmins()));
const newAdmins = _.uniq(_.sortBy(groupAdmins));
const existingAdmins = uniq(sortBy(this.getGroupAdmins()));
const newAdmins = uniq(sortBy(groupAdmins));
if (_.isEqual(existingAdmins, newAdmins)) {
if (isEqual(existingAdmins, newAdmins)) {
return false;
}
this.set({ groupAdmins });
@ -397,6 +437,23 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return true;
}
public async updateGroupModerators(groupModerators: Array<string>, shouldCommit: boolean) {
if (!this.isPublic()) {
throw new Error('group moderators are only possible on SOGS');
}
const existingModerators = uniq(sortBy(this.getGroupModerators()));
const newModerators = uniq(sortBy(groupModerators));
if (isEqual(existingModerators, newModerators)) {
return false;
}
this.set({ groupModerators: newModerators });
if (shouldCommit) {
await this.commit();
}
return true;
}
public async onReadMessage(message: MessageModel, readAt: number) {
// We mark as read everything older than this message - to clean up old stuff
// still marked unread in the database. If the user generally doesn't read in
@ -819,7 +876,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const messageModel = await this.addSingleOutgoingMessage({
body,
quote: _.isEmpty(quote) ? undefined : quote,
quote: isEmpty(quote) ? undefined : quote,
preview,
attachments,
sent_at: networkTimestamp,
@ -905,7 +962,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
let expireTimer = providedExpireTimer;
let source = providedSource;
_.defaults(options, { fromSync: false });
defaults(options, { fromSync: false });
if (!expireTimer) {
expireTimer = 0;
@ -1088,7 +1145,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
const options = providedOptions || {};
_.defaults(options, { sendReadReceipts: true });
defaults(options, { sendReadReceipts: true });
const conversationId = this.id;
Notifications.clearByConversationID(conversationId);
@ -1125,7 +1182,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
window.inboxStore?.dispatch(conversationActions.messagesChanged(allProps));
}
// Some messages we're marking read are local notifications with no sender
read = _.filter(read, m => Boolean(m.sender));
read = filter(read, m => Boolean(m.sender));
const realUnreadCount = await this.getUnreadCount();
if (read.length === 0) {
const cachedUnreadCountOnConvo = this.get('unreadCount');
@ -1165,7 +1222,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
read = read.filter(item => !item.hasErrors);
if (read.length && options.sendReadReceipts) {
const timestamps = _.map(read, 'timestamp').filter(t => !!t) as Array<number>;
const timestamps = map(read, 'timestamp').filter(t => !!t) as Array<number>;
await this.sendReadReceiptsIfNeeded(timestamps);
}
}
@ -1233,7 +1290,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
// if you change this behavior, double check all setSessionProfile calls (especially the one in EditProfileDialog)
if (newProfile.avatarPath) {
const originalAvatar = this.get('avatarInProfile');
if (!_.isEqual(originalAvatar, newProfile.avatarPath)) {
if (!isEqual(originalAvatar, newProfile.avatarPath)) {
this.set({ avatarInProfile: newProfile.avatarPath });
changes = true;
}
@ -1306,6 +1363,22 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return Array.isArray(groupAdmins) && groupAdmins.includes(pubKey);
}
/**
* Check if the provided pubkey is a moderator.
* Being a moderator only makes sense for a sogs as closed groups have their admin under the groupAdmins property
*/
public isModerator(pubKey?: string) {
if (!pubKey) {
throw new Error('isModerator() pubKey is falsy');
}
if (!this.isPublic()) {
return false;
}
const groupModerators = this.getGroupModerators();
return Array.isArray(groupModerators) && groupModerators.includes(pubKey);
}
public async setIsPinned(value: boolean) {
if (value !== this.isPinned()) {
this.set({
@ -1363,6 +1436,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
* Saves the infos of that room directly on the conversation table.
* This does not write anything to the db if no changes are detected
*/
// tslint:disable-next-line: cyclomatic-complexity
public async setPollInfo(infos?: {
subscriberCount: number;
read: boolean;
@ -1371,6 +1445,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
details: {
admins?: Array<string>;
image_id?: number;
moderators?: Array<string>;
};
}) {
if (!infos || isEmpty(infos)) {
@ -1403,24 +1478,26 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
if (details.admins && isArray(details.admins)) {
const roomInfos = OpenGroupData.getV2OpenGroupRoom(this.id);
const ourBlindedPubkeyForThisSogs =
roomInfos && roomHasBlindEnabled(roomInfos)
? await findCachedOurBlindedPubkeyOrLookItUp(
roomInfos?.serverPublicKey,
await getSodiumRenderer()
)
: UserUtils.getOurPubKeyStrFromCache();
const replacedWithOurRealSessionId = details.admins.map(m =>
m === ourBlindedPubkeyForThisSogs ? UserUtils.getOurPubKeyStrFromCache() : m
);
const replacedWithOurRealSessionId = await this.replaceWithOurRealSessionId(details.admins);
const adminChanged = await this.updateGroupAdmins(replacedWithOurRealSessionId, false);
if (adminChanged) {
hasChange = adminChanged;
}
}
if (details.moderators && isArray(details.moderators)) {
const replacedWithOurRealSessionId = await this.replaceWithOurRealSessionId(
details.moderators
);
const moderatorsChanged = await this.updateGroupModerators(
replacedWithOurRealSessionId,
false
);
if (moderatorsChanged) {
hasChange = moderatorsChanged;
}
}
if (this.isOpenGroupV2() && details.image_id && isNumber(details.image_id)) {
const roomInfos = OpenGroupData.getV2OpenGroupRoom(this.id);
if (roomInfos) {
@ -1458,7 +1535,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
public hasMember(pubkey: string) {
return _.includes(this.get('members'), pubkey);
return includes(this.get('members'), pubkey);
}
// returns true if this is a closed/medium or open group
public isGroup() {
@ -1836,9 +1913,22 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
.sendToPubKey(device, typingMessage)
.catch(window?.log?.error);
}
private async replaceWithOurRealSessionId(toReplace: Array<string>) {
const roomInfos = OpenGroupData.getV2OpenGroupRoom(this.id);
const sodium = await getSodiumRenderer();
const ourBlindedPubkeyForThisSogs =
roomInfos && roomHasBlindEnabled(roomInfos)
? await findCachedOurBlindedPubkeyOrLookItUp(roomInfos?.serverPublicKey, sodium)
: UserUtils.getOurPubKeyStrFromCache();
const replacedWithOurRealSessionId = toReplace.map(m =>
m === ourBlindedPubkeyForThisSogs ? UserUtils.getOurPubKeyStrFromCache() : m
);
return replacedWithOurRealSessionId;
}
}
const throttledAllConversationsDispatch = _.debounce(
const throttledAllConversationsDispatch = debounce(
() => {
if (updatesToDispatch.size === 0) {
return;

@ -42,7 +42,8 @@ export interface ConversationAttributes {
*/
lastMessage: string | null;
lastJoinedTimestamp: number; // ClosedGroup: last time we were added to this group
groupAdmins: Array<string>;
groupAdmins: Array<string>; // for sogs and closed group: the admins of that group.
groupModerators: Array<string>; // for sogs only, this is the moderator in that room.
isKickedFromGroup: boolean;
subscriberCount: number;

@ -28,6 +28,7 @@ export function toSqliteBoolean(val: boolean): number {
// this is used to make sure when storing something in the database you remember to add the wrapping for it in formatRowOfConversation
const allowedKeysFormatRowOfConversation = [
'groupAdmins',
'groupModerators',
'members',
'zombies',
'isTrustedForAttachmentDownload',
@ -80,7 +81,7 @@ export function formatRowOfConversation(row?: Record<string, any>): Conversation
const convo: ConversationAttributes = omit(row, 'json') as ConversationAttributes;
// if the stringified array of admins/members/zombies length is less than 5,
// if the stringified array of admins/moderators/members/zombies length is less than 5,
// we consider there is nothing to parse and just return []
const minLengthNoParsing = 5;
@ -88,6 +89,11 @@ export function formatRowOfConversation(row?: Record<string, any>): Conversation
row.groupAdmins?.length && row.groupAdmins.length > minLengthNoParsing
? jsonToArray(row.groupAdmins)
: [];
convo.groupModerators =
row.groupModerators?.length && row.groupModerators.length > minLengthNoParsing
? jsonToArray(row.groupModerators)
: [];
convo.members =
row.members?.length && row.members.length > minLengthNoParsing ? jsonToArray(row.members) : [];
convo.zombies =
@ -147,6 +153,7 @@ export function formatRowOfConversation(row?: Record<string, any>): Conversation
const allowedKeysOfConversationAttributes = [
'groupAdmins',
'groupModerators',
'members',
'zombies',
'isTrustedForAttachmentDownload',

@ -773,6 +773,7 @@ const LOKI_SCHEMA_VERSIONS = [
updateToLokiSchemaVersion23,
updateToLokiSchemaVersion24,
updateToLokiSchemaVersion25,
updateToLokiSchemaVersion26,
];
function updateToLokiSchemaVersion1(currentVersion: number, db: BetterSqlite3.Database) {
@ -1588,6 +1589,24 @@ function updateToLokiSchemaVersion25(currentVersion: number, db: BetterSqlite3.D
console.log(`updateToLokiSchemaVersion${targetVersion}: success!`);
}
function updateToLokiSchemaVersion26(currentVersion: number, db: BetterSqlite3.Database) {
const targetVersion = 26;
if (currentVersion >= targetVersion) {
return;
}
console.log(`updateToLokiSchemaVersion${targetVersion}: starting...`);
db.transaction(() => {
db.exec(`
ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN groupModerators TEXT DEFAULT "[]"; -- those are for sogs only (for closed groups we only need the groupAdmins)
`);
writeLokiSchemaVersion(targetVersion, db);
})();
console.log(`updateToLokiSchemaVersion${targetVersion}: success!`);
}
// function printTableColumns(table: string, db: BetterSqlite3.Database) {
// console.warn(db.pragma(`table_info('${table}');`));
// }
@ -1999,6 +2018,7 @@ function saveConversation(data: ConversationAttributes, instance?: BetterSqlite3
lastMessage,
lastJoinedTimestamp,
groupAdmins,
groupModerators,
isKickedFromGroup,
subscriberCount,
readCapability,
@ -2039,6 +2059,7 @@ function saveConversation(data: ConversationAttributes, instance?: BetterSqlite3
lastMessage,
lastJoinedTimestamp,
groupAdmins,
groupModerators,
isKickedFromGroup,
subscriberCount,
readCapability,
@ -2071,6 +2092,7 @@ function saveConversation(data: ConversationAttributes, instance?: BetterSqlite3
$lastMessage,
$lastJoinedTimestamp,
$groupAdmins,
$groupModerators,
$isKickedFromGroup,
$subscriberCount,
$readCapability,
@ -2106,6 +2128,8 @@ function saveConversation(data: ConversationAttributes, instance?: BetterSqlite3
lastJoinedTimestamp,
groupAdmins: groupAdmins && groupAdmins.length ? arrayStrToJson(groupAdmins) : '[]',
groupModerators:
groupModerators && groupModerators.length ? arrayStrToJson(groupModerators) : '[]',
isKickedFromGroup: toSqliteBoolean(isKickedFromGroup),
subscriberCount,
readCapability: toSqliteBoolean(readCapability),
@ -3789,6 +3813,7 @@ function fillWithTestData(numConvosToAdd: number, numMsgsToAdd: number) {
didApproveMe: false,
expireTimer: 0,
groupAdmins: [],
groupModerators: [],
isApproved: false,
isKickedFromGroup: false,
isPinned: false,

@ -73,12 +73,18 @@ export const downloadFileFromFileServer = async (
return null;
}
const urlToGet = `${POST_GET_FILE_ENDPOINT}/${fileId}`;
if (window.sessionFeatureFlags?.debug.debugFileServerRequests) {
window.log.info(`about to try to download fsv2: "${urlToGet}"`);
}
const result = await OnionSending.getBinaryViaOnionV4FromFileServer({
abortSignal: new AbortController().signal,
endpoint: `${POST_GET_FILE_ENDPOINT}/${fileId}`,
endpoint: urlToGet,
method: 'GET',
});
if (window.sessionFeatureFlags?.debug.debugFileServerRequests) {
window.log.info(`download fsv2: "${urlToGet} got result:`, JSON.stringify(result));
}
if (!result) {
return null;
}

@ -12,6 +12,7 @@ import {
OpenGroupBatchRow,
parseBatchGlobalStatusCode,
sogsBatchSend,
SubRequestMessagesObjectType,
} from '../sogsv3/sogsV3BatchPoll';
import { handleBatchPollResults } from '../sogsv3/sogsApiV3';
import {
@ -325,7 +326,8 @@ export class OpenGroupServerPoller {
export const getRoomAndUpdateLastFetchTimestamp = async (
conversationId: string,
newMessages: Array<OpenGroupMessageV2 | OpenGroupMessageV4>
newMessages: Array<OpenGroupMessageV2 | OpenGroupMessageV4>,
subRequest: SubRequestMessagesObjectType
) => {
const roomInfos = OpenGroupData.getV2OpenGroupRoom(conversationId);
if (!roomInfos || !roomInfos.serverUrl || !roomInfos.roomId) {
@ -336,7 +338,7 @@ export const getRoomAndUpdateLastFetchTimestamp = async (
// if we got no new messages, just write our last update timestamp to the db
roomInfos.lastFetchTimestamp = Date.now();
window?.log?.info(
`No new messages for ${roomInfos.roomId}... just updating our last fetched timestamp`
`No new messages for ${subRequest?.roomId}:${subRequest?.sinceSeqNo}... just updating our last fetched timestamp`
);
await OpenGroupData.saveV2OpenGroupRoom(roomInfos);
return null;

@ -73,13 +73,13 @@ async function handlePollInfoResponse(
token: string;
upload: boolean;
write: boolean;
details: { admins?: Array<string>; image_id: number };
details: { admins?: Array<string>; image_id: number; moderators?: Array<string> };
},
serverUrl: string,
roomIdsStillPolled: Set<string>
) {
if (statusCode !== 200) {
window.log.info('handlePollInfoResponse subRequest status code is not 200');
window.log.info('handlePollInfoResponse subRequest status code is not 200:', statusCode);
return;
}
@ -109,7 +109,7 @@ async function handlePollInfoResponse(
write,
upload,
subscriberCount: active_users,
details: pick(details, 'admins', 'image_id'),
details: pick(details, 'admins', 'image_id', 'moderators'),
});
}
@ -190,7 +190,11 @@ const handleMessagesResponseV4 = async (
}
const convoId = getOpenGroupV2ConversationId(serverUrl, roomId);
const roomInfos = await getRoomAndUpdateLastFetchTimestamp(convoId, messages);
const roomInfos = await getRoomAndUpdateLastFetchTimestamp(
convoId,
messages,
subrequestOption.messages
);
if (!roomInfos || !roomInfos.conversationId) {
return;
}

@ -121,12 +121,16 @@ export type SubrequestOptionType = 'capabilities' | 'messages' | 'pollInfo' | 'i
export type SubRequestCapabilitiesType = { type: 'capabilities' };
export type SubRequestMessagesObjectType =
| {
roomId: string;
sinceSeqNo?: number;
}
| undefined;
export type SubRequestMessagesType = {
type: 'messages';
messages?: {
roomId: string;
sinceSeqNo?: number;
};
messages?: SubRequestMessagesObjectType;
};
export type SubRequestPollInfoType = {

@ -782,6 +782,16 @@ async function sendOnionRequestHandlingSnodeEject({
abortSignal,
useV4,
});
if (window.sessionFeatureFlags?.debug.debugOnionRequests) {
window.log.info(
`sendOnionRequestHandlingSnodeEject: sendOnionRequestNoRetries: useV4:${useV4} destSnodeX25519:${destSnodeX25519}; \nfinalDestOptions:${JSON.stringify(
finalDestOptions
)}; \nfinalRelayOptions:${JSON.stringify(finalRelayOptions)}\n\n result: ${JSON.stringify(
result
)}`
);
}
response = result.response;
if (
!isEmpty(finalRelayOptions) &&

@ -102,6 +102,13 @@ const sendViaOnionV4ToNonSnodeWithRetries = async (
}
const payloadObj = buildSendViaOnionPayload(url, fetchOptions);
if (window.sessionFeatureFlags?.debug.debugNonSnodeRequests) {
window.log.info(
'sendViaOnionV4ToNonSnodeWithRetries: buildSendViaOnionPayload returned ',
JSON.stringify(payloadObj)
);
}
// if protocol is forced to 'http:' => just use http (without the ':').
// otherwise use https as protocol (this is the default)
const forcedHttp = url.protocol === PROTOCOLS.HTTP;
@ -121,7 +128,12 @@ const sendViaOnionV4ToNonSnodeWithRetries = async (
result = await pRetry(
async () => {
const pathNodes = await OnionSending.getOnionPathForSending();
if (window.sessionFeatureFlags?.debug.debugNonSnodeRequests) {
window.log.info(
'sendViaOnionV4ToNonSnodeWithRetries: getOnionPathForSending returned',
JSON.stringify(pathNodes)
);
}
if (!pathNodes) {
throw new Error('getOnionPathForSending is emtpy');
}
@ -140,6 +152,12 @@ const sendViaOnionV4ToNonSnodeWithRetries = async (
useV4: true,
throwErrors,
});
if (window.sessionFeatureFlags?.debug.debugNonSnodeRequests) {
window.log.info(
'sendViaOnionV4ToNonSnodeWithRetries: sendOnionRequestHandlingSnodeEject returned: ',
JSON.stringify(onionV4Response)
);
}
if (abortSignal?.aborted) {
// if the request was aborted, we just want to stop retries.
@ -175,6 +193,12 @@ const sendViaOnionV4ToNonSnodeWithRetries = async (
bodyBinary: decodedV4?.bodyBinary || null,
};
}
if (foundStatusCode === 404) {
// this is most likely that a 404 won't fix itself. So just stop right here retries by throwing a non retryable error
throw new pRetry.AbortError(
`Got 404 while sendViaOnionV4ToNonSnodeWithRetries with url:${url}. Stopping retries`
);
}
// we consider those cases as an error, and trigger a retry (if possible), by throwing a non-abortable error
throw new Error(
`sendViaOnionV4ToNonSnodeWithRetries failed with status code: ${foundStatusCode}. Retrying...`
@ -419,6 +443,9 @@ async function getBinaryViaOnionV4FromFileServer(sendOptions: {
}
const builtUrl = new URL(`${fileServerURL}${endpoint}`);
if (window.sessionFeatureFlags?.debug.debugFileServerRequests) {
window.log.info(`getBinaryViaOnionV4FromFileServer fsv2: "${builtUrl} `);
}
const res = await OnionSending.sendViaOnionV4ToNonSnodeWithRetries(
fileServerPubKey,
builtUrl,
@ -432,6 +459,12 @@ async function getBinaryViaOnionV4FromFileServer(sendOptions: {
abortSignal
);
if (window.sessionFeatureFlags?.debug.debugFileServerRequests) {
window.log.info(
`getBinaryViaOnionV4FromFileServer fsv2: "${builtUrl}; got:`,
JSON.stringify(res)
);
}
return res as OnionV4BinarySnodeResponse;
}

@ -238,6 +238,7 @@ export interface ReduxConversationType {
isGroup?: boolean;
isPrivate?: boolean;
weAreAdmin?: boolean;
weAreModerator?: boolean;
unreadCount?: number;
mentionedUs?: boolean;
isSelected?: boolean;
@ -249,7 +250,8 @@ export interface ReduxConversationType {
subscriberCount?: number;
left?: boolean;
avatarPath?: string | null; // absolute filepath to the avatar
groupAdmins?: Array<string>; // admins for closed groups and moderators for open groups
groupAdmins?: Array<string>; // admins for closed groups and admins for open groups
groupModerators?: Array<string>; // only for opengroups: moderators
members?: Array<string>; // members for closed groups only
zombies?: Array<string>; // members for closed groups only

@ -826,16 +826,21 @@ export const getMessagePropsByMessageId = createSelector(
const groupAdmins = (isGroup && foundMessageConversation.groupAdmins) || [];
const weAreAdmin = groupAdmins.includes(ourPubkey) || false;
const groupModerators = (isGroup && foundMessageConversation.groupModerators) || [];
const weAreModerator = groupModerators.includes(ourPubkey) || false;
// A message is deletable if
// either we sent it,
// or the convo is not a public one (in this case, we will only be able to delete for us)
// or the convo is public and we are an admin
const isDeletable = sender === ourPubkey || !isPublic || (isPublic && !!weAreAdmin);
// or the convo is public and we are an admin or moderator
const isDeletable =
sender === ourPubkey || !isPublic || (isPublic && (weAreAdmin || weAreModerator));
// A message is deletable for everyone if
// either we sent it no matter what the conversation type,
// or the convo is public and we are an admin
const isDeletableForEveryone = sender === ourPubkey || (isPublic && !!weAreAdmin) || false;
// or the convo is public and we are an admin or moderator
const isDeletableForEveryone =
sender === ourPubkey || (isPublic && (weAreAdmin || weAreModerator)) || false;
const isSenderAdmin = groupAdmins.includes(sender);
const senderIsUs = sender === ourPubkey;

@ -36,6 +36,7 @@ describe('state/selectors/conversations', () => {
avatarPath: '',
groupAdmins: [],
groupModerators: [],
lastMessage: undefined,
members: [],
expireTimer: 0,
@ -64,6 +65,7 @@ describe('state/selectors/conversations', () => {
avatarPath: '',
groupAdmins: [],
groupModerators: [],
lastMessage: undefined,
members: [],
expireTimer: 0,
@ -92,6 +94,7 @@ describe('state/selectors/conversations', () => {
avatarPath: '',
groupAdmins: [],
groupModerators: [],
lastMessage: undefined,
members: [],
expireTimer: 0,
@ -120,6 +123,7 @@ describe('state/selectors/conversations', () => {
avatarPath: '',
groupAdmins: [],
groupModerators: [],
expireTimer: 0,
lastMessage: undefined,
members: [],
@ -149,6 +153,7 @@ describe('state/selectors/conversations', () => {
avatarPath: '',
groupAdmins: [],
groupModerators: [],
lastMessage: undefined,
members: [],
isPinned: false,
@ -192,6 +197,7 @@ describe('state/selectors/conversations', () => {
avatarPath: '',
groupAdmins: [],
groupModerators: [],
lastMessage: undefined,
members: [],
isPinned: false,
@ -221,6 +227,7 @@ describe('state/selectors/conversations', () => {
avatarPath: '',
groupAdmins: [],
groupModerators: [],
lastMessage: undefined,
members: [],
isPinned: false,
@ -250,6 +257,7 @@ describe('state/selectors/conversations', () => {
avatarPath: '',
groupAdmins: [],
groupModerators: [],
lastMessage: undefined,
members: [],
isPinned: true,
@ -278,6 +286,7 @@ describe('state/selectors/conversations', () => {
avatarPath: '',
groupAdmins: [],
groupModerators: [],
lastMessage: undefined,
members: [],
isPinned: true,
@ -307,6 +316,7 @@ describe('state/selectors/conversations', () => {
avatarPath: '',
groupAdmins: [],
groupModerators: [],
lastMessage: undefined,
members: [],
isPinned: false,

5
ts/window.d.ts vendored

@ -38,6 +38,11 @@ declare global {
sessionFeatureFlags: {
useOnionRequests: boolean;
useTestNet: boolean;
debug: {
debugFileServerRequests: boolean;
debugNonSnodeRequests: boolean;
debugOnionRequests: boolean;
};
};
SessionSnodeAPI: SessionSnodeAPI;
onLogin: any;

Loading…
Cancel
Save