From e0f27ba712a0cbd07622a6ce6e05cfb7265d3d74 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Fri, 22 May 2020 10:25:30 +1000
Subject: [PATCH 01/10] basic classes for message sending

---
 ts/session/crypto/MessageEncrypter.ts       | 39 ++++++++++++++
 ts/session/crypto/index.ts                  |  3 ++
 ts/session/index.ts                         |  8 +++
 ts/session/protocols/MultiDeviceProtocol.ts |  6 +++
 ts/session/protocols/SessionProtocol.ts     | 57 +++++++++++++++++++++
 ts/session/protocols/index.ts               |  4 ++
 ts/session/sending/MessageQueue.ts          | 54 +++++++++++++++++++
 ts/session/sending/MessageQueueInterface.ts | 13 +++++
 ts/session/sending/MessageSender.ts         | 14 +++++
 ts/session/sending/PendingMessageCache.ts   | 36 +++++++++++++
 ts/session/sending/index.ts                 |  5 ++
 ts/session/types/EncryptionType.ts          |  5 ++
 ts/session/types/RawMessage.ts              | 12 +++++
 ts/session/utils/JobQueue.ts                | 40 +++++++++++++++
 14 files changed, 296 insertions(+)
 create mode 100644 ts/session/crypto/MessageEncrypter.ts
 create mode 100644 ts/session/crypto/index.ts
 create mode 100644 ts/session/index.ts
 create mode 100644 ts/session/protocols/MultiDeviceProtocol.ts
 create mode 100644 ts/session/protocols/SessionProtocol.ts
 create mode 100644 ts/session/protocols/index.ts
 create mode 100644 ts/session/sending/MessageQueue.ts
 create mode 100644 ts/session/sending/MessageQueueInterface.ts
 create mode 100644 ts/session/sending/MessageSender.ts
 create mode 100644 ts/session/sending/PendingMessageCache.ts
 create mode 100644 ts/session/sending/index.ts
 create mode 100644 ts/session/types/EncryptionType.ts
 create mode 100644 ts/session/types/RawMessage.ts
 create mode 100644 ts/session/utils/JobQueue.ts

diff --git a/ts/session/crypto/MessageEncrypter.ts b/ts/session/crypto/MessageEncrypter.ts
new file mode 100644
index 000000000..8b2457961
--- /dev/null
+++ b/ts/session/crypto/MessageEncrypter.ts
@@ -0,0 +1,39 @@
+import { EncryptionType } from '../types/EncryptionType';
+import { SignalService } from '../../protobuf';
+
+function padPlainTextBuffer(messageBuffer: Uint8Array): Uint8Array {
+  const plaintext = new Uint8Array(
+    getPaddedMessageLength(messageBuffer.byteLength + 1) - 1
+  );
+  plaintext.set(new Uint8Array(messageBuffer));
+  plaintext[messageBuffer.byteLength] = 0x80;
+
+  return plaintext;
+}
+
+function getPaddedMessageLength(originalLength: number): number {
+  const messageLengthWithTerminator = originalLength + 1;
+  let messagePartCount = Math.floor(messageLengthWithTerminator / 160);
+
+  if (messageLengthWithTerminator % 160 !== 0) {
+    messagePartCount += 1;
+  }
+
+  return messagePartCount * 160;
+}
+
+export function encrypt(
+  device: string,
+  plainTextBuffer: Uint8Array,
+  encryptionType: EncryptionType
+): {
+  envelopeType: SignalService.Envelope.Type;
+  cipherText: Uint8Array;
+} {
+  const plainText = padPlainTextBuffer(plainTextBuffer);
+  // TODO: Do encryption here?
+  return {
+    envelopeType: SignalService.Envelope.Type.CIPHERTEXT,
+    cipherText: new Uint8Array(),
+  };
+}
diff --git a/ts/session/crypto/index.ts b/ts/session/crypto/index.ts
new file mode 100644
index 000000000..02d1b8904
--- /dev/null
+++ b/ts/session/crypto/index.ts
@@ -0,0 +1,3 @@
+import * as MessageEncrypter from './MessageEncrypter';
+
+export { MessageEncrypter };
diff --git a/ts/session/index.ts b/ts/session/index.ts
new file mode 100644
index 000000000..4aeab7901
--- /dev/null
+++ b/ts/session/index.ts
@@ -0,0 +1,8 @@
+import * as Messages from './messages';
+import * as Protocols from './protocols';
+
+// TODO: Do we export class instances here?
+// E.g
+// export const messageQueue = new MessageQueue()
+
+export { Messages, Protocols };
diff --git a/ts/session/protocols/MultiDeviceProtocol.ts b/ts/session/protocols/MultiDeviceProtocol.ts
new file mode 100644
index 000000000..b144c20cf
--- /dev/null
+++ b/ts/session/protocols/MultiDeviceProtocol.ts
@@ -0,0 +1,6 @@
+// TODO: Populate this with multi device specific code, e.g getting linked devices for a user etc...
+// We need to deprecate the multi device code we have in js and slowly transition to this file
+
+export function implementStuffHere() {
+  throw new Error("Don't call me :(");
+}
diff --git a/ts/session/protocols/SessionProtocol.ts b/ts/session/protocols/SessionProtocol.ts
new file mode 100644
index 000000000..3f9cf2dc8
--- /dev/null
+++ b/ts/session/protocols/SessionProtocol.ts
@@ -0,0 +1,57 @@
+// TODO: Need to flesh out these functions
+// Structure of this can be changed for example sticking this all in a class
+// The reason i haven't done it is to avoid having instances of the protocol, rather you should be able to call the functions directly
+
+import { OutgoingContentMessage } from '../messages/outgoing';
+
+export function hasSession(device: string): boolean {
+  return false; // TODO: Implement
+}
+
+export function hasSentSessionRequest(device: string): boolean {
+  // TODO: need a way to keep track of if we've sent a session request
+  // My idea was to use the timestamp of when it was sent but there might be another better approach
+  return false;
+}
+
+export async function sendSessionRequestIfNeeded(
+  device: string
+): Promise<void> {
+  if (hasSession(device) || hasSentSessionRequest(device)) {
+    return Promise.resolve();
+  }
+
+  // TODO: Call sendSessionRequest with SessionReset
+  return Promise.reject(new Error('Need to implement this function'));
+}
+
+// TODO: Replace OutgoingContentMessage with SessionReset
+export async function sendSessionRequest(
+  message: OutgoingContentMessage
+): Promise<void> {
+  // TODO: Optimistically store timestamp of when session request was sent
+  // TODO: Send out the request via MessageSender
+  // TODO: On failure, unset the timestamp
+  return Promise.resolve();
+}
+
+export async function sessionEstablished(device: string) {
+  // TODO: this is called when we receive an encrypted message from the other user
+  // Maybe it should be renamed to something else
+  // TODO: This should make `hasSentSessionRequest` return `false`
+}
+
+export async function shouldProcessSessionRequest(
+  device: string,
+  messageTimestamp: number
+): boolean {
+  // TODO: Need to do the following here
+  // messageTimestamp > session request sent timestamp && messageTimestamp > session request processed timestamp
+  return false;
+}
+
+export async function sessionRequestProcessed(device: string) {
+  // TODO: this is called when we process the session request
+  // This should store the processed timestamp
+  // Again naming is crap so maybe some other name is better
+}
diff --git a/ts/session/protocols/index.ts b/ts/session/protocols/index.ts
new file mode 100644
index 000000000..e0cfeb680
--- /dev/null
+++ b/ts/session/protocols/index.ts
@@ -0,0 +1,4 @@
+import * as SessionProtocol from './SessionProtocol';
+import * as MultiDeviceProtocol from './MultiDeviceProtocol';
+
+export { SessionProtocol, MultiDeviceProtocol };
diff --git a/ts/session/sending/MessageQueue.ts b/ts/session/sending/MessageQueue.ts
new file mode 100644
index 000000000..fb9ed0445
--- /dev/null
+++ b/ts/session/sending/MessageQueue.ts
@@ -0,0 +1,54 @@
+import { MessageQueueInterface } from './MessageQueueInterface';
+import { OutgoingContentMessage, OpenGroupMessage } from '../messages/outgoing';
+import { JobQueue } from '../utils/JobQueue';
+import { PendingMessageCache } from './PendingMessageCache';
+
+export class MessageQueue implements MessageQueueInterface {
+  private readonly jobQueues: Map<string, JobQueue> = new Map();
+  private readonly cache: PendingMessageCache;
+
+  constructor() {
+    this.cache = new PendingMessageCache();
+    this.processAllPending();
+  }
+
+  public sendUsingMultiDevice(user: string, message: OutgoingContentMessage) {
+    throw new Error('Method not implemented.');
+  }
+  public send(device: string, message: OutgoingContentMessage) {
+    throw new Error('Method not implemented.');
+  }
+  public sendToGroup(message: OutgoingContentMessage | OpenGroupMessage) {
+    throw new Error('Method not implemented.');
+  }
+  public sendSyncMessage(message: OutgoingContentMessage) {
+    throw new Error('Method not implemented.');
+  }
+
+  public processPending(device: string) {
+    // TODO: implement
+  }
+
+  private processAllPending() {
+    // TODO: Get all devices which are pending here
+  }
+
+  private queue(device: string, message: OutgoingContentMessage) {
+    // TODO: implement
+  }
+
+  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
+  }
+
+  private getJobQueue(device: string): JobQueue {
+    let queue = this.jobQueues.get(device);
+    if (!queue) {
+      queue = new JobQueue();
+      this.jobQueues.set(device, queue);
+    }
+
+    return queue;
+  }
+}
diff --git a/ts/session/sending/MessageQueueInterface.ts b/ts/session/sending/MessageQueueInterface.ts
new file mode 100644
index 000000000..06f52cc7e
--- /dev/null
+++ b/ts/session/sending/MessageQueueInterface.ts
@@ -0,0 +1,13 @@
+import { OutgoingContentMessage, OpenGroupMessage } from '../messages/outgoing';
+
+// TODO: add all group messages here, replace OutgoingContentMessage with them
+type GroupMessageType = OpenGroupMessage | OutgoingContentMessage;
+export interface MessageQueueInterface {
+  sendUsingMultiDevice(user: string, message: OutgoingContentMessage): void;
+  send(device: string, message: OutgoingContentMessage): void;
+  sendToGroup(message: GroupMessageType): void;
+  sendSyncMessage(message: OutgoingContentMessage): void;
+  // TODO: Find a good way to handle events in this
+  // E.g if we do queue.onMessageSent() we want to also be able to stop listening to the event
+  // TODO: implement events here
+}
diff --git a/ts/session/sending/MessageSender.ts b/ts/session/sending/MessageSender.ts
new file mode 100644
index 000000000..852157ca9
--- /dev/null
+++ b/ts/session/sending/MessageSender.ts
@@ -0,0 +1,14 @@
+// REMOVE COMMENT AFTER: This can just export pure functions as it doesn't need state
+
+import { RawMessage } from '../types/RawMessage';
+import { OpenGroupMessage } from '../messages/outgoing';
+
+export async function send(message: RawMessage): Promise<void> {
+  return Promise.resolve();
+}
+
+export async function sendToOpenGroup(
+  message: OpenGroupMessage
+): Promise<void> {
+  return Promise.resolve();
+}
diff --git a/ts/session/sending/PendingMessageCache.ts b/ts/session/sending/PendingMessageCache.ts
new file mode 100644
index 000000000..1d722642b
--- /dev/null
+++ b/ts/session/sending/PendingMessageCache.ts
@@ -0,0 +1,36 @@
+import { RawMessage } from '../types/RawMessage';
+import { OutgoingContentMessage } from '../messages/outgoing';
+
+// TODO: We should be able to import functions straight from the db here without going through the window object
+
+export class PendingMessageCache {
+  private cachedMessages: Array<RawMessage> = [];
+
+  constructor() {
+    // TODO: We should load pending messages from db here
+  }
+
+  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 removePendingMessage(message: RawMessage) {
+    // TODO: implement
+  }
+
+  public getPendingDevices(): Array<String> {
+    // TODO: this should return all devices which have pending messages
+    return [];
+  }
+
+  public getPendingMessages(device: string): Array<RawMessage> {
+    return [];
+  }
+}
diff --git a/ts/session/sending/index.ts b/ts/session/sending/index.ts
new file mode 100644
index 000000000..69ca9153b
--- /dev/null
+++ b/ts/session/sending/index.ts
@@ -0,0 +1,5 @@
+import * as MessageSender from './MessageSender';
+import { MessageQueue } from './MessageQueue';
+import { MessageQueueInterface } from './MessageQueueInterface';
+
+export { MessageSender, MessageQueue, MessageQueueInterface };
diff --git a/ts/session/types/EncryptionType.ts b/ts/session/types/EncryptionType.ts
new file mode 100644
index 000000000..ed27e1023
--- /dev/null
+++ b/ts/session/types/EncryptionType.ts
@@ -0,0 +1,5 @@
+export enum EncryptionType {
+  Signal,
+  SessionReset,
+  MediumGroup,
+}
diff --git a/ts/session/types/RawMessage.ts b/ts/session/types/RawMessage.ts
new file mode 100644
index 000000000..30d2e0d9b
--- /dev/null
+++ b/ts/session/types/RawMessage.ts
@@ -0,0 +1,12 @@
+import { EncryptionType } from './EncryptionType';
+
+// TODO: Should we store failure count on raw messages??
+// Might be better to have a seperate interface which takes in a raw message aswell as a failure count
+export interface RawMessage {
+  identifier: string;
+  plainTextBuffer: Uint8Array;
+  timestamp: number;
+  device: string;
+  ttl: number;
+  encryption: EncryptionType;
+}
diff --git a/ts/session/utils/JobQueue.ts b/ts/session/utils/JobQueue.ts
new file mode 100644
index 000000000..d9a58d909
--- /dev/null
+++ b/ts/session/utils/JobQueue.ts
@@ -0,0 +1,40 @@
+import { v4 as uuid } from 'uuid';
+
+// TODO: This needs to replace js/modules/job_queue.js
+export class JobQueue {
+  private pending: Promise<any> = Promise.resolve();
+  private readonly jobs: Map<string, Promise<any>> = new Map();
+
+  public has(id: string): boolean {
+    return this.jobs.has(id);
+  }
+
+  public async add(job: () => any): Promise<any> {
+    const id = uuid();
+
+    return this.addWithId(id, job);
+  }
+
+  public async addWithId(id: string, job: () => any): Promise<any> {
+    if (this.jobs.has(id)) {
+      return this.jobs.get(id);
+    }
+
+    const previous = this.pending || Promise.resolve();
+    this.pending = previous.then(job, job);
+
+    const current = this.pending;
+    current
+      .finally(() => {
+        if (this.pending === current) {
+          delete this.pending;
+        }
+        this.jobs.delete(id);
+      })
+      .ignore();
+
+    this.jobs.set(id, current);
+
+    return current;
+  }
+}

