diff --git a/background.html b/background.html index 6d9188870..9255fdd79 100644 --- a/background.html +++ b/background.html @@ -39,7 +39,6 @@ - @@ -49,9 +48,6 @@ - - - diff --git a/js/background.js b/js/background.js index 541c9645c..e9cfde8e0 100644 --- a/js/background.js +++ b/js/background.js @@ -228,7 +228,8 @@ el: $('body'), }); - Whisper.WallClockListener.init(Whisper.events); + // FIXME audric2 ExpiringMessagesListener Whisper.Expi + throw new Error('plop'); Whisper.ExpiringMessagesListener.init(Whisper.events); if (Whisper.Registration.isDone() && !window.textsecure.storage.user.isSignInByLinking()) { diff --git a/js/expiring_messages.js b/js/expiring_messages.js deleted file mode 100644 index 10678c43c..000000000 --- a/js/expiring_messages.js +++ /dev/null @@ -1,142 +0,0 @@ -/* global - _, - Backbone, - i18n, - moment, - Whisper -*/ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - async function destroyExpiredMessages() { - try { - window.log.info('destroyExpiredMessages: Loading messages...'); - const messages = await window.Signal.Data.getExpiredMessages(); - - await Promise.all( - messages.map(async message => { - window.log.info('Message expired', { - sentAt: message.get('sent_at'), - }); - - // We delete after the trigger to allow the conversation time to process - // the expiration before the message is removed from the database. - await window.Signal.Data.removeMessage(message.id); - - Whisper.events.trigger('messageExpired', { - conversationKey: message.attributes.conversationId, - messageId: message.id, - }); - - const conversation = message.getConversation(); - if (conversation) { - conversation.onExpired(message); - } - }) - ); - } catch (error) { - window.log.error( - 'destroyExpiredMessages: Error deleting expired messages', - error && error.stack ? error.stack : error - ); - } - - window.log.info('destroyExpiredMessages: complete'); - checkExpiringMessages(); - } - - let timeout; - async function checkExpiringMessages() { - // Look up the next expiring message and set a timer to destroy it - const messages = await window.Signal.Data.getNextExpiringMessage(); - const next = messages.at(0); - if (!next) { - return; - } - - const expiresAt = next.get('expires_at'); - Whisper.ExpiringMessagesListener.nextExpiration = expiresAt; - window.log.info('next message expires', new Date(expiresAt).toISOString()); - window.log.info('next message expires in ', (expiresAt - Date.now()) / 1000); - - let wait = expiresAt - Date.now(); - - // In the past - if (wait < 0) { - wait = 0; - } - - // Too far in the future, since it's limited to a 32-bit value - if (wait > 2147483647) { - wait = 2147483647; - } - - clearTimeout(timeout); - timeout = setTimeout(destroyExpiredMessages, wait); - } - const throttledCheckExpiringMessages = _.throttle(checkExpiringMessages, 1000); - - Whisper.ExpiringMessagesListener = { - nextExpiration: null, - init(events) { - checkExpiringMessages(); - events.on('timetravel', throttledCheckExpiringMessages); - }, - update: throttledCheckExpiringMessages, - }; - - const TimerOption = Backbone.Model.extend({ - getName() { - return ( - i18n(['timerOption', this.get('time'), this.get('unit')].join('_')) || - moment.duration(this.get('time'), this.get('unit')).humanize() - ); - }, - getAbbreviated() { - return i18n(['timerOption', this.get('time'), this.get('unit'), 'abbreviated'].join('_')); - }, - }); - Whisper.ExpirationTimerOptions = new (Backbone.Collection.extend({ - model: TimerOption, - getName(seconds = 0) { - const o = this.findWhere({ seconds }); - if (o) { - return o.getName(); - } - return [seconds, 'seconds'].join(' '); - }, - getAbbreviated(seconds = 0) { - const o = this.findWhere({ seconds }); - if (o) { - return o.getAbbreviated(); - } - return [seconds, 's'].join(''); - }, - }))( - [ - [0, 'seconds'], - [5, 'seconds'], - [10, 'seconds'], - [30, 'seconds'], - [1, 'minute'], - [5, 'minutes'], - [30, 'minutes'], - [1, 'hour'], - [6, 'hours'], - [12, 'hours'], - [1, 'day'], - [1, 'week'], - ].map(o => { - const duration = moment.duration(o[0], o[1]); // 5, 'seconds' - return { - time: o[0], - unit: o[1], - seconds: duration.asSeconds(), - }; - }) - ); -})(); diff --git a/js/wall_clock_listener.js b/js/wall_clock_listener.js deleted file mode 100644 index 43d52f4e3..000000000 --- a/js/wall_clock_listener.js +++ /dev/null @@ -1,27 +0,0 @@ -/* global Whisper */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - let lastTime; - const interval = 10000; - let events; - function checkTime() { - const currentTime = Date.now(); - if (currentTime > lastTime + interval * 2) { - events.trigger('timetravel'); - } - lastTime = currentTime; - } - - Whisper.WallClockListener = { - init(_events) { - events = _events; - lastTime = Date.now(); - setInterval(checkTime, interval); - }, - }; -})(); diff --git a/main.js b/main.js index 19d9dda5d..827d97477 100644 --- a/main.js +++ b/main.js @@ -244,7 +244,7 @@ async function createWindow() { webPreferences: { nodeIntegration: false, enableRemoteModule: true, - nodeIntegrationInWorker: false, + nodeIntegrationInWorker: true, contextIsolation: false, preload: path.join(__dirname, 'preload.js'), nativeWindowOpen: true, diff --git a/ts/components/SessionInboxView.tsx b/ts/components/SessionInboxView.tsx index 6a58f07f2..5c7374e4d 100644 --- a/ts/components/SessionInboxView.tsx +++ b/ts/components/SessionInboxView.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { Provider } from 'react-redux'; -import { bindActionCreators } from 'redux'; import { LeftPane } from './leftpane/LeftPane'; // tslint:disable-next-line: no-submodule-imports @@ -10,7 +9,6 @@ import { getConversationController } from '../session/conversations'; import { UserUtils } from '../session/utils'; import { initialCallState } from '../state/ducks/call'; import { - actions as conversationActions, getEmptyConversationState, openConversationWithMessages, } from '../state/ducks/conversations'; @@ -27,6 +25,7 @@ import { StateType } from '../state/reducer'; import { makeLookup } from '../util'; import { SessionMainPanel } from './SessionMainPanel'; import { createStore } from '../state/createStore'; +import { ExpirationTimerOptions } from '../util/expiringMessages'; // Workaround: A react component's required properties are filtering up through connect() // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363 @@ -80,12 +79,7 @@ export class SessionInboxView extends React.Component { .getConversations() .map(conversation => conversation.getConversationModelProps()); - const timerOptions: TimerOptionsArray = window.Whisper.ExpirationTimerOptions.map( - (item: any) => ({ - name: item.getName(), - value: item.get('seconds'), - }) - ); + const timerOptions: TimerOptionsArray = ExpirationTimerOptions.getTimerSecondsWithName(); const initialState: StateType = { conversations: { @@ -112,13 +106,8 @@ export class SessionInboxView extends React.Component { this.store = createStore(initialState); window.inboxStore = this.store; - // Enables our redux store to be updated by backbone events in the outside world - const { messageExpired } = bindActionCreators(conversationActions, this.store.dispatch); window.openConversationWithMessages = openConversationWithMessages; - // messageExpired is currently inboked fropm js. So we link it to Redux that way - window.Whisper.events.on('messageExpired', messageExpired); - this.setState({ isInitialLoadComplete: true }); } } diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx index 8abd34b82..67373404b 100644 --- a/ts/components/conversation/ConversationHeader.tsx +++ b/ts/components/conversation/ConversationHeader.tsx @@ -41,6 +41,7 @@ import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/S import { SessionIconButton } from '../icon'; import { ConversationHeaderMenu } from '../menu/ConversationHeaderMenu'; import { Flex } from '../basic/Flex'; +import { ExpirationTimerOptions } from '../../util/expiringMessages'; export interface TimerOption { name: string; diff --git a/ts/data/data.ts b/ts/data/data.ts index f27a727ff..733b1c64a 100644 --- a/ts/data/data.ts +++ b/ts/data/data.ts @@ -18,6 +18,7 @@ import { getSodium } from '../session/crypto'; import { PubKey } from '../session/types'; import { fromArrayBufferToBase64, fromBase64ToArrayBuffer } from '../session/utils/String'; import { ReduxConversationType } from '../state/ducks/conversations'; +import { ExpirationTimerOptions } from '../util/expiringMessages'; import { channels } from './channels'; import { channelsToMake as channelstoMakeOpenGroupV2 } from './opengroups'; @@ -648,7 +649,7 @@ export async function updateLastHash(data: { export async function saveMessage(data: MessageAttributes): Promise { const cleanedData = _cleanData(data); const id = await channels.saveMessage(cleanedData); - window.Whisper.ExpiringMessagesListener.update(); + ExpirationTimerOptions.updateExpiringMessagesCheck(); return id; } diff --git a/ts/models/message.ts b/ts/models/message.ts index fb189e6c8..f5a1c9cbe 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -60,6 +60,7 @@ import { loadPreviewData, loadQuoteData, } from '../types/MessageAttachment'; +import { ExpirationTimerOptions } from '../util/expiringMessages'; // tslint:disable: cyclomatic-complexity /** @@ -214,10 +215,9 @@ export class MessageModel extends Backbone.Model { return window.i18n('disappearingMessagesDisabled'); } - return window.i18n( - 'timerSetTo', - window.Whisper.ExpirationTimerOptions.getAbbreviated(expireTimerUpdate.expireTimer || 0) - ); + return window.i18n('timerSetTo', [ + ExpirationTimerOptions.getAbbreviated(expireTimerUpdate.expireTimer || 0), + ]); } return ''; @@ -241,7 +241,7 @@ export class MessageModel extends Backbone.Model { } const { expireTimer, fromSync, source } = timerUpdate; - const timespan = window.Whisper.ExpirationTimerOptions.getName(expireTimer || 0) as string; + const timespan = ExpirationTimerOptions.getName(expireTimer || 0); const disabled = !expireTimer; const basicProps: PropsForExpirationTimer = { diff --git a/ts/util/expiringMessages.ts b/ts/util/expiringMessages.ts new file mode 100644 index 000000000..04b2a9f4f --- /dev/null +++ b/ts/util/expiringMessages.ts @@ -0,0 +1,167 @@ +import _ from 'lodash'; +import moment from 'moment'; +import { MessageModel } from '../models/message'; +import { messageExpired } from '../state/ducks/conversations'; +import { TimerOptionsArray } from '../state/ducks/timerOptions'; +import { LocalizerKeys } from '../types/LocalizerKeys'; +import { initWallClockListener } from './wallClockListener'; + +import * as Data from '../data/data'; + +async function destroyExpiredMessages() { + try { + window.log.info('destroyExpiredMessages: Loading messages...'); + const messages = await Data.getExpiredMessages(); + + await Promise.all( + messages.map(async (message: MessageModel) => { + window.log.info('Message expired', { + sentAt: message.get('sent_at'), + }); + + // We delete after the trigger to allow the conversation time to process + // the expiration before the message is removed from the database. + await Data.removeMessage(message.id); + + // trigger the expiration of the message on the redux itself. + window.inboxStore?.dispatch( + messageExpired({ + conversationKey: message.attributes.conversationId, + messageId: message.id, + }) + ); + + const conversation = message.getConversation(); + if (conversation) { + await conversation.onExpired(message); + } + }) + ); + } catch (error) { + window.log.error( + 'destroyExpiredMessages: Error deleting expired messages', + error && error.stack ? error.stack : error + ); + } + + window.log.info('destroyExpiredMessages: complete'); + void checkExpiringMessages(); +} + +let timeout: NodeJS.Timeout | undefined; +async function checkExpiringMessages() { + // Look up the next expiring message and set a timer to destroy it + const messages = await window.Signal.Data.getNextExpiringMessage(); + const next = messages.at(0); + if (!next) { + return; + } + + const expiresAt = next.get('expires_at'); + + window.log.info('next message expires', new Date(expiresAt).toISOString()); + window.log.info('next message expires in ', (expiresAt - Date.now()) / 1000); + + let wait = expiresAt - Date.now(); + + // In the past + if (wait < 0) { + wait = 0; + } + + // Too far in the future, since it's limited to a 32-bit value + if (wait > 2147483647) { + wait = 2147483647; + } + + if (timeout) { + global.clearTimeout(timeout); + } + timeout = global.setTimeout(destroyExpiredMessages, wait); +} +const throttledCheckExpiringMessages = _.throttle(checkExpiringMessages, 1000); + +let isInit = false; + +const initExpiringMessageListener = () => { + if (isInit) { + throw new Error('expiring messages listener is already init'); + } + + void checkExpiringMessages(); + + initWallClockListener(throttledCheckExpiringMessages); + isInit = true; +}; + +const updateExpiringMessagesCheck = () => { + void throttledCheckExpiringMessages(); +}; + +function getTimerOptionName(time: number, unit: moment.DurationInputArg2) { + return ( + window.i18n(['timerOption', time, unit].join('_') as LocalizerKeys) || + moment.duration(time, unit).humanize() + ); +} +function getTimerOptionAbbreviated(time: number, unit: string) { + return window.i18n(['timerOption', time, unit, 'abbreviated'].join('_') as LocalizerKeys); +} + +const timerOptionsDurations: Array<{ + time: number; + unit: moment.DurationInputArg2; + seconds: number; +}> = [ + { time: 0, unit: 'seconds' as moment.DurationInputArg2 }, + { time: 5, unit: 'seconds' as moment.DurationInputArg2 }, + { time: 10, unit: 'seconds' as moment.DurationInputArg2 }, + { time: 30, unit: 'seconds' as moment.DurationInputArg2 }, + { time: 1, unit: 'minute' as moment.DurationInputArg2 }, + { time: 5, unit: 'minutes' as moment.DurationInputArg2 }, + { time: 30, unit: 'minutes' as moment.DurationInputArg2 }, + { time: 1, unit: 'hour' as moment.DurationInputArg2 }, + { time: 6, unit: 'hours' as moment.DurationInputArg2 }, + { time: 12, unit: 'hours' as moment.DurationInputArg2 }, + { time: 1, unit: 'day' as moment.DurationInputArg2 }, + { time: 1, unit: 'week' as moment.DurationInputArg2 }, +].map(o => { + const duration = moment.duration(o.time, o.unit); // 5, 'seconds' + return { + time: o.time, + unit: o.unit, + seconds: duration.asSeconds(), + }; +}); + +function getName(seconds = 0) { + const o = timerOptionsDurations.find(m => m.seconds === seconds); + + if (o) { + return getTimerOptionName(o.time, o.unit); + } + return [seconds, 'seconds'].join(' '); +} +function getAbbreviated(seconds = 0) { + const o = timerOptionsDurations.find(m => m.seconds === seconds); + + if (o) { + return getTimerOptionAbbreviated(o.time, o.unit); + } + + return [seconds, 's'].join(''); +} + +function getTimerSecondsWithName(): TimerOptionsArray { + return timerOptionsDurations.map(t => { + return { name: getName(t.seconds), value: t.seconds }; + }); +} + +export const ExpirationTimerOptions = { + getName, + getAbbreviated, + updateExpiringMessagesCheck, + initExpiringMessageListener, + getTimerSecondsWithName, +}; diff --git a/ts/util/wallClockListener.ts b/ts/util/wallClockListener.ts new file mode 100644 index 000000000..66e7b6d40 --- /dev/null +++ b/ts/util/wallClockListener.ts @@ -0,0 +1,22 @@ +let lastTime = Date.now(); +const interval = 10 * 1000; +let timeTravelListener: (() => void) | undefined; + +function checkTime() { + const currentTime = Date.now(); + if (currentTime > lastTime + interval * 2) { + if (!timeTravelListener) { + throw new Error('timeTravelListener should have been set in initWallClockListener'); + } + timeTravelListener(); + } + lastTime = currentTime; +} + +export const initWallClockListener = (onTimeTravelDetectedListener: () => void) => { + if (timeTravelListener) { + throw new Error('Wall clock listener already init'); + } + timeTravelListener = onTimeTravelDetectedListener; + global.setInterval(checkTime, interval); +}; diff --git a/ts/webworker/workers/auth.worker.ts b/ts/webworker/workers/auth.worker.ts index 6c360655b..263064f7a 100644 --- a/ts/webworker/workers/auth.worker.ts +++ b/ts/webworker/workers/auth.worker.ts @@ -1,11 +1,11 @@ -const _ = require('lodash'); +import * as lodash from 'lodash'; // import * as _ from 'lodash'; const sleep = async (time: any) => new Promise(r => setTimeout(r, time)); ~(async function main() { while (true) { - console.log('lodash map exists:', typeof _.map); + console.log('lodash map exists:', typeof lodash.map); await sleep(1000); } })();