feat: add block msg requests from sogs

This commit is contained in:
Audric Ackermann 2023-08-15 11:23:04 +10:00
parent e4a4945dd4
commit 84deed19f9
28 changed files with 347 additions and 42 deletions

View File

@ -442,6 +442,8 @@
"clearAll": "Clear All",
"clearDataSettingsTitle": "Clear Data",
"messageRequests": "Message Requests",
"blindedMsgReqsSettingTitle": "Community Message Requests",
"blindedMsgReqsSettingDesc": "Allow message requests from Community conversations.",
"requestsSubtitle": "Pending Requests",
"requestsPlaceholder": "No requests",
"hideRequestBannerDescription": "Hide the Message Request banner until you receive a new message request.",
@ -489,6 +491,7 @@
"clearAllConfirmationTitle": "Clear All Message Requests",
"clearAllConfirmationBody": "Are you sure you want to clear all message requests?",
"noMessagesInReadOnly": "There are no messages in <b>$name$</b>.",
"noMessagesInBlindedDisabledMsgRequests": "<b>$name$</b> has message requests from Community conversations turned off, so you cannot send them a message.",
"noMessagesInNoteToSelf": "You have no messages in <b>$name$</b>.",
"noMessagesInEverythingElse": "You have no messages from <b>$name$</b>. Send a message to start the conversation!",
"hideBanner": "Hide",

View File

@ -94,7 +94,7 @@
"glob": "7.1.2",
"image-type": "^4.1.0",
"ip2country": "1.0.1",
"libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.2.5/libsession_util_nodejs-v0.2.5.tar.gz",
"libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.2.6/libsession_util_nodejs-v0.2.6.tar.gz",
"libsodium-wrappers-sumo": "^0.7.9",
"linkify-it": "^4.0.1",
"lodash": "^4.17.21",

View File

@ -224,6 +224,7 @@ message DataMessage {
optional OpenGroupInvitation openGroupInvitation = 102;
optional ClosedGroupControlMessage closedGroupControlMessage = 104;
optional string syncTarget = 105;
optional bool blocksCommunityMessageRequests = 106;
// optional GroupMessage groupMessage = 120;
}

View File

