after moving expiring message and wallclock to TS

pull/2239/head
Audric Ackermann 2 years ago
parent d7f84168ac
commit e78224db05
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -39,7 +39,6 @@
<script type="text/javascript" src="js/notifications.js"></script>
<script type="text/javascript" src="js/read_receipts.js"></script>
<script type="text/javascript" src="js/read_syncs.js"></script>
<script type="text/javascript" src="js/expiring_messages.js"></script>
<script type="text/javascript" src="js/registration.js"></script>
@ -49,9 +48,6 @@
<script type="text/javascript" src="js/views/session_inbox_view.js"></script>
<script type="text/javascript" src="js/views/session_registration_view.js"></script>
<script type="text/javascript" src="js/views/app_view.js"></script>
<!-- CRYPTO -->
<script type="text/javascript" src="js/wall_clock_listener.js"></script>
</head>
<body>

@ -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()) {

@ -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(),
};
})
);
})();

@ -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);
},
};
})();

@ -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,

@ -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<any, State> {
.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<any, State> {
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 });
}
}

@ -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;

@ -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<string> {
const cleanedData = _cleanData(data);
const id = await channels.saveMessage(cleanedData);
window.Whisper.ExpiringMessagesListener.update();
ExpirationTimerOptions.updateExpiringMessagesCheck();
return id;
}

@ -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<MessageAttributes> {
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<MessageAttributes> {
}
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 = {

@ -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,
};

@ -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);
};

@ -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);
}
})();

Loading…
Cancel
Save