Revert pulls

pull/1157/head
Vincent 5 years ago
parent d31c250e2d
commit dec7aa10c8

@ -415,4 +415,4 @@ export function getMessagesWithFileAttachments(
// Sender Keys
export function getSenderKeys(groupId: any, senderIdentity: any): Promise<any>;
export function createOrUpdateSenderKeys(data: any): Promise<void>;
export function createOrUpdateSenderKeys(data: any): Promise<void>;

@ -3,6 +3,7 @@
/* global window: false */
const path = require('path');
const electron = require('electron');
const Data = require('./js/modules/data')
const { webFrame } = electron;
const semver = require('semver');
@ -70,18 +71,31 @@ window.isBeforeVersion = (toCheck, baseVersion) => {
}
};
window.CONSTANTS = {
MAX_LOGIN_TRIES: 3,
MAX_PASSWORD_LENGTH: 64,
MAX_USERNAME_LENGTH: 20,
MAX_GROUP_NAME_LENGTH: 64,
DEFAULT_PUBLIC_CHAT_URL: appConfig.get('defaultPublicChatServer'),
MAX_CONNECTION_DURATION: 5000,
MAX_MESSAGE_BODY_LENGTH: 64 * 1024,
// eslint-disable-next-line func-names
window.CONSTANTS = new function() {
this.MAX_LOGIN_TRIES = 3;
this.MAX_PASSWORD_LENGTH = 64;
this.MAX_USERNAME_LENGTH = 20;
this.MAX_GROUP_NAME_LENGTH = 64;
this.DEFAULT_PUBLIC_CHAT_URL = appConfig.get('defaultPublicChatServer');
this.MAX_CONNECTION_DURATION = 5000;
this.MAX_MESSAGE_BODY_LENGTH = 64 * 1024;
// Limited due to the proof-of-work requirement
SMALL_GROUP_SIZE_LIMIT: 10,
NOTIFICATION_ENABLE_TIMEOUT_SECONDS: 10, // number of seconds to turn on notifications after reconnect/start of app
};
this.SMALL_GROUP_SIZE_LIMIT = 10;
// Number of seconds to turn on notifications after reconnect/start of app
this.NOTIFICATION_ENABLE_TIMEOUT_SECONDS = 10;
this.SESSION_ID_LENGTH = 66;
// Loki Name System (LNS)
this.LNS_DEFAULT_LOOKUP_TIMEOUT = 6000;
// Minimum nodes version for LNS lookup
this.LNS_CAPABLE_NODES_VERSION = '2.0.3';
this.LNS_MAX_LENGTH = 64;
// Conforms to naming rules here
// https://loki.network/2020/03/25/loki-name-system-the-facts/
this.LNS_REGEX = `^[a-zA-Z0-9_]([a-zA-Z0-9_-]{0,${this.LNS_MAX_LENGTH -
2}}[a-zA-Z0-9_]){0,1}$`;
}();
window.versionInfo = {
environment: window.getEnvironment(),
@ -95,6 +109,9 @@ window.wrapDeferred = deferredToPromise;
const ipc = electron.ipcRenderer;
const localeMessages = ipc.sendSync('locale-data');
// Initialise Data
Data.init();
window.blake2b = input =>
new Promise((resolve, reject) => {
ipc.once('blake2b-digest-response', (event, error, res) => {
@ -327,6 +344,7 @@ window.LokiMessageAPI = require('./js/modules/loki_message_api');
if (process.env.USE_STUBBED_NETWORK) {
window.StubMessageAPI = require('./integration_test/stubs/stub_message_api');
window.StubAppDotNetApi = require('./integration_test/stubs/stub_app_dot_net_api');
window.StubLokiSnodeAPI = require('./integration_test/stubs/stub_loki_snode_api');
}
window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api');
@ -414,6 +432,8 @@ window.lokiFeatureFlags = {
privateGroupChats: true,
useSnodeProxy: !process.env.USE_STUBBED_NETWORK,
useOnionRequests: true,
useFileOnionRequests: false,
enableSenderKeys: false,
onionRequestHops: 1,
};
@ -440,7 +460,7 @@ if (
};
/* eslint-enable global-require, import/no-extraneous-dependencies */
window.lokiFeatureFlags = {};
window.lokiSnodeAPI = {}; // no need stub out each function here
window.lokiSnodeAPI = new window.StubLokiSnodeAPI(); // no need stub out each function here
}
if (config.environment.includes('test-integration')) {
window.lokiFeatureFlags = {
@ -448,4 +468,4 @@ if (config.environment.includes('test-integration')) {
privateGroupChats: true,
useSnodeProxy: !process.env.USE_STUBBED_NETWORK,
};
}
}

@ -27,6 +27,12 @@ import {
import { SessionSpinner } from './SessionSpinner';
import { joinChannelStateManager } from './LeftPaneChannelSection';
// HIJACKING BUTTON FOR TESTING
import { PendingMessageCache } from '../../session/sending/PendingMessageCache';
import { MessageQueue } from '../../session/sending';
import { ExampleMessage } from '../../session/sending/MessageQueue';
export interface Props {
searchTerm: string;
isSecondaryDevice: boolean;
@ -45,6 +51,10 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
private readonly updateSearchBound: (searchedString: string) => void;
private readonly debouncedSearch: (searchTerm: string) => void;
// HIJACKED FOR TESTING
private readonly messageQueue: any;
private readonly pendingMessageCache: any;
public constructor(props: Props) {
super(props);
@ -82,6 +92,11 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
this.handleOnPasteSessionID = this.handleOnPasteSessionID.bind(this);
this.handleMessageButtonClick = this.handleMessageButtonClick.bind(this);
this.debouncedSearch = debounce(this.search.bind(this), 20);
// HIJACKING FOR TESTING
this.messageQueue = new MessageQueue();
this.pendingMessageCache = new PendingMessageCache();
}
public componentWillUnmount() {
@ -97,7 +112,7 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
if (conversationList !== undefined) {
conversationList = conversationList.filter(
conversation =>
!conversation.isSecondary && !conversation.isPendingFriendRequest
!conversation.isPendingFriendRequest && !conversation.isSecondary
);
}
@ -361,12 +376,29 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
);
}
private handleToggleOverlay() {
this.setState((state: any) => {
return { showComposeView: !state.showComposeView };
});
// empty our generalized searchedString (one for the whole app)
this.updateSearch('');
private async handleToggleOverlay() {
// HIJACKING BUTTON FOR TESTING
console.log('[vince] pendingMessageCache:', this.pendingMessageCache);
const pubkey = window.textsecure.storage.user.getNumber();
const exampleMessage = new ExampleMessage();
console.log('[vince] exampleMessage:', exampleMessage);
const devices = this.pendingMessageCache.getPendingDevices();
console.log('[vince] devices:', devices);
if ($('.session-search-input input').val()) {
this.pendingMessageCache.removePendingMessageByIdentifier(exampleMessage.identifier);
} else {
this.pendingMessageCache.addPendingMessage(pubkey, exampleMessage);
}
// this.setState((state: any) => {
// return { showComposeView: !state.showComposeView };
// });
// // empty our generalized searchedString (one for the whole app)
// this.updateSearch('');
}
private handleOnPasteSessionID(value: string) {
@ -408,4 +440,4 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
const serverURL = window.CONSTANTS.DEFAULT_PUBLIC_CHAT_URL;
joinChannelStateManager(this, serverURL, this.handleCloseOnboarding);
}
}
}

@ -1,12 +1,45 @@
import * as Data from '../../../js/modules/data';
import { EventEmitter } from 'events';
import {
MessageQueueInterface,
MessageQueueInterfaceEvents,
} from './MessageQueueInterface';
import { OpenGroupMessage, OutgoingContentMessage } from '../messages/outgoing';
import {
ContentMessage,
OpenGroupMessage,
SessionResetMessage,
} from '../messages/outgoing';
import { PendingMessageCache } from './PendingMessageCache';
import { JobQueue, TypedEventEmitter } from '../utils';
// Used for ExampleMessage
import { v4 as uuid } from 'uuid';
import { SignalService } from '../../protobuf';
export class ExampleMessage extends ContentMessage {
constructor() {
super({
timestamp: Math.floor(Math.random() * 10000000000000),
identifier: uuid(),
});
}
public ttl(): number {
// throw new Error("Method not implemented.");
return 5;
}
protected contentProto(): SignalService.Content {
// throw new Error("Method not implemented.");
// TODO - get actual content
const content = SignalService.Content.create();
return content;
}
}
export class MessageQueue implements MessageQueueInterface {
public readonly events: TypedEventEmitter<MessageQueueInterfaceEvents>;
private readonly jobQueues: Map<string, JobQueue> = new Map();
@ -18,29 +51,110 @@ export class MessageQueue implements MessageQueueInterface {
this.processAllPending();
}
public sendUsingMultiDevice(user: string, message: OutgoingContentMessage) {
throw new Error('Method not implemented.');
public async sendUsingMultiDevice(user: string, message: ContentMessage) {
// throw new Error('Method not implemented.');
// Update from TS Globals
const pairedDevices = await Data.getPairedDevicesFor(user);
const userDevices = [...pairedDevices, user];
console.log('[vince] userDevices:', userDevices);
}
public send(device: string, message: OutgoingContentMessage) {
throw new Error('Method not implemented.');
public send(device: string, message: ContentMessage) {
// throw new Error('Method not implemented.');
// Validation; early exists?
// TESTING
console.log(`[vince] send: Queueing message`, message);
this.queue(device, message);
}
public sendToGroup(message: OutgoingContentMessage | OpenGroupMessage) {
public sendToGroup(message: ContentMessage | OpenGroupMessage) {
throw new Error('Method not implemented.');
// If you see an open group message just call
// MessageSender.sendToOpenGroup directly.
}
public sendSyncMessage(message: OutgoingContentMessage) {
throw new Error('Method not implemented.');
public sendSyncMessage(message: ContentMessage) {
// PSEDUOCODE
// if message is undefined
// returnt
// for each of our device excluding current device:
// queue(device, syncMessage)
// throw new Error('Method not implemented.');
}
public processPending(device: string) {
public async processPending(device: string) {
// TODO: implement
// PSEDUDOCODE
// messages = PendingMessageCache.getPendingMessages(device)
// isMediumGroup = device is medium group
// hasSession = SessionManager.hasSession(device)
// if !isMediumGroup && !hasSession
// SessionManager.sendSessionRequestIfNeeded()
// return // Don't process any more messages
// jobQueue = getJobQueue(device)
// for each message:
// if !jobQueue.has(message.uuid)
// promise = jobQueue.queue(message.uuid, MessageSender.send(message))
// promise.then().catch() // Add or remove from pending message cache on success and failure
// Promise shouldn't be returned; we're firing an event when processed.
}
private processAllPending() {
// TODO: Get all devices which are pending here
}
private queue(device: string, message: OutgoingContentMessage) {
private queue(device: string, message: ContentMessage) {
// This should simply add to the queue. No processing
// TODO: implement
// PSEUDOCODE
// if message is Session Request
// SessionManager.sendSessionRequest(device, message)
// return
// PendingMessageCache.addPendingMessage(device, message)
// processPending(device)
if (message instanceof SessionResetMessage) {
return;
}
console.log(`[vince] queue: Message added to the queue`, message);
// Add the item to the queue
const queue = this.getJobQueue(device);
const job = new Promise(resolve => {
setTimeout(() => {
resolve();
}, 3000);
});
// tslint:disable-next-line: no-floating-promises
queue.add(async () => job);
// Saving offline and stuff
// Attach to event
}
private queueOpenGroupMessage(message: OpenGroupMessage) {
@ -57,4 +171,4 @@ export class MessageQueue implements MessageQueueInterface {
return queue;
}
}
}

@ -1,9 +1,9 @@
import { OpenGroupMessage, OutgoingContentMessage } from '../messages/outgoing';
import { OpenGroupMessage, ContentMessage, SyncMessage } from '../messages/outgoing';
import { RawMessage } from '../types/RawMessage';
import { TypedEventEmitter } from '../utils';
// TODO: add all group messages here, replace OutgoingContentMessage with them
type GroupMessageType = OpenGroupMessage | OutgoingContentMessage;
type GroupMessageType = OpenGroupMessage | ContentMessage;
export interface MessageQueueInterfaceEvents {
success: (message: RawMessage) => void;
@ -12,8 +12,8 @@ export interface MessageQueueInterfaceEvents {
export interface MessageQueueInterface {
events: TypedEventEmitter<MessageQueueInterfaceEvents>;
sendUsingMultiDevice(user: string, message: OutgoingContentMessage): void;
send(device: string, message: OutgoingContentMessage): void;
sendUsingMultiDevice(user: string, message: ContentMessage): void;
send(device: string, message: ContentMessage): void;
sendToGroup(message: GroupMessageType): void;
sendSyncMessage(message: OutgoingContentMessage): void;
}
sendSyncMessage(message: SyncMessage): void;
}

@ -1,36 +1,115 @@
import * as Data from '../../../js/modules/data';
import { RawMessage } from '../types/RawMessage';
import { OutgoingContentMessage } from '../messages/outgoing';
import { ChatMessage, ContentMessage } from '../messages/outgoing';
import { MessageUtils, PubKey } from '../utils';
// TODO: We should be able to import functions straight from the db here without going through the window object
// This is an abstraction for storing pending messages.
// Ideally we want to store pending messages in the database so that
// on next launch we can re-send the pending messages, but we don't want
// to constantly fetch pending messages from the database.
// Thus we have an intermediary cache which will store pending messagesin
// memory and sync its state with the database on modification (add or remove).
export class PendingMessageCache {
private readonly cachedMessages: Array<RawMessage> = [];
public cache: Array<RawMessage>;
constructor() {
// TODO: We should load pending messages from db here
// Load pending messages from the database
// You must call init() on this class in order to load from DB.
// const pendingMessageCache = new PendingMessageCache();
// await pendingMessageCache.init()
// >> do stuff
this.cache = [];
}
public async add(device: PubKey, message: ContentMessage): Promise<RawMessage> {
const rawMessage = MessageUtils.toRawMessage(device, message);
// Does it exist in cache already?
if(this.find(rawMessage)) {
return rawMessage;
}
this.cache.push(rawMessage);
await this.syncCacheWithDB();
return rawMessage;
}
public addPendingMessage(
device: string,
message: OutgoingContentMessage
): RawMessage {
// TODO: Maybe have a util for converting OutgoingContentMessage to RawMessage?
// TODO: Raw message has uuid, how are we going to set that? maybe use a different identifier?
// One could be device + timestamp would make a unique identifier
// TODO: Return previous pending message if it exists
return {} as RawMessage;
public async remove(message: RawMessage): Promise<Array<RawMessage> | undefined> {
// Should only be called after message is processed
// Return if message doesn't exist in cache
if (!this.find(message)) {
return;
}
// Remove item from cache and sync with database
const updatedCache = this.cache.filter(
m => m.identifier !== message.identifier
);
this.cache = updatedCache;
await this.syncCacheWithDB();
return updatedCache;
}
public removePendingMessage(message: RawMessage) {
// TODO: implement
public find(message: RawMessage): RawMessage | undefined {
// Find a message in the cache
return this.cache.find(
m => m.device === message.device && m.timestamp === message.timestamp
);
}
public getPendingDevices(): Array<String> {
// TODO: this should return all devices which have pending messages
return [];
public getForDevice(device: PubKey): Array<RawMessage> {
return this.cache.filter(m => m.device === device.key);
}
public getPendingMessages(device: string): Array<RawMessage> {
return [];
public async clear() {
// Clears the cache and syncs to DB
this.cache = [];
await this.syncCacheWithDB();
}
}
public getDevices(): Array<PubKey> {
// Gets all devices with pending messages
const pubkeys = [...new Set(this.cache.map(m => m.device))];
return pubkeys.map(d => PubKey.from(d));
}
public async init() {
const messages = await this.getFromStorage();
this.cache = messages;
}
public async getFromStorage(): Promise<Array<RawMessage>> {
// tslint:disable-next-line: no-backbone-get-set-outside-model
const pendingMessagesData = await Data.getItemById('pendingMessages');
const pendingMessagesJSON = pendingMessagesData
? String(pendingMessagesData.value)
: '';
// tslint:disable-next-line: no-unnecessary-local-variable
const encodedPendingMessages = pendingMessagesJSON
? JSON.parse(pendingMessagesJSON)
: [];
// Set pubkey from string to PubKey.from()
// TODO:
// Build up Uint8Array from painTextBuffer in JSON
return encodedPendingMessages;
}
public async syncCacheWithDB() {
// Only call when adding / removing from cache.
const encodedPendingMessages = JSON.stringify(this.cache) || '';
await Data.createOrUpdateItem({id: 'pendingMessages', value: encodedPendingMessages});
}
}

@ -0,0 +1,91 @@
import { RawMessage } from '../types/RawMessage';
import { ContentMessage } from '../messages/outgoing';
import { EncryptionType } from '../types/EncryptionType';
import * as crypto from 'crypto';
function toRawMessage(device: PubKey, message: ContentMessage): RawMessage {
const ttl = message.ttl();
const timestamp = message.timestamp;
const plainTextBuffer = message.plainTextBuffer();
// Get EncryptionType depending on message type.
// let encryption: EncryptionType;
// switch (message.constructor.name) {
// case MessageType.Chat:
// encryption = EncryptionType.Signal;
// break;
// case MessageType.SessionReset:
// encryption = EncryptionType
// }
// export enum EncryptionType {
// Signal,
// SessionReset,
// MediumGroup,
// }
// tslint:disable-next-line: no-unnecessary-local-variable
const rawMessage: RawMessage = {
identifier: message.identifier,
plainTextBuffer,
timestamp,
device: device.key,
ttl,
encryption: EncryptionType.Signal,
};
return rawMessage;
}
export enum PubKeyType {
Primary = 'priamry',
Secondary = 'secondary',
Group = 'group',
}
export class PubKey {
private static readonly regex: string = '^0[0-9a-fA-F]{65}$';
public readonly key: string;
public type?: PubKeyType;
constructor(pubkeyString: string, type?: PubKeyType) {
PubKey.validate(pubkeyString);
this.key = pubkeyString;
this.type = type;
}
public static from(pubkeyString: string): PubKey {
// Returns a new instance if the pubkey is valid
if (PubKey.validate(pubkeyString)) {
return new PubKey(pubkeyString);
}
throw new Error('Invalid pubkey format');
}
public static validate(pubkeyString: string): boolean {
if (pubkeyString.match(PubKey.regex)) {
return true;
}
throw new Error('Invalid pubkey format');
}
public static generate(): PubKey {
// Generates a mock pubkey for testing
const PUBKEY_LEN = 66;
const numBytes = PUBKEY_LEN / 2;
const hexBuffer = crypto.randomBytes(numBytes).toString('hex');
const pubkeyString = `0${hexBuffer}`.slice(0, PUBKEY_LEN);
return new PubKey(pubkeyString);
}
}
// Functions / Tools
export const MessageUtils = {
toRawMessage,
};

@ -1,2 +1,3 @@
export * from './TypedEmitter';
export * from './JobQueue';
export * from './MessageUtils';

@ -0,0 +1,33 @@
// import { expect } from 'chai';
// import { ChatMessage, SessionResetMessage, } from '../../../session/messages/outgoing';
// import { TextEncoder } from 'util';
// import { MessageUtils } from '../../../session/utils';
// import { PendingMessageCache } from '../../../session/sending/PendingMessageCache';
// describe('PendingMessageCache', () => {
// const pendingMessageCache = new PendingMessageCache();
// let sessionResetMessage: SessionResetMessage;
// const preKeyBundle = {
// deviceId: 123456,
// preKeyId: 654321,
// signedKeyId: 111111,
// preKey: new TextEncoder().encode('preKey'),
// signature: new TextEncoder().encode('signature'),
// signedKey: new TextEncoder().encode('signedKey'),
// identityKey: new TextEncoder().encode('identityKey'),
// };
// // queue with session reset message.
// // should return undefined
// // TOOD: Send me to MESSAGE QUEUE TEST
// it('queue session reset message', () => {
// const timestamp = Date.now();
// sessionResetMessage = new SessionResetMessage({timestamp, preKeyBundle});
// });
// });

@ -0,0 +1,212 @@
// tslint:disable-next-line: no-require-imports no-var-requires
const Data = require('../../../../js/modules/data');
import { expect, assert } from 'chai';
import sinon from 'sinon';
import uuid from 'uuid';
import { ChatMessage } from '../../../session/messages/outgoing';
import { MessageUtils, PubKey } from '../../../session/utils';
import { PendingMessageCache } from '../../../session/sending/PendingMessageCache';
import { RawMessage } from '../../../session/types/RawMessage';
import { SignalService } from '../../../protobuf';
describe('PendingMessageCache', () => {
const sandbox = sinon.createSandbox();
let pendingMessageCacheStub: PendingMessageCache;
// tslint:disable-next-line: promise-function-async
const wrapInPromise = (value: any) => new Promise(r => {
r(value);
});
const generateUniqueMessage = (): ChatMessage => {
return new ChatMessage({
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
identifier: uuid(),
timestamp: Date.now(),
attachments: undefined,
quote: undefined,
expireTimer: undefined,
lokiProfile: undefined,
preview: undefined,
});
};
beforeEach(async () => {
const mockStorageObject = wrapInPromise([] as Array<RawMessage>);
const voidPromise = wrapInPromise(undefined);
// Stub out methods which touch the database.
sandbox.stub(PendingMessageCache.prototype, 'getFromStorage').returns(mockStorageObject);
sandbox.stub(PendingMessageCache.prototype, 'syncCacheWithDB').returns(voidPromise);
// Initialize new stubbed cache
pendingMessageCacheStub = new PendingMessageCache();
await pendingMessageCacheStub.init();
});
afterEach(() => {
sandbox.restore();
});
it('can initialize cache', async () => {
const { cache } = pendingMessageCacheStub;
// We expect the cache to initialise as an empty array
expect(cache).to.be.instanceOf(Array);
expect(cache).to.have.length(0);
});
it('can add to cache', async () => {
const device = PubKey.generate();
const message = generateUniqueMessage();
const rawMessage = MessageUtils.toRawMessage(device, message);
await pendingMessageCacheStub.add(device, message);
// Verify that the message is in the cache
const finalCache = pendingMessageCacheStub.cache;
expect(finalCache).to.have.length(1);
const addedMessage = finalCache[0];
expect(addedMessage.device).to.deep.equal(rawMessage.device);
expect(addedMessage.timestamp).to.deep.equal(rawMessage.timestamp);
});
it('can remove from cache', async () => {
const device = PubKey.generate();
const message = generateUniqueMessage();
const rawMessage = MessageUtils.toRawMessage(device, message);
await pendingMessageCacheStub.add(device, message);
const initialCache = pendingMessageCacheStub.cache;
expect(initialCache).to.have.length(1);
// Remove the message
await pendingMessageCacheStub.remove(rawMessage);
const finalCache = pendingMessageCacheStub.cache;
// Verify that the message was removed
expect(finalCache).to.have.length(0);
});
it('can get devices', async () => {
const cacheItems = [
{
device: PubKey.generate(),
message: generateUniqueMessage(),
},
{
device: PubKey.generate(),
message: generateUniqueMessage(),
},
{
device: PubKey.generate(),
message: generateUniqueMessage(),
},
];
cacheItems.forEach(async item => {
await pendingMessageCacheStub.add(item.device, item.message);
});
const { cache } = pendingMessageCacheStub;
expect(cache).to.have.length(cacheItems.length);
// Get list of devices
const devicesKeys = cacheItems.map(item => item.device.key);
const pulledDevices = pendingMessageCacheStub.getDevices();
const pulledDevicesKeys = pulledDevices.map(d => d.key);
// Verify that device list from cache is equivalent to devices added
expect(pulledDevicesKeys).to.have.members(devicesKeys);
});
it('can get pending for device', async () => {
const cacheItems = [
{
device: PubKey.generate(),
message: generateUniqueMessage(),
},
{
device: PubKey.generate(),
message: generateUniqueMessage(),
},
];
cacheItems.forEach(async item => {
await pendingMessageCacheStub.add(item.device, item.message);
});
const initialCache = pendingMessageCacheStub.cache;
expect(initialCache).to.have.length(cacheItems.length);
// Get pending for each specific device
cacheItems.forEach(item => {
const pendingForDevice = pendingMessageCacheStub.getForDevice(item.device);
expect(pendingForDevice).to.have.length(1);
expect(pendingForDevice[0].device).to.equal(item.device.key);
});
});
it('can find nothing when empty', async () => {
const device = PubKey.generate();
const message = generateUniqueMessage();
const rawMessage = MessageUtils.toRawMessage(device, message);
const foundMessage = pendingMessageCacheStub.find(rawMessage);
expect(foundMessage, 'a message was found in empty cache').to.be.undefined;
});
it('can find message in cache', async () => {
const device = PubKey.generate();
const message = generateUniqueMessage();
const rawMessage = MessageUtils.toRawMessage(device, message);
await pendingMessageCacheStub.add(device, message);
const finalCache = pendingMessageCacheStub.cache;
expect(finalCache).to.have.length(1);
const foundMessage = pendingMessageCacheStub.find(rawMessage);
expect(foundMessage, 'message not found in cache').to.be.ok;
foundMessage && expect(foundMessage.device).to.equal(device.key);
});
it('can clear cache', async () => {
const cacheItems = [
{
device: PubKey.generate(),
message: generateUniqueMessage(),
},
{
device: PubKey.generate(),
message: generateUniqueMessage(),
},
{
device: PubKey.generate(),
message: generateUniqueMessage(),
},
];
cacheItems.forEach(async item => {
await pendingMessageCacheStub.add(item.device, item.message);
});
const initialCache = pendingMessageCacheStub.cache;
expect(initialCache).to.have.length(cacheItems.length);
// Clear cache
await pendingMessageCacheStub.clear();
const finalCache = pendingMessageCacheStub.cache;
expect(finalCache).to.have.length(0);
});
});

@ -0,0 +1,72 @@
import { expect, should } from 'chai';
import { SignalService } from '../../../protobuf';
import { ChatMessage } from '../../../session/messages/outgoing';
import { RawMessage } from '../../../session/types/RawMessage';
import { MessageUtils, PubKey, PubKeyType } from '../../../session/utils';
describe('MessageUtils', () => {
it('can convert to RawMessage', () => {
// TOOD: MOVE ME TO MESSAGE UTILS TEST
const pubkey = "0582fe8822c684999663cc6636148328fbd47c0836814c118af4e326bb4f0e1000";
const messageText = "This is some message content";
const isRawMessage = (object: any): object is RawMessage => {
return (
'identifier' in object &&
'plainTextBuffer' in object &&
'timestamp' in object &&
'device' in object &&
'ttl' in object &&
'encryption' in object
);
}
const message = new ChatMessage({
body: messageText,
identifier: '1234567890',
timestamp: Date.now(),
attachments: undefined,
quote: undefined,
expireTimer: undefined,
lokiProfile: undefined,
preview: undefined,
});
// Explicitly check that it's a RawMessage
const rawMessage = MessageUtils.toRawMessage(pubkey, message);
expect(isRawMessage(rawMessage)).to.be.equal(true);
// console.log('[vince] isRawMessage(rawMessage):', isRawMessage(rawMessage));
// Check plaintext
const plainText = message.plainTextBuffer();
const decoded = SignalService.Content.decode(plainText);
expect(decoded.dataMessage?.body).to.be.equal(messageText);
});
// Pubkeys
it('can create new valid pubkey', () => {
const validPubkey = '0582fe8822c684999663cc6636148328fbd47c0836814c118af4e326bb4f0e1000';
should().not.Throw(() => new PubKey(validPubkey), Error);
const pubkey = new PubKey(validPubkey);
expect(pubkey instanceof PubKey).to.be.equal(true);
});
it('invalid pubkey should throw error', () => {
const invalidPubkey = 'Lorem Ipsum';
should().Throw(() => new PubKey(invalidPubkey), Error);
});
it('can set pubkey type', () => {
const validPubkey = '0582fe8822c684999663cc6636148328fbd47c0836814c118af4e326bb4f0e1000';
const pubkeyType = PubKeyType.Primary;
should().not.Throw(() => new PubKey(validPubkey, pubkeyType), Error);
const pubkey = new PubKey(validPubkey, pubkeyType);
expect(pubkey.type).to.be.equal(PubKeyType.Primary);
});
});

@ -0,0 +1,27 @@
import { SignalProtocolAddress } from '../../../window/types/libsignal-protocol';
export class SignalProtocolAddressStub extends SignalProtocolAddress {
private readonly hexEncodedPublicKey: string;
private readonly deviceId: number;
constructor(hexEncodedPublicKey: string, deviceId: number) {
super(hexEncodedPublicKey, deviceId);
this.hexEncodedPublicKey = hexEncodedPublicKey;
this.deviceId = deviceId;
}
// tslint:disable-next-line: function-name
public static fromString(encodedAddress: string): SignalProtocolAddressStub {
const values = encodedAddress.split('.');
return new SignalProtocolAddressStub(values[0], Number(values[1]));
}
public getName(): string { return this.hexEncodedPublicKey; }
public getDeviceId(): number { return this.deviceId; }
public equals(other: SignalProtocolAddress): boolean {
return other.getName() === this.hexEncodedPublicKey;
}
public toString(): string { return this.hexEncodedPublicKey; }
}
Loading…
Cancel
Save