|
|
|
@ -8,23 +8,33 @@ import {
|
|
|
|
|
MessageQueueInterfaceEvents,
|
|
|
|
|
GroupMessageType,
|
|
|
|
|
} from './MessageQueueInterface';
|
|
|
|
|
import { ContentMessage, OpenGroupMessage, SyncMessage, SessionResetMessage, ClosedGroupMessage } from '../messages/outgoing';
|
|
|
|
|
import {
|
|
|
|
|
ContentMessage,
|
|
|
|
|
OpenGroupMessage,
|
|
|
|
|
SyncMessage,
|
|
|
|
|
SessionResetMessage,
|
|
|
|
|
ClosedGroupMessage,
|
|
|
|
|
} from '../messages/outgoing';
|
|
|
|
|
import { PendingMessageCache } from './PendingMessageCache';
|
|
|
|
|
import { JobQueue, TypedEventEmitter, toRawMessage, toSyncMessage } from '../utils';
|
|
|
|
|
import {
|
|
|
|
|
JobQueue,
|
|
|
|
|
TypedEventEmitter,
|
|
|
|
|
toRawMessage,
|
|
|
|
|
toSyncMessage,
|
|
|
|
|
} from '../utils';
|
|
|
|
|
import { PubKey } from '../types';
|
|
|
|
|
import { ConversationController } from '../../window';
|
|
|
|
|
import { MessageSender } from '.';
|
|
|
|
|
import { SessionProtocol } from '../protocols';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class MessageQueue implements MessageQueueInterface {
|
|
|
|
|
public readonly events: TypedEventEmitter<MessageQueueInterfaceEvents>;
|
|
|
|
|
private readonly jobQueues: Map<PubKey, JobQueue> = new Map();
|
|
|
|
|
private readonly cache: PendingMessageCache;
|
|
|
|
|
private readonly pendingMessageCache: PendingMessageCache;
|
|
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
this.events = new EventEmitter();
|
|
|
|
|
this.cache = new PendingMessageCache();
|
|
|
|
|
this.pendingMessageCache = new PendingMessageCache();
|
|
|
|
|
void this.processAllPending();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -39,17 +49,19 @@ export class MessageQueue implements MessageQueueInterface {
|
|
|
|
|
await this.sendMessageToDevices([device], message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async sendMessageToDevices(devices: Array<PubKey>, message: ContentMessage) {
|
|
|
|
|
public async sendMessageToDevices(
|
|
|
|
|
devices: Array<PubKey>,
|
|
|
|
|
message: ContentMessage
|
|
|
|
|
) {
|
|
|
|
|
let currentDevices = [...devices];
|
|
|
|
|
|
|
|
|
|
if (SyncMessage.canSync(message)) {
|
|
|
|
|
if (message.canSync(message)) {
|
|
|
|
|
// Sync to our devices
|
|
|
|
|
const syncMessage = toSyncMessage.from(message);
|
|
|
|
|
await this.sendSyncMessage(syncMessage);
|
|
|
|
|
const syncMessage = toSyncMessage(message);
|
|
|
|
|
const ourDevices = await this.sendSyncMessage(syncMessage);
|
|
|
|
|
|
|
|
|
|
// Remove our devices from currentDevices
|
|
|
|
|
const ourDevices = await this.getOurDevices();
|
|
|
|
|
currentDevices = currentDevices.filter(device => !_.includes(ourDevices, device));
|
|
|
|
|
currentDevices = _.xor(currentDevices, ourDevices);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentDevices.forEach(async device => {
|
|
|
|
@ -58,8 +70,10 @@ export class MessageQueue implements MessageQueueInterface {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async sendToGroup(message: OpenGroupMessage | ContentMessage) {
|
|
|
|
|
|
|
|
|
|
if (!(message instanceof OpenGroupMessage) && !(message instanceof ClosedGroupMessage)) {
|
|
|
|
|
if (
|
|
|
|
|
!(message instanceof OpenGroupMessage) &&
|
|
|
|
|
!(message instanceof ClosedGroupMessage)
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -73,31 +87,31 @@ export class MessageQueue implements MessageQueueInterface {
|
|
|
|
|
await this.sendMessageToDevices(recipients, message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Open groups
|
|
|
|
|
// Open groups
|
|
|
|
|
if (message instanceof OpenGroupMessage) {
|
|
|
|
|
// No queue needed for Open Groups; send directly
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async sendSyncMessage(message: ContentMessage) {
|
|
|
|
|
public async sendSyncMessage(
|
|
|
|
|
message: ContentMessage
|
|
|
|
|
): Promise<Array<PubKey>> {
|
|
|
|
|
// Sync with our devices
|
|
|
|
|
|
|
|
|
|
const syncMessage = toSyncMessage();
|
|
|
|
|
if (!syncMessage.canSync()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ourDevices = await this.getOurDevices();
|
|
|
|
|
|
|
|
|
|
ourDevices.forEach(async device => {
|
|
|
|
|
await this.queue(device, message);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return ourDevices;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async processPending(device: PubKey) {
|
|
|
|
|
const messages = this.cache.getForDevice(device);
|
|
|
|
|
const messages = this.pendingMessageCache.getForDevice(device);
|
|
|
|
|
|
|
|
|
|
const hasSession = SessionProtocol.hasSession(device);
|
|
|
|
|
const conversation = ConversationController.get(device.key);
|
|
|
|
@ -114,18 +128,20 @@ export class MessageQueue implements MessageQueueInterface {
|
|
|
|
|
if (!jobQueue.has(message.identifier)) {
|
|
|
|
|
const promise = jobQueue.add(async () => MessageSender.send(message));
|
|
|
|
|
|
|
|
|
|
promise.then(() => {
|
|
|
|
|
// Message sent; remove from cache
|
|
|
|
|
void this.cache.remove(message);
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
// Message failed to send
|
|
|
|
|
});
|
|
|
|
|
promise
|
|
|
|
|
.then(() => {
|
|
|
|
|
// Message sent; remove from cache
|
|
|
|
|
void this.pendingMessageCache.remove(message);
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {
|
|
|
|
|
// Message failed to send
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async processAllPending() {
|
|
|
|
|
const devices = this.cache.getDevices();
|
|
|
|
|
const devices = this.pendingMessageCache.getDevices();
|
|
|
|
|
const promises = devices.map(async device => this.processPending(device));
|
|
|
|
|
|
|
|
|
|
return Promise.all(promises);
|
|
|
|
@ -136,7 +152,7 @@ export class MessageQueue implements MessageQueueInterface {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await this.cache.add(device, message);
|
|
|
|
|
await this.pendingMessageCache.add(device, message);
|
|
|
|
|
await this.processPending(device);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|