Merge pull request #1161 from Mikunj/message-sender

Message Sender
pull/1169/head
Mikunj Varsani 5 years ago committed by GitHub
commit 3b7a2b31b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,25 @@
import {
Quote,
AttachmentPointer,
Preview,
} from '../../ts/session/messages/outgoing';
declare class LokiAppDotNetServerAPI {
constructor(ourKey: string, url: string);
findOrCreateChannel(
api: LokiPublicChatFactoryAPI,
channelId: number,
conversationId: string
): Promise<LokiPublicChannelAPI>;
}
export interface LokiPublicChannelAPI {
sendMessage(data: {
quote?: Quote;
attachments: Array<AttachmentPointer>;
preview: Array<Preview>;
body?: string;
}): Promise<boolean>;
}
export default LokiAppDotNetServerAPI;

@ -2344,4 +2344,6 @@ class LokiPublicChannelAPI {
LokiAppDotNetServerAPI.serverRequest = serverRequest;
LokiAppDotNetServerAPI.sendViaOnion = sendViaOnion;
// These files are expected to be in commonjs so we can't use es6 syntax :(
// If we move these to TS then we should be able to use es6
module.exports = LokiAppDotNetServerAPI;

@ -0,0 +1,11 @@
declare class LokiMessageAPI {
constructor(ourKey: string);
sendMessage(
pubKey: string,
data: Uint8Array,
messageTimeStamp: number,
ttl: number
): Promise<void>;
}
export default LokiMessageAPI;

@ -76,6 +76,16 @@ class LokiMessageAPI {
this.groupIdsToPoll = {};
}
/**
* Refactor note: We should really clean this up ... it's very messy
*
* We need to split it into 2 sends:
* - Snodes
* - Open Groups
*
* Mikunj:
* Temporarily i've made it so `MessageSender` handles open group sends and calls this function for regular sends.
*/
async sendMessage(pubKey, data, messageTimeStamp, ttl, options = {}) {
const {
isPublic = false,
@ -601,4 +611,6 @@ class LokiMessageAPI {
}
}
// These files are expected to be in commonjs so we can't use es6 syntax :(
// If we move these to TS then we should be able to use es6
module.exports = LokiMessageAPI;

@ -0,0 +1,13 @@
import { LokiPublicChannelAPI } from './loki_app_dot_net_api';
declare class LokiPublicChatFactoryAPI {
constructor(ourKey: string);
findOrCreateServer(url: string): Promise<void>;
findOrCreateChannel(
url: string,
channelId: number,
conversationId: string
): Promise<LokiPublicChannelAPI>;
}
export default LokiPublicChatFactoryAPI;

@ -232,4 +232,6 @@ class LokiPublicChatFactoryAPI extends EventEmitter {
}
}
// These files are expected to be in commonjs so we can't use es6 syntax :(
// If we move these to TS then we should be able to use es6
module.exports = LokiPublicChatFactoryAPI;

@ -1,4 +1,4 @@
/* global libsignal, libloki, textsecure, StringView */
/* global libsignal, libloki, textsecure, StringView, dcodeIO */
'use strict';
@ -31,7 +31,11 @@ describe('Crypto', () => {
it('should encrypt and then decrypt a message with the same result', async () => {
const arr = new Uint8Array([1, 2, 3, 4, 5]);
const { body } = await fallbackCipher.encrypt(arr.buffer);
const result = await fallbackCipher.decrypt(body);
const bufferBody = dcodeIO.ByteBuffer.wrap(
body,
'binary'
).toArrayBuffer();
const result = await fallbackCipher.decrypt(bufferBody);
assert.deepEqual(result, arr.buffer);
});
});

@ -25,8 +25,6 @@ function getPaddedMessageLength(originalLength: number): number {
return messagePartCount * 160;
}
export type Base64String = string;
/**
* Encrypt `plainTextBuffer` with given `encryptionType` for `device`.
*
@ -41,7 +39,7 @@ export async function encrypt(
encryptionType: EncryptionType
): Promise<{
envelopeType: SignalService.Envelope.Type;
cipherText: Base64String;
cipherText: Uint8Array;
}> {
const plainText = padPlainTextBuffer(plainTextBuffer);
const address = new libsignal.SignalProtocolAddress(device, 1);
@ -71,7 +69,7 @@ async function encryptUsingSealedSender(
innerCipherText: CipherTextObject
): Promise<{
envelopeType: SignalService.Envelope.Type;
cipherText: Base64String;
cipherText: Uint8Array;
}> {
const ourNumber = await UserUtil.getCurrentDevicePubKey();
if (!ourNumber) {
@ -94,6 +92,6 @@ async function encryptUsingSealedSender(
return {
envelopeType: SignalService.Envelope.Type.UNIDENTIFIED_SENDER,
cipherText: Buffer.from(cipherTextBuffer).toString('base64'),
cipherText: new Uint8Array(cipherTextBuffer),
};
}

@ -1,32 +1,41 @@
import { Message, MessageParams } from './Message';
import { AttachmentType } from '../../../types/Attachment';
import { QuotedAttachmentType } from '../../../components/conversation/Quote';
import { AttachmentPointer, Preview, Quote } from './content';
interface OpenGroupMessageParams extends MessageParams {
interface OpenGroup {
server: string;
attachments?: Array<AttachmentType>;
channel: number;
conversationId: string;
}
interface OpenGroupMessageParams extends MessageParams {
group: OpenGroup;
attachments?: Array<AttachmentPointer>;
preview?: Array<Preview>;
body?: string;
quote?: QuotedAttachmentType;
quote?: Quote;
}
export class OpenGroupMessage extends Message {
public readonly server: string;
public readonly group: OpenGroup;
public readonly body?: string;
public readonly attachments?: Array<AttachmentType>;
public readonly quote?: QuotedAttachmentType;
public readonly attachments: Array<AttachmentPointer>;
public readonly quote?: Quote;
public readonly preview: Array<Preview>;
constructor({
timestamp,
server,
group,
attachments,
body,
quote,
identifier,
preview,
}: OpenGroupMessageParams) {
super({ timestamp, identifier });
this.server = server;
this.group = group;
this.body = body;
this.attachments = attachments;
this.attachments = attachments ?? [];
this.quote = quote;
this.preview = preview ?? [];
}
}

@ -14,7 +14,7 @@ export class ClosedGroupChatMessage extends ClosedGroupMessage {
constructor(params: ClosedGroupChatMessageParams) {
super({
timestamp: params.chatMessage.timestamp,
identifier: params.identifier,
identifier: params.identifier ?? params.chatMessage.identifier,
groupId: params.groupId,
});
this.chatMessage = params.chatMessage;

@ -2,13 +2,110 @@
import { RawMessage } from '../types/RawMessage';
import { OpenGroupMessage } from '../messages/outgoing';
import { SignalService } from '../../protobuf';
import { UserUtil } from '../../util';
import { MessageEncrypter } from '../crypto';
import { lokiMessageAPI, lokiPublicChatAPI, textsecure } from '../../window';
export async function send(message: RawMessage): Promise<void> {
return Promise.resolve();
// ================ Regular ================
export function canSendToSnode(): boolean {
// Seems like lokiMessageAPI is not always guaranteed to be initialized
return Boolean(lokiMessageAPI);
}
export async function send({
device,
plainTextBuffer,
encryption,
timestamp,
ttl,
}: RawMessage): Promise<void> {
if (!canSendToSnode()) {
throw new Error('lokiMessageAPI is not initialized.');
}
const { envelopeType, cipherText } = await MessageEncrypter.encrypt(
device,
plainTextBuffer,
encryption
);
const envelope = await buildEnvelope(envelopeType, timestamp, cipherText);
const data = wrapEnvelope(envelope);
// TODO: Somehow differentiate between Retryable and Regular erros
return lokiMessageAPI.sendMessage(device, data, timestamp, ttl);
}
async function buildEnvelope(
type: SignalService.Envelope.Type,
timestamp: number,
content: Uint8Array
): Promise<SignalService.Envelope> {
let source: string | undefined;
if (type !== SignalService.Envelope.Type.UNIDENTIFIED_SENDER) {
source = await UserUtil.getCurrentDevicePubKey();
}
return SignalService.Envelope.create({
type,
source,
sourceDevice: 1,
timestamp,
content,
});
}
/**
* This is an outdated practice and we should probably just send the envelope data directly.
* Something to think about in the future.
*/
function wrapEnvelope(envelope: SignalService.Envelope): Uint8Array {
const request = SignalService.WebSocketRequestMessage.create({
id: 0,
body: SignalService.Envelope.encode(envelope).finish(),
});
const websocket = SignalService.WebSocketMessage.create({
type: SignalService.WebSocketMessage.Type.REQUEST,
request,
});
return SignalService.WebSocketMessage.encode(websocket).finish();
}
// ================ Open Group ================
export async function sendToOpenGroup(
message: OpenGroupMessage
): Promise<void> {
return Promise.resolve();
): Promise<boolean> {
const { group, quote, attachments, preview, body } = message;
const channelAPI = await lokiPublicChatAPI.findOrCreateChannel(
group.server,
group.channel,
group.conversationId
);
// Don't think returning true/false on `sendMessage` is a good way
// We should either: return nothing (success) or throw an error (failure)
return channelAPI.sendMessage({
quote,
attachments: attachments || [],
preview,
body,
});
// TODO: The below should be handled in whichever class calls this
/*
const res = await sendToOpenGroup(message);
if (!res) {
throw new textsecure.PublicChatError('Failed to send public chat message');
}
const messageEventData = {
pubKey,
timestamp: messageTimeStamp,
};
messageEventData.serverId = res;
window.Whisper.events.trigger('publicMessageSent', messageEventData);
*/
}

@ -59,4 +59,31 @@ describe('ClosedGroupChatMessage', () => {
'identifier cannot be undefined'
);
});
it('should use the identifier passed into it over the one set in chatMessage', () => {
const chatMessage = new ChatMessage({
timestamp: Date.now(),
body: 'body',
identifier: 'chatMessage',
});
const message = new ClosedGroupChatMessage({
groupId: '12',
chatMessage,
identifier: 'closedGroupMessage',
});
expect(message.identifier).to.be.equal('closedGroupMessage');
});
it('should use the identifier of the chatMessage if one is not specified on the closed group message', () => {
const chatMessage = new ChatMessage({
timestamp: Date.now(),
body: 'body',
identifier: 'chatMessage',
});
const message = new ClosedGroupChatMessage({
groupId: '12',
chatMessage,
});
expect(message.identifier).to.be.equal('chatMessage');
});
});