From e7826cfb34962e8baa7e4db87f9188e9b0c38f36 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Fri, 22 May 2020 10:30:57 +1000
Subject: [PATCH 02/10] linting

---
 ts/session/crypto/MessageEncrypter.ts       | 1 +
 ts/session/protocols/SessionProtocol.ts     | 6 +++---
 ts/session/sending/MessageQueue.ts          | 2 +-
 ts/session/sending/MessageQueueInterface.ts | 2 +-
 ts/session/sending/PendingMessageCache.ts   | 2 +-
 5 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/ts/session/crypto/MessageEncrypter.ts b/ts/session/crypto/MessageEncrypter.ts
index 8b2457961..22ce64ccc 100644
--- a/ts/session/crypto/MessageEncrypter.ts
+++ b/ts/session/crypto/MessageEncrypter.ts
@@ -32,6 +32,7 @@ export function encrypt(
 } {
   const plainText = padPlainTextBuffer(plainTextBuffer);
   // TODO: Do encryption here?
+
   return {
     envelopeType: SignalService.Envelope.Type.CIPHERTEXT,
     cipherText: new Uint8Array(),
diff --git a/ts/session/protocols/SessionProtocol.ts b/ts/session/protocols/SessionProtocol.ts
index 3f9cf2dc8..ebeac9f37 100644
--- a/ts/session/protocols/SessionProtocol.ts
+++ b/ts/session/protocols/SessionProtocol.ts
@@ -35,13 +35,13 @@ export async function sendSessionRequest(
   return Promise.resolve();
 }
 
-export async function sessionEstablished(device: string) {
+export function sessionEstablished(device: string) {
   // TODO: this is called when we receive an encrypted message from the other user
   // Maybe it should be renamed to something else
   // TODO: This should make `hasSentSessionRequest` return `false`
 }
 
-export async function shouldProcessSessionRequest(
+export function shouldProcessSessionRequest(
   device: string,
   messageTimestamp: number
 ): boolean {
@@ -50,7 +50,7 @@ export async function shouldProcessSessionRequest(
   return false;
 }
 
-export async function sessionRequestProcessed(device: string) {
+export function sessionRequestProcessed(device: string) {
   // TODO: this is called when we process the session request
   // This should store the processed timestamp
   // Again naming is crap so maybe some other name is better
diff --git a/ts/session/sending/MessageQueue.ts b/ts/session/sending/MessageQueue.ts
index fb9ed0445..5eeaa2426 100644
--- a/ts/session/sending/MessageQueue.ts
+++ b/ts/session/sending/MessageQueue.ts
@@ -1,5 +1,5 @@
 import { MessageQueueInterface } from './MessageQueueInterface';
-import { OutgoingContentMessage, OpenGroupMessage } from '../messages/outgoing';
+import { OpenGroupMessage, OutgoingContentMessage } from '../messages/outgoing';
 import { JobQueue } from '../utils/JobQueue';
 import { PendingMessageCache } from './PendingMessageCache';
 
diff --git a/ts/session/sending/MessageQueueInterface.ts b/ts/session/sending/MessageQueueInterface.ts
index 06f52cc7e..a231ad02c 100644
--- a/ts/session/sending/MessageQueueInterface.ts
+++ b/ts/session/sending/MessageQueueInterface.ts
@@ -1,4 +1,4 @@
-import { OutgoingContentMessage, OpenGroupMessage } from '../messages/outgoing';
+import { OpenGroupMessage, OutgoingContentMessage } from '../messages/outgoing';
 
 // TODO: add all group messages here, replace OutgoingContentMessage with them
 type GroupMessageType = OpenGroupMessage | OutgoingContentMessage;
diff --git a/ts/session/sending/PendingMessageCache.ts b/ts/session/sending/PendingMessageCache.ts
index 1d722642b..2f10e58a6 100644
--- a/ts/session/sending/PendingMessageCache.ts
+++ b/ts/session/sending/PendingMessageCache.ts
@@ -4,7 +4,7 @@ import { OutgoingContentMessage } from '../messages/outgoing';
 // TODO: We should be able to import functions straight from the db here without going through the window object
 
 export class PendingMessageCache {
-  private cachedMessages: Array<RawMessage> = [];
+  private readonly cachedMessages: Array<RawMessage> = [];
 
   constructor() {
     // TODO: We should load pending messages from db here

From 3dfc1ca213cc53f8396fd55620ab5164849ba6d8 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Fri, 22 May 2020 14:04:10 +1000
Subject: [PATCH 03/10] Improved JobQueue. Added tests.

---
 package.json                           |   3 +-
 ts/session/utils/JobQueue.ts           |  21 +++--
 ts/test/session/utils/JobQueue_test.ts | 109 +++++++++++++++++++++++++
 ts/test/tslint.json                    |   6 +-
 ts/test/utils/timeout.ts               |   4 +
 yarn.lock                              |  31 +++++++
 6 files changed, 165 insertions(+), 9 deletions(-)
 create mode 100644 ts/test/session/utils/JobQueue_test.ts
 create mode 100644 ts/test/utils/timeout.ts

diff --git a/package.json b/package.json
index 4603dedd2..4e78cb491 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
     "test-electron": "yarn grunt test",
     "test-integration": "ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js",
     "test-integration-parts": "ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'registration' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'openGroup' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'addFriends' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'linkDevice' && ELECTRON_DISABLE_SANDBOX=1 mocha --exit --timeout 10000 integration_test/integration_test.js --grep 'closedGroup'",
-    "test-node": "mocha --recursive --exit test/app test/modules ts/test libloki/test/node",
+    "test-node": "mocha --recursive --exit test/app test/modules ts/test libloki/test/node --timeout 10000",
     "eslint": "eslint --cache .",
     "eslint-fix": "eslint --fix .",
     "eslint-full": "eslint .",
@@ -120,6 +120,7 @@
   },
   "devDependencies": {
     "@types/chai": "4.1.2",
+    "@types/chai-as-promised": "^7.1.2",
     "@types/classnames": "2.2.3",
     "@types/color": "^3.0.0",
     "@types/config": "0.0.34",
diff --git a/ts/session/utils/JobQueue.ts b/ts/session/utils/JobQueue.ts
index d9a58d909..fa5082836 100644
--- a/ts/session/utils/JobQueue.ts
+++ b/ts/session/utils/JobQueue.ts
@@ -1,37 +1,44 @@
 import { v4 as uuid } from 'uuid';
 
+type Job<ResultType> = (() => PromiseLike<ResultType>) | (() => ResultType);
+
 // TODO: This needs to replace js/modules/job_queue.js
 export class JobQueue {
   private pending: Promise<any> = Promise.resolve();
-  private readonly jobs: Map<string, Promise<any>> = new Map();
+  private readonly jobs: Map<string, Promise<unknown>> = new Map();
 
   public has(id: string): boolean {
     return this.jobs.has(id);
   }
 
-  public async add(job: () => any): Promise<any> {
+  public async add<Result>(job: Job<Result>): Promise<Result> {
     const id = uuid();
 
     return this.addWithId(id, job);
   }
 
-  public async addWithId(id: string, job: () => any): Promise<any> {
+  public async addWithId<Result>(
+    id: string,
+    job: Job<Result>
+  ): Promise<Result> {
     if (this.jobs.has(id)) {
-      return this.jobs.get(id);
+      return this.jobs.get(id) as Promise<Result>;
     }
 
     const previous = this.pending || Promise.resolve();
     this.pending = previous.then(job, job);
 
     const current = this.pending;
-    current
+    void current
+      .catch(() => {
+        // This is done to avoid UnhandledPromiseError
+      })
       .finally(() => {
         if (this.pending === current) {
           delete this.pending;
         }
         this.jobs.delete(id);
-      })
-      .ignore();
+      });
 
     this.jobs.set(id, current);
 
diff --git a/ts/test/session/utils/JobQueue_test.ts b/ts/test/session/utils/JobQueue_test.ts
new file mode 100644
index 000000000..f431b455d
--- /dev/null
+++ b/ts/test/session/utils/JobQueue_test.ts
@@ -0,0 +1,109 @@
+import chai from 'chai';
+import { v4 as uuid } from 'uuid';
+import { JobQueue } from '../../../session/utils/JobQueue';
+import { delay } from '../../utils/delay';
+
+// tslint:disable-next-line: no-require-imports no-var-requires
+const chaiAsPromised = require('chai-as-promised');
+chai.use(chaiAsPromised);
+
+const { assert } = chai;
+
+describe('JobQueue', () => {
+  describe('has', () => {
+    it('should return the correct value', async () => {
+      const queue = new JobQueue();
+      const id = 'jobId';
+
+      assert.isFalse(queue.has(id));
+      const promise = queue.addWithId(id, async () => delay(100));
+      assert.isTrue(queue.has(id));
+      await promise;
+      assert.isFalse(queue.has(id));
+    });
+  });
+
+  describe('addWithId', () => {
+    it('should run the jobs concurrently', async () => {
+      const input = [[10, 300], [20, 200], [30, 100]];
+      const queue = new JobQueue();
+      const mapper = async ([value, ms]: Array<number>): Promise<number> =>
+        queue.addWithId(uuid(), async () => {
+          await delay(ms);
+
+          return value;
+        });
+
+      const start = Date.now();
+      assert.deepEqual(await Promise.all(input.map(mapper)), [10, 20, 30]);
+      const timeTaken = Date.now() - start;
+      assert.closeTo(timeTaken, 600, 50, 'Queue was delayed');
+    });
+
+    it('should return the result of the job', async () => {
+      const queue = new JobQueue();
+      const success = queue.addWithId(uuid(), async () => {
+        await delay(100);
+
+        return 'success';
+      });
+      const failure = queue.addWithId(uuid(), async () => {
+        await delay(100);
+        throw new Error('failed');
+      });
+
+      assert.equal(await success, 'success');
+      await assert.isRejected(failure, /failed/);
+    });
+
+    it('should handle sync and async tasks', async () => {
+      const queue = new JobQueue();
+      const first = queue.addWithId(uuid(), () => 'first');
+      const second = queue.addWithId(uuid(), async () => {
+        await delay(100);
+
+        return 'second';
+      });
+      const third = queue.addWithId(uuid(), () => 'third');
+
+      assert.deepEqual(await Promise.all([first, second, third]), [
+        'first',
+        'second',
+        'third',
+      ]);
+    });
+
+    it('should return the previous job if same id was passed', async () => {
+      const queue = new JobQueue();
+      const id = uuid();
+      const job = async () => {
+        await delay(100);
+
+        return 'job1';
+      };
+
+      const promise = queue.addWithId(id, job);
+      const otherPromise = queue.addWithId(id, () => 'job2');
+      assert.equal(await promise, await otherPromise);
+      await promise;
+    });
+
+    it('should remove completed jobs', async () => {
+      const queue = new JobQueue();
+      const id = uuid();
+
+      const successfullJob = queue.addWithId(id, async () => delay(100));
+      assert.isTrue(queue.has(id));
+      await successfullJob;
+      assert.isFalse(queue.has(id));
+
+      const failJob = queue.addWithId(id, async () => {
+        await delay(100);
+        throw new Error('failed');
+      });
+      assert.isTrue(queue.has(id));
+      await assert.isRejected(failJob, /failed/);
+      assert.isFalse(queue.has(id));
+    });
+  });
+});
diff --git a/ts/test/tslint.json b/ts/test/tslint.json
index 4645335d0..21571a6db 100644
--- a/ts/test/tslint.json
+++ b/ts/test/tslint.json
@@ -6,6 +6,10 @@
     "no-implicit-dependencies": false,
 
     // All tests use arrow functions, and they can be long
-    "max-func-body-length": false
+    "max-func-body-length": false,
+
+    "no-unused-expression": false,
+
+    "await-promise": [true, "PromiseLike"]
   }
 }
diff --git a/ts/test/utils/timeout.ts b/ts/test/utils/timeout.ts
new file mode 100644
index 000000000..cafd9cf55
--- /dev/null
+++ b/ts/test/utils/timeout.ts
@@ -0,0 +1,4 @@
+export async function timeout(ms: number): Promise<void> {
+  // tslint:disable-next-line no-string-based-set-timeout
+  return new Promise(resolve => setTimeout(resolve, ms));
+}
diff --git a/yarn.lock b/yarn.lock
index 09e1f4b11..c335da9f3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -156,6 +156,18 @@
   dependencies:
     defer-to-connect "^1.0.1"
 
+"@types/chai-as-promised@^7.1.2":
+  version "7.1.2"
+  resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.2.tgz#2f564420e81eaf8650169e5a3a6b93e096e5068b"
+  integrity sha512-PO2gcfR3Oxa+u0QvECLe1xKXOqYTzCmWf0FhLhjREoW3fPAVamjihL7v1MOVLJLsnAMdLcjkfrs01yvDMwVK4Q==
+  dependencies:
+    "@types/chai" "*"
+
+"@types/chai@*":
+  version "4.2.11"
+  resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.11.tgz#d3614d6c5f500142358e6ed24e1bf16657536c50"
+  integrity sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==
+
 "@types/chai@4.1.2":
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.2.tgz#f1af664769cfb50af805431c407425ed619daa21"
@@ -4836,6 +4848,13 @@ in-publish@^2.0.0:
   resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.1.tgz#948b1a535c8030561cea522f73f78f4be357e00c"
   integrity sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==
 
+indent-string@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
+  integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
+  dependencies:
+    repeating "^2.0.0"
+
 indexes-of@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
@@ -9193,6 +9212,11 @@ slice-ansi@1.0.0:
   dependencies:
     is-fullwidth-code-point "^2.0.0"
 
+slide@^1.1.5:
+  version "1.1.6"
+  resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
+  integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=
+
 snapdragon-node@^2.0.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
@@ -9650,6 +9674,13 @@ strip-eof@^1.0.0:
   resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
   integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
 
+strip-indent@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
+  integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=
+  dependencies:
+    get-stdin "^4.0.1"
+
 strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"

From 6557d7bcb3aebaf809a5414e412fdabb378f53b0 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Fri, 22 May 2020 15:49:03 +1000
Subject: [PATCH 04/10] Updated JobQueue tests

---
 ts/test/session/utils/JobQueue_test.ts | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/ts/test/session/utils/JobQueue_test.ts b/ts/test/session/utils/JobQueue_test.ts
index f431b455d..79c249735 100644
--- a/ts/test/session/utils/JobQueue_test.ts
+++ b/ts/test/session/utils/JobQueue_test.ts
@@ -1,7 +1,7 @@
 import chai from 'chai';
 import { v4 as uuid } from 'uuid';
 import { JobQueue } from '../../../session/utils/JobQueue';
-import { delay } from '../../utils/delay';
+import { timeout } from '../../utils/timeout';
 
 // tslint:disable-next-line: no-require-imports no-var-requires
 const chaiAsPromised = require('chai-as-promised');
@@ -16,7 +16,7 @@ describe('JobQueue', () => {
       const id = 'jobId';
 
       assert.isFalse(queue.has(id));
-      const promise = queue.addWithId(id, async () => delay(100));
+      const promise = queue.addWithId(id, async () => timeout(100));
       assert.isTrue(queue.has(id));
       await promise;
       assert.isFalse(queue.has(id));
@@ -29,7 +29,7 @@ describe('JobQueue', () => {
       const queue = new JobQueue();
       const mapper = async ([value, ms]: Array<number>): Promise<number> =>
         queue.addWithId(uuid(), async () => {
-          await delay(ms);
+          await timeout(ms);
 
           return value;
         });
@@ -43,12 +43,12 @@ describe('JobQueue', () => {
     it('should return the result of the job', async () => {
       const queue = new JobQueue();
       const success = queue.addWithId(uuid(), async () => {
-        await delay(100);
+        await timeout(100);
 
         return 'success';
       });
       const failure = queue.addWithId(uuid(), async () => {
-        await delay(100);
+        await timeout(100);
         throw new Error('failed');
       });
 
@@ -60,7 +60,7 @@ describe('JobQueue', () => {
       const queue = new JobQueue();
       const first = queue.addWithId(uuid(), () => 'first');
       const second = queue.addWithId(uuid(), async () => {
-        await delay(100);
+        await timeout(100);
 
         return 'second';
       });
@@ -77,7 +77,7 @@ describe('JobQueue', () => {
       const queue = new JobQueue();
       const id = uuid();
       const job = async () => {
-        await delay(100);
+        await timeout(100);
 
         return 'job1';
       };
@@ -92,13 +92,13 @@ describe('JobQueue', () => {
       const queue = new JobQueue();
       const id = uuid();
 
-      const successfullJob = queue.addWithId(id, async () => delay(100));
+      const successfullJob = queue.addWithId(id, async () => timeout(100));
       assert.isTrue(queue.has(id));
       await successfullJob;
       assert.isFalse(queue.has(id));
 
       const failJob = queue.addWithId(id, async () => {
-        await delay(100);
+        await timeout(100);
         throw new Error('failed');
       });
       assert.isTrue(queue.has(id));

From 956dea8a1f17b7afa51381e98fe5859c4a8e0ee9 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Fri, 22 May 2020 15:55:59 +1000
Subject: [PATCH 05/10] Use correcy syntax for async asserts

---
 ts/test/session/utils/JobQueue_test.ts | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/ts/test/session/utils/JobQueue_test.ts b/ts/test/session/utils/JobQueue_test.ts
index 79c249735..b3f4a5c18 100644
--- a/ts/test/session/utils/JobQueue_test.ts
+++ b/ts/test/session/utils/JobQueue_test.ts
@@ -2,6 +2,7 @@ import chai from 'chai';
 import { v4 as uuid } from 'uuid';
 import { JobQueue } from '../../../session/utils/JobQueue';
 import { timeout } from '../../utils/timeout';
+import { SignalService } from '../../../protobuf';
 
 // tslint:disable-next-line: no-require-imports no-var-requires
 const chaiAsPromised = require('chai-as-promised');
@@ -35,7 +36,7 @@ describe('JobQueue', () => {
         });
 
       const start = Date.now();
-      assert.deepEqual(await Promise.all(input.map(mapper)), [10, 20, 30]);
+      await assert.eventually.deepEqual(Promise.all(input.map(mapper)), [10, 20, 30]);
       const timeTaken = Date.now() - start;
       assert.closeTo(timeTaken, 600, 50, 'Queue was delayed');
     });
@@ -52,7 +53,7 @@ describe('JobQueue', () => {
         throw new Error('failed');
       });
 
-      assert.equal(await success, 'success');
+      await assert.eventually.equal(success, 'success');
       await assert.isRejected(failure, /failed/);
     });
 
@@ -66,7 +67,7 @@ describe('JobQueue', () => {
       });
       const third = queue.addWithId(uuid(), () => 'third');
 
-      assert.deepEqual(await Promise.all([first, second, third]), [
+      await assert.eventually.deepEqual(Promise.all([first, second, third]), [
         'first',
         'second',
         'third',
@@ -84,8 +85,8 @@ describe('JobQueue', () => {
 
       const promise = queue.addWithId(id, job);
       const otherPromise = queue.addWithId(id, () => 'job2');
-      assert.equal(await promise, await otherPromise);
-      await promise;
+      await assert.eventually.equal(promise, 'job1');
+      await assert.eventually.equal(otherPromise, 'job1');
     });
 
     it('should remove completed jobs', async () => {

From 21586f8e1491dab144d55448a01c75cbf007eb45 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Fri, 22 May 2020 16:06:31 +1000
Subject: [PATCH 06/10] Linting + Improve tslint rules in tests

---
 ts/test/session/utils/JobQueue_test.ts | 7 +++++--
 ts/test/tslint.json                    | 3 ++-
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/ts/test/session/utils/JobQueue_test.ts b/ts/test/session/utils/JobQueue_test.ts
index b3f4a5c18..864c69247 100644
--- a/ts/test/session/utils/JobQueue_test.ts
+++ b/ts/test/session/utils/JobQueue_test.ts
@@ -2,7 +2,6 @@ import chai from 'chai';
 import { v4 as uuid } from 'uuid';
 import { JobQueue } from '../../../session/utils/JobQueue';
 import { timeout } from '../../utils/timeout';
-import { SignalService } from '../../../protobuf';
 
 // tslint:disable-next-line: no-require-imports no-var-requires
 const chaiAsPromised = require('chai-as-promised');
@@ -36,7 +35,11 @@ describe('JobQueue', () => {
         });
 
       const start = Date.now();
-      await assert.eventually.deepEqual(Promise.all(input.map(mapper)), [10, 20, 30]);
+      await assert.eventually.deepEqual(Promise.all(input.map(mapper)), [
+        10,
+        20,
+        30,
+      ]);
       const timeTaken = Date.now() - start;
       assert.closeTo(timeTaken, 600, 50, 'Queue was delayed');
     });
diff --git a/ts/test/tslint.json b/ts/test/tslint.json
index 21571a6db..9d1d3e71b 100644
--- a/ts/test/tslint.json
+++ b/ts/test/tslint.json
@@ -10,6 +10,7 @@
 
     "no-unused-expression": false,
 
-    "await-promise": [true, "PromiseLike"]
+    "await-promise": [true, "PromiseLike"],
+    "no-floating-promises": [true, "PromiseLike"]
   }
 }

From 7a85d69970f7ef5302f91114f99ca23674156f7f Mon Sep 17 00:00:00 2001
From: Vincent <vincent@loki.network>
Date: Tue, 26 May 2020 11:00:50 +1000
Subject: [PATCH 07/10] Mostly strictly types Signal Data

---
 js/modules/data.d.ts | 388 ++++++++++++++++++++++++++++++++++++++++++-
 ts/window.ts         |  77 +++++++++
 2 files changed, 464 insertions(+), 1 deletion(-)
 create mode 100644 ts/window.ts

diff --git a/js/modules/data.d.ts b/js/modules/data.d.ts
index 29e42df98..e9543b99b 100644
--- a/js/modules/data.d.ts
+++ b/js/modules/data.d.ts
@@ -1,3 +1,389 @@
+import { ConversationType } from '../../ts/state/ducks/conversations';
+import { Mesasge } from '../../ts/types/Message';
+
+type IdentityKey = {
+  id: string;
+  publicKey: ArrayBuffer;
+  firstUse: boolean;
+  verified: number;
+  nonblockingApproval: boolean;
+} | null;
+
+type PreKey = {
+  id: string;
+  publicKey: string;
+  privateKey: string;
+  recipient: string;
+} | null;
+
+type PairingAuthorisation = {
+  primaryDevicePubKey: string;
+  secondaryDevicePubKey: string;
+  requestSignature: string;
+  grantSignature: string | null;
+} | null;
+
+type PairingAuthorisationInit = {
+  requestSignature: string;
+  grantSignature: string;
+};
+
+type GuardNode = {
+  ed25519PubKey: string;
+};
+
+type SwarmNode = {
+  address: string;
+  ip: string;
+  port: string;
+  pubkey_ed25519: string;
+  pubkey_x25519: string;
+};
+
+type StorageItem = {
+  id: string;
+  value: any;
+};
+
+type SessionDataInfo = {
+  id: string;
+  number: string;
+  deviceId: number;
+  record: string;
+};
+
+type ServerToken = {
+  serverUrl: string;
+  token: string;
+};
+
+// Basic
 export function searchMessages(query: string): Promise<Array<any>>;
 export function searchConversations(query: string): Promise<Array<any>>;
-export function getPrimaryDeviceFor(pubKey: string): Promise<string | null>;
+export function shutdown(): Promise<void>;
+export function close(): Promise<void>;
+export function removeDB(): Promise<void>;
+export function removeIndexedDBFiles(): Promise<void>;
+export function getPasswordHash(): Promise<string | null>;
+
+// Identity Keys
+export function createOrUpdateIdentityKey(data: any): Promise<void>;
+export function getIdentityKeyById(id: string): Promise<IdentityKey>;
+export function bulkAddIdentityKeys(array: Array<IdentityKey>): Promise<void>;
+export function removeIdentityKeyById(id: string): Promise<void>;
+export function removeAllIdentityKeys(): Promise<void>;
+
+// Pre Keys
+export function createOrUpdatePreKey(data: PreKey): Promise<void>;
+export function getPreKeyById(id: string): Promise<PreKey>;
+export function getPreKeyByRecipient(recipient: string): Promise<PreKey>;
+export function bulkAddPreKeys(data: Array<PreKey>): Promise<void>;
+export function removePreKeyById(id: string): Promise<void>;
+export function getAllPreKeys(): Promise<Array<PreKey>>;
+
+// Signed Pre Keys
+export function createOrUpdateSignedPreKey(data: PreKey): Promise<void>;
+export function getSignedPreKeyById(id: string): Promise<PreKey>;
+export function getAllSignedPreKeys(recipient: string): Promise<PreKey>;
+export function bulkAddSignedPreKeys(array: Array<PreKey>): Promise<void>;
+export function removeSignedPreKeyById(id: string): Promise<void>;
+export function removeAllSignedPreKeys(): Promise<void>;
+
+// Contact Pre Key
+export function createOrUpdateContactPreKey(data: PreKey): Promise<void>;
+export function getContactPreKeyById(id: string): Promise<PreKey>;
+export function getContactPreKeyByIdentityKey(key: string): Promise<PreKey>;
+export function getContactPreKeys(
+  keyId: string,
+  identityKeyString: string
+): Promise<Array<PreKey>>;
+export function getAllContactPreKeys(): Promise<Array<PreKey>>;
+export function bulkAddContactPreKeys(array: Array<PreKey>): Promise<void>;
+export function removeContactPreKeyByIdentityKey(id: string): Promise<void>;
+export function removeAllContactPreKeys(): Promise<void>;
+
+// Contact Signed Pre Key
+export function createOrUpdateContactSignedPreKey(data: PreKey): Promise<void>;
+export function getContactSignedPreKeyByIdid(string): Promise<PreKey>;
+export function getContactSignedPreKeyByIdentityKey(
+  key: string
+): Promise<PreKey>;
+export function getContactSignedPreKeys(
+  keyId: string,
+  identityKeyString: string
+): Promise<Array<PreKey>>;
+export function bulkAddContactSignedPreKeys(
+  array: Array<PreKey>
+): Promise<void>;
+export function removeContactSignedPreKeyByIdentityKey(
+  id: string
+): Promise<void>;
+export function removeAllContactSignedPreKeys(): Promise<void>;
+
+// Authorisations & Linking
+export function createOrUpdatePairingAuthorisation(
+  data: PairingAuthorisationInit
+): Promise<PairingAuthorisation>;
+export function removePairingAuthorisationForSecondaryPubKey(
+  pubKey: string
+): Promise<void>;
+export function getGrantAuthorisationsForPrimaryPubKey(
+  pubKey: string
+): Promise<Array<PairingAuthorisation>>;
+export function getGrantAuthorisationForSecondaryPubKey(
+  pubKey: string
+): Promise<PairingAuthorisation>;
+export function getAuthorisationForSecondaryPubKey(
+  pubKey: string
+): PairingAuthorisation;
+export function getSecondaryDevicesFor(
+  primaryDevicePubKey: string
+): Array<string>;
+export function getPrimaryDeviceFor(
+  secondaryDevicePubKey: string
+): string | null;
+export function getPairedDevicesFor(pubKey: string): Array<string>;
+
+// Guard Nodes
+export function getGuardNodes(): Promise<GuardNode>;
+export function updateGuardNodes(nodes: Array<string>): Promise<void>;
+
+// Storage Items
+export function createOrUpdateItem(data: StorageItem): Promise<void>;
+export function getItemById(id: string): Promise<StorageItem>;
+export function getAlItems(): Promise<Array<StorageItem>>;
+export function bulkAddItems(array: Array<StorageItem>): Promise<void>;
+export function removeItemById(id: string): Promise<void>;
+export function removeAllItems(): Promise<void>;
+
+// Sessions
+export function createOrUpdateSession(data: SessionDataInfo): Promise<void>;
+export function getAllSessions(): Promise<Array<SessionDataInfo>>;
+export function getSessionById(id: string): Promise<SessionDataInfo>;
+export function getSessionsByNumber(number: string): Promise<SessionDataInfo>;
+export function bulkAddSessions(array: Array<SessionDataInfo>): Promise<void>;
+export function removeSessionById(id: string): Promise<void>;
+export function removeSessionsByNumber(number: string): Promise<void>;
+export function removeAllSessions(): Promise<void>;
+
+// Conversations
+export function getConversationCount(): Promise<number>;
+export function saveConversation(data: ConversationType): Promise<void>;
+export function saveConversations(data: Array<ConversationType>): Promise<void>;
+export function updateConversation(data: ConversationType): Promise<void>;
+export function removeConversation(id: string): Promise<void>;
+
+export function getAllConversations({
+  ConversationCollection,
+}: {
+  ConversationCollection: any;
+}): Promise<Array<ConversationCollection>>;
+
+export function getAllConversationIds(): Promise<Array<string>>;
+export function getAllPrivateConversations(): Promise<Array<string>>;
+export function getAllPublicConversations(): Promise<Array<string>>;
+export function getPublicConversationsByServer(
+  server: string,
+  { ConversationCollection }: { ConversationCollection: any }
+): Promise<ConversationCollection>;
+export function getPubkeysInPublicConversation(
+  id: string
+): Promise<Array<string>>;
+export function savePublicServerToken(data: ServerToken): Promise<void>;
+export function getPublicServerTokenByServerUrl(
+  serverUrl: string
+): Promise<string>;
+export function getAllGroupsInvolvingId(
+  id: string,
+  { ConversationCollection }: { ConversationCollection: any }
+): Promise<Array<ConversationCollection>>;
+
+// Returns conversation row
+// TODO: Make strict return types for search
+export function searchConversations(query: string): Promise<any>;
+export function searchMessages(query: string): Promise<any>;
+export function searchMessagesInConversation(
+  query: string,
+  conversationId: string,
+  { limit }?: { limit: any }
+): Promise<any>;
+export function getMessageCount(): Promise<number>;
+export function saveMessage(
+  data: Mesasge,
+  { forceSave, Message }?: { forceSave: any; Message: any }
+): Promise<string>;
+export function cleanSeenMessages(): Promise<void>;
+export function cleanLastHashes(): Promise<void>;
+export function saveSeenMessageHash(data: {
+  expiresAt: number;
+  hash: string;
+}): Promise<void>;
+
+// TODO: Strictly type the following
+export function updateLastHash(data: any): Promise<any>;
+export function saveSeenMessageHashes(data: any): Promise<any>;
+export function saveLegacyMessage(data: any): Promise<any>;
+export function saveMessages(
+  arrayOfMessages: any,
+  { forceSave }?: any
+): Promise<any>;
+export function removeMessage(id: string, { Message }?: any): Promise<any>;
+export function getUnreadByConversation(
+  conversationId: string,
+  { MessageCollection }?: any
+): Promise<any>;
+export function removeAllMessagesInConversation(
+  conversationId: string,
+  { MessageCollection }?: any
+): Promise<void>;
+
+export function getMessageBySender(
+  {
+    source,
+    sourceDevice,
+    sent_at,
+  }: { source: any; sourceDevice: any; sent_at: any },
+  { Message }: { Message: any }
+): Promise<any>;
+export function getMessageIdsFromServerIds(
+  serverIds: any,
+  conversationId: any
+): Promise<any>;
+export function getMessageById(
+  id: string,
+  { Message }: { Message: any }
+): Promise<any>;
+export function getAllMessages({
+  MessageCollection,
+}: {
+  MessageCollection: any;
+}): Promise<any>;
+export function getAllUnsentMessages({
+  MessageCollection,
+}: {
+  MessageCollection: any;
+}): Promise<any>;
+export function getAllMessageIds(): Promise<any>;
+export function getMessagesBySentAt(
+  sentAt: any,
+  { MessageCollection }: { MessageCollection: any }
+): Promise<any>;
+export function getExpiredMessages({
+  MessageCollection,
+}: {
+  MessageCollection: any;
+}): Promise<any>;
+export function getOutgoingWithoutExpiresAt({
+  MessageCollection,
+}: any): Promise<any>;
+export function getNextExpiringMessage({
+  MessageCollection,
+}: {
+  MessageCollection: any;
+}): Promise<any>;
+export function getNextExpiringMessage({
+  MessageCollection,
+}: {
+  MessageCollection: any;
+}): Promise<any>;
+export function getMessagesByConversation(
+  conversationId: any,
+  {
+    limit,
+    receivedAt,
+    MessageCollection,
+    type,
+  }: {
+    limit?: number;
+    receivedAt?: number;
+    MessageCollection: any;
+    type?: string;
+  }
+): Promise<any>;
+export function getSeenMessagesByHashList(hashes: any): Promise<any>;
+export function getLastHashBySnode(convoId: any, snode: any): Promise<any>;
+
+// Unprocessed
+export function getUnprocessedCount(): Promise<any>;
+export function getAllUnprocessed(): Promise<any>;
+export function getUnprocessedById(id: any): Promise<any>;
+export function saveUnprocessed(
+  data: any,
+  {
+    forceSave,
+  }?: {
+    forceSave: any;
+  }
+): Promise<any>;
+export function saveUnprocesseds(
+  arrayOfUnprocessed: any,
+  {
+    forceSave,
+  }?: {
+    forceSave: any;
+  }
+): Promise<void>;
+export function updateUnprocessedAttempts(
+  id: any,
+  attempts: any
+): Promise<void>;
+export function updateUnprocessedWithData(id: any, data: any): Promise<void>;
+export function removeUnprocessed(id: any): Promise<void>;
+export function removeAllUnprocessed(): Promise<void>;
+
+// Attachment Downloads
+export function getNextAttachmentDownloadJobs(limit: any): Promise<any>;
+export function saveAttachmentDownloadJob(job: any): Promise<void>;
+export function setAttachmentDownloadJobPending(
+  id: any,
+  pending: any
+): Promise<void>;
+export function resetAttachmentDownloadPending(): Promise<void>;
+export function removeAttachmentDownloadJob(id: any): Promise<void>;
+export function removeAllAttachmentDownloadJobs(): Promise<void>;
+
+// Other
+export function removeAll(): Promise<void>;
+export function removeAllConfiguration(): Promise<void>;
+export function removeAllConversations(): Promise<void>;
+export function removeAllPrivateConversations(): Promise<void>;
+export function removeOtherData(): Promise<void>;
+export function cleanupOrphanedAttachments(): Promise<void>;
+
+// Getters
+export function getMessagesNeedingUpgrade(
+  limit: any,
+  {
+    maxVersion,
+  }: {
+    maxVersion?: number;
+  }
+): Promise<any>;
+export function getLegacyMessagesNeedingUpgrade(
+  limit: any,
+  {
+    maxVersion,
+  }: {
+    maxVersion?: number;
+  }
+): Promise<any>;
+export function getMessagesWithVisualMediaAttachments(
+  conversationId: any,
+  {
+    limit,
+  }: {
+    limit: any;
+  }
+): Promise<any>;
+export function getMessagesWithFileAttachments(
+  conversationId: any,
+  {
+    limit,
+  }: {
+    limit: any;
+  }
+): Promise<any>;
+
+// Sender Keys
+export function getSenderKeys(groupId: any, senderIdentity: any): Promise<any>;
+export function createOrUpdateSenderKeys(data: any): Promise<void>;
diff --git a/ts/window.ts b/ts/window.ts
new file mode 100644
index 000000000..2490690a4
--- /dev/null
+++ b/ts/window.ts
@@ -0,0 +1,77 @@
+declare global {
+  interface Window {
+    seedNodeList: any;
+
+    WebAPI: any;
+    LokiSnodeAPI: any;
+    SenderKeyAPI: any;
+    LokiMessageAPI: any;
+    StubMessageAPI: any;
+    StubAppDotNetApi: any;
+    LokiPublicChatAPI: any;
+    LokiAppDotNetServerAPI: any;
+    LokiFileServerAPI: any;
+    LokiRssAPI: any;
+  }
+}
+
+// window.WebAPI = initializeWebAPI();
+// const LokiSnodeAPI = require('./js/modules/loki_snode_api');
+// window.SenderKeyAPI = require('./js/modules/loki_sender_key_api');
+// window.lokiSnodeAPI
+// window.LokiMessageAPI = require('./js/modules/loki_message_api');
+// window.StubMessageAPI = require('./integration_test/stubs/stub_message_api');
+// window.StubAppDotNetApi = require('./integration_test/stubs/stub_app_dot_net_api');
+// window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api');
+// window.LokiAppDotNetServerAPI = require('./js/modules/loki_app_dot_net_api');
+// window.LokiFileServerAPI = require('./js/modules/loki_file_server_api');
+// window.LokiRssAPI = require('./js/modules/loki_rss_api');
+
+export const exporttts = {
+  // APIs
+  WebAPI: window.WebAPI,
+
+  // Utilities
+  Events: () => window.Events,
+  Signal: () => window.Signal,
+  Whisper: () => window.Whisper,
+  ConversationController: () => window.ConversationController,
+  passwordUtil: () => window.passwordUtil,
+
+  // Values
+  CONSTANTS: () => window.CONSTANTS,
+  versionInfo: () => window.versionInfo,
+  mnemonic: () => window.mnemonic,
+  lokiFeatureFlags: () => window.lokiFeatureFlags,
+
+  // Getters
+  getAccountManager: () => window.getAccountManager,
+  getConversations: () => window.getConversations,
+  getFriendsFromContacts: () => window.getFriendsFromContacts,
+  getSettingValue: () => window.getSettingValue,
+
+  // Setters
+  setPassword: () => window.setPassword,
+  setSettingValue: () => window.setSettingValue,
+
+  // UI Events
+  pushToast: () => window.pushToast,
+  confirmationDialog: () => window.confirmationDialog,
+
+  showQRDialog: () => window.showQRDialog,
+  showSeedDialog: () => window.showSeedDialog,
+  showPasswordDialog: () => window.showPasswordDialog,
+  showEditProfileDialog: () => window.showEditProfileDialog,
+
+  toggleTheme: () => window.toggleTheme,
+  toggleMenuBar: () => window.toggleMenuBar,
+  toggleSpellCheck: () => window.toggleSpellCheck,
+  toggleLinkPreview: () => window.toggleLinkPreview,
+  toggleMediaPermissions: () => window.toggleMediaPermissions,
+
+  // Actions
+  clearLocalData: () => window.clearLocalData,
+  deleteAccount: () => window.deleteAccount,
+  resetDatabase: () => window.resetDatabase,
+  attemptConnection: () => window.attemptConnection,
+};

From 0f6053ce08e58e4b0d7f2de2a6e245555877cfbb Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Wed, 27 May 2020 11:05:30 +1000
Subject: [PATCH 08/10] Add events to MessageQueueInterface. Added strict
 typings for events.

---
 ts/session/sending/MessageQueue.ts          | 10 +++-
 ts/session/sending/MessageQueueInterface.ts | 12 +++--
 ts/session/sending/index.ts                 |  7 +--
 ts/session/utils/TypedEmitter.ts            | 53 +++++++++++++++++++++
 ts/session/utils/index.ts                   |  2 +
 5 files changed, 76 insertions(+), 8 deletions(-)
 create mode 100644 ts/session/utils/TypedEmitter.ts
 create mode 100644 ts/session/utils/index.ts

diff --git a/ts/session/sending/MessageQueue.ts b/ts/session/sending/MessageQueue.ts
index 5eeaa2426..274640ac4 100644
--- a/ts/session/sending/MessageQueue.ts
+++ b/ts/session/sending/MessageQueue.ts
@@ -1,13 +1,19 @@
-import { MessageQueueInterface } from './MessageQueueInterface';
+import { EventEmitter } from 'events';
+import {
+  MessageQueueInterface,
+  MessageQueueInterfaceEvents,
+} from './MessageQueueInterface';
 import { OpenGroupMessage, OutgoingContentMessage } from '../messages/outgoing';
-import { JobQueue } from '../utils/JobQueue';
 import { PendingMessageCache } from './PendingMessageCache';
+import { JobQueue, TypedEventEmitter } from '../utils';
 
 export class MessageQueue implements MessageQueueInterface {
+  public readonly events: TypedEventEmitter<MessageQueueInterfaceEvents>;
   private readonly jobQueues: Map<string, JobQueue> = new Map();
   private readonly cache: PendingMessageCache;
 
   constructor() {
+    this.events = new EventEmitter();
     this.cache = new PendingMessageCache();
     this.processAllPending();
   }
diff --git a/ts/session/sending/MessageQueueInterface.ts b/ts/session/sending/MessageQueueInterface.ts
index a231ad02c..553b25ed0 100644
--- a/ts/session/sending/MessageQueueInterface.ts
+++ b/ts/session/sending/MessageQueueInterface.ts
@@ -1,13 +1,19 @@
 import { OpenGroupMessage, OutgoingContentMessage } 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;
+
+export interface MessageQueueInterfaceEvents {
+  success: (message: RawMessage) => void;
+  fail: (message: RawMessage, error: Error) => void;
+}
+
 export interface MessageQueueInterface {
+  events: TypedEventEmitter<MessageQueueInterfaceEvents>;
   sendUsingMultiDevice(user: string, message: OutgoingContentMessage): void;
   send(device: string, message: OutgoingContentMessage): void;
   sendToGroup(message: GroupMessageType): void;
   sendSyncMessage(message: OutgoingContentMessage): void;
-  // TODO: Find a good way to handle events in this
-  // E.g if we do queue.onMessageSent() we want to also be able to stop listening to the event
-  // TODO: implement events here
 }
diff --git a/ts/session/sending/index.ts b/ts/session/sending/index.ts
index 69ca9153b..f6cf299b6 100644
--- a/ts/session/sending/index.ts
+++ b/ts/session/sending/index.ts
@@ -1,5 +1,6 @@
+// TS 3.8 supports export * as X from 'Y'
 import * as MessageSender from './MessageSender';
-import { MessageQueue } from './MessageQueue';
-import { MessageQueueInterface } from './MessageQueueInterface';
+export { MessageSender };
 
-export { MessageSender, MessageQueue, MessageQueueInterface };
+export * from './MessageQueue';
+export * from './MessageQueueInterface';
diff --git a/ts/session/utils/TypedEmitter.ts b/ts/session/utils/TypedEmitter.ts
new file mode 100644
index 000000000..a6a27f200
--- /dev/null
+++ b/ts/session/utils/TypedEmitter.ts
@@ -0,0 +1,53 @@
+// Code from https://github.com/andywer/typed-emitter
+
+type Arguments<T> = [T] extends [(...args: infer U) => any]
+  ? U
+  : [T] extends [void] ? [] : [T];
+
+/**
+ * Type-safe event emitter.
+ *
+ * Use it like this:
+ *
+ * interface MyEvents {
+ *   error: (error: Error) => void
+ *   message: (from: string, content: string) => void
+ * }
+ *
+ * const myEmitter = new EventEmitter() as TypedEmitter<MyEvents>
+ *
+ * myEmitter.on("message", (from, content) => {
+ *   // ...
+ * })
+ *
+ * myEmitter.emit("error", "x")  // <- Will catch this type error
+ *
+ * or
+ *
+ * class MyEmitter extends EventEmitter implements TypedEventEmitter<MyEvents>
+ */
+export interface TypedEventEmitter<Events> {
+  addListener<E extends keyof Events>(event: E, listener: Events[E]): this;
+  on<E extends keyof Events>(event: E, listener: Events[E]): this;
+  once<E extends keyof Events>(event: E, listener: Events[E]): this;
+  prependListener<E extends keyof Events>(event: E, listener: Events[E]): this;
+  prependOnceListener<E extends keyof Events>(
+    event: E,
+    listener: Events[E]
+  ): this;
+
+  off<E extends keyof Events>(event: E, listener: Events[E]): this;
+  removeAllListeners<E extends keyof Events>(event?: E): this;
+  removeListener<E extends keyof Events>(event: E, listener: Events[E]): this;
+
+  emit<E extends keyof Events>(
+    event: E,
+    ...args: Arguments<Events[E]>
+  ): boolean;
+  eventNames(): Array<keyof Events | string | symbol>;
+  listeners<E extends keyof Events>(event: E): Array<Function>;
+  listenerCount<E extends keyof Events>(event: E): number;
+
+  getMaxListeners(): number;
+  setMaxListeners(maxListeners: number): this;
+}
diff --git a/ts/session/utils/index.ts b/ts/session/utils/index.ts
new file mode 100644
index 000000000..a33d528ba
--- /dev/null
+++ b/ts/session/utils/index.ts
@@ -0,0 +1,2 @@
+export * from './TypedEmitter';
+export * from './JobQueue';

From 729fa594b84d864a8edbb56fb4a26dd9663037a0 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Thu, 28 May 2020 12:14:49 +1000
Subject: [PATCH 09/10] Updated window exports

---
 js/modules/data.d.ts |   8 +-
 ts/global.d.ts       |   1 +
 ts/window.ts         | 193 ++++++++++++++++++++++++++-----------------
 3 files changed, 123 insertions(+), 79 deletions(-)

diff --git a/js/modules/data.d.ts b/js/modules/data.d.ts
index e9543b99b..e154cbf3c 100644
--- a/js/modules/data.d.ts
+++ b/js/modules/data.d.ts
@@ -135,14 +135,14 @@ export function getGrantAuthorisationForSecondaryPubKey(
 ): Promise<PairingAuthorisation>;
 export function getAuthorisationForSecondaryPubKey(
   pubKey: string
-): PairingAuthorisation;
+): Promise<PairingAuthorisation>;
 export function getSecondaryDevicesFor(
   primaryDevicePubKey: string
-): Array<string>;
+): Promise<Array<string>>;
 export function getPrimaryDeviceFor(
   secondaryDevicePubKey: string
-): string | null;
-export function getPairedDevicesFor(pubKey: string): Array<string>;
+): Promise<string | null>;
+export function getPairedDevicesFor(pubKey: string): Promise<Array<string>>;
 
 // Guard Nodes
 export function getGuardNodes(): Promise<GuardNode>;
diff --git a/ts/global.d.ts b/ts/global.d.ts
index 34564325a..39e55c39f 100644
--- a/ts/global.d.ts
+++ b/ts/global.d.ts
@@ -1,3 +1,4 @@
+// TODO: Delete this and depend on window.ts instead
 interface Window {
   CONSTANTS: any;
   versionInfo: any;
diff --git a/ts/window.ts b/ts/window.ts
index 2490690a4..aeb19ec5d 100644
--- a/ts/window.ts
+++ b/ts/window.ts
@@ -1,77 +1,120 @@
-declare global {
-  interface Window {
-    seedNodeList: any;
-
-    WebAPI: any;
-    LokiSnodeAPI: any;
-    SenderKeyAPI: any;
-    LokiMessageAPI: any;
-    StubMessageAPI: any;
-    StubAppDotNetApi: any;
-    LokiPublicChatAPI: any;
-    LokiAppDotNetServerAPI: any;
-    LokiFileServerAPI: any;
-    LokiRssAPI: any;
-  }
+import { LocalizerType } from './types/Util';
+
+interface Window {
+  seedNodeList: any;
+
+  WebAPI: any;
+  LokiSnodeAPI: any;
+  SenderKeyAPI: any;
+  LokiMessageAPI: any;
+  StubMessageAPI: any;
+  StubAppDotNetApi: any;
+  LokiPublicChatAPI: any;
+  LokiAppDotNetServerAPI: any;
+  LokiFileServerAPI: any;
+  LokiRssAPI: any;
+
+  CONSTANTS: any;
+  versionInfo: any;
+
+  Events: any;
+  Lodash: any;
+  clearLocalData: any;
+  getAccountManager: any;
+  getConversations: any;
+  getFriendsFromContacts: any;
+  mnemonic: any;
+  clipboard: any;
+  attemptConnection: any;
+
+  passwordUtil: any;
+  userConfig: any;
+  shortenPubkey: any;
+
+  dcodeIO: any;
+  libsignal: any;
+  libloki: any;
+  displayNameRegex: any;
+
+  Signal: any;
+  Whisper: any;
+  ConversationController: any;
+
+  onLogin: any;
+  setPassword: any;
+  textsecure: any;
+  Session: any;
+  log: any;
+  i18n: LocalizerType;
+  friends: any;
+  generateID: any;
+  storage: any;
+  pushToast: any;
+
+  confirmationDialog: any;
+  showQRDialog: any;
+  showSeedDialog: any;
+  showPasswordDialog: any;
+  showEditProfileDialog: any;
+
+  deleteAccount: any;
+
+  toggleTheme: any;
+  toggleMenuBar: any;
+  toggleSpellCheck: any;
+  toggleLinkPreview: any;
+  toggleMediaPermissions: any;
+
+  getSettingValue: any;
+  setSettingValue: any;
+  lokiFeatureFlags: any;
+
+  resetDatabase: any;
 }
 
-// window.WebAPI = initializeWebAPI();
-// const LokiSnodeAPI = require('./js/modules/loki_snode_api');
-// window.SenderKeyAPI = require('./js/modules/loki_sender_key_api');
-// window.lokiSnodeAPI
-// window.LokiMessageAPI = require('./js/modules/loki_message_api');
-// window.StubMessageAPI = require('./integration_test/stubs/stub_message_api');
-// window.StubAppDotNetApi = require('./integration_test/stubs/stub_app_dot_net_api');
-// window.LokiPublicChatAPI = require('./js/modules/loki_public_chat_api');
-// window.LokiAppDotNetServerAPI = require('./js/modules/loki_app_dot_net_api');
-// window.LokiFileServerAPI = require('./js/modules/loki_file_server_api');
-// window.LokiRssAPI = require('./js/modules/loki_rss_api');
-
-export const exporttts = {
-  // APIs
-  WebAPI: window.WebAPI,
-
-  // Utilities
-  Events: () => window.Events,
-  Signal: () => window.Signal,
-  Whisper: () => window.Whisper,
-  ConversationController: () => window.ConversationController,
-  passwordUtil: () => window.passwordUtil,
-
-  // Values
-  CONSTANTS: () => window.CONSTANTS,
-  versionInfo: () => window.versionInfo,
-  mnemonic: () => window.mnemonic,
-  lokiFeatureFlags: () => window.lokiFeatureFlags,
-
-  // Getters
-  getAccountManager: () => window.getAccountManager,
-  getConversations: () => window.getConversations,
-  getFriendsFromContacts: () => window.getFriendsFromContacts,
-  getSettingValue: () => window.getSettingValue,
-
-  // Setters
-  setPassword: () => window.setPassword,
-  setSettingValue: () => window.setSettingValue,
-
-  // UI Events
-  pushToast: () => window.pushToast,
-  confirmationDialog: () => window.confirmationDialog,
-
-  showQRDialog: () => window.showQRDialog,
-  showSeedDialog: () => window.showSeedDialog,
-  showPasswordDialog: () => window.showPasswordDialog,
-  showEditProfileDialog: () => window.showEditProfileDialog,
-
-  toggleTheme: () => window.toggleTheme,
-  toggleMenuBar: () => window.toggleMenuBar,
-  toggleSpellCheck: () => window.toggleSpellCheck,
-  toggleLinkPreview: () => window.toggleLinkPreview,
-  toggleMediaPermissions: () => window.toggleMediaPermissions,
-
-  // Actions
-  clearLocalData: () => window.clearLocalData,
-  deleteAccount: () => window.deleteAccount,
-  resetDatabase: () => window.resetDatabase,
-  attemptConnection: () => window.attemptConnection,
-};
+declare const window: Window;
+
+// Utilities
+export const WebAPI = window.WebAPI;
+export const Events = window.Events;
+export const Signal = window.Signal;
+export const Whisper = window.Whisper;
+export const ConversationController = window.ConversationController;
+export const passwordUtil = window.passwordUtil;
+
+// Values
+export const CONSTANTS = window.CONSTANTS;
+export const versionInfo = window.versionInfo;
+export const mnemonic = window.mnemonic;
+export const lokiFeatureFlags = window.lokiFeatureFlags;
+
+// Getters
+export const getAccountManager = window.getAccountManager;
+export const getConversations = window.getConversations;
+export const getFriendsFromContacts = window.getFriendsFromContacts;
+export const getSettingValue = window.getSettingValue;
+
+// Setters
+export const setPassword = window.setPassword;
+export const setSettingValue = window.setSettingValue;
+
+// UI Events
+export const pushToast = window.pushToast;
+export const confirmationDialog = window.confirmationDialog;
+
+export const showQRDialog = window.showQRDialog;
+export const showSeedDialog = window.showSeedDialog;
+export const showPasswordDialog = window.showPasswordDialog;
+export const showEditProfileDialog = window.showEditProfileDialog;
+
+export const toggleTheme = window.toggleTheme;
+export const toggleMenuBar = window.toggleMenuBar;
+export const toggleSpellCheck = window.toggleSpellCheck;
+export const toggleLinkPreview = window.toggleLinkPreview;
+export const toggleMediaPermissions = window.toggleMediaPermissions;
+
+// Actions
+export const clearLocalData = window.clearLocalData;
+export const deleteAccount = window.deleteAccount;
+export const resetDatabase = window.resetDatabase;
+export const attemptConnection = window.attemptConnection;

From 85d3c35c0cc39271fb043c08f4c60ba6303d1c06 Mon Sep 17 00:00:00 2001
From: Mikunj <mikunj@live.com.au>
Date: Thu, 28 May 2020 12:46:42 +1000
Subject: [PATCH 10/10] Fix types

---
 js/modules/data.d.ts | 109 +++++++++++++++++++++++++++----------------
 1 file changed, 69 insertions(+), 40 deletions(-)

diff --git a/js/modules/data.d.ts b/js/modules/data.d.ts
index e154cbf3c..92441555f 100644
--- a/js/modules/data.d.ts
+++ b/js/modules/data.d.ts
@@ -7,25 +7,46 @@ type IdentityKey = {
   firstUse: boolean;
   verified: number;
   nonblockingApproval: boolean;
-} | null;
+};
 
 type PreKey = {
-  id: string;
-  publicKey: string;
-  privateKey: string;
+  id: number;
+  publicKey: ArrayBuffer;
+  privateKey: ArrayBuffer;
   recipient: string;
-} | null;
+};
+
+type SignedPreKey = {
+  id: number;
+  publicKey: ArrayBuffer;
+  privateKey: ArrayBuffer;
+  created_at: number;
+  confirmed: boolean;
+  signature: ArrayBuffer;
+};
+
+type ContactPreKey = {
+  id: number;
+  identityKeyString: string;
+  publicKey: ArrayBuffer;
+  keyId: number;
+};
+
+type ContactSignedPreKey = {
+  id: number;
+  identityKeyString: string;
+  publicKey: ArrayBuffer;
+  keyId: number;
+  signature: ArrayBuffer;
+  created_at: number;
+  confirmed: boolean;
+};
 
 type PairingAuthorisation = {
   primaryDevicePubKey: string;
   secondaryDevicePubKey: string;
-  requestSignature: string;
-  grantSignature: string | null;
-} | null;
-
-type PairingAuthorisationInit = {
-  requestSignature: string;
-  grantSignature: string;
+  requestSignature: ArrayBuffer;
+  grantSignature: ArrayBuffer | null;
 };
 
 type GuardNode = {
@@ -67,53 +88,61 @@ export function removeIndexedDBFiles(): Promise<void>;
 export function getPasswordHash(): Promise<string | null>;
 
 // Identity Keys
-export function createOrUpdateIdentityKey(data: any): Promise<void>;
-export function getIdentityKeyById(id: string): Promise<IdentityKey>;
+export function createOrUpdateIdentityKey(data: IdentityKey): Promise<void>;
+export function getIdentityKeyById(id: string): Promise<IdentityKey | null>;
 export function bulkAddIdentityKeys(array: Array<IdentityKey>): Promise<void>;
 export function removeIdentityKeyById(id: string): Promise<void>;
 export function removeAllIdentityKeys(): Promise<void>;
 
 // Pre Keys
 export function createOrUpdatePreKey(data: PreKey): Promise<void>;
-export function getPreKeyById(id: string): Promise<PreKey>;
-export function getPreKeyByRecipient(recipient: string): Promise<PreKey>;
+export function getPreKeyById(id: number): Promise<PreKey | null>;
+export function getPreKeyByRecipient(recipient: string): Promise<PreKey | null>;
 export function bulkAddPreKeys(data: Array<PreKey>): Promise<void>;
-export function removePreKeyById(id: string): Promise<void>;
+export function removePreKeyById(id: number): Promise<void>;
 export function getAllPreKeys(): Promise<Array<PreKey>>;
 
 // Signed Pre Keys
-export function createOrUpdateSignedPreKey(data: PreKey): Promise<void>;
-export function getSignedPreKeyById(id: string): Promise<PreKey>;
-export function getAllSignedPreKeys(recipient: string): Promise<PreKey>;
-export function bulkAddSignedPreKeys(array: Array<PreKey>): Promise<void>;
-export function removeSignedPreKeyById(id: string): Promise<void>;
+export function createOrUpdateSignedPreKey(data: SignedPreKey): Promise<void>;
+export function getSignedPreKeyById(id: number): Promise<SignedPreKey | null>;
+export function getAllSignedPreKeys(): Promise<SignedPreKey | null>;
+export function bulkAddSignedPreKeys(array: Array<SignedPreKey>): Promise<void>;
+export function removeSignedPreKeyById(id: number): Promise<void>;
 export function removeAllSignedPreKeys(): Promise<void>;
 
 // Contact Pre Key
-export function createOrUpdateContactPreKey(data: PreKey): Promise<void>;
-export function getContactPreKeyById(id: string): Promise<PreKey>;
-export function getContactPreKeyByIdentityKey(key: string): Promise<PreKey>;
+export function createOrUpdateContactPreKey(data: ContactPreKey): Promise<void>;
+export function getContactPreKeyById(id: number): Promise<ContactPreKey | null>;
+export function getContactPreKeyByIdentityKey(
+  key: string
+): Promise<ContactPreKey | null>;
 export function getContactPreKeys(
-  keyId: string,
+  keyId: number,
   identityKeyString: string
-): Promise<Array<PreKey>>;
-export function getAllContactPreKeys(): Promise<Array<PreKey>>;
-export function bulkAddContactPreKeys(array: Array<PreKey>): Promise<void>;
-export function removeContactPreKeyByIdentityKey(id: string): Promise<void>;
+): Promise<Array<ContactPreKey>>;
+export function getAllContactPreKeys(): Promise<Array<ContactPreKey>>;
+export function bulkAddContactPreKeys(
+  array: Array<ContactPreKey>
+): Promise<void>;
+export function removeContactPreKeyByIdentityKey(id: number): Promise<void>;
 export function removeAllContactPreKeys(): Promise<void>;
 
 // Contact Signed Pre Key
-export function createOrUpdateContactSignedPreKey(data: PreKey): Promise<void>;
-export function getContactSignedPreKeyByIdid(string): Promise<PreKey>;
+export function createOrUpdateContactSignedPreKey(
+  data: ContactSignedPreKey
+): Promise<void>;
+export function getContactSignedPreKeyById(
+  id: number
+): Promise<ContactSignedPreKey | null>;
 export function getContactSignedPreKeyByIdentityKey(
   key: string
-): Promise<PreKey>;
+): Promise<ContactSignedPreKey | null>;
 export function getContactSignedPreKeys(
-  keyId: string,
+  keyId: number,
   identityKeyString: string
-): Promise<Array<PreKey>>;
+): Promise<Array<ContactSignedPreKey>>;
 export function bulkAddContactSignedPreKeys(
-  array: Array<PreKey>
+  array: Array<ContactSignedPreKey>
 ): Promise<void>;
 export function removeContactSignedPreKeyByIdentityKey(
   id: string
@@ -122,8 +151,8 @@ export function removeAllContactSignedPreKeys(): Promise<void>;
 
 // Authorisations & Linking
 export function createOrUpdatePairingAuthorisation(
-  data: PairingAuthorisationInit
-): Promise<PairingAuthorisation>;
+  data: PairingAuthorisation
+): Promise<void>;
 export function removePairingAuthorisationForSecondaryPubKey(
   pubKey: string
 ): Promise<void>;
@@ -132,10 +161,10 @@ export function getGrantAuthorisationsForPrimaryPubKey(
 ): Promise<Array<PairingAuthorisation>>;
 export function getGrantAuthorisationForSecondaryPubKey(
   pubKey: string
-): Promise<PairingAuthorisation>;
+): Promise<PairingAuthorisation | null>;
 export function getAuthorisationForSecondaryPubKey(
   pubKey: string
-): Promise<PairingAuthorisation>;
+): Promise<PairingAuthorisation | null>;
 export function getSecondaryDevicesFor(
   primaryDevicePubKey: string
 ): Promise<Array<string>>;