mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
just-cache
This commit is contained in:
parent
38f64cf172
commit
affba056d2
|
@ -0,0 +1,31 @@
|
|||
import { SignalService } from '../../../../../protobuf';
|
||||
import { MessageParams } from '../../Message';
|
||||
import { SyncMessage } from '../';
|
||||
import { PubKey } from '../../../../types';
|
||||
import { DataMessage } from '../data';
|
||||
|
||||
|
||||
interface ContactSyncMessageParams extends MessageParams {
|
||||
// Send to our devices
|
||||
contacts: Array<PubKey>;
|
||||
dataMessage?: DataMessage;
|
||||
}
|
||||
|
||||
export class ContactSyncMessage extends SyncMessage {
|
||||
private readonly contacts: Array<PubKey>;
|
||||
private readonly dataMessage?: DataMessage;
|
||||
|
||||
constructor(params: ContactSyncMessageParams) {
|
||||
super(params);
|
||||
|
||||
// Stubbed for now
|
||||
this.contacts = params.contacts;
|
||||
this.dataMessage = params.dataMessage;
|
||||
|
||||
this.syncProto();
|
||||
}
|
||||
|
||||
protected syncProto(): SignalService.SyncMessage {
|
||||
return new SignalService.SyncMessage();
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import * as SyncMessage from './SyncMessage';
|
||||
import { SyncMessage } from './SyncMessage';
|
||||
import { ContactSyncMessage } from './ContactSyncMessage';
|
||||
|
||||
export { SyncMessage };
|
||||
export { ContactSyncMessage, SyncMessage };
|
||||
|
|
|
@ -3,7 +3,7 @@ import { SessionRequestMessage } from '../messages/outgoing';
|
|||
import { createOrUpdateItem, getItemById } from '../../../js/modules/data';
|
||||
import { libloki, libsignal, textsecure } from '../../window';
|
||||
import { MessageSender } from '../sending';
|
||||
import * as MessageUtils from '../utils';
|
||||
import { MessageUtils } from '../utils';
|
||||
import { PubKey } from '../types';
|
||||
|
||||
interface StringToNumberMap {
|
||||
|
|
|
@ -1,54 +1,167 @@
|
|||
import * as _ from 'lodash';
|
||||
import * as Data from '../../../js/modules/data';
|
||||
import { ConversationController } from '../../window';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import {
|
||||
MessageQueueInterface,
|
||||
MessageQueueInterfaceEvents,
|
||||
} from './MessageQueueInterface';
|
||||
import { ContentMessage, OpenGroupMessage } from '../messages/outgoing';
|
||||
import {
|
||||
ClosedGroupMessage,
|
||||
ContentMessage,
|
||||
OpenGroupMessage,
|
||||
SessionRequestMessage,
|
||||
} from '../messages/outgoing';
|
||||
import { PendingMessageCache } from './PendingMessageCache';
|
||||
import { JobQueue, TypedEventEmitter } from '../utils';
|
||||
import {
|
||||
JobQueue,
|
||||
SyncMessageUtils,
|
||||
TypedEventEmitter,
|
||||
} from '../utils';
|
||||
import { PubKey } from '../types';
|
||||
import { MessageSender } from '.';
|
||||
import { SessionProtocol } from '../protocols';
|
||||
|
||||
export class MessageQueue implements MessageQueueInterface {
|
||||
public readonly events: TypedEventEmitter<MessageQueueInterfaceEvents>;
|
||||
private readonly jobQueues: Map<string, JobQueue> = new Map();
|
||||
private readonly cache: PendingMessageCache;
|
||||
private readonly jobQueues: Map<PubKey, JobQueue> = new Map();
|
||||
private readonly pendingMessageCache: PendingMessageCache;
|
||||
|
||||
constructor() {
|
||||
this.events = new EventEmitter();
|
||||
this.cache = new PendingMessageCache();
|
||||
this.processAllPending();
|
||||
this.pendingMessageCache = new PendingMessageCache();
|
||||
void this.processAllPending();
|
||||
}
|
||||
|
||||
public sendUsingMultiDevice(user: string, message: ContentMessage) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public send(device: string, message: ContentMessage) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public sendToGroup(message: ContentMessage | OpenGroupMessage) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public sendSyncMessage(message: ContentMessage) {
|
||||
throw new Error('Method not implemented.');
|
||||
public async sendUsingMultiDevice(user: PubKey, message: ContentMessage) {
|
||||
const userLinked = await Data.getPairedDevicesFor(user.key);
|
||||
const userDevices = userLinked.map(d => new PubKey(d));
|
||||
|
||||
await this.sendMessageToDevices(userDevices, message);
|
||||
}
|
||||
|
||||
public processPending(device: string) {
|
||||
// TODO: implement
|
||||
public async send(device: PubKey, message: ContentMessage) {
|
||||
await this.sendMessageToDevices([device], message);
|
||||
}
|
||||
|
||||
private processAllPending() {
|
||||
// TODO: Get all devices which are pending here
|
||||
public async sendMessageToDevices(
|
||||
devices: Array<PubKey>,
|
||||
message: ContentMessage
|
||||
) {
|
||||
let currentDevices = [...devices];
|
||||
|
||||
// Sync to our devices if syncable
|
||||
if (SyncMessageUtils.canSync(message)) {
|
||||
|
||||
const ourDevices = await SyncMessageUtils.getOurPairedDevices();
|
||||
await this.sendSyncMessage(message, ourDevices);
|
||||
|
||||
// Remove our devices from currentDevices
|
||||
const ourDeviceContacts = ourDevices.map(device => ConversationController.get(device.key));
|
||||
currentDevices = _.xor(currentDevices, ourDeviceContacts);
|
||||
}
|
||||
|
||||
const promises = currentDevices.map(async device => {
|
||||
await this.queue(device, message);
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
private queue(device: string, message: ContentMessage) {
|
||||
// TODO: implement
|
||||
public async sendToGroup(message: OpenGroupMessage | ContentMessage): Promise<boolean> {
|
||||
if (
|
||||
!(message instanceof OpenGroupMessage) &&
|
||||
!(message instanceof ClosedGroupMessage)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Closed groups
|
||||
if (message instanceof ClosedGroupMessage) {
|
||||
// Get devices in closed group
|
||||
const conversation = ConversationController.get(message.groupId);
|
||||
const recipientsModels = conversation.contactCollection.models;
|
||||
const recipients: Array<PubKey> = recipientsModels.map(
|
||||
(recipient: any) => new PubKey(recipient.id)
|
||||
);
|
||||
|
||||
await this.sendMessageToDevices(recipients, message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Open groups
|
||||
if (message instanceof OpenGroupMessage) {
|
||||
// No queue needed for Open Groups; send directly
|
||||
await MessageSender.sendToOpenGroup(message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private queueOpenGroupMessage(message: OpenGroupMessage) {
|
||||
// TODO: Do we need to queue open group messages?
|
||||
// If so we can get open group job queue and add the send job here
|
||||
public async sendSyncMessage(
|
||||
message: ContentMessage,
|
||||
sendTo: Array<PubKey>
|
||||
) {
|
||||
// Sync with our devices
|
||||
const promises = sendTo.map(async device => {
|
||||
const syncMessage = await SyncMessageUtils.from(message, device);
|
||||
|
||||
return this.queue(device, syncMessage);
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
private getJobQueue(device: string): JobQueue {
|
||||
public async processPending(device: PubKey) {
|
||||
const messages = this.pendingMessageCache.getForDevice(device);
|
||||
|
||||
const hasSession = SessionProtocol.hasSession(device);
|
||||
const conversation = ConversationController.get(device.key);
|
||||
const isMediumGroup = conversation.isMediumGroup();
|
||||
|
||||
if (!isMediumGroup && !hasSession) {
|
||||
await SessionProtocol.sendSessionRequestIfNeeded(device);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const jobQueue = this.getJobQueue(device);
|
||||
messages.forEach(message => {
|
||||
if (!jobQueue.has(message.identifier)) {
|
||||
const promise = jobQueue.add(async () => MessageSender.send(message));
|
||||
|
||||
promise
|
||||
.then(() => {
|
||||
// Message sent; remove from cache
|
||||
void this.pendingMessageCache.remove(message);
|
||||
})
|
||||
// Message failed to send
|
||||
.catch(() => null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async processAllPending() {
|
||||
const devices = this.pendingMessageCache.getDevices();
|
||||
const promises = devices.map(async device => this.processPending(device));
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
private async queue(device: PubKey, message: ContentMessage) {
|
||||
if (message instanceof SessionRequestMessage) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.pendingMessageCache.add(device, message);
|
||||
await this.processPending(device);
|
||||
}
|
||||
|
||||
private getJobQueue(device: PubKey): JobQueue {
|
||||
let queue = this.jobQueues.get(device);
|
||||
if (!queue) {
|
||||
queue = new JobQueue();
|
||||
|
@ -57,4 +170,5 @@ export class MessageQueue implements MessageQueueInterface {
|
|||
|
||||
return queue;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
} from '../messages/outgoing';
|
||||
import { RawMessage } from '../types/RawMessage';
|
||||
import { TypedEventEmitter } from '../utils';
|
||||
import { PubKey } from '../types';
|
||||
|
||||
type GroupMessageType = OpenGroupMessage | ClosedGroupMessage;
|
||||
|
||||
|
@ -15,8 +16,8 @@ export interface MessageQueueInterfaceEvents {
|
|||
|
||||
export interface MessageQueueInterface {
|
||||
events: TypedEventEmitter<MessageQueueInterfaceEvents>;
|
||||
sendUsingMultiDevice(user: string, message: ContentMessage): void;
|
||||
send(device: string, message: ContentMessage): void;
|
||||
sendUsingMultiDevice(user: PubKey, message: ContentMessage): void;
|
||||
send(device: PubKey, message: ContentMessage): void;
|
||||
sendToGroup(message: GroupMessageType): void;
|
||||
sendSyncMessage(message: ContentMessage): void;
|
||||
sendSyncMessage(message: ContentMessage, sendTo: Array<PubKey>): Promise<Array<void>>;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { createOrUpdateItem, getItemById } from '../../../js/modules/data';
|
|||
import { PartialRawMessage, RawMessage } from '../types/RawMessage';
|
||||
import { ContentMessage } from '../messages/outgoing';
|
||||
import { PubKey } from '../types';
|
||||
import * as MessageUtils from '../utils';
|
||||
import { MessageUtils } from '../utils';
|
||||
|
||||
// This is an abstraction for storing pending messages.
|
||||
// Ideally we want to store pending messages in the database so that
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export class PubKey {
|
||||
public static readonly PUBKEY_LEN = 66;
|
||||
private static readonly regex: string = `^05[0-9a-fA-F]{${PubKey.PUBKEY_LEN -
|
||||
2}}$`;
|
||||
private static readonly regex: RegExp = new RegExp(`^05[0-9a-fA-F]{${PubKey.PUBKEY_LEN -
|
||||
2}}$`);
|
||||
public readonly key: string;
|
||||
|
||||
constructor(pubkeyString: string) {
|
||||
|
@ -19,7 +19,7 @@ export class PubKey {
|
|||
}
|
||||
|
||||
public static validate(pubkeyString: string): boolean {
|
||||
if (pubkeyString.match(PubKey.regex)) {
|
||||
if (this.regex.test(pubkeyString)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
80
ts/session/utils/SyncMessageUtils.ts
Normal file
80
ts/session/utils/SyncMessageUtils.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
import {
|
||||
ContactSyncMessage,
|
||||
ContentMessage,
|
||||
SyncMessageEnum,
|
||||
} from '../messages/outgoing';
|
||||
import { PubKey } from '../types';
|
||||
|
||||
import * as _ from 'lodash';
|
||||
import * as Data from '../../../js/modules/data';
|
||||
import { ConversationController, textsecure, Whisper } from '../../window';
|
||||
import { OpenGroup } from '../types/OpenGroup';
|
||||
|
||||
|
||||
export async function from(
|
||||
message: ContentMessage,
|
||||
sendTo: PubKey | OpenGroup,
|
||||
syncType: SyncMessageEnum.CONTACTS | SyncMessageEnum.GROUPS = SyncMessageEnum.CONTACTS
|
||||
): Promise<ContactSyncMessage> {
|
||||
const { timestamp, identifier } = message;
|
||||
|
||||
// Stubbed for now
|
||||
return new ContactSyncMessage({
|
||||
timestamp,
|
||||
identifier,
|
||||
contacts: [],
|
||||
});
|
||||
}
|
||||
|
||||
export async function canSync(message: ContentMessage): Promise<boolean> {
|
||||
// This function should be agnostic to the device; it shouldn't need
|
||||
// to know about the recipient
|
||||
// return Boolean(from(message, device));
|
||||
// Stubbed for now
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function getSyncContacts(): Promise<Array<any>> {
|
||||
const thisDevice = textsecure.storage.user.getNumber();
|
||||
const primaryDevice = await Data.getPrimaryDeviceFor(thisDevice);
|
||||
const conversations = await Data.getAllConversations({ ConversationCollection: Whisper.ConversationCollection });
|
||||
|
||||
// We are building a set of all contacts
|
||||
const primaryContacts = conversations.filter(c =>
|
||||
c.isPrivate() &&
|
||||
!c.isOurLocalDevice() &&
|
||||
c.isFriend() &&
|
||||
!c.attributes.secondaryStatus
|
||||
) || [];
|
||||
|
||||
const secondaryContactsPartial = conversations.filter(c =>
|
||||
c.isPrivate() &&
|
||||
!c.isOurLocalDevice() &&
|
||||
c.isFriend() &&
|
||||
c.attributes.secondaryStatus
|
||||
);
|
||||
|
||||
const seondaryContactsPromise = secondaryContactsPartial.map(async c =>
|
||||
ConversationController.getOrCreateAndWait(
|
||||
c.getPrimaryDevicePubKey(),
|
||||
'private'
|
||||
)
|
||||
);
|
||||
|
||||
const secondaryContacts = (await Promise.all(seondaryContactsPromise))
|
||||
// Filter out our primary key if it was added here
|
||||
.filter(c => c.id !== primaryDevice);
|
||||
|
||||
// Return unique contacts
|
||||
return _.uniqBy([
|
||||
...primaryContacts,
|
||||
...secondaryContacts,
|
||||
], device => !!device);
|
||||
}
|
||||
|
||||
export async function getOurPairedDevices(): Promise<Array<PubKey>> {
|
||||
const ourPubKey = textsecure.storage.user.getNumber();
|
||||
const ourDevices = await Data.getPairedDevicesFor(ourPubKey);
|
||||
|
||||
return ourDevices.map(device => new PubKey(device));
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
import * as MessageUtils from './Messages';
|
||||
import * as SyncMessageUtils from './SyncMessageUtils';
|
||||
|
||||
export * from './TypedEmitter';
|
||||
export * from './JobQueue';
|
||||
export * from './Messages';
|
||||
|
||||
export { MessageUtils, SyncMessageUtils };
|
||||
|
|
Loading…
Reference in a new issue