@ -1,94 +1,88 @@
import { expect } from 'chai';
import { OpenGroupMessage } from '../../../session/messages/outgoing';
import { AttachmentType } from '../../../types/Attachment';
import {
AttachmentPointer,
OpenGroupMessage,
} from '../../../session/messages/outgoing';
import * as MIME from '../../../../ts/types/MIME';
import { QuotedAttachmentType } from '../../../components/conversation/Quote';
describe('OpenGroupMessage', () => {
it('can create empty message with just a timestamp and server', () => {
const message = new OpenGroupMessage({
timestamp: Date.now(),
server: 'server',
});
expect(message)
.to.have.property('timestamp')
.to.be.approximately(Date.now(), 10);
expect(message).to.have.deep.property('server', 'server');
});
const group = {
server: 'server',
channel: 1,
conversationId: '0',
};
it('can create message with a body', () => {
it('can create empty message with just a timestamp and group', () => {
const message = new OpenGroupMessage({
timestamp: Date.now(),
server: 'server',
body: 'body',
group,
});
expect(message).to.have.deep.property('body', 'body');
expect(message?.timestamp).to.be.approximately(Date.now(), 10);
expect(message?.group).to.deep.equal(group);
expect(message?.body).to.be.equal(undefined, 'body should be undefined');
expect(message?.quote).to.be.equal(undefined, 'quote should be undefined');
expect(message?.attachments).to.have.lengthOf(0);
expect(message?.preview).to.have.lengthOf(0);
});
it('can create message with a expire timer', () => {
it('can create message with a body', () => {
const message = new OpenGroupMessage({
timestamp: Date.now(),
server: 'server',
group,
body: 'body',
});
expect(message).to.have.deep.property('body', 'body');
});
it('can create message with a quote', () => {
let quote: QuotedAttachmentType;
quote = {
const attachment = {
contentType: MIME.IMAGE_JPEG,
fileName: 'fileName',
isVoiceMessage: false,
};
const quote = {
id: 0,
author: 'me',
text: 'hi',
attachments: [attachment],
};
const message = new OpenGroupMessage({
timestamp: Date.now(),
server: 'server',
group,
quote,
});
expect(message?.quote).to.have.property('contentType', MIME.IMAGE_JPEG);
expect(message?.quote).to.have.deep.property('fileName', 'fileName');
expect(message?.quote).to.have.deep.property('isVoiceMessage', false);
expect(message?.quote).to.deep.equal(quote);
});
it('can create message with an attachment', () => {
let attachment: AttachmentType;
attachment = {
url: 'url',
const attachment: AttachmentPointer = {
id: 0,
contentType: 'type',
key: new Uint8Array(1),
size: 10,
thumbnail: new Uint8Array(2),
digest: new Uint8Array(3),
filename: 'filename',
flags: 0,
width: 10,
height: 20,
caption: 'caption',
fileName: 'fileName',
contentType: MIME.AUDIO_AAC,
url: 'url',
};
const attachments = new Array<AttachmentType>();
attachments.push(attachment);
const message = new OpenGroupMessage({
timestamp: Date.now(),
server: 'server',
attachments: attachments,
group,
attachments: [attachment],
});
expect(message?.attachments).to.have.lengthOf(1);
expect(message)
.to.have.nested.property('attachments[0].caption')
.to.have.be.deep.equal('caption');
expect(message)
.to.have.nested.property('attachments[0].fileName')
.to.have.be.deep.equal('fileName');
expect(message)
.to.have.nested.property('attachments[0].contentType')
.to.be.deep.equal(MIME.AUDIO_AAC);
expect(message)
.to.have.nested.property('attachments[0].url')
.to.be.deep.equal('url');
expect(message?.attachments[0]).to.deep.equal(attachment);
});
it('has an identifier', () => {
const message = new OpenGroupMessage({
timestamp: Date.now(),
server: 'server',
group,
});
expect(message.identifier).to.not.equal(null, 'identifier cannot be null');
expect(message.identifier).to.not.equal(

@ -1,6 +1,8 @@
import { LibsignalProtocol } from './types/libsignal-protocol';
import { SignalInterface } from './types/signal';
import { LocalizerType } from '../types/Util';
import LokiMessageAPI from '../../js/modules/loki_message_api';
import LokiPublicChatFactoryAPI from '../../js/modules/loki_public_chat_api';
interface WindowInterface extends Window {
seedNodeList: any;
@ -8,7 +10,6 @@ interface WindowInterface extends Window {
WebAPI: any;
LokiSnodeAPI: any;
SenderKeyAPI: any;
LokiMessageAPI: any;
StubMessageAPI: any;
StubAppDotNetApi: any;
LokiPublicChatAPI: any;
@ -72,6 +73,9 @@ interface WindowInterface extends Window {
lokiFeatureFlags: any;
resetDatabase: any;
lokiMessageAPI: LokiMessageAPI;
lokiPublicChatAPI: LokiPublicChatFactoryAPI;
}
// In the case for tests
@ -133,3 +137,6 @@ export const attemptConnection = window.attemptConnection;
export const libloki = window.libloki;
export const libsignal = window.libsignal;
export const textsecure = window.textsecure;
export const lokiMessageAPI = window.lokiMessageAPI;
export const lokiPublicChatAPI = window.lokiPublicChatAPI;

@ -26,8 +26,8 @@
"strict": true, // Enable all strict type-checking options.
"skipLibCheck": true,
// Additional Checks
"noUnusedLocals": true, // Report errors on unused locals.
"noUnusedParameters": true, // Report errors on unused parameters.
"noUnusedLocals": false, // Report errors on unused locals. Loki - Enable this after refactor
"noUnusedParameters": false, // Report errors on unused parameters. Loki - Enable this after refactor
"noImplicitReturns": true, // Report error when not all code paths in function return a value.
"noFallthroughCasesInSwitch": true, // Report errors for fallthrough cases in switch statement.

Loading…
Cancel
Save