Cleans up mute state after mute expires
This commit is contained in:
parent
a581f6ea81
commit
9510fd1eec
7 changed files with 125 additions and 8 deletions
|
@ -121,6 +121,7 @@ const {
|
||||||
} = require('../../ts/services/updateListener');
|
} = require('../../ts/services/updateListener');
|
||||||
const { notify } = require('../../ts/services/notify');
|
const { notify } = require('../../ts/services/notify');
|
||||||
const { calling } = require('../../ts/services/calling');
|
const { calling } = require('../../ts/services/calling');
|
||||||
|
const { onTimeout, removeTimeout } = require('../../ts/services/timers');
|
||||||
const {
|
const {
|
||||||
enableStorageService,
|
enableStorageService,
|
||||||
eraseAllStorageServiceState,
|
eraseAllStorageServiceState,
|
||||||
|
@ -341,7 +342,9 @@ exports.setup = (options = {}) => {
|
||||||
initializeGroupCredentialFetcher,
|
initializeGroupCredentialFetcher,
|
||||||
initializeNetworkObserver,
|
initializeNetworkObserver,
|
||||||
initializeUpdateListener,
|
initializeUpdateListener,
|
||||||
|
onTimeout,
|
||||||
notify,
|
notify,
|
||||||
|
removeTimeout,
|
||||||
runStorageServiceSyncJob,
|
runStorageServiceSyncJob,
|
||||||
storageServiceUploadJob,
|
storageServiceUploadJob,
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,6 +40,9 @@ export function start(): void {
|
||||||
|
|
||||||
this.on('add remove change:unreadCount', debouncedUpdateUnreadCount);
|
this.on('add remove change:unreadCount', debouncedUpdateUnreadCount);
|
||||||
window.Whisper.events.on('updateUnreadCount', debouncedUpdateUnreadCount);
|
window.Whisper.events.on('updateUnreadCount', debouncedUpdateUnreadCount);
|
||||||
|
this.on('add', (model: ConversationModel): void => {
|
||||||
|
this.initMuteExpirationTimer(model);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
addActive(model: ConversationModel) {
|
addActive(model: ConversationModel) {
|
||||||
if (model.get('active_at')) {
|
if (model.get('active_at')) {
|
||||||
|
@ -48,6 +51,22 @@ export function start(): void {
|
||||||
this.remove(model);
|
this.remove(model);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// If the conversation is muted we set a timeout so when the mute expires
|
||||||
|
// we can reset the mute state on the model. If the mute has already expired
|
||||||
|
// then we reset the state right away.
|
||||||
|
initMuteExpirationTimer(model: ConversationModel): void {
|
||||||
|
if (model.isMuted()) {
|
||||||
|
window.Signal.Services.onTimeout(
|
||||||
|
model.get('muteExpiresAt'),
|
||||||
|
() => {
|
||||||
|
model.set({ muteExpiresAt: undefined });
|
||||||
|
},
|
||||||
|
model.getMuteTimeoutId()
|
||||||
|
);
|
||||||
|
} else if (model.get('muteExpiresAt')) {
|
||||||
|
model.set({ muteExpiresAt: undefined });
|
||||||
|
}
|
||||||
|
},
|
||||||
updateUnreadCount() {
|
updateUnreadCount() {
|
||||||
const canCountMutedConversations = window.storage.get(
|
const canCountMutedConversations = window.storage.get(
|
||||||
'badge-count-muted-conversations'
|
'badge-count-muted-conversations'
|
||||||
|
|
|
@ -206,7 +206,7 @@ export class ConversationListItem extends React.PureComponent<Props> {
|
||||||
: null
|
: null
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{muteExpiresAt && (
|
{muteExpiresAt && Date.now() < muteExpiresAt && (
|
||||||
<span className="module-conversation-list-item__muted" />
|
<span className="module-conversation-list-item__muted" />
|
||||||
)}
|
)}
|
||||||
{!isAccepted ? (
|
{!isAccepted ? (
|
||||||
|
|
|
@ -3553,8 +3553,14 @@ export class ConversationModel extends window.Backbone.Model<
|
||||||
}
|
}
|
||||||
|
|
||||||
isMuted(): boolean {
|
isMuted(): boolean {
|
||||||
return (this.get('muteExpiresAt') &&
|
return (
|
||||||
Date.now() < this.get('muteExpiresAt')) as boolean;
|
Boolean(this.get('muteExpiresAt')) &&
|
||||||
|
Date.now() < this.get('muteExpiresAt')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMuteTimeoutId(): string {
|
||||||
|
return `mute(${this.get('id')})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async notify(message: WhatIsThis, reaction?: WhatIsThis): Promise<void> {
|
async notify(message: WhatIsThis, reaction?: WhatIsThis): Promise<void> {
|
||||||
|
|
68
ts/services/timers.ts
Normal file
68
ts/services/timers.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { v4 as getGuid } from 'uuid';
|
||||||
|
|
||||||
|
type TimeoutType = {
|
||||||
|
timestamp: number;
|
||||||
|
uuid: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeoutStore: Map<string, () => void> = new Map();
|
||||||
|
const allTimeouts: Set<TimeoutType> = new Set();
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
if (!allTimeouts.size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
allTimeouts.forEach((timeout: TimeoutType) => {
|
||||||
|
const { timestamp, uuid } = timeout;
|
||||||
|
|
||||||
|
if (now >= timestamp) {
|
||||||
|
if (timeoutStore.has(uuid)) {
|
||||||
|
const callback = timeoutStore.get(uuid);
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
timeoutStore.delete(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
allTimeouts.delete(timeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
export function onTimeout(
|
||||||
|
timestamp: number,
|
||||||
|
callback: () => void,
|
||||||
|
id?: string
|
||||||
|
): string {
|
||||||
|
if (id && timeoutStore.has(id)) {
|
||||||
|
throw new ReferenceError(`onTimeout: ${id} already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuid = id || getGuid();
|
||||||
|
while (timeoutStore.has(uuid)) {
|
||||||
|
uuid = getGuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutStore.set(uuid, callback);
|
||||||
|
allTimeouts.add({
|
||||||
|
timestamp,
|
||||||
|
uuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeTimeout(uuid: string): void {
|
||||||
|
if (timeoutStore.has(uuid)) {
|
||||||
|
timeoutStore.delete(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
allTimeouts.forEach((timeout: TimeoutType) => {
|
||||||
|
if (uuid === timeout.uuid) {
|
||||||
|
allTimeouts.delete(timeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -395,7 +395,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
|
|
||||||
getMuteExpirationLabel() {
|
getMuteExpirationLabel() {
|
||||||
const muteExpiresAt = this.model.get('muteExpiresAt');
|
const muteExpiresAt = this.model.get('muteExpiresAt');
|
||||||
if (!muteExpiresAt) {
|
if (!this.model.isMuted()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2614,10 +2614,29 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setMuteNotifications(ms: any) {
|
setMuteNotifications(ms: number) {
|
||||||
this.model.set({
|
const muteExpiresAt = ms > 0 ? Date.now() + ms : undefined;
|
||||||
muteExpiresAt: ms > 0 ? Date.now() + ms : undefined,
|
|
||||||
});
|
if (muteExpiresAt) {
|
||||||
|
// we use a timeoutId here so that we can reference the mute that was
|
||||||
|
// potentially set in the ConversationController. Specifically for a
|
||||||
|
// scenario where a conversation is already muted and we boot up the app,
|
||||||
|
// a timeout will be already set. But if we change the mute to a later
|
||||||
|
// date a new timeout would need to be set and the old one cleared. With
|
||||||
|
// this ID we can reference the existing timeout.
|
||||||
|
const timeoutId = this.model.getMuteTimeoutId();
|
||||||
|
window.Signal.Services.removeTimeout(timeoutId);
|
||||||
|
window.Signal.Services.onTimeout(
|
||||||
|
muteExpiresAt,
|
||||||
|
() => {
|
||||||
|
this.setMuteNotifications(0);
|
||||||
|
},
|
||||||
|
timeoutId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.model.set({ muteExpiresAt });
|
||||||
|
this.saveModel();
|
||||||
},
|
},
|
||||||
|
|
||||||
async destroyMessages() {
|
async destroyMessages() {
|
||||||
|
|
2
ts/window.d.ts
vendored
2
ts/window.d.ts
vendored
|
@ -190,6 +190,8 @@ declare global {
|
||||||
updates: WhatIsThis,
|
updates: WhatIsThis,
|
||||||
events: WhatIsThis
|
events: WhatIsThis
|
||||||
) => void;
|
) => void;
|
||||||
|
onTimeout: (timestamp: number, cb: () => void, id?: string) => string;
|
||||||
|
removeTimeout: (uuid: string) => void;
|
||||||
runStorageServiceSyncJob: () => Promise<void>;
|
runStorageServiceSyncJob: () => Promise<void>;
|
||||||
storageServiceUploadJob: () => void;
|
storageServiceUploadJob: () => void;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue