|
|
|
import { ipcRenderer } from 'electron';
|
|
|
|
|
|
|
|
// tslint:disable: no-require-imports no-var-requires one-variable-per-declaration no-void-expression
|
|
|
|
|
|
|
|
import _ from 'lodash';
|
|
|
|
import { ConversationCollection, ConversationModel } from '../models/conversation';
|
|
|
|
import { MessageCollection, MessageModel } from '../models/message';
|
|
|
|
import { MessageAttributes } from '../models/messageType';
|
|
|
|
import { HexKeyPair } from '../receiver/keypairs';
|
|
|
|
import { getSodium } from '../session/crypto';
|
|
|
|
import { PubKey } from '../session/types';
|
|
|
|
import { fromArrayBufferToBase64, fromBase64ToArrayBuffer } from '../session/utils/String';
|
|
|
|
import { ReduxConversationType } from '../state/ducks/conversations';
|
|
|
|
import { channels } from './channels';
|
|
|
|
import { channelsToMake as channelstoMakeOpenGroupV2 } from './opengroups';
|
|
|
|
|
|
|
|
const DATABASE_UPDATE_TIMEOUT = 2 * 60 * 1000; // two minutes
|
|
|
|
|
|
|
|
const SQL_CHANNEL_KEY = 'sql-channel';
|
|
|
|
const ERASE_SQL_KEY = 'erase-sql-key';
|
|
|
|
const ERASE_ATTACHMENTS_KEY = 'erase-attachments';
|
|
|
|
const CLEANUP_ORPHANED_ATTACHMENTS_KEY = 'cleanup-orphaned-attachments';
|
|
|
|
|
|
|
|
export const _jobs = Object.create(null);
|
|
|
|
const _DEBUG = false;
|
|
|
|
let _jobCounter = 0;
|
|
|
|
let _shuttingDown = false;
|
|
|
|
let _shutdownCallback: any = null;
|
|
|
|
let _shutdownPromise: any = null;
|
|
|
|
|
|
|
|
export type StorageItem = {
|
|
|
|
id: string;
|
|
|
|
value: any;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type IdentityKey = {
|
|
|
|
id: string;
|
|
|
|
publicKey: ArrayBuffer;
|
|
|
|
firstUse: boolean;
|
|
|
|
nonblockingApproval: boolean;
|
|
|
|
secretKey?: string; // found in medium groups
|
|
|
|
};
|
|
|
|
|
|
|
|
export type GuardNode = {
|
|
|
|
ed25519PubKey: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export interface Snode {
|
|
|
|
ip: string;
|
|
|
|
port: number;
|
|
|
|
pubkey_x25519: string;
|
|
|
|
pubkey_ed25519: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export type SwarmNode = Snode & {
|
|
|
|
address: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type ServerToken = {
|
|
|
|
serverUrl: string;
|
|
|
|
token: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const hasSyncedInitialConfigurationItem = 'hasSyncedInitialConfigurationItem';
|
|
|
|
export const lastAvatarUploadTimestamp = 'lastAvatarUploadTimestamp';
|
|
|
|
export const hasLinkPreviewPopupBeenDisplayed = 'hasLinkPreviewPopupBeenDisplayed';
|
|
|
|
|
|
|
|
const channelsToMake = {
|
|
|
|
shutdown,
|
|
|
|
close,
|
|
|
|
removeDB,
|
|
|
|
getPasswordHash,
|
|
|
|
|
|
|
|
getGuardNodes,
|
|
|
|
updateGuardNodes,
|
|
|
|
|
|
|
|
createOrUpdateItem,
|
|
|
|
getItemById,
|
|
|
|
getAllItems,
|
|
|
|
removeItemById,
|
|
|
|
|
|
|
|
getSwarmNodesForPubkey,
|
|
|
|
updateSwarmNodesForPubkey,
|
|
|
|
|
|
|
|
saveConversation,
|
|
|
|
getConversationById,
|
|
|
|
updateConversation,
|
|
|
|
removeConversation,
|
|
|
|
|
|
|
|
getAllConversations,
|
|
|
|
getAllOpenGroupV1Conversations,
|
|
|
|
getPubkeysInPublicConversation,
|
|
|
|
getAllGroupsInvolvingId,
|
|
|
|
|
|
|
|
searchConversations,
|
|
|
|
searchMessages,
|
|
|
|
searchMessagesInConversation,
|
|
|
|
|
|
|
|
saveMessage,
|
|
|
|
cleanSeenMessages,
|
|
|
|
cleanLastHashes,
|
|
|
|
updateLastHash,
|
|
|
|
saveSeenMessageHashes,
|
|
|
|
saveMessages,
|
|
|
|
removeMessage,
|
|
|
|
_removeMessages,
|
|
|
|
getUnreadByConversation,
|
|
|
|
getUnreadCountByConversation,
|
|
|
|
|
|
|
|
removeAllMessagesInConversation,
|
|
|
|
|
|
|
|
getMessageBySender,
|
|
|
|
getMessageBySenderAndServerTimestamp,
|
|
|
|
getMessageBySenderAndTimestamp,
|
|
|
|
getMessageIdsFromServerIds,
|
|
|
|
getMessageById,
|
|
|
|
getMessagesBySentAt,
|
|
|
|
getExpiredMessages,
|
|
|
|
getOutgoingWithoutExpiresAt,
|
|
|
|
getNextExpiringMessage,
|
|
|
|
getMessagesByConversation,
|
|
|
|
getFirstUnreadMessageIdInConversation,
|
Session 1.7.5 (#2094)
* Added message requests disabled for now
* no longer showing empty space for conversations moved from list.
* Added syncing accepting of contact between running instances.
* Adding blocking of individual requests and syncing of block to devices. Added approval by replying to a message.
* fixed typos for translations and method name.
* Blocking, accepting on click and accepting on msg send
working across clients.
* adding setting of active_at to hide unapproved messages.
* adding feature flag for config message receiving
* fix archlinux pw unused issue
on archlinux, the appimage links to the system sqlite by default which
does not support sqlcipher
* hide activeAt = 0 convo from search results
Fixes #2033
* opengroup messages from blocked user are dropped
Fixes #2019
* opengroup messages from blocked user are dropped
Fixes #2019
* dismiss a call when answered from another of our devices
* add data-testid for leftpane sections and edit profile dialog
* update turn servers
* cleanup sessionprotobuf
* move the state of calling to its own slice
* no video track by default and will be turn ON if asked to
* message request refactoring.
* create offer and answer ourselves and do not use the negotiation needed
event.
this event is causing us to loop in negotiation needed when each side
try to create one, gets the answer and so on...
* auto select the first audio input on connection success webrtc
* add a way to choose the audioouput/mute a webrtc call
* mute audio from bg when video is in fullscreen
this is to avoid having two times the remote sound playing
one in the bg and one in the fullscreen
* Adding improvements to message request handling.
* Only updating approval when it is a true value as we consider a block a decline.
* Linting and formatting.
* More formatting and linting
* fixing merge conflicts
* linting and formatting changes
* darken a bit the green of sent message box in light theme
* disable deduplication based serverId+sender
only use the serverTimestamp+sender for searching because
serverId+sender might have false positive
* Fixing up block all logic.
* speed up fetching closed group's members avatar
* Applying PR changes.
* cleanup props passing of avatar and name with a custom hook
* fix a bug releasing the decrypted attachment blobs too early
* Adding trigger logic for conversation filtering of requests.
* Fixing rimraf transpile bug. Adding PR fixes - icon buttons.
* Minor call tweaks (#2051)
* show missed-call,started-call and answered call notification in chat
* fix types for createLastMessageUpdate
* show incoming dialog if we have a pending call when enable call receptio
* simplify a bit the avatar component
* move disableDrag to a custom hook
* speed up hash colors of avatarPlaceHolders
* fixup text selection and double click reply on message
* keep avatar decoded items longer before releasing memory
* add incoming/outgoing/missed call notification
also, merge that notification with the timer and group notification
component
* hangup call if no answer after 30sec
* refactor SessionInput using hook + add testid field for recovery
* disable message request feature flag for now
* fix merge issue
* force loading screen to be black instead of white
for our dark theme user's eyes safety
* Fetch translations (#2056)
* show missed-call,started-call and answered call notification in chat
* fix types for createLastMessageUpdate
* show incoming dialog if we have a pending call when enable call receptio
* simplify a bit the avatar component
* move disableDrag to a custom hook
* speed up hash colors of avatarPlaceHolders
* fixup text selection and double click reply on message
* keep avatar decoded items longer before releasing memory
* add incoming/outgoing/missed call notification
also, merge that notification with the timer and group notification
component
* hangup call if no answer after 30sec
* refactor SessionInput using hook + add testid field for recovery
* disable message request feature flag for now
* fix merge issue
* force loading screen to be black instead of white
for our dark theme user's eyes safety
* add type for i18n to run update after crowdin fetch with tools/updateI18nKeysType.py
* update to latest translations
* Open group regex fixes (#2058)
* Open group URL regex fixes
- Capital letters in room tokens were not being accepted (it eventually
gets lower-cased internally, which works fine, but that happens
*after* the URL is tested for acceptability).
- `-` in room was not being allowed (it is and always has been on SOGS,
session-android, and session-ios).
- single-letter room ids are valid, but only 2+ letter ids were being
accepted.
- complete URL regex wasn't anchored so something like
`garbagehttps://example.com/room?public_key=<64hex>moregarbage` was
being accepted in the GUI input (it fails later when other code tries
to parse it as a URL).
- removed `m` modifier from open group regex: without anchors it wasn't
doing anything anyway, but *with* anchors it would still allow
leading/trailing garbage if delineated by newlines.
- public key regex was accepting g-z letters, and not accepting A-F.
- various regex cleanups:
- use non-capture groups (?:...) rather than capturing groups (...)
- avoid repetition in host segment matching
- tightened up host pattern matching a bit:
- DNS host segments have a max length of 63
- Limit port max length to 5, and disallow starting with 0
* Show an error when the open group URL is invalid
It's quite disconcerting when you have a bad open group URL and try to
add it and the join button just "doesn't work" without any feedback at
all. Fix it to show an error message. (There is already an i18n entry
for this because this same message is thrown if the URL can't be parsed
later on).
* Add call duration (#2059)
* add call duration once connected
* close incoming call dialog if endCall from same sender
* disable message request toggle if featureFlag is OFF
* Cleanup message request (#2063)
* close incoming call dialog if endCall from seame sender
* disable message request toggle if featureFlag is OFF
* cleanup UI of message requests
* mark all existing conversations as approved in a migration
* fix regex with conversationID for opengroups
* Various UI fixes (#2070)
* cleanup unused convo json fields in db
* display a toast if the user is not approved yet on call OFFER received
* enable CBR for calls
* do not update active_at on configMessage if !!active_at
* remove mkdirp dependency
* disable call button if focused convo is blocked
* quote: do not include the full body in quote, but just the first 100
* click on the edit profile qr code padding
* Allow longer input for opengroup join overlay
Fixes #2068
* Fix overlay feature for start new session button
* make ringing depend on redux CALL status
* turn ON read-receipt by default
* keep read-receipts disabled by default (#2071)
* refactor most of the components to outside of their Session folder (#2072)
* refactor most of the components to outside of their Session folder
* finish moving overlay and memberListItem to react hook
* fix bug with kicked member len >2 not being displayed
also sort admins first in UpdateGroupMembers dialog
* fix admin leaving text of groupNotification
* add a useFocusMount hook to focus input fields on mount
* make click avatar convo item open only user dialog
* cleanup config default.json
* make sure to use convoController to build sync message
* disable showing pubkey on opengroups
* add a pause on audio playback
Fixes #2079
* Minor styling fix for large amount of message requests (#2080)
* Minor styling fix for large amount of message requests
* Vertical center fix for message request banner.
* removing top margin from banner again.
* reactify group updates text bubble from redux store (#2083)
* add crown icon for closed group admins (#2084)
* disable call for now + fix left pane actions overflow (#2085)
* Fix attachment dl freeze (#2086)
* fix attachment download freezing app for some opengroups
* make registration page work with smaller height
* Unban UI (#2091)
* adding basic functionaliy for unbanning a user
* merge ban and unban user dialog in to one dialog
Co-authored-by: warrickct <warrickct@gmail.com>
* use React Provider for convoListItem (#2088)
this is to avoid passing down the prop to all the components
* fix closed group updates undefined on no names (#2092)
Co-authored-by: Warrick Corfe-Tan <warrickct@gmail.com>
Co-authored-by: Jason Rhinelander <jason@imaginary.ca>
Co-authored-by: Warrick <wcor690@aucklanduni.ac.nz>
3 years ago
|
|
|
hasConversationOutgoingMessage,
|
|
|
|
getSeenMessagesByHashList,
|
|
|
|
getLastHashBySnode,
|
|
|
|
|
|
|
|
getUnprocessedCount,
|
|
|
|
getAllUnprocessed,
|
|
|
|
getUnprocessedById,
|
|
|
|
saveUnprocessed,
|
|
|
|
updateUnprocessedAttempts,
|
|
|
|
updateUnprocessedWithData,
|
|
|
|
removeUnprocessed,
|
|
|
|
removeAllUnprocessed,
|
|
|
|
|
|
|
|
getNextAttachmentDownloadJobs,
|
|
|
|
saveAttachmentDownloadJob,
|
|
|
|
resetAttachmentDownloadPending,
|
|
|
|
setAttachmentDownloadJobPending,
|
|
|
|
removeAttachmentDownloadJob,
|
|
|
|
removeAllAttachmentDownloadJobs,
|
|
|
|
|
|
|
|
removeAll,
|
|
|
|
removeAllConversations,
|
|
|
|
|
|
|
|
removeOtherData,
|
|
|
|
cleanupOrphanedAttachments,
|
|
|
|
|
|
|
|
// Returning plain JSON
|
|
|
|
getMessagesWithVisualMediaAttachments,
|
|
|
|
getMessagesWithFileAttachments,
|
|
|
|
|
|
|
|
getAllEncryptionKeyPairsForGroup,
|
|
|
|
getLatestClosedGroupEncryptionKeyPair,
|
|
|
|
addClosedGroupEncryptionKeyPair,
|
|
|
|
removeAllClosedGroupEncryptionKeyPairs,
|
|
|
|
removeOneOpenGroupV1Message,
|
|
|
|
|
|
|
|
// open group v2
|
|
|
|
...channelstoMakeOpenGroupV2,
|
|
|
|
};
|
|
|
|
|
|
|
|
export function init() {
|
|
|
|
// We listen to a lot of events on ipcRenderer, often on the same channel. This prevents
|
|
|
|
// any warnings that might be sent to the console in that case.
|
|
|
|
ipcRenderer.setMaxListeners(0);
|
|
|
|
|
|
|
|
_.forEach(channelsToMake, fn => {
|
|
|
|
if (_.isFunction(fn)) {
|
|
|
|
makeChannel(fn.name);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
ipcRenderer.on(`${SQL_CHANNEL_KEY}-done`, (_event, jobId, errorForDisplay, result) => {
|
|
|
|
const job = _getJob(jobId);
|
|
|
|
if (!job) {
|
|
|
|
throw new Error(
|
|
|
|
`Received SQL channel reply to job ${jobId}, but did not have it in our registry!`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const { resolve, reject, fnName } = job;
|
|
|
|
|
|
|
|
if (errorForDisplay) {
|
|
|
|
return reject(
|
|
|
|
new Error(`Error received from SQL channel job ${jobId} (${fnName}): ${errorForDisplay}`)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolve(result);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// When IPC arguments are prepared for the cross-process send, they are JSON.stringified.
|
|
|
|
// We can't send ArrayBuffers or BigNumbers (what we get from proto library for dates).
|
|
|
|
function _cleanData(data: any): any {
|
|
|
|
const keys = Object.keys(data);
|
|
|
|
|
|
|
|
for (let index = 0, max = keys.length; index < max; index += 1) {
|
|
|
|
const key = keys[index];
|
|
|
|
const value = data[key];
|
|
|
|
|
|
|
|
if (value === null || value === undefined) {
|
|
|
|
// eslint-disable-next-line no-continue
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// eslint-disable no-param-reassign
|
|
|
|
|
|
|
|
if (_.isFunction(value.toNumber)) {
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
data[key] = value.toNumber();
|
|
|
|
} else if (_.isFunction(value)) {
|
|
|
|
// just skip a function which has not a toNumber function. We don't want to save a function to the db.
|
|
|
|
// an attachment comes with a toJson() function
|
|
|
|
// tslint:disable-next-line: no-dynamic-delete
|
|
|
|
delete data[key];
|
|
|
|
} else if (Array.isArray(value)) {
|
|
|
|
data[key] = value.map(_cleanData);
|
|
|
|
} else if (_.isObject(value) && value instanceof File) {
|
|
|
|
data[key] = { name: value.name, path: value.path, size: value.size, type: value.type };
|
|
|
|
} else if (_.isObject(value)) {
|
|
|
|
data[key] = _cleanData(value);
|
|
|
|
} else if (_.isBoolean(value)) {
|
|
|
|
data[key] = value ? 1 : 0;
|
|
|
|
} else if (
|
|
|
|
typeof value !== 'string' &&
|
|
|
|
typeof value !== 'number' &&
|
|
|
|
typeof value !== 'boolean'
|
|
|
|
) {
|
|
|
|
window?.log?.info(`_cleanData: key ${key} had type ${typeof value}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function _shutdown() {
|
|
|
|
if (_shutdownPromise) {
|
|
|
|
return _shutdownPromise;
|
|
|
|
}
|
|
|
|
|
|
|
|
_shuttingDown = true;
|
|
|
|
|
|
|
|
const jobKeys = Object.keys(_jobs);
|
|
|
|
window?.log?.info(`data.shutdown: starting process. ${jobKeys.length} jobs outstanding`);
|
|
|
|
|
|
|
|
// No outstanding jobs, return immediately
|
|
|
|
if (jobKeys.length === 0) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Outstanding jobs; we need to wait until the last one is done
|
|
|
|
_shutdownPromise = new Promise((resolve, reject) => {
|
|
|
|
_shutdownCallback = (error: any) => {
|
|
|
|
window?.log?.info('data.shutdown: process complete');
|
|
|
|
if (error) {
|
|
|
|
return reject(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolve(undefined);
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
return _shutdownPromise;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _makeJob(fnName: string) {
|
|
|
|
if (_shuttingDown && fnName !== 'close') {
|
|
|
|
throw new Error(`Rejecting SQL channel job (${fnName}); application is shutting down`);
|
|
|
|
}
|
|
|
|
|
|
|
|
_jobCounter += 1;
|
|
|
|
const id = _jobCounter;
|
|
|
|
|
|
|
|
if (_DEBUG) {
|
|
|
|
window?.log?.debug(`SQL channel job ${id} (${fnName}) started`);
|
|
|
|
}
|
|
|
|
_jobs[id] = {
|
|
|
|
fnName,
|
|
|
|
start: Date.now(),
|
|
|
|
};
|
|
|
|
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _updateJob(id: number, data: any) {
|
|
|
|
const { resolve, reject } = data;
|
|
|
|
const { fnName, start } = _jobs[id];
|
|
|
|
|
|
|
|
_jobs[id] = {
|
|
|
|
..._jobs[id],
|
|
|
|
...data,
|
|
|
|
resolve: (value: any) => {
|
|
|
|
_removeJob(id);
|
|
|
|
if (_DEBUG) {
|
|
|
|
const end = Date.now();
|
|
|
|
const delta = end - start;
|
|
|
|
if (delta > 10) {
|
|
|
|
window?.log?.debug(`SQL channel job ${id} (${fnName}) succeeded in ${end - start}ms`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return resolve(value);
|
|
|
|
},
|
|
|
|
reject: (error: any) => {
|
|
|
|
_removeJob(id);
|
|
|
|
const end = Date.now();
|
|
|
|
window?.log?.warn(`SQL channel job ${id} (${fnName}) failed in ${end - start}ms`);
|
|
|
|
return reject(error);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function _removeJob(id: number) {
|
|
|
|
if (_DEBUG) {
|
|
|
|
_jobs[id].complete = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_jobs[id].timer) {
|
|
|
|
global.clearTimeout(_jobs[id].timer);
|
|
|
|
_jobs[id].timer = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// tslint:disable-next-line: no-dynamic-delete
|
|
|
|
delete _jobs[id];
|
|
|
|
|
|
|
|
if (_shutdownCallback) {
|
|
|
|
const keys = Object.keys(_jobs);
|
|
|
|
if (keys.length === 0) {
|
|
|
|
_shutdownCallback();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function _getJob(id: number) {
|
|
|
|
return _jobs[id];
|
|
|
|
}
|
|
|
|
|
|
|
|
function makeChannel(fnName: string) {
|
|
|
|
channels[fnName] = async (...args: any) => {
|
|
|
|
const jobId = _makeJob(fnName);
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
ipcRenderer.send(SQL_CHANNEL_KEY, jobId, fnName, ...args);
|
|
|
|
|
|
|
|
_updateJob(jobId, {
|
|
|
|
resolve,
|
|
|
|
reject,
|
|
|
|
args: _DEBUG ? args : null,
|
|
|
|
});
|
|
|
|
|
|
|
|
_jobs[jobId].timer = setTimeout(
|
|
|
|
() => reject(new Error(`SQL channel job ${jobId} (${fnName}) timed out`)),
|
|
|
|
DATABASE_UPDATE_TIMEOUT
|
|
|
|
);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function keysToArrayBuffer(keys: any, data: any) {
|
|
|
|
const updated = _.cloneDeep(data);
|
|
|
|
for (let i = 0, max = keys.length; i < max; i += 1) {
|
|
|
|
const key = keys[i];
|
|
|
|
const value = _.get(data, key);
|
|
|
|
|
|
|
|
if (value) {
|
|
|
|
_.set(updated, key, fromBase64ToArrayBuffer(value));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return updated;
|
|
|
|
}
|
|
|
|
|
|
|
|
function keysFromArrayBuffer(keys: any, data: any) {
|
|
|
|
const updated = _.cloneDeep(data);
|
|
|
|
for (let i = 0, max = keys.length; i < max; i += 1) {
|
|
|
|
const key = keys[i];
|
|
|
|
const value = _.get(data, key);
|
|
|
|
|
|
|
|
if (value) {
|
|
|
|
_.set(updated, key, fromArrayBufferToBase64(value));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return updated;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Basic
|
|
|
|
export async function shutdown(): Promise<void> {
|
|
|
|
// Stop accepting new SQL jobs, flush outstanding queue
|
|
|
|
await _shutdown();
|
|
|
|
await close();
|
|
|
|
}
|
|
|
|
// Note: will need to restart the app after calling this, to set up afresh
|
|
|
|
export async function close(): Promise<void> {
|
|
|
|
await channels.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: will need to restart the app after calling this, to set up afresh
|
|
|
|
export async function removeDB(): Promise<void> {
|
|
|
|
await channels.removeDB();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Password hash
|
|
|
|
|
|
|
|
export async function getPasswordHash(): Promise<string | null> {
|
|
|
|
return channels.getPasswordHash();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Guard Nodes
|
|
|
|
export async function getGuardNodes(): Promise<Array<GuardNode>> {
|
|
|
|
return channels.getGuardNodes();
|
|
|
|
}
|
|
|
|
export async function updateGuardNodes(nodes: Array<string>): Promise<void> {
|
|
|
|
return channels.updateGuardNodes(nodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Items
|
|
|
|
|
|
|
|
const ITEM_KEYS: Object = {
|
|
|
|
identityKey: ['value.pubKey', 'value.privKey'],
|
|
|
|
profileKey: ['value'],
|
|
|
|
};
|
|
|
|
export async function createOrUpdateItem(data: StorageItem): Promise<void> {
|
|
|
|
const { id } = data;
|
|
|
|
if (!id) {
|
|
|
|
throw new Error('createOrUpdateItem: Provided data did not have a truthy id');
|
|
|
|
}
|
|
|
|
|
|
|
|
const keys = (ITEM_KEYS as any)[id];
|
|
|
|
const updated = Array.isArray(keys) ? keysFromArrayBuffer(keys, data) : data;
|
|
|
|
|
|
|
|
await channels.createOrUpdateItem(updated);
|
|
|
|
}
|
|
|
|
export async function getItemById(id: string): Promise<StorageItem | undefined> {
|
|
|
|
const keys = (ITEM_KEYS as any)[id];
|
|
|
|
const data = await channels.getItemById(id);
|
|
|
|
|
|
|
|
return Array.isArray(keys) ? keysToArrayBuffer(keys, data) : data;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function generateAttachmentKeyIfEmpty() {
|
|
|
|
const existingKey = await getItemById('local_attachment_encrypted_key');
|
|
|
|
if (!existingKey) {
|
|
|
|
const sodium = await getSodium();
|
|
|
|
const encryptingKey = sodium.to_hex(sodium.randombytes_buf(32));
|
|
|
|
await createOrUpdateItem({
|
|
|
|
id: 'local_attachment_encrypted_key',
|
|
|
|
value: encryptingKey,
|
|
|
|
});
|
|
|
|
// be sure to write the new key to the cache. so we can access it straight away
|
|
|
|
window.textsecure.storage.put('local_attachment_encrypted_key', encryptingKey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getAllItems(): Promise<Array<StorageItem>> {
|
|
|
|
const items = await channels.getAllItems();
|
|
|
|
return _.map(items, item => {
|
|
|
|
const { id } = item;
|
|
|
|
const keys = (ITEM_KEYS as any)[id];
|
|
|
|
return Array.isArray(keys) ? keysToArrayBuffer(keys, item) : item;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
export async function removeItemById(id: string): Promise<void> {
|
|
|
|
await channels.removeItemById(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Swarm nodes
|
|
|
|
export async function getSwarmNodesForPubkey(pubkey: string): Promise<Array<string>> {
|
|
|
|
return channels.getSwarmNodesForPubkey(pubkey);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function updateSwarmNodesForPubkey(
|
|
|
|
pubkey: string,
|
|
|
|
snodeEdKeys: Array<string>
|
|
|
|
): Promise<void> {
|
|
|
|
await channels.updateSwarmNodesForPubkey(pubkey, snodeEdKeys);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Closed group
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The returned array is ordered based on the timestamp, the latest is at the end.
|
|
|
|
*/
|
|
|
|
export async function getAllEncryptionKeyPairsForGroup(
|
|
|
|
groupPublicKey: string | PubKey
|
|
|
|
): Promise<Array<HexKeyPair> | undefined> {
|
|
|
|
const pubkey = (groupPublicKey as PubKey).key || (groupPublicKey as string);
|
|
|
|
return channels.getAllEncryptionKeyPairsForGroup(pubkey);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getLatestClosedGroupEncryptionKeyPair(
|
|
|
|
groupPublicKey: string
|
|
|
|
): Promise<HexKeyPair | undefined> {
|
|
|
|
return channels.getLatestClosedGroupEncryptionKeyPair(groupPublicKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function addClosedGroupEncryptionKeyPair(
|
|
|
|
groupPublicKey: string,
|
|
|
|
keypair: HexKeyPair
|
|
|
|
): Promise<void> {
|
|
|
|
await channels.addClosedGroupEncryptionKeyPair(groupPublicKey, keypair);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function removeAllClosedGroupEncryptionKeyPairs(
|
|
|
|
groupPublicKey: string
|
|
|
|
): Promise<void> {
|
|
|
|
return channels.removeAllClosedGroupEncryptionKeyPairs(groupPublicKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Conversation
|
|
|
|
export async function saveConversation(data: ReduxConversationType): Promise<void> {
|
|
|
|
const cleaned = _.omit(data, 'isOnline');
|
|
|
|
await channels.saveConversation(cleaned);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getConversationById(id: string): Promise<ConversationModel | undefined> {
|
|
|
|
const data = await channels.getConversationById(id);
|
|
|
|
if (data) {
|
|
|
|
return new ConversationModel(data);
|
|
|
|
}
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function updateConversation(data: ReduxConversationType): Promise<void> {
|
Session 1.7.5 (#2094)
* Added message requests disabled for now
* no longer showing empty space for conversations moved from list.
* Added syncing accepting of contact between running instances.
* Adding blocking of individual requests and syncing of block to devices. Added approval by replying to a message.
* fixed typos for translations and method name.
* Blocking, accepting on click and accepting on msg send
working across clients.
* adding setting of active_at to hide unapproved messages.
* adding feature flag for config message receiving
* fix archlinux pw unused issue
on archlinux, the appimage links to the system sqlite by default which
does not support sqlcipher
* hide activeAt = 0 convo from search results
Fixes #2033
* opengroup messages from blocked user are dropped
Fixes #2019
* opengroup messages from blocked user are dropped
Fixes #2019
* dismiss a call when answered from another of our devices
* add data-testid for leftpane sections and edit profile dialog
* update turn servers
* cleanup sessionprotobuf
* move the state of calling to its own slice
* no video track by default and will be turn ON if asked to
* message request refactoring.
* create offer and answer ourselves and do not use the negotiation needed
event.
this event is causing us to loop in negotiation needed when each side
try to create one, gets the answer and so on...
* auto select the first audio input on connection success webrtc
* add a way to choose the audioouput/mute a webrtc call
* mute audio from bg when video is in fullscreen
this is to avoid having two times the remote sound playing
one in the bg and one in the fullscreen
* Adding improvements to message request handling.
* Only updating approval when it is a true value as we consider a block a decline.
* Linting and formatting.
* More formatting and linting
* fixing merge conflicts
* linting and formatting changes
* darken a bit the green of sent message box in light theme
* disable deduplication based serverId+sender
only use the serverTimestamp+sender for searching because
serverId+sender might have false positive
* Fixing up block all logic.
* speed up fetching closed group's members avatar
* Applying PR changes.
* cleanup props passing of avatar and name with a custom hook
* fix a bug releasing the decrypted attachment blobs too early
* Adding trigger logic for conversation filtering of requests.
* Fixing rimraf transpile bug. Adding PR fixes - icon buttons.
* Minor call tweaks (#2051)
* show missed-call,started-call and answered call notification in chat
* fix types for createLastMessageUpdate
* show incoming dialog if we have a pending call when enable call receptio
* simplify a bit the avatar component
* move disableDrag to a custom hook
* speed up hash colors of avatarPlaceHolders
* fixup text selection and double click reply on message
* keep avatar decoded items longer before releasing memory
* add incoming/outgoing/missed call notification
also, merge that notification with the timer and group notification
component
* hangup call if no answer after 30sec
* refactor SessionInput using hook + add testid field for recovery
* disable message request feature flag for now
* fix merge issue
* force loading screen to be black instead of white
for our dark theme user's eyes safety
* Fetch translations (#2056)
* show missed-call,started-call and answered call notification in chat
* fix types for createLastMessageUpdate
* show incoming dialog if we have a pending call when enable call receptio
* simplify a bit the avatar component
* move disableDrag to a custom hook
* speed up hash colors of avatarPlaceHolders
* fixup text selection and double click reply on message
* keep avatar decoded items longer before releasing memory
* add incoming/outgoing/missed call notification
also, merge that notification with the timer and group notification
component
* hangup call if no answer after 30sec
* refactor SessionInput using hook + add testid field for recovery
* disable message request feature flag for now
* fix merge issue
* force loading screen to be black instead of white
for our dark theme user's eyes safety
* add type for i18n to run update after crowdin fetch with tools/updateI18nKeysType.py
* update to latest translations
* Open group regex fixes (#2058)
* Open group URL regex fixes
- Capital letters in room tokens were not being accepted (it eventually
gets lower-cased internally, which works fine, but that happens
*after* the URL is tested for acceptability).
- `-` in room was not being allowed (it is and always has been on SOGS,
session-android, and session-ios).
- single-letter room ids are valid, but only 2+ letter ids were being
accepted.
- complete URL regex wasn't anchored so something like
`garbagehttps://example.com/room?public_key=<64hex>moregarbage` was
being accepted in the GUI input (it fails later when other code tries
to parse it as a URL).
- removed `m` modifier from open group regex: without anchors it wasn't
doing anything anyway, but *with* anchors it would still allow
leading/trailing garbage if delineated by newlines.
- public key regex was accepting g-z letters, and not accepting A-F.
- various regex cleanups:
- use non-capture groups (?:...) rather than capturing groups (...)
- avoid repetition in host segment matching
- tightened up host pattern matching a bit:
- DNS host segments have a max length of 63
- Limit port max length to 5, and disallow starting with 0
* Show an error when the open group URL is invalid
It's quite disconcerting when you have a bad open group URL and try to
add it and the join button just "doesn't work" without any feedback at
all. Fix it to show an error message. (There is already an i18n entry
for this because this same message is thrown if the URL can't be parsed
later on).
* Add call duration (#2059)
* add call duration once connected
* close incoming call dialog if endCall from same sender
* disable message request toggle if featureFlag is OFF
* Cleanup message request (#2063)
* close incoming call dialog if endCall from seame sender
* disable message request toggle if featureFlag is OFF
* cleanup UI of message requests
* mark all existing conversations as approved in a migration
* fix regex with conversationID for opengroups
* Various UI fixes (#2070)
* cleanup unused convo json fields in db
* display a toast if the user is not approved yet on call OFFER received
* enable CBR for calls
* do not update active_at on configMessage if !!active_at
* remove mkdirp dependency
* disable call button if focused convo is blocked
* quote: do not include the full body in quote, but just the first 100
* click on the edit profile qr code padding
* Allow longer input for opengroup join overlay
Fixes #2068
* Fix overlay feature for start new session button
* make ringing depend on redux CALL status
* turn ON read-receipt by default
* keep read-receipts disabled by default (#2071)
* refactor most of the components to outside of their Session folder (#2072)
* refactor most of the components to outside of their Session folder
* finish moving overlay and memberListItem to react hook
* fix bug with kicked member len >2 not being displayed
also sort admins first in UpdateGroupMembers dialog
* fix admin leaving text of groupNotification
* add a useFocusMount hook to focus input fields on mount
* make click avatar convo item open only user dialog
* cleanup config default.json
* make sure to use convoController to build sync message
* disable showing pubkey on opengroups
* add a pause on audio playback
Fixes #2079
* Minor styling fix for large amount of message requests (#2080)
* Minor styling fix for large amount of message requests
* Vertical center fix for message request banner.
* removing top margin from banner again.
* reactify group updates text bubble from redux store (#2083)
* add crown icon for closed group admins (#2084)
* disable call for now + fix left pane actions overflow (#2085)
* Fix attachment dl freeze (#2086)
* fix attachment download freezing app for some opengroups
* make registration page work with smaller height
* Unban UI (#2091)
* adding basic functionaliy for unbanning a user
* merge ban and unban user dialog in to one dialog
Co-authored-by: warrickct <warrickct@gmail.com>
* use React Provider for convoListItem (#2088)
this is to avoid passing down the prop to all the components
* fix closed group updates undefined on no names (#2092)
Co-authored-by: Warrick Corfe-Tan <warrickct@gmail.com>
Co-authored-by: Jason Rhinelander <jason@imaginary.ca>
Co-authored-by: Warrick <wcor690@aucklanduni.ac.nz>
3 years ago
|
|
|
const cleanedData = _cleanData(data);
|
|
|
|
await channels.updateConversation(cleanedData);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function removeConversation(id: string): Promise<void> {
|
|
|
|
const existing = await getConversationById(id);
|
|
|
|
|
|
|
|
// Note: It's important to have a fully database-hydrated model to delete here because
|
|
|
|
// it needs to delete all associated on-disk files along with the database delete.
|
|
|
|
if (existing) {
|
|
|
|
await channels.removeConversation(id);
|
|
|
|
await existing.cleanup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getAllConversations(): Promise<ConversationCollection> {
|
|
|
|
const conversations = await channels.getAllConversations();
|
|
|
|
|
|
|
|
const collection = new ConversationCollection();
|
|
|
|
collection.add(conversations);
|
|
|
|
return collection;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getAllOpenGroupV1Conversations(): Promise<ConversationCollection> {
|
|
|
|
const conversations = await channels.getAllOpenGroupV1Conversations();
|
|
|
|
|
|
|
|
const collection = new ConversationCollection();
|
|
|
|
collection.add(conversations);
|
|
|
|
return collection;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This returns at most MAX_PUBKEYS_MEMBERS members, the last MAX_PUBKEYS_MEMBERS members who wrote in the chat
|
|
|
|
*/
|
|
|
|
export async function getPubkeysInPublicConversation(id: string): Promise<Array<string>> {
|
|
|
|
return channels.getPubkeysInPublicConversation(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getAllGroupsInvolvingId(id: string): Promise<ConversationCollection> {
|
|
|
|
const conversations = await channels.getAllGroupsInvolvingId(id);
|
|
|
|
|
|
|
|
const collection = new ConversationCollection();
|
|
|
|
collection.add(conversations);
|
|
|
|
return collection;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function searchConversations(query: string): Promise<Array<any>> {
|
|
|
|
const conversations = await channels.searchConversations(query);
|
|
|
|
return conversations;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function searchMessages(query: string, { limit }: any = {}): Promise<Array<any>> {
|
|
|
|
const messages = await channels.searchMessages(query, { limit });
|
|
|
|
return messages;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns just json objects not MessageModel
|
|
|
|
*/
|
|
|
|
export async function searchMessagesInConversation(
|
|
|
|
query: string,
|
|
|
|
conversationId: string,
|
|
|
|
options: { limit: number } | undefined
|
|
|
|
): Promise<Object> {
|
|
|
|
const messages = await channels.searchMessagesInConversation(query, conversationId, {
|
|
|
|
limit: options?.limit,
|
|
|
|
});
|
|
|
|
return messages;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Message
|
|
|
|
|
|
|
|
export async function cleanSeenMessages(): Promise<void> {
|
|
|
|
await channels.cleanSeenMessages();
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function cleanLastHashes(): Promise<void> {
|
|
|
|
await channels.cleanLastHashes();
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function saveSeenMessageHashes(
|
|
|
|
data: Array<{
|
|
|
|
expiresAt: number;
|
|
|
|
hash: string;
|
|
|
|
}>
|
|
|
|
): Promise<void> {
|
|
|
|
await channels.saveSeenMessageHashes(_cleanData(data));
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function updateLastHash(data: {
|
|
|
|
convoId: string;
|
|
|
|
snode: string;
|
|
|
|
hash: string;
|
|
|
|
expiresAt: number;
|
|
|
|
}): Promise<void> {
|
|
|
|
await channels.updateLastHash(_cleanData(data));
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function saveMessage(data: MessageAttributes): Promise<string> {
|
|
|
|
const cleanedData = _cleanData(data);
|
|
|
|
const id = await channels.saveMessage(cleanedData);
|
|
|
|
window.Whisper.ExpiringMessagesListener.update();
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function saveMessages(arrayOfMessages: Array<MessageAttributes>): Promise<void> {
|
|
|
|
await channels.saveMessages(_cleanData(arrayOfMessages));
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function removeMessage(id: string): Promise<void> {
|
|
|
|
const message = await getMessageById(id, true);
|
|
|
|
|
|
|
|
// Note: It's important to have a fully database-hydrated model to delete here because
|
|
|
|
// it needs to delete all associated on-disk files along with the database delete.
|
|
|
|
if (message) {
|
|
|
|
await channels.removeMessage(id);
|
|
|
|
await message.cleanup();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: this method will not clean up external files, just delete from SQL
|
|
|
|
export async function _removeMessages(ids: Array<string>): Promise<void> {
|
|
|
|
await channels.removeMessage(ids);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getMessageIdsFromServerIds(
|
|
|
|
serverIds: Array<string> | Array<number>,
|
|
|
|
conversationId: string
|
|
|
|
): Promise<Array<string> | undefined> {
|
|
|
|
return channels.getMessageIdsFromServerIds(serverIds, conversationId);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getMessageById(
|
|
|
|
id: string,
|
|
|
|
skipTimerInit: boolean = false
|
|
|
|
): Promise<MessageModel | null> {
|
|
|
|
const message = await channels.getMessageById(id);
|
|
|
|
if (!message) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (skipTimerInit) {
|
|
|
|
message.skipTimerInit = skipTimerInit;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new MessageModel(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getMessageBySender({
|
|
|
|
source,
|
|
|
|
sourceDevice,
|
|
|
|
sentAt,
|
|
|
|
}: {
|
|
|
|
source: string;
|
|
|
|
sourceDevice: number;
|
|
|
|
sentAt: number;
|
|
|
|
}): Promise<MessageModel | null> {
|
|
|
|
const messages = await channels.getMessageBySender({
|
|
|
|
source,
|
|
|
|
sourceDevice,
|
|
|
|
sentAt,
|
|
|
|
});
|
|
|
|
if (!messages || !messages.length) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new MessageModel(messages[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getMessageBySenderAndServerTimestamp({
|
|
|
|
source,
|
|
|
|
serverTimestamp,
|
|
|
|
}: {
|
|
|
|
source: string;
|
|
|
|
serverTimestamp: number;
|
|
|
|
}): Promise<MessageModel | null> {
|
|
|
|
const messages = await channels.getMessageBySenderAndServerTimestamp({
|
|
|
|
source,
|
|
|
|
serverTimestamp,
|
|
|
|
});
|
|
|
|
if (!messages || !messages.length) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new MessageModel(messages[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param source senders id
|
|
|
|
* @param timestamp the timestamp of the message - not to be confused with the serverTimestamp. This is equivalent to sent_at
|
|
|
|
*/
|
|
|
|
export async function getMessageBySenderAndTimestamp({
|
|
|
|
source,
|
|
|
|
timestamp,
|
|
|
|
}: {
|
|
|
|
source: string;
|
|
|
|
timestamp: number;
|
|
|
|
}): Promise<MessageModel | null> {
|
|
|
|
const messages = await channels.getMessageBySenderAndTimestamp({
|
|
|
|
source,
|
|
|
|
timestamp,
|
|
|
|
});
|
|
|
|
if (!messages || !messages.length) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new MessageModel(messages[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getUnreadByConversation(conversationId: string): Promise<MessageCollection> {
|
|
|
|
const messages = await channels.getUnreadByConversation(conversationId);
|
|
|
|
return new MessageCollection(messages);
|
|
|
|
}
|
|
|
|
|
|
|
|
// might throw
|
|
|
|
export async function getUnreadCountByConversation(conversationId: string): Promise<number> {
|
|
|
|
return channels.getUnreadCountByConversation(conversationId);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getMessagesByConversation(
|
|
|
|
conversationId: string,
|
|
|
|
{ limit = 100, receivedAt = Number.MAX_VALUE, type = '%', skipTimerInit = false }
|
|
|
|
): Promise<MessageCollection> {
|
|
|
|
const messages = await channels.getMessagesByConversation(conversationId, {
|
|
|
|
limit,
|
|
|
|
receivedAt,
|
|
|
|
type,
|
|
|
|
});
|
|
|
|
if (skipTimerInit) {
|
|
|
|
for (const message of messages) {
|
|
|
|
message.skipTimerInit = skipTimerInit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new MessageCollection(messages);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getFirstUnreadMessageIdInConversation(
|
|
|
|
conversationId: string
|
|
|
|
): Promise<string | undefined> {
|
|
|
|
return channels.getFirstUnreadMessageIdInConversation(conversationId);
|
|
|
|
}
|
|
|
|
|
Session 1.7.5 (#2094)
* Added message requests disabled for now
* no longer showing empty space for conversations moved from list.
* Added syncing accepting of contact between running instances.
* Adding blocking of individual requests and syncing of block to devices. Added approval by replying to a message.
* fixed typos for translations and method name.
* Blocking, accepting on click and accepting on msg send
working across clients.
* adding setting of active_at to hide unapproved messages.
* adding feature flag for config message receiving
* fix archlinux pw unused issue
on archlinux, the appimage links to the system sqlite by default which
does not support sqlcipher
* hide activeAt = 0 convo from search results
Fixes #2033
* opengroup messages from blocked user are dropped
Fixes #2019
* opengroup messages from blocked user are dropped
Fixes #2019
* dismiss a call when answered from another of our devices
* add data-testid for leftpane sections and edit profile dialog
* update turn servers
* cleanup sessionprotobuf
* move the state of calling to its own slice
* no video track by default and will be turn ON if asked to
* message request refactoring.
* create offer and answer ourselves and do not use the negotiation needed
event.
this event is causing us to loop in negotiation needed when each side
try to create one, gets the answer and so on...
* auto select the first audio input on connection success webrtc
* add a way to choose the audioouput/mute a webrtc call
* mute audio from bg when video is in fullscreen
this is to avoid having two times the remote sound playing
one in the bg and one in the fullscreen
* Adding improvements to message request handling.
* Only updating approval when it is a true value as we consider a block a decline.
* Linting and formatting.
* More formatting and linting
* fixing merge conflicts
* linting and formatting changes
* darken a bit the green of sent message box in light theme
* disable deduplication based serverId+sender
only use the serverTimestamp+sender for searching because
serverId+sender might have false positive
* Fixing up block all logic.
* speed up fetching closed group's members avatar
* Applying PR changes.
* cleanup props passing of avatar and name with a custom hook
* fix a bug releasing the decrypted attachment blobs too early
* Adding trigger logic for conversation filtering of requests.
* Fixing rimraf transpile bug. Adding PR fixes - icon buttons.
* Minor call tweaks (#2051)
* show missed-call,started-call and answered call notification in chat
* fix types for createLastMessageUpdate
* show incoming dialog if we have a pending call when enable call receptio
* simplify a bit the avatar component
* move disableDrag to a custom hook
* speed up hash colors of avatarPlaceHolders
* fixup text selection and double click reply on message
* keep avatar decoded items longer before releasing memory
* add incoming/outgoing/missed call notification
also, merge that notification with the timer and group notification
component
* hangup call if no answer after 30sec
* refactor SessionInput using hook + add testid field for recovery
* disable message request feature flag for now
* fix merge issue
* force loading screen to be black instead of white
for our dark theme user's eyes safety
* Fetch translations (#2056)
* show missed-call,started-call and answered call notification in chat
* fix types for createLastMessageUpdate
* show incoming dialog if we have a pending call when enable call receptio
* simplify a bit the avatar component
* move disableDrag to a custom hook
* speed up hash colors of avatarPlaceHolders
* fixup text selection and double click reply on message
* keep avatar decoded items longer before releasing memory
* add incoming/outgoing/missed call notification
also, merge that notification with the timer and group notification
component
* hangup call if no answer after 30sec
* refactor SessionInput using hook + add testid field for recovery
* disable message request feature flag for now
* fix merge issue
* force loading screen to be black instead of white
for our dark theme user's eyes safety
* add type for i18n to run update after crowdin fetch with tools/updateI18nKeysType.py
* update to latest translations
* Open group regex fixes (#2058)
* Open group URL regex fixes
- Capital letters in room tokens were not being accepted (it eventually
gets lower-cased internally, which works fine, but that happens
*after* the URL is tested for acceptability).
- `-` in room was not being allowed (it is and always has been on SOGS,
session-android, and session-ios).
- single-letter room ids are valid, but only 2+ letter ids were being
accepted.
- complete URL regex wasn't anchored so something like
`garbagehttps://example.com/room?public_key=<64hex>moregarbage` was
being accepted in the GUI input (it fails later when other code tries
to parse it as a URL).
- removed `m` modifier from open group regex: without anchors it wasn't
doing anything anyway, but *with* anchors it would still allow
leading/trailing garbage if delineated by newlines.
- public key regex was accepting g-z letters, and not accepting A-F.
- various regex cleanups:
- use non-capture groups (?:...) rather than capturing groups (...)
- avoid repetition in host segment matching
- tightened up host pattern matching a bit:
- DNS host segments have a max length of 63
- Limit port max length to 5, and disallow starting with 0
* Show an error when the open group URL is invalid
It's quite disconcerting when you have a bad open group URL and try to
add it and the join button just "doesn't work" without any feedback at
all. Fix it to show an error message. (There is already an i18n entry
for this because this same message is thrown if the URL can't be parsed
later on).
* Add call duration (#2059)
* add call duration once connected
* close incoming call dialog if endCall from same sender
* disable message request toggle if featureFlag is OFF
* Cleanup message request (#2063)
* close incoming call dialog if endCall from seame sender
* disable message request toggle if featureFlag is OFF
* cleanup UI of message requests
* mark all existing conversations as approved in a migration
* fix regex with conversationID for opengroups
* Various UI fixes (#2070)
* cleanup unused convo json fields in db
* display a toast if the user is not approved yet on call OFFER received
* enable CBR for calls
* do not update active_at on configMessage if !!active_at
* remove mkdirp dependency
* disable call button if focused convo is blocked
* quote: do not include the full body in quote, but just the first 100
* click on the edit profile qr code padding
* Allow longer input for opengroup join overlay
Fixes #2068
* Fix overlay feature for start new session button
* make ringing depend on redux CALL status
* turn ON read-receipt by default
* keep read-receipts disabled by default (#2071)
* refactor most of the components to outside of their Session folder (#2072)
* refactor most of the components to outside of their Session folder
* finish moving overlay and memberListItem to react hook
* fix bug with kicked member len >2 not being displayed
also sort admins first in UpdateGroupMembers dialog
* fix admin leaving text of groupNotification
* add a useFocusMount hook to focus input fields on mount
* make click avatar convo item open only user dialog
* cleanup config default.json
* make sure to use convoController to build sync message
* disable showing pubkey on opengroups
* add a pause on audio playback
Fixes #2079
* Minor styling fix for large amount of message requests (#2080)
* Minor styling fix for large amount of message requests
* Vertical center fix for message request banner.
* removing top margin from banner again.
* reactify group updates text bubble from redux store (#2083)
* add crown icon for closed group admins (#2084)
* disable call for now + fix left pane actions overflow (#2085)
* Fix attachment dl freeze (#2086)
* fix attachment download freezing app for some opengroups
* make registration page work with smaller height
* Unban UI (#2091)
* adding basic functionaliy for unbanning a user
* merge ban and unban user dialog in to one dialog
Co-authored-by: warrickct <warrickct@gmail.com>
* use React Provider for convoListItem (#2088)
this is to avoid passing down the prop to all the components
* fix closed group updates undefined on no names (#2092)
Co-authored-by: Warrick Corfe-Tan <warrickct@gmail.com>
Co-authored-by: Jason Rhinelander <jason@imaginary.ca>
Co-authored-by: Warrick <wcor690@aucklanduni.ac.nz>
3 years ago
|
|
|
export async function hasConversationOutgoingMessage(conversationId: string): Promise<boolean> {
|
|
|
|
return channels.hasConversationOutgoingMessage(conversationId);
|
|
|
|
}
|
|
|
|
export async function getLastHashBySnode(convoId: string, snode: string): Promise<string> {
|
|
|
|
return channels.getLastHashBySnode(convoId, snode);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getSeenMessagesByHashList(hashes: Array<string>): Promise<any> {
|
|
|
|
return channels.getSeenMessagesByHashList(hashes);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function removeAllMessagesInConversation(conversationId: string): Promise<void> {
|
|
|
|
let messages;
|
|
|
|
do {
|
|
|
|
// Yes, we really want the await in the loop. We're deleting 100 at a
|
|
|
|
// time so we don't use too much memory.
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
messages = await getMessagesByConversation(conversationId, {
|
|
|
|
limit: 500,
|
|
|
|
});
|
|
|
|
if (!messages.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const ids = messages.map(message => message.id);
|
|
|
|
|
|
|
|
// Note: It's very important that these models are fully hydrated because
|
|
|
|
// we need to delete all associated on-disk files along with the database delete.
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
|
|
|
|
await Promise.all(messages.map(message => message.cleanup()));
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
await channels.removeMessage(ids);
|
|
|
|
} while (messages.length > 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getMessagesBySentAt(sentAt: number): Promise<MessageCollection> {
|
|
|
|
const messages = await channels.getMessagesBySentAt(sentAt);
|
|
|
|
return new MessageCollection(messages);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getExpiredMessages(): Promise<MessageCollection> {
|
|
|
|
const messages = await channels.getExpiredMessages();
|
|
|
|
return new MessageCollection(messages);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getOutgoingWithoutExpiresAt(): Promise<MessageCollection> {
|
|
|
|
const messages = await channels.getOutgoingWithoutExpiresAt();
|
|
|
|
return new MessageCollection(messages);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getNextExpiringMessage(): Promise<MessageCollection> {
|
|
|
|
const messages = await channels.getNextExpiringMessage();
|
|
|
|
return new MessageCollection(messages);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unprocessed
|
|
|
|
|
|
|
|
export async function getUnprocessedCount(): Promise<number> {
|
|
|
|
return channels.getUnprocessedCount();
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getAllUnprocessed(): Promise<any> {
|
|
|
|
return channels.getAllUnprocessed();
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getUnprocessedById(id: string): Promise<any> {
|
|
|
|
return channels.getUnprocessedById(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
export type UnprocessedParameter = {
|
|
|
|
id: string;
|
|
|
|
version: number;
|
|
|
|
envelope: string;
|
|
|
|
timestamp: number;
|
|
|
|
attempts: number;
|
|
|
|
messageHash: string;
|
|
|
|
senderIdentity?: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export async function saveUnprocessed(data: UnprocessedParameter): Promise<string> {
|
|
|
|
const id = await channels.saveUnprocessed(_cleanData(data));
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function updateUnprocessedAttempts(id: string, attempts: number): Promise<void> {
|
|
|
|
await channels.updateUnprocessedAttempts(id, attempts);
|
|
|
|
}
|
|
|
|
export async function updateUnprocessedWithData(id: string, data: any): Promise<void> {
|
|
|
|
await channels.updateUnprocessedWithData(id, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function removeUnprocessed(id: string): Promise<void> {
|
|
|
|
await channels.removeUnprocessed(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function removeAllUnprocessed(): Promise<void> {
|
|
|
|
await channels.removeAllUnprocessed();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attachment downloads
|
|
|
|
|
|
|
|
export async function getNextAttachmentDownloadJobs(limit: number): Promise<any> {
|
|
|
|
return channels.getNextAttachmentDownloadJobs(limit);
|
|
|
|
}
|
|
|
|
export async function saveAttachmentDownloadJob(job: any): Promise<void> {
|
|
|
|
await channels.saveAttachmentDownloadJob(job);
|
|
|
|
}
|
|
|
|
export async function setAttachmentDownloadJobPending(id: string, pending: boolean): Promise<void> {
|
|
|
|
await channels.setAttachmentDownloadJobPending(id, pending ? 1 : 0);
|
|
|
|
}
|
|
|
|
export async function resetAttachmentDownloadPending(): Promise<void> {
|
|
|
|
await channels.resetAttachmentDownloadPending();
|
|
|
|
}
|
|
|
|
export async function removeAttachmentDownloadJob(id: string): Promise<void> {
|
|
|
|
await channels.removeAttachmentDownloadJob(id);
|
|
|
|
}
|
|
|
|
export async function removeAllAttachmentDownloadJobs(): Promise<void> {
|
|
|
|
await channels.removeAllAttachmentDownloadJobs();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Other
|
|
|
|
|
|
|
|
export async function removeAll(): Promise<void> {
|
|
|
|
await channels.removeAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function removeAllConversations(): Promise<void> {
|
|
|
|
await channels.removeAllConversations();
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function cleanupOrphanedAttachments(): Promise<void> {
|
|
|
|
await callChannel(CLEANUP_ORPHANED_ATTACHMENTS_KEY);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: will need to restart the app after calling this, to set up afresh
|
|
|
|
export async function removeOtherData(): Promise<void> {
|
|
|
|
await Promise.all([callChannel(ERASE_SQL_KEY), callChannel(ERASE_ATTACHMENTS_KEY)]);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function callChannel(name: string): Promise<any> {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
ipcRenderer.send(name);
|
|
|
|
ipcRenderer.once(`${name}-done`, (_event, error) => {
|
|
|
|
if (error) {
|
|
|
|
return reject(error);
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolve(undefined);
|
|
|
|
});
|
|
|
|
|
|
|
|
setTimeout(
|
|
|
|
() => reject(new Error(`callChannel call to ${name} timed out`)),
|
|
|
|
DATABASE_UPDATE_TIMEOUT
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Functions below here return plain JSON instead of Backbone Models
|
|
|
|
|
|
|
|
export async function getMessagesWithVisualMediaAttachments(
|
|
|
|
conversationId: string,
|
|
|
|
options?: { limit: number }
|
|
|
|
): Promise<Array<MessageAttributes>> {
|
|
|
|
return channels.getMessagesWithVisualMediaAttachments(conversationId, {
|
|
|
|
limit: options?.limit,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function getMessagesWithFileAttachments(
|
|
|
|
conversationId: string,
|
|
|
|
options?: { limit: number }
|
|
|
|
): Promise<Array<MessageAttributes>> {
|
|
|
|
return channels.getMessagesWithFileAttachments(conversationId, {
|
|
|
|
limit: options?.limit,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export const SNODE_POOL_ITEM_ID = 'SNODE_POOL_ITEM_ID';
|
|
|
|
|
|
|
|
export async function getSnodePoolFromDb(): Promise<Array<Snode> | null> {
|
|
|
|
// this is currently all stored as a big string as we don't really need to do anything with them (no filtering or anything)
|
|
|
|
// everything is made in memory and written to disk
|
|
|
|
const snodesJson = await exports.getItemById(SNODE_POOL_ITEM_ID);
|
|
|
|
if (!snodesJson || !snodesJson.value) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return JSON.parse(snodesJson.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function updateSnodePoolOnDb(snodesAsJsonString: string): Promise<void> {
|
|
|
|
await exports.createOrUpdateItem({ id: SNODE_POOL_ITEM_ID, value: snodesAsJsonString });
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Returns the number of message left to remove (opengroupv1) */
|
|
|
|
export async function removeOneOpenGroupV1Message(): Promise<number> {
|
|
|
|
return channels.removeOneOpenGroupV1Message();
|
|
|
|
}
|