@ -8,6 +8,7 @@ import {
import {
getSelectedCanWrite,
useSelectedConversationKey,
useSelectedHasDisabledBlindedMsgRequests,
useSelectedNicknameOrProfileNameOrShortenedPubkey,
useSelectedisNoteToSelf,
} from '../../state/selectors/selectedConversation';
@ -61,6 +62,7 @@ export const NoMessageInConversation = () => {
const isMe = useSelectedisNoteToSelf();
const canWrite = useSelector(getSelectedCanWrite);
const privateBlindedAndBlockingMsgReqs = useSelectedHasDisabledBlindedMsgRequests();
// TODOLATER use this selector accross the whole application (left pane excluded)
const nameToRender = useSelectedNicknameOrProfileNameOrShortenedPubkey();
@ -69,7 +71,11 @@ export const NoMessageInConversation = () => {
}
let localizedKey: LocalizerKeys = 'noMessagesInEverythingElse';
if (!canWrite) {
localizedKey = 'noMessagesInReadOnly';
if (privateBlindedAndBlockingMsgReqs) {
localizedKey = 'noMessagesInBlindedDisabledMsgRequests';
} else {
localizedKey = 'noMessagesInReadOnly';
}
} else if (isMe) {
localizedKey = 'noMessagesInNoteToSelf';
}

View File

@ -441,7 +441,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
{typingEnabled && (
<ToggleEmojiButton ref={this.emojiPanelButton} onClick={this.toggleEmojiPanel} />
)}
<SendMessageButton onClick={this.onSendMessage} />
{typingEnabled && <SendMessageButton onClick={this.onSendMessage} />}
{typingEnabled && showEmojiPanel && (
<StyledEmojiPanelContainer role="button">
<SessionEmojiPanel

View File

@ -45,6 +45,7 @@ import {
import { isDarkTheme } from '../../state/selectors/theme';
import { ThemeStateType } from '../../themes/constants/colors';
import { switchThemeTo } from '../../themes/switchTheme';
import { ConfigurationSync } from '../../session/utils/job_runners/jobs/ConfigurationSyncJob';
const Section = (props: { type: SectionType }) => {
const ourNumber = useSelector(getOurNumber);
@ -212,6 +213,11 @@ const doAppStartUp = async () => {
// this call does nothing except calling the constructor, which will continue sending message in the pipeline
void getMessageQueue().processAllPending();
}, 3000);
global.setTimeout(() => {
// Schedule a confSyncJob in some time to let anything incoming from the network be applied and see if there is a push needed
void ConfigurationSync.queueNewJobIfNeeded();
}, 20000);
};
async function fetchReleaseFromFSAndUpdateMain() {

View File

@ -9,10 +9,16 @@ import { SessionButtonColor } from '../../basic/SessionButton';
import { SpacerLG } from '../../basic/Text';
import { TypingBubble } from '../../conversation/TypingBubble';
import { UserUtils } from '../../../session/utils';
import { ConfigurationSync } from '../../../session/utils/job_runners/jobs/ConfigurationSyncJob';
import { SessionUtilUserProfile } from '../../../session/utils/libsession/libsession_utils_user_profile';
import {
useHasBlindedMsgRequestsEnabled,
useHasLinkPreviewEnabled,
} from '../../../state/selectors/settings';
import { Storage } from '../../../util/storage';
import { SessionSettingButtonItem, SessionToggleWithDescription } from '../SessionSettingListItem';
import { displayPasswordModal } from '../SessionSettings';
import { Storage } from '../../../util/storage';
import { useHasLinkPreviewEnabled } from '../../../state/selectors/settings';
async function toggleLinkPreviews(isToggleOn: boolean, forceUpdate: () => void) {
if (!isToggleOn) {
@ -50,6 +56,7 @@ export const SettingsCategoryPrivacy = (props: {
}) => {
const forceUpdate = useUpdate();
const isLinkPreviewsOn = useHasLinkPreviewEnabled();
const areBlindedRequestsEnabled = useHasBlindedMsgRequestsEnabled();
if (props.hasPassword !== null) {
return (
@ -84,6 +91,20 @@ export const SettingsCategoryPrivacy = (props: {
description={window.i18n('linkPreviewDescription')}
active={isLinkPreviewsOn}
/>
<SessionToggleWithDescription
onClickToggle={async () => {
const toggledValue = !areBlindedRequestsEnabled;
await window.setSettingValue(SettingsKey.hasBlindedMsgRequestsEnabled, toggledValue);
await SessionUtilUserProfile.insertUserProfileIntoWrapper(
UserUtils.getOurPubKeyStrFromCache()
);
await ConfigurationSync.queueNewJobIfNeeded();
forceUpdate();
}}
title={window.i18n('blindedMsgReqsSettingTitle')}
description={window.i18n('blindedMsgReqsSettingDesc')}
active={areBlindedRequestsEnabled}
/>
{!props.hasPassword && (
<SessionSettingButtonItem

View File

@ -5,6 +5,7 @@ const settingsAutoUpdate = 'auto-update';
const settingsMenuBar = 'hide-menu-bar';
const settingsSpellCheck = 'spell-check';
const settingsLinkPreview = 'link-preview-setting';
const hasBlindedMsgRequestsEnabled = 'hasBlindedMsgRequestsEnabled';
const settingsStartInTray = 'start-in-tray-setting';
const settingsOpengroupPruning = 'prune-setting';
const settingsNotification = 'notification-setting';
@ -28,6 +29,7 @@ export const SettingsKey = {
settingsLinkPreview,
settingsStartInTray,
settingsOpengroupPruning,
hasBlindedMsgRequestsEnabled,
settingsNotification,
settingsAudioNotification,
someDeviceOutdatedSyncing,

View File

@ -8,6 +8,7 @@ import {
isEmpty,
isEqual,
isFinite,
isNil,
isNumber,
isString,
sortBy,
@ -291,6 +292,11 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
toRet.isMarkedUnread = !!this.get('markedAsUnread');
}
const blocksSogsMsgReqsTimestamp = this.get('blocksSogsMsgReqsTimestamp');
if (blocksSogsMsgReqsTimestamp) {
toRet.blocksSogsMsgReqsTimestamp = blocksSogsMsgReqsTimestamp;
}
if (isPrivate) {
toRet.isPrivate = true;
if (this.typingTimer) {
@ -1243,6 +1249,35 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return !!this.get('markedAsUnread');
}
public async updateBlocksSogsMsgReqsTimestamp(
blocksSogsMsgReqsTimestamp: number,
shouldCommit: boolean = true
) {
if (!PubKey.isBlinded(this.id)) {
return; // this thing only applies to sogs blinded conversations
}
if (
(isNil(this.get('blocksSogsMsgReqsTimestamp')) && !isNil(blocksSogsMsgReqsTimestamp)) ||
(blocksSogsMsgReqsTimestamp === 0 && this.get('blocksSogsMsgReqsTimestamp') !== 0) ||
blocksSogsMsgReqsTimestamp > this.get('blocksSogsMsgReqsTimestamp')
) {
this.set({
blocksSogsMsgReqsTimestamp,
});
if (shouldCommit) {
await this.commit();
}
}
}
public blocksSogsMsgReqsTimestamp(): number {
if (!PubKey.isBlinded(this.id)) {
return 0; // this thing only applies to sogs blinded conversations
}
return this.get('blocksSogsMsgReqsTimestamp') || 0;
}
/**
* Mark a private conversation as approved to the specified value.
* Does not do anything on non private chats.

View File

@ -101,6 +101,8 @@ export interface ConversationAttributes {
didApproveMe: boolean; // if our message request was approved already (or they've sent us a message request/message themselves). If isApproved & didApproveMe, a message request becomes a contact
markedAsUnread: boolean; // Force the conversation as unread even if all the messages are read. Used to highlight a conversation the user wants to check again later, synced.
blocksSogsMsgReqsTimestamp: number; // if that convo is a blinded one and that user denied be contacted through sogs, this field will be set to his latest message timestamp
}
/**
@ -133,6 +135,7 @@ export const fillConvoAttributesWithDefaults = (
left: false,
priority: CONVERSATION_PRIORITIES.default,
markedAsUnread: false,
blocksSogsMsgReqsTimestamp: 0,
});
};

View File

@ -1,4 +1,4 @@
import { difference, omit, pick } from 'lodash';
import { difference, isNumber, omit, pick } from 'lodash';
import * as BetterSqlite3 from '@signalapp/better-sqlite3';
import {
ConversationAttributes,
@ -72,6 +72,7 @@ const allowedKeysFormatRowOfConversation = [
'displayNameInProfile',
'conversationIdOrigin',
'markedAsUnread',
'blocksSogsMsgReqsTimestamp',
'priority',
];
@ -138,6 +139,10 @@ export function formatRowOfConversation(
convo.lastMessageStatus = undefined;
}
if (!isNumber(convo.blocksSogsMsgReqsTimestamp)) {
convo.blocksSogsMsgReqsTimestamp = 0;
}
if (!convo.triggerNotificationsFor) {
convo.triggerNotificationsFor = 'all';
}
@ -189,6 +194,7 @@ const allowedKeysOfConversationAttributes = [
'displayNameInProfile',
'conversationIdOrigin',
'markedAsUnread',
'blocksSogsMsgReqsTimestamp',
'priority',
];

View File

@ -6,7 +6,7 @@ import {
UserConfigWrapperNode,
UserGroupsWrapperNode,
} from 'libsession_util_nodejs';
import { compact, isArray, isEmpty, isFinite, isNumber, isString, map, pick } from 'lodash';
import { compact, isArray, isEmpty, isFinite, isNil, isNumber, isString, map, pick } from 'lodash';
import {
CONVERSATION_PRIORITIES,
ConversationAttributes,
@ -15,6 +15,7 @@ import { HexKeyPair } from '../../receiver/keypairs';
import { fromHexToArray } from '../../session/utils/String';
import {
CONFIG_DUMP_TABLE,
ConfigDumpRow,
getCommunityInfoFromDBValues,
getContactInfoFromDBValues,
getLegacyGroupInfoFromDBValues,
@ -35,6 +36,7 @@ import {
import { getIdentityKeys, sqlNode } from '../sql';
import { sleepFor } from '../../session/utils/Promise';
import { SettingsKey } from '../../data/settings-key';
const hasDebugEnvVariable = Boolean(process.env.SESSION_DEBUG);
@ -103,6 +105,7 @@ const LOKI_SCHEMA_VERSIONS = [
updateToSessionSchemaVersion30,
updateToSessionSchemaVersion31,
updateToSessionSchemaVersion32,
updateToSessionSchemaVersion33,
];
function updateToSessionSchemaVersion1(currentVersion: number, db: BetterSqlite3.Database) {
@ -1582,6 +1585,24 @@ function updateToSessionSchemaVersion30(currentVersion: number, db: BetterSqlite
console.log(`updateToSessionSchemaVersion${targetVersion}: success!`);
}
/**
* Returns the logged in user conversation attributes and the keys.
* If the keys exists but a conversation for that pubkey does not exist yet, the keys are still returned
*/
function getLoggedInUserConvoDuringMigration(db: BetterSqlite3.Database) {
const ourKeys = getIdentityKeys(db);
if (!ourKeys || !ourKeys.publicKeyHex || !ourKeys.privateEd25519) {
return null;
}
const ourConversation = db.prepare(`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`).get({
id: ourKeys.publicKeyHex,
}) as Record<string, any> | null;
return { ourKeys, ourConversation: ourConversation || null };
}
function updateToSessionSchemaVersion31(currentVersion: number, db: BetterSqlite3.Database) {
const targetVersion = 31;
if (currentVersion >= targetVersion) {
@ -1593,16 +1614,13 @@ function updateToSessionSchemaVersion31(currentVersion: number, db: BetterSqlite
// In the migration 30, we made all the changes which didn't require the user to be logged in yet.
// in this one, we check if a user is logged in, and if yes we build and save the config dumps for the current state of the database.
try {
const keys = getIdentityKeys(db);
const loggedInUser = getLoggedInUserConvoDuringMigration(db);
const userAlreadyCreated = !!keys && !isEmpty(keys.privateEd25519);
if (!userAlreadyCreated) {
if (!loggedInUser || !loggedInUser.ourKeys) {
throw new Error('privateEd25519 was empty. Considering no users are logged in');
}
const blockedNumbers = getBlockedNumbersDuringMigration(db);
const { privateEd25519, publicKeyHex } = keys;
const { privateEd25519, publicKeyHex } = loggedInUser.ourKeys;
const userProfileWrapper = new UserConfigWrapperNode(privateEd25519, null);
const contactsConfigWrapper = new ContactsConfigWrapperNode(privateEd25519, null);
const userGroupsConfigWrapper = new UserGroupsWrapperNode(privateEd25519, null);
@ -1612,11 +1630,7 @@ function updateToSessionSchemaVersion31(currentVersion: number, db: BetterSqlite
* Setup up the User profile wrapper with what is stored in our own conversation
*/
const ourConversation = db
.prepare(`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`)
.get({
id: publicKeyHex,
}) as Record<string, any> | undefined;
const { ourConversation } = loggedInUser;
if (!ourConversation) {
throw new Error('Failed to find our logged in conversation while migrating');
@ -1636,7 +1650,7 @@ function updateToSessionSchemaVersion31(currentVersion: number, db: BetterSqlite
url: ourDbProfileUrl,
key: ourDbProfileKey,
}
// ourConvoExpire
// ourConvoExpire,
);
}
@ -1854,6 +1868,91 @@ function updateToSessionSchemaVersion32(currentVersion: number, db: BetterSqlite
console.log(`updateToSessionSchemaVersion${targetVersion}: success!`);
}
function fetchUserConfigDump(
db: BetterSqlite3.Database,
userPubkeyhex: string
): ConfigDumpRow | null {
const userConfigWrapperDumps = db
.prepare(
`SELECT * FROM ${CONFIG_DUMP_TABLE} WHERE variant = $variant AND publicKey = $publicKey;`
)
.all({ variant: 'UserConfig', publicKey: userPubkeyhex }) as Array<ConfigDumpRow>;
if (!userConfigWrapperDumps || !userConfigWrapperDumps.length) {
return null;
}
// we can only have one dump with the "UserConfig" variant and our pubkey
return userConfigWrapperDumps[0];
}
function writeUserConfigDump(db: BetterSqlite3.Database, userPubkeyhex: string, dump: Uint8Array) {
db.prepare(
`INSERT OR REPLACE INTO ${CONFIG_DUMP_TABLE} (
publicKey,
variant,
data
) values (
$publicKey,
$variant,
$data
);`
).run({
publicKey: userPubkeyhex,
variant: 'UserConfig',
data: dump,
});
}
function updateToSessionSchemaVersion33(currentVersion: number, db: BetterSqlite3.Database) {
const targetVersion = 33;
if (currentVersion >= targetVersion) {
return;
}
console.log(`updateToSessionSchemaVersion${targetVersion}: starting...`);
db.transaction(() => {
db.exec(`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN blocksSogsMsgReqsTimestamp INTEGER;`);
const loggedInUser = getLoggedInUserConvoDuringMigration(db);
if (!loggedInUser?.ourKeys) {
// no user loggedin was empty. Considering no users are logged in
writeSessionSchemaVersion(targetVersion, db);
return;
}
// a user is logged in, we want to enable the 'inbox' polling for sogs, only if the current userwrapper for that field is undefined
const { privateEd25519, publicKeyHex } = loggedInUser.ourKeys;
// Get existing config wrapper dump and update it
const userConfigWrapperDump = fetchUserConfigDump(db, publicKeyHex);
if (!userConfigWrapperDump) {
writeSessionSchemaVersion(targetVersion, db);
return;
}
const userConfigData = userConfigWrapperDump.data;
const userProfileWrapper = new UserConfigWrapperNode(privateEd25519, userConfigData);
let blindedReqEnabled = userProfileWrapper.getEnableBlindedMsgRequest();
// if the value stored in the wrapper is undefined, we want to have blinded request enabled
if (isNil(blindedReqEnabled)) {
// this change will be part of the next ConfSyncJob (one is always made on app startup)
userProfileWrapper.setEnableBlindedMsgRequest(true);
writeUserConfigDump(db, publicKeyHex, userProfileWrapper.dump());
}
blindedReqEnabled = userProfileWrapper.getEnableBlindedMsgRequest();
// update the item stored in the DB with that value too
sqlNode.createOrUpdateItem(
{ id: SettingsKey.hasBlindedMsgRequestsEnabled, value: blindedReqEnabled },
db
);
writeSessionSchemaVersion(targetVersion, db);
})();
}
export function printTableColumns(table: string, db: BetterSqlite3.Database) {
console.info(db.pragma(`table_info('${table}');`));
}

View File

@ -442,6 +442,7 @@ function saveConversation(data: ConversationAttributes): SaveConversationReturn
conversationIdOrigin,
priority,
markedAsUnread,
blocksSogsMsgReqsTimestamp,
} = formatted;
const omited = omit(formatted);
@ -491,6 +492,7 @@ function saveConversation(data: ConversationAttributes): SaveConversationReturn
displayNameInProfile,
conversationIdOrigin,
markedAsUnread: toSqliteBoolean(markedAsUnread),
blocksSogsMsgReqsTimestamp,
});
return fetchConvoMemoryDetails(id);

View File

@ -45,7 +45,7 @@ export const configDumpData: ConfigDumpDataNode = {
getByVariantAndPubkey: (variant: ConfigWrapperObjectTypes, publicKey: string) => {
const rows = assertGlobalInstance()
.prepare(
`SELECT publicKey, variant, data from ${CONFIG_DUMP_TABLE} WHERE variant = $variant AND publicKey = $publicKey;`
`SELECT publicKey, variant, data FROM ${CONFIG_DUMP_TABLE} WHERE variant = $variant AND publicKey = $publicKey;`
)
.all({
publicKey,

View File

@ -1,6 +1,6 @@
/* eslint-disable no-await-in-loop */
import { ContactInfo } from 'libsession_util_nodejs';
import { compact, difference, isEmpty, isNumber, toNumber } from 'lodash';
import { compact, difference, isEmpty, isNil, isNumber, toNumber } from 'lodash';
import { ConfigDumpData } from '../data/configDump/configDump';
import { Data } from '../data/data';
import { SettingsKey } from '../data/settings-key';
@ -39,8 +39,9 @@ import {
isSignInByLinking,
setLastProfileUpdateTimestamp,
} from '../util/storage';
import { deleteAllMessagesByConvoIdNoConfirmation } from '../interactions/conversationInteractions';
// eslint-disable-next-line import/no-unresolved, import/extensions
import { ConfigWrapperObjectTypes } from '../webworker/workers/browser/libsession_worker_functions';
import { ConfigWrapperObjectTypes } from '../../ts/webworker/workers/browser/libsession_worker_functions';
import {
ContactsWrapperActions,
ConvoInfoVolatileWrapperActions,
@ -53,7 +54,6 @@ import { addKeyPairToCacheAndDBIfNeeded, handleNewClosedGroup } from './closedGr
import { HexKeyPair } from './keypairs';
import { queueAllCachedFromSource } from './receiver';
import { EnvelopePlus } from './types';
import { deleteAllMessagesByConvoIdNoConfirmation } from '../interactions/conversationInteractions';
function groupByVariant(
incomingConfigs: Array<IncomingMessage<SignalService.ISharedConfigMessage>>
@ -197,8 +197,16 @@ async function handleUserProfileUpdate(result: IncomingConfResult): Promise<Inco
if (!updateUserInfo) {
return result;
}
const currentBlindedMsgRequest = Storage.get(SettingsKey.hasBlindedMsgRequestsEnabled);
const newBlindedMsgRequest = await UserConfigWrapperActions.getEnableBlindedMsgRequest();
if (!isNil(newBlindedMsgRequest) && newBlindedMsgRequest !== currentBlindedMsgRequest) {
await window.setSettingValue(SettingsKey.hasBlindedMsgRequestsEnabled, newBlindedMsgRequest); // this does the dispatch to redux
}
const picUpdate = !isEmpty(updateUserInfo.key) && !isEmpty(updateUserInfo.url);
// NOTE: if you do any changes to the settings of a user which are synced, it should be done above the `updateOurProfileLegacyOrViaLibSession` call
await updateOurProfileLegacyOrViaLibSession(
result.latestEnvelopeTimestamp,
updateUserInfo.name,

View File

@ -50,14 +50,14 @@ const handleOpenGroupMessage = async (
const dataUint = new Uint8Array(removeMessagePadding(arr));
const decoded = SignalService.Content.decode(dataUint);
const decodedContent = SignalService.Content.decode(dataUint);
const conversationId = getOpenGroupV2ConversationId(serverUrl, roomId);
if (!conversationId) {
window?.log?.error('We cannot handle a message without a conversationId');
return;
}
const idataMessage = decoded?.dataMessage;
const idataMessage = decodedContent?.dataMessage;
if (!idataMessage) {
window?.log?.error('Invalid decoded opengroup message: no dataMessage');
return;
@ -101,7 +101,9 @@ const handleOpenGroupMessage = async (
await handleMessageJob(
msgModel,
groupConvo,
toRegularMessage(cleanIncomingDataMessage(decoded?.dataMessage as SignalService.DataMessage)),
toRegularMessage(
cleanIncomingDataMessage(decodedContent?.dataMessage as SignalService.DataMessage)
),
noop,
sender,
''

View File

@ -1,4 +1,4 @@
import _ from 'lodash';
import _, { isNumber } from 'lodash';
import { queueAttachmentDownloads } from './attachments';
@ -18,6 +18,7 @@ import { GoogleChrome } from '../util';
import { LinkPreviews } from '../util/linkPreviews';
import { ReleasedFeatures } from '../util/releaseFeature';
import { PropsForMessageWithoutConvoProps, lookupQuote } from '../state/ducks/conversations';
import { PubKey } from '../session/types';
function contentTypeSupported(type: string): boolean {
const Chrome = GoogleChrome;
@ -198,6 +199,7 @@ export type RegularMessageType = Pick<
| 'profile'
| 'profileKey'
| 'expireTimer'
| 'blocksCommunityMessageRequests'
> & { isRegularMessage: true };
/**
@ -216,6 +218,7 @@ export function toRegularMessage(rawDataMessage: SignalService.DataMessage): Reg
'quote',
'profile',
'expireTimer',
'blocksCommunityMessageRequests',
]),
isRegularMessage: true,
};
@ -254,6 +257,18 @@ async function handleRegularMessage(
message.set({ expireTimer: existingExpireTimer });
}
const serverTimestamp = message.get('serverTimestamp');
if (
conversation.isPublic() &&
PubKey.isBlinded(sendingDeviceConversation.id) &&
isNumber(serverTimestamp)
) {
const updateBlockTimestamp = !rawDataMessage.blocksCommunityMessageRequests
? 0
: serverTimestamp;
await sendingDeviceConversation.updateBlocksSogsMsgReqsTimestamp(updateBlockTimestamp, false);
}
// Expire timer updates are now explicit.
// We don't handle an expire timer from a incoming message except if it is an ExpireTimerUpdate message.

View File

@ -25,6 +25,8 @@ import {
openConversationWithMessages,
} from '../../../../state/ducks/conversations';
import { roomHasBlindEnabled } from '../../../../types/sqlSharedTypes';
import { Storage } from '../../../../util/storage';
import { SettingsKey } from '../../../../data/settings-key';
export type OpenGroupMessageV4 = {
/** AFAIK: indicates the number of the message in the group. e.g. 2nd message will be 1 or 2 */
@ -244,12 +246,14 @@ export class OpenGroupServerPoller {
if (roomHasBlindEnabled(rooms[0])) {
const maxInboxId = Math.max(...rooms.map(r => r.lastInboxIdFetched || 0));
// This only works for servers with blinding capabilities
// adding inbox subrequest info
subrequestOptions.push({
type: 'inbox',
inboxSince: { id: isNumber(maxInboxId) && maxInboxId > 0 ? maxInboxId : undefined },
});
if (Storage.get(SettingsKey.hasBlindedMsgRequestsEnabled)) {
// This only works for servers with blinding capabilities
// adding inbox subrequest info
subrequestOptions.push({
type: 'inbox',
inboxSince: { id: isNumber(maxInboxId) && maxInboxId > 0 ? maxInboxId : undefined },
});
}
const maxOutboxId = Math.max(...rooms.map(r => r.lastOutboxIdFetched || 0));

View File

@ -1,3 +1,25 @@
import { VisibleMessage } from './VisibleMessage';
import { SettingsKey } from '../../../../data/settings-key';
import { SignalService } from '../../../../protobuf';
import { Storage } from '../../../../util/storage';
import { VisibleMessage, VisibleMessageParams } from './VisibleMessage';
export class OpenGroupVisibleMessage extends VisibleMessage {}
// eslint-disable-next-line @typescript-eslint/ban-types
export type OpenGroupVisibleMessageParams = VisibleMessageParams & {};
export class OpenGroupVisibleMessage extends VisibleMessage {
private readonly blocksCommunityMessageRequests: boolean;
constructor(params: OpenGroupVisibleMessageParams) {
super(params);
// they are the opposite of each others
this.blocksCommunityMessageRequests = !Storage.get(SettingsKey.hasBlindedMsgRequestsEnabled);
}
public dataProto(): SignalService.DataMessage {
const dataMessage = super.dataProto();
dataMessage.blocksCommunityMessageRequests = this.blocksCommunityMessageRequests;
return dataMessage;
}
}

View File

@ -4,6 +4,8 @@ import { UserConfigWrapperActions } from '../../../webworker/workers/browser/lib
import { getConversationController } from '../../conversations';
import { fromHexToArray } from '../String';
import { CONVERSATION_PRIORITIES } from '../../../models/conversationAttributes';
import { Storage } from '../../../util/storage';
import { SettingsKey } from '../../../data/settings-key';
async function insertUserProfileIntoWrapper(convoId: string) {
if (!isUserProfileToStoreInWrapper(convoId)) {
@ -21,10 +23,12 @@ async function insertUserProfileIntoWrapper(convoId: string) {
const dbProfileKey = fromHexToArray(ourConvo.get('profileKey') || '');
const priority = ourConvo.get('priority') || CONVERSATION_PRIORITIES.default;
const areBlindedMsgRequestEnabled = !!Storage.get(SettingsKey.hasBlindedMsgRequestsEnabled);
window.log.debug(
`inserting into userprofile wrapper: username:"${dbName}", priority:${priority} image:${JSON.stringify(
{ url: dbProfileUrl, key: dbProfileKey }
)} `
)}, settings: ${JSON.stringify({ areBlindedMsgRequestEnabled })}`
);
// const expirySeconds = ourConvo.get('expireTimer') || 0;
if (dbProfileUrl && !isEmpty(dbProfileKey)) {
@ -40,6 +44,7 @@ async function insertUserProfileIntoWrapper(convoId: string) {
} else {
await UserConfigWrapperActions.setUserInfo(dbName, priority, null); // expirySeconds
}
await UserConfigWrapperActions.setEnableBlindedMsgRequest(areBlindedMsgRequestEnabled);
}
function isUserProfileToStoreInWrapper(convoId: string) {

View File

@ -258,6 +258,8 @@ export interface ReduxConversationType {
didApproveMe?: boolean;
isMarkedUnread?: boolean;
blocksSogsMsgReqsTimestamp?: number; // undefined means 0
}
export interface NotificationForConvoOption {

View File

@ -7,6 +7,7 @@ import { Storage } from '../../util/storage';
const SettingsBoolsKeyTrackedInRedux = [
SettingsKey.someDeviceOutdatedSyncing,
SettingsKey.settingsLinkPreview,
SettingsKey.hasBlindedMsgRequestsEnabled,
] as const;
export type SettingsState = {
@ -18,6 +19,7 @@ export function getSettingsInitialState() {
settingsBools: {
someDeviceOutdatedSyncing: false,
'link-preview-setting': false, // this is the value of SettingsKey.settingsLinkPreview
hasBlindedMsgRequestsEnabled: false,
},
};
}
@ -41,10 +43,17 @@ const settingsSlice = createSlice({
updateAllOnStorageReady(state) {
const linkPreview = Storage.get(SettingsKey.settingsLinkPreview, false);
const outdatedSync = Storage.get(SettingsKey.someDeviceOutdatedSyncing, false);
const hasBlindedMsgRequestsEnabled = Storage.get(
SettingsKey.hasBlindedMsgRequestsEnabled,
false
);
state.settingsBools.someDeviceOutdatedSyncing = isBoolean(outdatedSync)
? outdatedSync
: false;
state.settingsBools['link-preview-setting'] = isBoolean(linkPreview) ? linkPreview : false; // this is the value of SettingsKey.settingsLinkPreview
state.settingsBools.hasBlindedMsgRequestsEnabled = isBoolean(hasBlindedMsgRequestsEnabled)
? hasBlindedMsgRequestsEnabled
: false;
return state;
},
updateSettingsBoolValue(state, action: PayloadAction<{ id: string; value: boolean }>) {

View File

@ -80,7 +80,35 @@ export function getSelectedCanWrite(state: StateType) {
const canWriteSogs = getCanWrite(state, selectedConvoPubkey);
const { isBlocked, isKickedFromGroup, left, isPublic } = selectedConvo;
return !(isBlocked || isKickedFromGroup || left || (isPublic && !canWriteSogs));
const readOnlySogs = isPublic && !canWriteSogs;
const isBlindedAndDisabledMsgRequests = getSelectedBlindedDisabledMsgRequests(state); // true if isPrivate, blinded and explicitely disabled msgreq
return !(
isBlocked ||
isKickedFromGroup ||
left ||
readOnlySogs ||
isBlindedAndDisabledMsgRequests
);
}
function getSelectedBlindedDisabledMsgRequests(state: StateType) {
const selectedConvoPubkey = getSelectedConversationKey(state);
if (!selectedConvoPubkey) {
return false;
}
const selectedConvo = getSelectedConversation(state);
if (!selectedConvo) {
return false;
}
const { blocksSogsMsgReqsTimestamp, isPrivate } = selectedConvo;
const isBlindedAndDisabledMsgRequests = Boolean(
isPrivate && PubKey.isBlinded(selectedConvoPubkey) && blocksSogsMsgReqsTimestamp
);
return isBlindedAndDisabledMsgRequests;
}
/**
@ -156,6 +184,10 @@ export function useSelectedApprovedMe() {
return useSelector(getSelectedApprovedMe);
}
export function useSelectedHasDisabledBlindedMsgRequests() {
return useSelector(getSelectedBlindedDisabledMsgRequests);
}
/**
* Returns true if the given arguments corresponds to a private contact which is approved both sides. i.e. a friend.
*/

View File

@ -8,6 +8,9 @@ const getLinkPreviewEnabled = (state: StateType) =>
const getHasDeviceOutdatedSyncing = (state: StateType) =>
state.settings.settingsBools[SettingsKey.someDeviceOutdatedSyncing];
const getHasBlindedMsgRequestsEnabled = (state: StateType) =>
state.settings.settingsBools[SettingsKey.hasBlindedMsgRequestsEnabled];
export const useHasLinkPreviewEnabled = () => {
const value = useSelector(getLinkPreviewEnabled);
return Boolean(value);
@ -17,3 +20,8 @@ export const useHasDeviceOutdatedSyncing = () => {
const value = useSelector(getHasDeviceOutdatedSyncing);
return Boolean(value);
};
export const useHasBlindedMsgRequestsEnabled = () => {
const value = useSelector(getHasBlindedMsgRequestsEnabled);
return Boolean(value);
};

View File

@ -442,6 +442,8 @@ export type LocalizerKeys =
| 'clearAll'
| 'clearDataSettingsTitle'
| 'messageRequests'
| 'blindedMsgReqsSettingTitle'
| 'blindedMsgReqsSettingDesc'
| 'requestsSubtitle'
| 'requestsPlaceholder'
| 'hideRequestBannerDescription'
@ -489,6 +491,7 @@ export type LocalizerKeys =
| 'clearAllConfirmationTitle'
| 'clearAllConfirmationBody'
| 'noMessagesInReadOnly'
| 'noMessagesInBlindedDisabledMsgRequests'
| 'noMessagesInNoteToSelf'
| 'noMessagesInEverythingElse'
| 'hideBanner'

View File

@ -1,9 +1,9 @@
/* eslint-disable import/extensions */
/* eslint-disable import/no-unresolved */
// eslint-disable-next-line camelcase
import { ContactInfoSet, LegacyGroupInfo, LegacyGroupMemberInfo } from 'libsession_util_nodejs';
import { from_hex } from 'libsodium-wrappers-sumo';
import { isArray, isEmpty, isEqual } from 'lodash';
import { ContactInfoSet, LegacyGroupInfo, LegacyGroupMemberInfo } from 'libsession_util_nodejs';
import { OpenGroupV2Room } from '../data/opengroups';
import { ConversationAttributes } from '../models/conversationAttributes';
import { OpenGroupRequestCommonType } from '../session/apis/open_group_api/opengroupV2/ApiUtil';

View File

@ -107,7 +107,7 @@ export const UserConfigWrapperActions: UserConfigWrapperActionsCalls = {
name: string,
priority: number,
profilePic: { url: string; key: Uint8Array } | null
// expireSeconds: number
// expireSeconds: number,
) =>
callLibSessionWorker([
'UserConfig',
@ -117,6 +117,17 @@ export const UserConfigWrapperActions: UserConfigWrapperActionsCalls = {
profilePic,
// expireSeconds,
]) as Promise<ReturnType<UserConfigWrapperActionsCalls['setUserInfo']>>,
getEnableBlindedMsgRequest: async () =>
callLibSessionWorker(['UserConfig', 'getEnableBlindedMsgRequest']) as Promise<
ReturnType<UserConfigWrapperActionsCalls['getEnableBlindedMsgRequest']>
>,
setEnableBlindedMsgRequest: async (blindedMsgRequests: boolean) =>
callLibSessionWorker([
'UserConfig',
'setEnableBlindedMsgRequest',
blindedMsgRequests,
]) as Promise<ReturnType<UserConfigWrapperActionsCalls['setEnableBlindedMsgRequest']>>,
};
export const ContactsWrapperActions: ContactsWrapperActionsCalls = {

View File

@ -4793,9 +4793,9 @@ levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
"libsession_util_nodejs@https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.2.5/libsession_util_nodejs-v0.2.5.tar.gz":
version "0.2.5"
resolved "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.2.5/libsession_util_nodejs-v0.2.5.tar.gz#e1db928eaca58f22c66494c6179e13bdd4e38cd4"
"libsession_util_nodejs@https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.2.6/libsession_util_nodejs-v0.2.6.tar.gz":
version "0.2.6"
resolved "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.2.6/libsession_util_nodejs-v0.2.6.tar.gz#79574dac7d24957c44376397201fc6fa1d4a45ee"
dependencies:
cmake-js "^7.2.1"
node-addon-api "^6.1.0"