move storage.js to ts

This commit is contained in:
Audric Ackermann 2022-03-23 15:57:11 +11:00
parent 747bcb766c
commit 6bd835dfc3
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4
27 changed files with 232 additions and 403 deletions

View File

@ -67,9 +67,6 @@ module.exports = grunt => {
'libtextsecure/errors.js', 'libtextsecure/errors.js',
'libtextsecure/libsignal-protocol.js', 'libtextsecure/libsignal-protocol.js',
'libtextsecure/crypto.js', 'libtextsecure/crypto.js',
'libtextsecure/storage.js',
'libtextsecure/storage/user.js',
'libtextsecure/helpers.js',
], ],
dest: 'js/libtextsecure.js', dest: 'js/libtextsecure.js',
}, },

View File

@ -32,7 +32,6 @@
--> -->
<script type="text/javascript" src="js/components.js"></script> <script type="text/javascript" src="js/components.js"></script>
<script type="text/javascript" src="js/storage.js"></script>
<script type="text/javascript" src="js/libtextsecure.js"></script> <script type="text/javascript" src="js/libtextsecure.js"></script>
<script type="text/javascript" src="js/views/react_wrapper_view.js"></script> <script type="text/javascript" src="js/views/react_wrapper_view.js"></script>

View File

@ -329,8 +329,7 @@
initialLoadComplete, initialLoadComplete,
}); });
} }
}); };
Whisper.events.on('openInbox', () => { Whisper.events.on('openInbox', () => {
appView.openInbox({ appView.openInbox({

View File

@ -1,107 +0,0 @@
/* eslint-disable more/no-then */
// eslint-disable-next-line func-names
(function() {
'use strict';
window.Whisper = window.Whisper || {};
let ready = false;
let items;
let callbacks = [];
reset();
async function put(key, value) {
if (value === undefined) {
throw new Error('Tried to store undefined');
}
if (!ready) {
window.log.warn('Called storage.put before storage is ready. key:', key);
}
const data = { id: key, value };
items[key] = data;
await window.Signal.Data.createOrUpdateItem(data);
}
function get(key, defaultValue) {
if (!ready) {
window.log.warn('Called storage.get before storage is ready. key:', key);
}
const item = items[key];
if (!item) {
return defaultValue;
}
return item.value;
}
async function remove(key) {
if (!ready) {
window.log.warn('Called storage.get before storage is ready. key:', key);
}
delete items[key];
await window.Signal.Data.removeItemById(key);
}
function onready(callback) {
if (ready) {
callback();
} else {
callbacks.push(callback);
}
}
function callListeners() {
if (ready) {
callbacks.forEach(callback => callback());
callbacks = [];
}
}
async function fetch() {
this.reset();
const array = await window.Signal.Data.getAllItems();
for (let i = 0, max = array.length; i < max; i += 1) {
const item = array[i];
const { id } = item;
items[id] = item;
}
ready = true;
callListeners();
}
function reset() {
ready = false;
items = Object.create(null);
}
const storage = {
fetch,
put,
get,
remove,
onready,
reset,
};
// Keep a reference to this storage system, since there are scenarios where
// we need to replace it with the legacy storage system for a while.
window.newStorage = storage;
window.textsecure = window.textsecure || {};
window.textsecure.storage = window.textsecure.storage || {};
window.installStorage = newStorage => {
window.storage = newStorage;
window.textsecure.storage.impl = newStorage;
};
window.installStorage(storage);
})();

View File

@ -1,76 +0,0 @@
/* global window, dcodeIO */
/* eslint-disable no-proto, no-restricted-syntax, guard-for-in */
window.textsecure = window.textsecure || {};
/** *******************************
*** Type conversion utilities ***
******************************** */
// Strings/arrays
// TODO: Throw all this shit in favor of consistent types
// TODO: Namespace
const StaticByteBufferProto = new dcodeIO.ByteBuffer().__proto__;
const StaticArrayBufferProto = new ArrayBuffer().__proto__;
const StaticUint8ArrayProto = new Uint8Array().__proto__;
function getString(thing) {
if (thing === Object(thing)) {
if (thing.__proto__ === StaticUint8ArrayProto) {
return String.fromCharCode.apply(null, thing);
}
if (thing.__proto__ === StaticArrayBufferProto) {
return getString(new Uint8Array(thing));
}
if (thing.__proto__ === StaticByteBufferProto) {
return thing.toString('binary');
}
}
return thing;
}
function getStringable(thing) {
return (
typeof thing === 'string' ||
typeof thing === 'number' ||
typeof thing === 'boolean' ||
(thing === Object(thing) &&
(thing.__proto__ === StaticArrayBufferProto ||
thing.__proto__ === StaticUint8ArrayProto ||
thing.__proto__ === StaticByteBufferProto))
);
}
// Number formatting utils
window.textsecure.utils = (() => {
const self = {};
self.unencodeNumber = number => number.split('.');
self.isNumberSane = number => number[0] === '+' && /^[0-9]+$/.test(number.substring(1));
/** ************************
*** JSON'ing Utilities ***
************************* */
function ensureStringed(thing) {
if (getStringable(thing)) {
return getString(thing);
} else if (thing instanceof Array) {
const res = [];
for (let i = 0; i < thing.length; i += 1) {
res[i] = ensureStringed(thing[i]);
}
return res;
} else if (thing === Object(thing)) {
const res = {};
for (const key in thing) {
res[key] = ensureStringed(thing[key]);
}
return res;
} else if (thing === null) {
return null;
}
throw new Error(`unsure of how to jsonify object of type ${typeof thing}`);
}
self.jsonThing = thing => JSON.stringify(ensureStringed(thing));
return self;
})();

View File

@ -1,40 +0,0 @@
/* global window, textsecure, localStorage */
// eslint-disable-next-line func-names
(function() {
/** **********************************************
*** Utilities to store data in local storage ***
*********************************************** */
window.textsecure = window.textsecure || {};
window.textsecure.storage = window.textsecure.storage || {};
// Overrideable storage implementation
window.textsecure.storage.impl = window.textsecure.storage.impl || {
/** ***************************
*** Base Storage Routines ***
**************************** */
put(key, value) {
if (value === undefined) {
throw new Error('Tried to store undefined');
}
localStorage.setItem(`${key}`, textsecure.utils.jsonThing(value));
},
get(key, defaultValue) {
const value = localStorage.getItem(`${key}`);
if (value === null) {
return defaultValue;
}
return JSON.parse(value);
},
remove(key) {
localStorage.removeItem(`${key}`);
},
};
window.textsecure.storage.put = (key, value) => textsecure.storage.impl.put(key, value);
window.textsecure.storage.get = (key, defaultValue) =>
textsecure.storage.impl.get(key, defaultValue);
window.textsecure.storage.remove = key => textsecure.storage.impl.remove(key);
})();

View File

@ -1,70 +0,0 @@
/* global textsecure, window */
// eslint-disable-next-line func-names
(function() {
/** *******************************************
*** Utilities to store data about the user ***
********************************************* */
window.textsecure = window.textsecure || {};
window.textsecure.storage = window.textsecure.storage || {};
window.textsecure.storage.user = {
setNumberAndDeviceId(number, deviceId, deviceName) {
textsecure.storage.put('number_id', `${number}.${deviceId}`);
if (deviceName) {
textsecure.storage.put('device_name', deviceName);
}
},
getNumber() {
const numberId = textsecure.storage.get('number_id');
if (numberId === undefined) {
return undefined;
}
return textsecure.utils.unencodeNumber(numberId)[0];
},
isSignInByLinking() {
const isSignInByLinking = textsecure.storage.get('is_sign_in_by_linking');
if (isSignInByLinking === undefined) {
return false;
}
return isSignInByLinking;
},
setSignInByLinking(isLinking) {
textsecure.storage.put('is_sign_in_by_linking', isLinking);
},
isSignWithRecoveryPhrase() {
const isRecoveryPhraseUsed = textsecure.storage.get('is_sign_in_recovery_phrase');
if (isRecoveryPhraseUsed === undefined) {
return false;
}
return isRecoveryPhraseUsed;
},
setSignWithRecoveryPhrase(isRecoveryPhraseUsed) {
textsecure.storage.put('is_sign_in_recovery_phrase', isRecoveryPhraseUsed);
},
getLastProfileUpdateTimestamp() {
return textsecure.storage.get('last_profile_update_timestamp');
},
setLastProfileUpdateTimestamp(lastUpdateTimestamp) {
textsecure.storage.put('last_profile_update_timestamp', lastUpdateTimestamp);
},
getDeviceId() {
const numberId = textsecure.storage.get('number_id');
if (numberId === undefined) {
return undefined;
}
return textsecure.utils.unencodeNumber(numberId)[1];
},
getDeviceName() {
return textsecure.storage.get('device_name');
},
};
})();

View File

@ -20,7 +20,7 @@ import { SplitViewContainer } from '../SplitViewContainer';
import { LightboxGallery, MediaItemType } from '../lightbox/LightboxGallery'; import { LightboxGallery, MediaItemType } from '../lightbox/LightboxGallery';
import { getLastMessageInConversation, getPubkeysInPublicConversation } from '../../data/data'; import { getLastMessageInConversation, getPubkeysInPublicConversation } from '../../data/data';
import { getConversationController } from '../../session/conversations'; import { getConversationController } from '../../session/conversations';
import { ToastUtils, UserUtils } from '../../session/utils'; import { ToastUtils } from '../../session/utils';
import { import {
openConversationToSpecificMessage, openConversationToSpecificMessage,
quoteMessage, quoteMessage,
@ -50,6 +50,7 @@ import { blobToArrayBuffer } from 'blob-util';
import { MAX_ATTACHMENT_FILESIZE_BYTES } from '../../session/constants'; import { MAX_ATTACHMENT_FILESIZE_BYTES } from '../../session/constants';
import { ConversationMessageRequestButtons } from './ConversationRequestButtons'; import { ConversationMessageRequestButtons } from './ConversationRequestButtons';
import { ConversationRequestinfo } from './ConversationRequestInfo'; import { ConversationRequestinfo } from './ConversationRequestInfo';
import { getCurrentRecoveryPhrase } from '../../util/storage';
// tslint:disable: jsx-curly-spacing // tslint:disable: jsx-curly-spacing
interface State { interface State {
@ -176,8 +177,7 @@ export class SessionConversation extends React.Component<Props, State> {
await this.scrollToNow(); await this.scrollToNow();
}; };
// const recoveryPhrase = window.textsecure.storage.get('mnemonic'); const recoveryPhrase = getCurrentRecoveryPhrase() as string;
const recoveryPhrase = UserUtils.getCurrentRecoveryPhrase();
// string replace to fix case where pasted text contains invis characters causing false negatives // string replace to fix case where pasted text contains invis characters causing false negatives
if (msg.body.replace(/\s/g, '').includes(recoveryPhrase.replace(/\s/g, ''))) { if (msg.body.replace(/\s/g, '').includes(recoveryPhrase.replace(/\s/g, ''))) {

View File

@ -21,6 +21,7 @@ import { MAX_USERNAME_LENGTH } from '../registration/RegistrationStages';
import { SessionWrapperModal } from '../SessionWrapperModal'; import { SessionWrapperModal } from '../SessionWrapperModal';
import { pickFileForAvatar } from '../../types/attachments/VisualAttachment'; import { pickFileForAvatar } from '../../types/attachments/VisualAttachment';
import { sanitizeSessionUsername } from '../../session/utils/String'; import { sanitizeSessionUsername } from '../../session/utils/String';
import { setLastProfileUpdateTimestamp } from '../../util/storage';
interface State { interface State {
profileName: string; profileName: string;
@ -319,7 +320,7 @@ async function commitProfileEdits(newName: string, scaledAvatarUrl: string | nul
}); });
// might be good to not trigger a sync if the name did not change // might be good to not trigger a sync if the name did not change
await conversation.commit(); await conversation.commit();
UserUtils.setLastProfileUpdateTimestamp(Date.now()); await setLastProfileUpdateTimestamp(Date.now());
await SyncUtils.forceSyncConfigurationNowIfNeeded(true); await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
} }

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { ToastUtils, UserUtils } from '../../session/utils'; import { ToastUtils } from '../../session/utils';
import { PasswordUtil } from '../../util'; import { PasswordUtil } from '../../util';
import { getPasswordHash } from '../../data/data'; import { getPasswordHash } from '../../data/data';
import { QRCode } from 'react-qr-svg'; import { QRCode } from 'react-qr-svg';
@ -10,6 +10,7 @@ import { recoveryPhraseModal } from '../../state/ducks/modalDialog';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { SessionButton, SessionButtonColor } from '../basic/SessionButton'; import { SessionButton, SessionButtonColor } from '../basic/SessionButton';
import { SessionWrapperModal } from '../SessionWrapperModal'; import { SessionWrapperModal } from '../SessionWrapperModal';
import { getCurrentRecoveryPhrase } from '../../util/storage';
interface PasswordProps { interface PasswordProps {
setPasswordValid: (val: boolean) => any; setPasswordValid: (val: boolean) => any;
@ -168,7 +169,7 @@ const SessionSeedModalInner = (props: ModalInnerProps) => {
if (recoveryPhrase) { if (recoveryPhrase) {
return false; return false;
} }
const newRecoveryPhrase = UserUtils.getCurrentRecoveryPhrase(); const newRecoveryPhrase = getCurrentRecoveryPhrase();
setRecoveryPhrase(newRecoveryPhrase); setRecoveryPhrase(newRecoveryPhrase);
setLoadingSeed(false); setLoadingSeed(false);

View File

@ -7,9 +7,9 @@ import { recoveryPhraseModal } from '../../state/ducks/modalDialog';
import { Flex } from '../basic/Flex'; import { Flex } from '../basic/Flex';
import { getFocusedSection, getOverlayMode } from '../../state/selectors/section'; import { getFocusedSection, getOverlayMode } from '../../state/selectors/section';
import { SectionType, setOverlayMode } from '../../state/ducks/section'; import { SectionType, setOverlayMode } from '../../state/ducks/section';
import { UserUtils } from '../../session/utils';
import { SessionButton, SessionButtonType } from '../basic/SessionButton'; import { SessionButton, SessionButtonType } from '../basic/SessionButton';
import { SessionIcon, SessionIconButton } from '../icon'; import { SessionIcon, SessionIconButton } from '../icon';
import { isSignWithRecoveryPhrase } from '../../util/storage';
const SectionTitle = styled.h1` const SectionTitle = styled.h1`
padding: 0 var(--margins-sm); padding: 0 var(--margins-sm);
@ -94,7 +94,7 @@ const BannerInner = () => {
export const LeftPaneBanner = () => { export const LeftPaneBanner = () => {
const section = useSelector(getFocusedSection); const section = useSelector(getFocusedSection);
const isSignInWithRecoveryPhrase = UserUtils.isSignWithRecoveryPhrase(); const isSignInWithRecoveryPhrase = isSignWithRecoveryPhrase();
if (section !== SectionType.Message || isSignInWithRecoveryPhrase) { if (section !== SectionType.Message || isSignInWithRecoveryPhrase) {
return null; return null;

View File

@ -5,7 +5,7 @@ import { createOrUpdateItem, removeAll } from '../../data/data';
import { getSwarmPollingInstance } from '../../session/apis/snode_api'; import { getSwarmPollingInstance } from '../../session/apis/snode_api';
import { getConversationController } from '../../session/conversations'; import { getConversationController } from '../../session/conversations';
import { mn_decode } from '../../session/crypto/mnemonic'; import { mn_decode } from '../../session/crypto/mnemonic';
import { PromiseUtils, StringUtils, ToastUtils, UserUtils } from '../../session/utils'; import { PromiseUtils, StringUtils, ToastUtils } from '../../session/utils';
import { TaskTimedOutError } from '../../session/utils/Promise'; import { TaskTimedOutError } from '../../session/utils/Promise';
import { trigger } from '../../shims/events'; import { trigger } from '../../shims/events';
import { import {
@ -15,14 +15,15 @@ import {
signInByLinkingDevice, signInByLinkingDevice,
} from '../../util/accountManager'; } from '../../util/accountManager';
import { fromHex } from '../../session/utils/String'; import { fromHex } from '../../session/utils/String';
import { setSignInByLinking, setSignWithRecoveryPhrase, Storage } from '../../util/storage';
export const MAX_USERNAME_LENGTH = 26; export const MAX_USERNAME_LENGTH = 26;
// tslint:disable: use-simple-attributes // tslint:disable: use-simple-attributes
export async function resetRegistration() { export async function resetRegistration() {
await removeAll(); await removeAll();
await window.storage.reset(); Storage.reset();
await window.storage.fetch(); await Storage.fetch();
getConversationController().reset(); getConversationController().reset();
await getConversationController().load(); await getConversationController().load();
} }
@ -64,7 +65,7 @@ export async function signUp(signUpDetails: {
value: true, value: true,
timestamp: Date.now(), timestamp: Date.now(),
}); });
UserUtils.setSignWithRecoveryPhrase(false); setSignWithRecoveryPhrase(false);
trigger('openInbox'); trigger('openInbox');
} catch (e) { } catch (e) {
await resetRegistration(); await resetRegistration();
@ -95,7 +96,7 @@ export async function signInWithRecovery(signInDetails: {
await resetRegistration(); await resetRegistration();
await registerSingleDevice(userRecoveryPhrase, 'english', trimName); await registerSingleDevice(userRecoveryPhrase, 'english', trimName);
UserUtils.setSignWithRecoveryPhrase(true); setSignWithRecoveryPhrase(true);
trigger('openInbox'); trigger('openInbox');
} catch (e) { } catch (e) {
@ -120,10 +121,10 @@ export async function signInWithLinking(signInDetails: { userRecoveryPhrase: str
await getSwarmPollingInstance().start(); await getSwarmPollingInstance().start();
await PromiseUtils.waitForTask(done => { await PromiseUtils.waitForTask(done => {
window.Whisper.events.on('configurationMessageReceived', (displayName: string) => { window.Whisper.events.on('configurationMessageReceived', async (displayName: string) => {
window.Whisper.events.off('configurationMessageReceived'); window.Whisper.events.off('configurationMessageReceived');
UserUtils.setSignInByLinking(false); await setSignInByLinking(false);
UserUtils.setSignWithRecoveryPhrase(true); await setSignWithRecoveryPhrase(true);
done(displayName); done(displayName);
displayNameFromNetwork = displayName; displayNameFromNetwork = displayName;

View File

@ -4,9 +4,9 @@ import { AccentText } from './AccentText';
import { RegistrationStages } from './RegistrationStages'; import { RegistrationStages } from './RegistrationStages';
import { SessionIcon } from '../icon'; import { SessionIcon } from '../icon';
import { SessionToastContainer } from '../SessionToastContainer'; import { SessionToastContainer } from '../SessionToastContainer';
import { setSignInByLinking } from '../../session/utils/User';
import { SessionTheme } from '../../state/ducks/SessionTheme'; import { SessionTheme } from '../../state/ducks/SessionTheme';
import { Flex } from '../basic/Flex'; import { Flex } from '../basic/Flex';
import { setSignInByLinking } from '../../util/storage';
export const SessionRegistrationView = () => { export const SessionRegistrationView = () => {
useEffect(() => { useEffect(() => {

View File

@ -19,6 +19,7 @@ import { PubKey } from '../session/types';
import { fromArrayBufferToBase64, fromBase64ToArrayBuffer } from '../session/utils/String'; import { fromArrayBufferToBase64, fromBase64ToArrayBuffer } from '../session/utils/String';
import { ReduxConversationType } from '../state/ducks/conversations'; import { ReduxConversationType } from '../state/ducks/conversations';
import { ExpirationTimerOptions } from '../util/expiringMessages'; import { ExpirationTimerOptions } from '../util/expiringMessages';
import { Storage } from '../util/storage';
import { channels } from './channels'; import { channels } from './channels';
import { channelsToMake as channelstoMakeOpenGroupV2 } from './opengroups'; import { channelsToMake as channelstoMakeOpenGroupV2 } from './opengroups';
@ -469,7 +470,7 @@ export async function generateAttachmentKeyIfEmpty() {
value: encryptingKey, value: encryptingKey,
}); });
// be sure to write the new key to the cache. so we can access it straight away // 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); await Storage.put('local_attachment_encrypted_key', encryptingKey);
} }
} }

View File

@ -45,6 +45,7 @@ import { perfEnd, perfStart } from '../session/utils/Performance';
import { processNewAttachment } from '../types/MessageAttachment'; import { processNewAttachment } from '../types/MessageAttachment';
import { urlToBlob } from '../types/attachments/VisualAttachment'; import { urlToBlob } from '../types/attachments/VisualAttachment';
import { MIME } from '../types'; import { MIME } from '../types';
import { setLastProfileUpdateTimestamp } from '../util/storage';
export const getCompleteUrlForV2ConvoId = async (convoId: string) => { export const getCompleteUrlForV2ConvoId = async (convoId: string) => {
if (convoId.match(openGroupV2ConversationIdRegex)) { if (convoId.match(openGroupV2ConversationIdRegex)) {
@ -462,7 +463,7 @@ export async function uploadOurAvatar(newAvatarDecrypted?: ArrayBuffer) {
await createOrUpdateItem({ id: lastAvatarUploadTimestamp, value: newTimestampReupload }); await createOrUpdateItem({ id: lastAvatarUploadTimestamp, value: newTimestampReupload });
if (newAvatarDecrypted) { if (newAvatarDecrypted) {
UserUtils.setLastProfileUpdateTimestamp(Date.now()); await setLastProfileUpdateTimestamp(Date.now());
await SyncUtils.forceSyncConfigurationNowIfNeeded(true); await SyncUtils.forceSyncConfigurationNowIfNeeded(true);
} else { } else {
window.log.info( window.log.info(

View File

@ -64,6 +64,7 @@ import {
import { getOurPubKeyStrFromCache } from '../session/utils/User'; import { getOurPubKeyStrFromCache } from '../session/utils/User';
import { MessageRequestResponse } from '../session/messages/outgoing/controlMessage/MessageRequestResponse'; import { MessageRequestResponse } from '../session/messages/outgoing/controlMessage/MessageRequestResponse';
import { Notifications } from '../util/notifications'; import { Notifications } from '../util/notifications';
import { Storage } from '../util/storage';
export enum ConversationTypeEnum { export enum ConversationTypeEnum {
GROUP = 'group', GROUP = 'group',
@ -1146,10 +1147,10 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
if (this.isPrivate() && read.length && options.sendReadReceipts) { if (this.isPrivate() && read.length && options.sendReadReceipts) {
window?.log?.info( window?.log?.info(
`Sending ${read.length} read receipts?`, `Sending ${read.length} read receipts?`,
window.storage.get(SettingsKey.settingsReadReceipt) || false Storage.get(SettingsKey.settingsReadReceipt) || false
); );
const dontSendReceipt = this.isBlocked() || this.isIncomingRequest(); const dontSendReceipt = this.isBlocked() || this.isIncomingRequest();
if (window.storage.get(SettingsKey.settingsReadReceipt) && !dontSendReceipt) { if (Storage.get(SettingsKey.settingsReadReceipt) && !dontSendReceipt) {
const timestamps = _.map(read, 'timestamp').filter(t => !!t) as Array<number>; const timestamps = _.map(read, 'timestamp').filter(t => !!t) as Array<number>;
const receiptMessage = new ReadReceiptMessage({ const receiptMessage = new ReadReceiptMessage({
timestamp: Date.now(), timestamp: Date.now(),
@ -1658,7 +1659,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
// for typing to happen, this must be a private unblocked active convo, and the settings to be on // for typing to happen, this must be a private unblocked active convo, and the settings to be on
if ( if (
!this.isActive() || !this.isActive() ||
!window.storage.get(SettingsKey.settingsTypingIndicator) || !Storage.get(SettingsKey.settingsTypingIndicator) ||
this.isBlocked() || this.isBlocked() ||
!this.isPrivate() !this.isPrivate()
) { ) {

View File

@ -62,6 +62,7 @@ import {
} from '../types/MessageAttachment'; } from '../types/MessageAttachment';
import { ExpirationTimerOptions } from '../util/expiringMessages'; import { ExpirationTimerOptions } from '../util/expiringMessages';
import { Notifications } from '../util/notifications'; import { Notifications } from '../util/notifications';
import { Storage } from '../util/storage';
// tslint:disable: cyclomatic-complexity // tslint:disable: cyclomatic-complexity
/** /**
@ -428,7 +429,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
} }
const readBy = this.get('read_by') || []; const readBy = this.get('read_by') || [];
if (window.storage.get(SettingsKey.settingsReadReceipt) && readBy.length > 0) { if (Storage.get(SettingsKey.settingsReadReceipt) && readBy.length > 0) {
return 'read'; return 'read';
} }
const sent = this.get('sent'); const sent = this.get('sent');

View File

@ -14,7 +14,7 @@ interface Status {
type: Type; type: Type;
} }
type UserSetting = 'off' | 'count' | 'name' | 'message'; export type UserSetting = 'off' | 'count' | 'name' | 'message';
type Type = 'ok' | 'disabled' | 'appIsFocused' | 'noNotifications' | 'userSetting'; type Type = 'ok' | 'disabled' | 'appIsFocused' | 'noNotifications' | 'userSetting';

View File

@ -17,13 +17,14 @@ import { handleNewClosedGroup } from './closedGroups';
import { updateProfileOneAtATime } from './dataMessage'; import { updateProfileOneAtATime } from './dataMessage';
import { EnvelopePlus } from './types'; import { EnvelopePlus } from './types';
import { ConversationInteraction } from '../interactions'; import { ConversationInteraction } from '../interactions';
import { getLastProfileUpdateTimestamp, setLastProfileUpdateTimestamp } from '../util/storage';
async function handleOurProfileUpdate( async function handleOurProfileUpdate(
sentAt: number | Long, sentAt: number | Long,
configMessage: SignalService.ConfigurationMessage, configMessage: SignalService.ConfigurationMessage,
ourPubkey: string ourPubkey: string
) { ) {
const latestProfileUpdateTimestamp = UserUtils.getLastProfileUpdateTimestamp(); const latestProfileUpdateTimestamp = getLastProfileUpdateTimestamp();
if (!latestProfileUpdateTimestamp || sentAt > latestProfileUpdateTimestamp) { if (!latestProfileUpdateTimestamp || sentAt > latestProfileUpdateTimestamp) {
window?.log?.info( window?.log?.info(
`Handling our profileUdpate ourLastUpdate:${latestProfileUpdateTimestamp}, envelope sent at: ${sentAt}` `Handling our profileUdpate ourLastUpdate:${latestProfileUpdateTimestamp}, envelope sent at: ${sentAt}`
@ -41,7 +42,7 @@ async function handleOurProfileUpdate(
profilePicture, profilePicture,
}; };
await updateProfileOneAtATime(ourConversation, lokiProfile, profileKey); await updateProfileOneAtATime(ourConversation, lokiProfile, profileKey);
UserUtils.setLastProfileUpdateTimestamp(_.toNumber(sentAt)); await setLastProfileUpdateTimestamp(_.toNumber(sentAt));
// do not trigger a signin by linking if the display name is empty // do not trigger a signin by linking if the display name is empty
if (displayName) { if (displayName) {
trigger(configurationMessageReceived, displayName); trigger(configurationMessageReceived, displayName);

View File

@ -20,6 +20,7 @@ import { handleCallMessage } from './callMessage';
import { SettingsKey } from '../data/settings-key'; import { SettingsKey } from '../data/settings-key';
import { ConversationTypeEnum } from '../models/conversation'; import { ConversationTypeEnum } from '../models/conversation';
import { ReadReceipts } from '../util/readReceipts'; import { ReadReceipts } from '../util/readReceipts';
import { Storage } from '../util/storage';
export async function handleSwarmContentMessage(envelope: EnvelopePlus, messageHash: string) { export async function handleSwarmContentMessage(envelope: EnvelopePlus, messageHash: string) {
try { try {
@ -491,7 +492,7 @@ async function handleTypingMessage(
await removeFromCache(envelope); await removeFromCache(envelope);
// We don't do anything with incoming typing messages if the setting is disabled // We don't do anything with incoming typing messages if the setting is disabled
if (!window.storage.get(SettingsKey.settingsTypingIndicator)) { if (!Storage.get(SettingsKey.settingsTypingIndicator)) {
return; return;
} }

View File

@ -6,6 +6,7 @@ import { PubKey } from '../types';
import { fromHexToArray, toHex } from './String'; import { fromHexToArray, toHex } from './String';
import { getConversationController } from '../conversations'; import { getConversationController } from '../conversations';
import { LokiProfile } from '../../types/Message'; import { LokiProfile } from '../../types/Message';
import { getNumber, Storage } from '../../util/storage';
export type HexKeyPair = { export type HexKeyPair = {
pubKey: string; pubKey: string;
@ -29,7 +30,7 @@ export function isUsFromCache(pubKey: string | PubKey | undefined): boolean {
* Returns the public key of this current device as a STRING, or throws an error * Returns the public key of this current device as a STRING, or throws an error
*/ */
export function getOurPubKeyStrFromCache(): string { export function getOurPubKeyStrFromCache(): string {
const ourNumber = window.textsecure.storage.user.getNumber(); const ourNumber = getNumber();
if (!ourNumber) { if (!ourNumber) {
throw new Error('ourNumber is not set'); throw new Error('ourNumber is not set');
} }
@ -78,27 +79,11 @@ export async function getUserED25519KeyPair(): Promise<HexKeyPair | undefined> {
return undefined; return undefined;
} }
export function isSignInByLinking(): boolean {
return window.textsecure.storage.user.isSignInByLinking();
}
export function setSignInByLinking(isLinking: boolean) {
window.textsecure.storage.user.setSignInByLinking(isLinking);
}
export function isSignWithRecoveryPhrase(): boolean {
return window.textsecure.storage.user.isSignWithRecoveryPhrase();
}
export function setSignWithRecoveryPhrase(isLinking: boolean) {
window.textsecure.storage.user.setSignWithRecoveryPhrase(isLinking);
}
export function getOurProfile(): LokiProfile | undefined { export function getOurProfile(): LokiProfile | undefined {
try { try {
// Secondary devices have their profile stored // Secondary devices have their profile stored
// in their primary device's conversation // in their primary device's conversation
const ourNumber = window.storage.get('primaryDevicePubKey'); const ourNumber = Storage.get('primaryDevicePubKey') as string;
const ourConversation = getConversationController().get(ourNumber); const ourConversation = getConversationController().get(ourNumber);
const ourProfileKeyHex = ourConversation.get('profileKey'); const ourProfileKeyHex = ourConversation.get('profileKey');
const profileKeyAsBytes = ourProfileKeyHex ? fromHexToArray(ourProfileKeyHex) : null; const profileKeyAsBytes = ourProfileKeyHex ? fromHexToArray(ourProfileKeyHex) : null;
@ -115,19 +100,3 @@ export function getOurProfile(): LokiProfile | undefined {
return undefined; return undefined;
} }
} }
export function getLastProfileUpdateTimestamp(): number | undefined {
return window.textsecure.storage.user.getLastProfileUpdateTimestamp();
}
export function setLastProfileUpdateTimestamp(lastUpdateTimestamp: number) {
return window.textsecure.storage.user.setLastProfileUpdateTimestamp(lastUpdateTimestamp);
}
export function getCurrentRecoveryPhrase() {
return window.textsecure.storage.get('mnemonic');
}
export function saveRecoveryPhrase(mnemonic: string) {
return window.textsecure.storage.put('mnemonic', mnemonic);
}

View File

@ -33,6 +33,7 @@ import { GenericReadableMessageSelectorProps } from '../../components/conversati
import { LightBoxOptions } from '../../components/conversation/SessionConversation'; import { LightBoxOptions } from '../../components/conversation/SessionConversation';
import { getConversationController } from '../../session/conversations'; import { getConversationController } from '../../session/conversations';
import { UserUtils } from '../../session/utils'; import { UserUtils } from '../../session/utils';
import { Storage } from '../../util/storage';
export const getConversations = (state: StateType): ConversationsStateType => state.conversations; export const getConversations = (state: StateType): ConversationsStateType => state.conversations;
@ -129,7 +130,7 @@ export const isPublicGroupConversation = createSelector(
export const getOurPrimaryConversation = createSelector( export const getOurPrimaryConversation = createSelector(
getConversations, getConversations,
(state: ConversationsStateType): ReduxConversationType => (state: ConversationsStateType): ReduxConversationType =>
state.conversationLookup[window.storage.get('primaryDevicePubKey')] state.conversationLookup[Storage.get('primaryDevicePubKey') as string]
); );
const getMessagesOfSelectedConversation = createSelector( const getMessagesOfSelectedConversation = createSelector(

View File

@ -7,6 +7,7 @@ import { SignalService } from '../protobuf';
import { isImageTypeSupported, isVideoTypeSupported } from '../util/GoogleChrome'; import { isImageTypeSupported, isVideoTypeSupported } from '../util/GoogleChrome';
import { fromHexToArray } from '../session/utils/String'; import { fromHexToArray } from '../session/utils/String';
import { ATTACHMENT_DEFAULT_MAX_SIDE } from '../util/attachmentsUtil'; import { ATTACHMENT_DEFAULT_MAX_SIDE } from '../util/attachmentsUtil';
import { Storage } from '../util/storage';
const MAX_WIDTH = 200; const MAX_WIDTH = 200;
const MAX_HEIGHT = MAX_WIDTH; const MAX_HEIGHT = MAX_WIDTH;
@ -396,9 +397,8 @@ export const encryptAttachmentBuffer = async (bufferIn: ArrayBuffer) => {
if (!isArrayBuffer(bufferIn)) { if (!isArrayBuffer(bufferIn)) {
throw new TypeError("'bufferIn' must be an array buffer"); throw new TypeError("'bufferIn' must be an array buffer");
} }
const encryptingKey = fromHexToArray( const key = Storage.get('local_attachment_encrypted_key') as string;
window.textsecure.storage.get('local_attachment_encrypted_key') const encryptingKey = fromHexToArray(key);
);
return window.callWorker('encryptAttachmentBuffer', encryptingKey, bufferIn); return window.callWorker('encryptAttachmentBuffer', encryptingKey, bufferIn);
}; };
@ -406,8 +406,7 @@ export const decryptAttachmentBuffer = async (bufferIn: ArrayBuffer): Promise<Ui
if (!isArrayBuffer(bufferIn)) { if (!isArrayBuffer(bufferIn)) {
throw new TypeError("'bufferIn' must be an array buffer"); throw new TypeError("'bufferIn' must be an array buffer");
} }
const encryptingKey = fromHexToArray( const key = Storage.get('local_attachment_encrypted_key') as string;
window.textsecure.storage.get('local_attachment_encrypted_key') const encryptingKey = fromHexToArray(key);
);
return window.callWorker('decryptAttachmentBuffer', encryptingKey, bufferIn); return window.callWorker('decryptAttachmentBuffer', encryptingKey, bufferIn);
}; };

View File

@ -1,6 +1,5 @@
import { getConversationController } from '../session/conversations'; import { getConversationController } from '../session/conversations';
import { getSodium } from '../session/crypto'; import { getSodium } from '../session/crypto';
import { UserUtils } from '../session/utils';
import { fromArrayBufferToBase64, fromHex, toHex } from '../session/utils/String'; import { fromArrayBufferToBase64, fromHex, toHex } from '../session/utils/String';
import { getOurPubKeyStrFromCache } from '../session/utils/User'; import { getOurPubKeyStrFromCache } from '../session/utils/User';
import { trigger } from '../shims/events'; import { trigger } from '../shims/events';
@ -9,6 +8,14 @@ import { actions as userActions } from '../state/ducks/user';
import { mn_decode, mn_encode } from '../session/crypto/mnemonic'; import { mn_decode, mn_encode } from '../session/crypto/mnemonic';
import { ConversationTypeEnum } from '../models/conversation'; import { ConversationTypeEnum } from '../models/conversation';
import { SettingsKey } from '../data/settings-key'; import { SettingsKey } from '../data/settings-key';
import {
saveRecoveryPhrase,
setLastProfileUpdateTimestamp,
setLocalPubKey,
setSignInByLinking,
Storage,
} from './storage';
import { Registration } from './registration';
/** /**
* Might throw * Might throw
@ -76,9 +83,9 @@ export async function signInByLinkingDevice(mnemonic: string, mnemonicLanguage:
} }
const identityKeyPair = await generateKeypair(mnemonic, mnemonicLanguage); const identityKeyPair = await generateKeypair(mnemonic, mnemonicLanguage);
UserUtils.setSignInByLinking(true); await setSignInByLinking(true);
await createAccount(identityKeyPair); await createAccount(identityKeyPair);
UserUtils.saveRecoveryPhrase(mnemonic); await saveRecoveryPhrase(mnemonic);
const pubKeyString = toHex(identityKeyPair.pubKey); const pubKeyString = toHex(identityKeyPair.pubKey);
// await for the first configuration message to come in. // await for the first configuration message to come in.
@ -109,8 +116,8 @@ export async function registerSingleDevice(
const identityKeyPair = await generateKeypair(generatedMnemonic, mnemonicLanguage); const identityKeyPair = await generateKeypair(generatedMnemonic, mnemonicLanguage);
await createAccount(identityKeyPair); await createAccount(identityKeyPair);
UserUtils.saveRecoveryPhrase(generatedMnemonic); await saveRecoveryPhrase(generatedMnemonic);
await UserUtils.setLastProfileUpdateTimestamp(Date.now()); await setLastProfileUpdateTimestamp(Date.now());
const pubKeyString = toHex(identityKeyPair.pubKey); const pubKeyString = toHex(identityKeyPair.pubKey);
await registrationDone(pubKeyString, profileName); await registrationDone(pubKeyString, profileName);
@ -131,39 +138,39 @@ async function createAccount(identityKeyPair: any) {
password = password.substring(0, password.length - 2); password = password.substring(0, password.length - 2);
await Promise.all([ await Promise.all([
window.textsecure.storage.remove('identityKey'), Storage.remove('identityKey'),
window.textsecure.storage.remove('signaling_key'), Storage.remove('signaling_key'),
window.textsecure.storage.remove('password'), Storage.remove('password'),
window.textsecure.storage.remove('registrationId'), Storage.remove('registrationId'),
window.textsecure.storage.remove('number_id'), Storage.remove('number_id'),
window.textsecure.storage.remove('device_name'), Storage.remove('device_name'),
window.textsecure.storage.remove('userAgent'), Storage.remove('userAgent'),
window.textsecure.storage.remove(SettingsKey.settingsReadReceipt), Storage.remove(SettingsKey.settingsReadReceipt),
window.textsecure.storage.remove(SettingsKey.settingsTypingIndicator), Storage.remove(SettingsKey.settingsTypingIndicator),
window.textsecure.storage.remove('regionCode'), Storage.remove('regionCode'),
window.textsecure.storage.remove('local_attachment_encrypted_key'), Storage.remove('local_attachment_encrypted_key'),
]); ]);
// update our own identity key, which may have changed // update our own identity key, which may have changed
// if we're relinking after a reinstall on the master device // if we're relinking after a reinstall on the master device
const pubKeyString = toHex(identityKeyPair.pubKey); const pubKeyString = toHex(identityKeyPair.pubKey);
await window.textsecure.storage.put('identityKey', identityKeyPair); await Storage.put('identityKey', identityKeyPair);
await window.textsecure.storage.put('password', password); await Storage.put('password', password);
// disable read-receipt by default // disable read-receipt by default
await window.textsecure.storage.put(SettingsKey.settingsReadReceipt, false); await Storage.put(SettingsKey.settingsReadReceipt, false);
// Enable typing indicators by default // Enable typing indicators by default
await window.textsecure.storage.put(SettingsKey.settingsTypingIndicator, false); await Storage.put(SettingsKey.settingsTypingIndicator, false);
await window.textsecure.storage.user.setNumberAndDeviceId(pubKeyString, 1); await setLocalPubKey(pubKeyString);
} }
async function registrationDone(ourPubkey: string, displayName: string) { async function registrationDone(ourPubkey: string, displayName: string) {
window?.log?.info('registration done'); window?.log?.info('registration done');
window.textsecure.storage.put('primaryDevicePubKey', ourPubkey); await Storage.put('primaryDevicePubKey', ourPubkey);
// Ensure that we always have a conversation for ourself // Ensure that we always have a conversation for ourself
const conversation = await getConversationController().getOrCreateAndWait( const conversation = await getConversationController().getOrCreateAndWait(
@ -174,10 +181,10 @@ async function registrationDone(ourPubkey: string, displayName: string) {
await conversation.setIsApproved(true); await conversation.setIsApproved(true);
const user = { const user = {
ourNumber: getOurPubKeyStrFromCache(), ourNumber: getOurPubKeyStrFromCache(),
ourPrimary: window.textsecure.storage.get('primaryDevicePubKey'), ourPrimary: ourPubkey,
}; };
window.inboxStore?.dispatch(userActions.userChanged(user)); window.inboxStore?.dispatch(userActions.userChanged(user));
window.Whisper.Registration.markDone(); await Registration.markDone();
window?.log?.info('dispatching registration event'); window?.log?.info('dispatching registration event');
trigger('registration_done'); trigger('registration_done');
} }

View File

@ -1,8 +1,10 @@
import _ from 'lodash'; import _ from 'lodash';
import { getStatus } from '../notifications'; import { getStatus } from '../notifications';
import { UserSetting } from '../notifications/getStatus';
import { isMacOS } from '../OS'; import { isMacOS } from '../OS';
import { isAudioNotificationSupported } from '../types/Settings'; import { isAudioNotificationSupported } from '../types/Settings';
import { isWindowFocused } from './focusListener'; import { isWindowFocused } from './focusListener';
import { Storage } from './storage';
const SettingNames = { const SettingNames = {
COUNT: 'count', COUNT: 'count',
@ -106,7 +108,7 @@ function update() {
} }
const isAppFocused = isWindowFocused(); const isAppFocused = isWindowFocused();
const isAudioNotificationEnabled = storage.get('audio-notification') || false; const isAudioNotificationEnabled = (Storage.get('audio-notification') as boolean) || false;
const audioNotificationSupported = isAudioNotificationSupported(); const audioNotificationSupported = isAudioNotificationSupported();
// const isNotificationGroupingSupported = Settings.isNotificationGroupingSupported(); // const isNotificationGroupingSupported = Settings.isNotificationGroupingSupported();
const numNotifications = currentNotifications.length; const numNotifications = currentNotifications.length;
@ -221,7 +223,7 @@ function update() {
// 10 new messages) assuming that `Notification::close` does its job. // 10 new messages) assuming that `Notification::close` does its job.
} }
function getUserSetting() { function getUserSetting() {
return storage.get('notification-setting') || SettingNames.MESSAGE; return (Storage.get('notification-setting') as UserSetting) || SettingNames.MESSAGE;
} }
function onRemove() { function onRemove() {
// window.log.info('Remove notification'); // window.log.info('Remove notification');

View File

@ -1,21 +1,23 @@
function markEverDone() { import { Storage } from './storage';
storage.put('chromiumRegistrationDoneEver', '');
async function markEverDone() {
await Storage.put('chromiumRegistrationDoneEver', '');
} }
function markDone() { async function markDone() {
this.markEverDone(); await markEverDone();
storage.put('chromiumRegistrationDone', ''); await Storage.put('chromiumRegistrationDone', '');
} }
function isDone() { function isDone() {
return storage.get('chromiumRegistrationDone') === ''; return Storage.get('chromiumRegistrationDone') === '';
} }
function everDone() { function everDone() {
return ( return (
storage.get('chromiumRegistrationDoneEver') === '' || Storage.get('chromiumRegistrationDoneEver') === '' ||
storage.get('chromiumRegistrationDone') === '' Storage.get('chromiumRegistrationDone') === ''
); );
} }
function remove() { async function remove() {
storage.remove('chromiumRegistrationDone'); await Storage.remove('chromiumRegistrationDone');
} }
export const Registration = { markEverDone, markDone, isDone, everDone, remove }; export const Registration = { markEverDone, markDone, isDone, everDone, remove };

138
ts/util/storage.ts Normal file
View File

@ -0,0 +1,138 @@
import * as Data from '../data/data';
let ready = false;
type ValueType = string | number | boolean;
type InsertedValueType = { id: string; value: ValueType };
let items: Record<string, InsertedValueType>;
let callbacks: Array<() => void> = [];
reset();
async function put(key: string, value: ValueType) {
if (value === undefined) {
throw new Error('Tried to store undefined');
}
if (!ready) {
window.log.warn('Called storage.put before storage is ready. key:', key);
}
const data: InsertedValueType = { id: key, value };
items[key] = data;
await Data.createOrUpdateItem(data);
}
function get(key: string, defaultValue?: ValueType) {
if (!ready) {
window.log.warn('Called storage.get before storage is ready. key:', key);
}
const item = items[key];
if (!item) {
return defaultValue;
}
return item.value;
}
async function remove(key: string) {
if (!ready) {
window.log.warn('Called storage.get before storage is ready. key:', key);
}
// tslint:disable-next-line: no-dynamic-delete
delete items[key];
await Data.removeItemById(key);
}
function onready(callback: () => void) {
if (ready) {
callback();
} else {
callbacks.push(callback);
}
}
function callListeners() {
if (ready) {
callbacks.forEach(callback => {
callback();
});
callbacks = [];
}
}
async function fetch() {
reset();
const array = await Data.getAllItems();
// tslint:disable-next-line: one-variable-per-declaration
for (let i = 0, max = array.length; i < max; i += 1) {
const item = array[i];
const { id } = item;
items[id] = item;
}
ready = true;
callListeners();
}
function reset() {
ready = false;
items = Object.create(null);
}
export async function setLocalPubKey(pubkey: string) {
await put('number_id', `${pubkey}.1`);
}
export function getNumber() {
const numberId = get('number_id') as string | undefined;
if (numberId === undefined) {
return undefined;
}
return numberId.split('.')[0];
}
export function isSignInByLinking() {
const isByLinking = get('is_sign_in_by_linking');
if (isByLinking === undefined) {
return false;
}
return isByLinking;
}
export async function setSignInByLinking(isLinking: boolean) {
await put('is_sign_in_by_linking', isLinking);
}
export function isSignWithRecoveryPhrase() {
const isRecoveryPhraseUsed = get('is_sign_in_recovery_phrase');
if (isRecoveryPhraseUsed === undefined) {
return false;
}
return isRecoveryPhraseUsed;
}
export async function setSignWithRecoveryPhrase(isRecoveryPhraseUsed: boolean) {
await put('is_sign_in_recovery_phrase', isRecoveryPhraseUsed);
}
export function getLastProfileUpdateTimestamp() {
return get('last_profile_update_timestamp');
}
export async function setLastProfileUpdateTimestamp(lastUpdateTimestamp: number) {
await put('last_profile_update_timestamp', lastUpdateTimestamp);
}
export function getCurrentRecoveryPhrase() {
return Storage.get('mnemonic') as string;
}
export async function saveRecoveryPhrase(mnemonic: string) {
return Storage.put('mnemonic', mnemonic);
}
export const Storage = { fetch, put, get, remove, onready, reset };