diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index e560f146e..d16a544ad 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -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 $name$.",
+ "noMessagesInBlindedDisabledMsgRequests": "$name$ has message requests from Community conversations turned off, so you cannot send them a message.",
"noMessagesInNoteToSelf": "You have no messages in $name$.",
"noMessagesInEverythingElse": "You have no messages from $name$. Send a message to start the conversation!",
"hideBanner": "Hide",
diff --git a/package.json b/package.json
index 454bf2434..21017d061 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/protos/SignalService.proto b/protos/SignalService.proto
index 99b690f35..4bbb80a83 100644
--- a/protos/SignalService.proto
+++ b/protos/SignalService.proto
@@ -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;
}
diff --git a/ts/components/conversation/SubtleNotification.tsx b/ts/components/conversation/SubtleNotification.tsx
index 084bda830..0e3eb862d 100644
--- a/ts/components/conversation/SubtleNotification.tsx
+++ b/ts/components/conversation/SubtleNotification.tsx
@@ -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';
}
diff --git a/ts/components/conversation/composition/CompositionBox.tsx b/ts/components/conversation/composition/CompositionBox.tsx
index 7a150ff19..44856e2f4 100644
--- a/ts/components/conversation/composition/CompositionBox.tsx
+++ b/ts/components/conversation/composition/CompositionBox.tsx
@@ -441,7 +441,7 @@ class CompositionBoxInner extends React.Component {
{typingEnabled && (
)}
-
+ {typingEnabled && }
{typingEnabled && showEmojiPanel && (
{
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() {
diff --git a/ts/components/settings/section/CategoryPrivacy.tsx b/ts/components/settings/section/CategoryPrivacy.tsx
index a66bdf4fb..4e0f5be3b 100644
--- a/ts/components/settings/section/CategoryPrivacy.tsx
+++ b/ts/components/settings/section/CategoryPrivacy.tsx
@@ -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}
/>
+ {
+ 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 && (
{
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 {
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.
diff --git a/ts/models/conversationAttributes.ts b/ts/models/conversationAttributes.ts
index be2a83477..9d54661d3 100644
--- a/ts/models/conversationAttributes.ts
+++ b/ts/models/conversationAttributes.ts
@@ -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 the convo is blinded and the user has denied contact through sogs, this field be set to the user's latest message timestamp
}
/**
@@ -133,6 +135,7 @@ export const fillConvoAttributesWithDefaults = (
left: false,
priority: CONVERSATION_PRIORITIES.default,
markedAsUnread: false,
+ blocksSogsMsgReqsTimestamp: 0,
});
};
diff --git a/ts/node/database_utility.ts b/ts/node/database_utility.ts
index 6ce8a86c4..e78a4e53d 100644
--- a/ts/node/database_utility.ts
+++ b/ts/node/database_utility.ts
@@ -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',
];
diff --git a/ts/node/migration/sessionMigrations.ts b/ts/node/migration/sessionMigrations.ts
index 988403634..2135f0440 100644
--- a/ts/node/migration/sessionMigrations.ts
+++ b/ts/node/migration/sessionMigrations.ts
@@ -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 | 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 | 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;
+
+ 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}');`));
}
diff --git a/ts/node/sql.ts b/ts/node/sql.ts
index a8414c481..7b1d5d5f3 100644
--- a/ts/node/sql.ts
+++ b/ts/node/sql.ts
@@ -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);
diff --git a/ts/node/sql_calls/config_dump.ts b/ts/node/sql_calls/config_dump.ts
index 0435979e5..cfc9d9747 100644
--- a/ts/node/sql_calls/config_dump.ts
+++ b/ts/node/sql_calls/config_dump.ts
@@ -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,
diff --git a/ts/receiver/configMessage.ts b/ts/receiver/configMessage.ts
index 4ad37d0ca..de5ab601a 100644
--- a/ts/receiver/configMessage.ts
+++ b/ts/receiver/configMessage.ts
@@ -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>
@@ -197,8 +197,16 @@ async function handleUserProfileUpdate(result: IncomingConfResult): Promise & { 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.
diff --git a/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts b/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts
index d91efc166..20ccd9510 100644
--- a/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts
+++ b/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts
@@ -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));
diff --git a/ts/session/apis/open_group_api/sogsv3/sogsV3SendReaction.ts b/ts/session/apis/open_group_api/sogsv3/sogsV3SendReaction.ts
index e5628febd..936b1e0c8 100644
--- a/ts/session/apis/open_group_api/sogsv3/sogsV3SendReaction.ts
+++ b/ts/session/apis/open_group_api/sogsv3/sogsV3SendReaction.ts
@@ -1,5 +1,5 @@
import { AbortSignal } from 'abort-controller';
-import { getEmojiDataFromNative } from 'emoji-mart';
+import { SearchIndex } from 'emoji-mart';
import { Data } from '../../../../data/data';
import { ConversationModel } from '../../../../models/conversation';
import { Action, OpenGroupReactionResponse, Reaction } from '../../../../types/Reaction';
@@ -68,9 +68,8 @@ export const sendSogsReactionOnionV4 = async (
// The SOGS endpoint supports any text input so we need to make sure we are sending a valid unicode emoji
// for an invalid input we use https://emojipedia.org/frame-with-an-x/ as a replacement since it cannot rendered as an emoji but is valid unicode
- // NOTE emoji-mart v5.2.2 types for getEmojiDataFromNative are broken
// eslint-disable-next-line @typescript-eslint/await-thenable
- const emoji = (await getEmojiDataFromNative(reaction.emoji)) ? reaction.emoji : '🖾';
+ const emoji = (await SearchIndex.search(reaction.emoji)) ? reaction.emoji : '🖾';
const endpoint = `/room/${room}/reaction/${reaction.id}/${emoji}`;
const method = reaction.action === Action.REACT ? 'PUT' : 'DELETE';
const serverPubkey = allValidRoomInfos[0].serverPublicKey;
diff --git a/ts/session/messages/outgoing/visibleMessage/OpenGroupVisibleMessage.ts b/ts/session/messages/outgoing/visibleMessage/OpenGroupVisibleMessage.ts
index 13ff6ba37..e4b78a620 100644
--- a/ts/session/messages/outgoing/visibleMessage/OpenGroupVisibleMessage.ts
+++ b/ts/session/messages/outgoing/visibleMessage/OpenGroupVisibleMessage.ts
@@ -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;
+ }
+}
diff --git a/ts/session/utils/libsession/libsession_utils_user_profile.ts b/ts/session/utils/libsession/libsession_utils_user_profile.ts
index 96c3948ef..fca83bddf 100644
--- a/ts/session/utils/libsession/libsession_utils_user_profile.ts
+++ b/ts/session/utils/libsession/libsession_utils_user_profile.ts
@@ -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) {
diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts
index 5e26b98c8..94ea32051 100644
--- a/ts/state/ducks/conversations.ts
+++ b/ts/state/ducks/conversations.ts
@@ -258,6 +258,8 @@ export interface ReduxConversationType {
didApproveMe?: boolean;
isMarkedUnread?: boolean;
+
+ blocksSogsMsgReqsTimestamp?: number; // undefined means 0
}
export interface NotificationForConvoOption {
diff --git a/ts/state/ducks/settings.tsx b/ts/state/ducks/settings.tsx
index b499c78d1..43868f4c1 100644
--- a/ts/state/ducks/settings.tsx
+++ b/ts/state/ducks/settings.tsx
@@ -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 }>) {
diff --git a/ts/state/selectors/selectedConversation.ts b/ts/state/selectors/selectedConversation.ts
index ad79c090f..6c23c6bab 100644
--- a/ts/state/selectors/selectedConversation.ts
+++ b/ts/state/selectors/selectedConversation.ts
@@ -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.
*/
diff --git a/ts/state/selectors/settings.ts b/ts/state/selectors/settings.ts
index ffca0da3e..eb70ef9b1 100644
--- a/ts/state/selectors/settings.ts
+++ b/ts/state/selectors/settings.ts
@@ -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);
+};
diff --git a/ts/types/LocalizerKeys.ts b/ts/types/LocalizerKeys.ts
index eb2123cce..592b1f592 100644
--- a/ts/types/LocalizerKeys.ts
+++ b/ts/types/LocalizerKeys.ts
@@ -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'
diff --git a/ts/types/sqlSharedTypes.ts b/ts/types/sqlSharedTypes.ts
index ba54939b0..35cd5c396 100644
--- a/ts/types/sqlSharedTypes.ts
+++ b/ts/types/sqlSharedTypes.ts
@@ -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';
diff --git a/ts/webworker/workers/browser/libsession_worker_interface.ts b/ts/webworker/workers/browser/libsession_worker_interface.ts
index 419aba445..6ae6f734e 100644
--- a/ts/webworker/workers/browser/libsession_worker_interface.ts
+++ b/ts/webworker/workers/browser/libsession_worker_interface.ts
@@ -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>,
+
+ getEnableBlindedMsgRequest: async () =>
+ callLibSessionWorker(['UserConfig', 'getEnableBlindedMsgRequest']) as Promise<
+ ReturnType
+ >,
+ setEnableBlindedMsgRequest: async (blindedMsgRequests: boolean) =>
+ callLibSessionWorker([
+ 'UserConfig',
+ 'setEnableBlindedMsgRequest',
+ blindedMsgRequests,
+ ]) as Promise>,
};
export const ContactsWrapperActions: ContactsWrapperActionsCalls = {
diff --git a/yarn.lock b/yarn.lock
index 298e95cc9..94940ec04 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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"