From 9f8920ef2ccb6e064f10e251330975cd2f7f9af7 Mon Sep 17 00:00:00 2001
From: Audric Ackermann <audric@loki.network>
Date: Mon, 4 Apr 2022 08:53:37 +1000
Subject: [PATCH] mostly working but need to improve perfs

---
 background.html                               |   7 +-
 package.json                                  |   1 -
 preload.js                                    |   7 -
 ts/attachments/attachments.ts                 |   2 +-
 ts/components/dialog/SessionSeedModal.tsx     |   4 +-
 ts/components/leftpane/ActionsPanel.tsx       |   3 -
 ts/components/menu/Menu.tsx                   |   4 +-
 ts/components/settings/SessionSettings.tsx    |   8 +-
 ts/data/dataInit.ts                           |   2 -
 ts/mains/main_node.ts                         |   2 +-
 ts/mains/main_renderer.tsx                    |   1 -
 ts/node/util_worker_interface.ts              | 121 -----------
 ts/receiver/attachments.ts                    |   8 +-
 .../apis/file_server_api/FileServerApiV2.ts   |   5 +-
 .../open_group_api/opengroupV2/ApiAuth.ts     |   9 +-
 .../open_group_api/opengroupV2/ApiUtil.ts     |  11 +-
 .../opengroupV2/OpenGroupAPIV2.ts             |   9 +-
 .../opengroupV2/OpenGroupMessageV2.ts         |   3 +-
 .../opengroupV2/OpenGroupServerPoller.ts      |   3 +-
 .../apis/push_notification_api/PnServer.ts    |   3 +-
 ts/session/apis/snode_api/onions.ts           |  19 +-
 .../crypto/DecryptedAttachmentsManager.ts     |   2 +-
 .../local_attachments_encrypter.ts            |  11 +-
 ts/webworker/master.ts                        |  12 --
 ts/webworker/worker_interface.ts              | 120 +++++++++++
 ts/webworker/workers/auth.worker.ts           |  17 --
 ts/webworker/workers/index.ts                 |   2 +
 .../webworker/workers/util.worker.ts          | 201 +++++++++---------
 ts/webworker/workers/util_worker_interface.ts |   9 +
 ts/window.d.ts                                |   1 -
 30 files changed, 299 insertions(+), 308 deletions(-)
 rename ts/{node => util}/local_attachments_encrypter.ts (63%)
 delete mode 100644 ts/webworker/master.ts
 create mode 100644 ts/webworker/worker_interface.ts
 delete mode 100644 ts/webworker/workers/auth.worker.ts
 create mode 100644 ts/webworker/workers/index.ts
 rename js/util_worker_tasks.js => ts/webworker/workers/util.worker.ts (72%)
 create mode 100644 ts/webworker/workers/util_worker_interface.ts

diff --git a/background.html b/background.html
index 93d30a21a..b14e5ee3f 100644
--- a/background.html
+++ b/background.html
@@ -26,7 +26,9 @@
     <title>Session</title>
     <link href="images/sesion/session_icon_128.png" rel="shortcut icon" />
     <link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
-
+    <script>
+      var exports = {};
+    </script>
     <script type="text/javascript" src="js/components.js"></script>
     <script type="text/javascript" src="js/libtextsecure.js"></script>
   </head>
@@ -40,6 +42,9 @@
         </div>
       </div>
     </div>
+    <script>
+      var exports = {};
+    </script>
     <script type="text/javascript">
       require('./ts/mains/main_renderer.js');
     </script>
diff --git a/package.json b/package.json
index 1dde93083..0764d7eea 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,6 @@
     "name": "Oxen Labs",
     "email": "team@oxen.io"
   },
-
   "repository": {
     "type": "git",
     "url": "https://github.com/oxen-io/session-desktop.git"
diff --git a/preload.js b/preload.js
index 3975e4f7d..f8ada49b5 100644
--- a/preload.js
+++ b/preload.js
@@ -204,15 +204,8 @@ window.nodeSetImmediate = setImmediate;
 const data = require('./ts/data/dataInit');
 const { setupi18n } = require('./ts/util/i18n');
 window.Signal = data.initData();
-const { WorkerInterface } = require('./ts/node/util_worker_interface');
 
-// A Worker with a 3 minute timeout
 
-// console.warn('app', require('electron/main').app);
-// const utilWorkerPath = path.join(app.getAppPath(), 'js', 'util_worker.js');
-// const utilWorker = new WorkerInterface(utilWorkerPath, 3 * 60 * 1000);
-
-window.callWorker = (fnName, ...args) => utilWorker.callWorker(fnName, ...args);
 // Linux seems to periodically let the event loop stop, so this is a global workaround
 setInterval(() => {
   // tslint:disable-next-line: no-empty
diff --git a/ts/attachments/attachments.ts b/ts/attachments/attachments.ts
index 6fc7f5cb1..f9308bb0f 100644
--- a/ts/attachments/attachments.ts
+++ b/ts/attachments/attachments.ts
@@ -8,7 +8,7 @@ import { isArrayBuffer, isString, map } from 'lodash';
 import {
   decryptAttachmentBuffer,
   encryptAttachmentBuffer,
-} from '../node/local_attachments_encrypter';
+} from '../util/local_attachments_encrypter';
 
 const PATH = 'attachments.noindex';
 
diff --git a/ts/components/dialog/SessionSeedModal.tsx b/ts/components/dialog/SessionSeedModal.tsx
index f98205793..bc1c6735e 100644
--- a/ts/components/dialog/SessionSeedModal.tsx
+++ b/ts/components/dialog/SessionSeedModal.tsx
@@ -26,7 +26,7 @@ const Password = (props: PasswordProps) => {
   const onClose = () => dispatch(recoveryPhraseModal(null));
 
   const confirmPassword = () => {
-    const passwordValue = jQuery('#seed-input-password').val();
+    const passwordValue = (document.getElementById('seed-input-password') as any)?.val();
     const isPasswordValid = matchesHash(passwordValue as string, passwordHash);
 
     if (!passwordValue) {
@@ -145,7 +145,7 @@ const SessionSeedModalInner = (props: ModalInnerProps) => {
   const dispatch = useDispatch();
 
   useEffect(() => {
-    setTimeout(() => (jQuery('#seed-input-password') as any).focus(), 100);
+    setTimeout(() => (document.getElementById('seed-input-password') as any)?.focus(), 100);
     void checkHasPassword();
     void getRecoveryPhrase();
   }, []);
diff --git a/ts/components/leftpane/ActionsPanel.tsx b/ts/components/leftpane/ActionsPanel.tsx
index 6786cf515..189f86103 100644
--- a/ts/components/leftpane/ActionsPanel.tsx
+++ b/ts/components/leftpane/ActionsPanel.tsx
@@ -53,7 +53,6 @@ import { SessionToastContainer } from '../SessionToastContainer';
 import { LeftPaneSectionContainer } from './LeftPaneSectionContainer';
 import { getLatestDesktopReleaseFileToFsV2 } from '../../session/apis/file_server_api/FileServerApiV2';
 import { ipcRenderer } from 'electron';
-import { yo } from '../../webworker/master';
 
 const Section = (props: { type: SectionType }) => {
   const ourNumber = useSelector(getOurNumber);
@@ -260,8 +259,6 @@ const doAppStartUp = () => {
 
   void loadDefaultRooms();
 
-  void yo();
-
   debounce(triggerAvatarReUploadIfNeeded, 200);
 };
 
diff --git a/ts/components/menu/Menu.tsx b/ts/components/menu/Menu.tsx
index 950b9bcf1..a7b0ab68f 100644
--- a/ts/components/menu/Menu.tsx
+++ b/ts/components/menu/Menu.tsx
@@ -518,7 +518,9 @@ export const NotificationForConvoMenuItem = (): JSX.Element | null => {
 };
 
 export function isRtlBody(): boolean {
-  return (jQuery('body') as any).hasClass('rtl');
+  const body = document.getElementsByTagName('body').item(0);
+
+  return body?.classList.contains('rtl') || false;
 }
 
 export const BlockMenuItem = (): JSX.Element | null => {
diff --git a/ts/components/settings/SessionSettings.tsx b/ts/components/settings/SessionSettings.tsx
index d8eabee5a..3bf38e389 100644
--- a/ts/components/settings/SessionSettings.tsx
+++ b/ts/components/settings/SessionSettings.tsx
@@ -110,7 +110,7 @@ export class SessionSettingsView extends React.Component<SettingsViewProps, Stat
     const callMediaSetting = getCallMediaPermissionsSettings();
     this.setState({ mediaSetting, callMediaSetting });
 
-    setTimeout(() => (jQuery('#password-lock-input') as any).focus(), 100);
+    setTimeout(() => (document.getElementById('password-lock-input') as any)?.focus(), 100);
   }
 
   public componentWillUnmount() {
@@ -149,7 +149,7 @@ export class SessionSettingsView extends React.Component<SettingsViewProps, Stat
   }
 
   public async validatePasswordLock() {
-    const enteredPassword = String(jQuery('#password-lock-input').val());
+    const enteredPassword = String((document.getElementById('password-lock-input') as any)?.val());
 
     if (!enteredPassword) {
       this.setState({
@@ -236,7 +236,9 @@ export class SessionSettingsView extends React.Component<SettingsViewProps, Stat
   }
 
   private async onKeyUp(event: any) {
-    const lockPasswordFocussed = (jQuery('#password-lock-input') as any).is(':focus');
+    const lockPasswordFocussed = (document.getElementById('password-lock-input') as any)?.is(
+      ':focus'
+    );
 
     if (event.key === 'Enter' && lockPasswordFocussed) {
       await this.validatePasswordLock();
diff --git a/ts/data/dataInit.ts b/ts/data/dataInit.ts
index db6cd8888..fc035b21c 100644
--- a/ts/data/dataInit.ts
+++ b/ts/data/dataInit.ts
@@ -186,8 +186,6 @@ export function initData() {
 
   channelsToMake.forEach(makeChannel);
 
-  console.warn('after initData channels', channels);
-
   ipcRenderer.on(`${SQL_CHANNEL_KEY}-done`, (_event, jobId, errorForDisplay, result) => {
     const job = _getJob(jobId);
     if (!job) {
diff --git a/ts/mains/main_node.ts b/ts/mains/main_node.ts
index 5769938e3..222e063f2 100644
--- a/ts/mains/main_node.ts
+++ b/ts/mains/main_node.ts
@@ -484,7 +484,7 @@ async function showPasswordWindow() {
       nodeIntegration: false,
       enableRemoteModule: true,
       nodeIntegrationInWorker: false,
-      contextIsolation: true,
+      contextIsolation: false,
 
       // sandbox: true,
       preload: path.join(__dirname, '..', 'password_preload.js'),
diff --git a/ts/mains/main_renderer.tsx b/ts/mains/main_renderer.tsx
index 6fdf12c89..46a6e2a92 100644
--- a/ts/mains/main_renderer.tsx
+++ b/ts/mains/main_renderer.tsx
@@ -110,7 +110,6 @@ Storage.onready(async () => {
     return;
   }
   first = false;
-  console.warn('storage is ready');
   // Update zoom
   window.updateZoomFactor();
 
diff --git a/ts/node/util_worker_interface.ts b/ts/node/util_worker_interface.ts
index e88a7665b..2ac60bc4d 100644
--- a/ts/node/util_worker_interface.ts
+++ b/ts/node/util_worker_interface.ts
@@ -1,122 +1 @@
 /* global Worker, window, setTimeout */
-
-const WORKER_TIMEOUT = 60 * 1000; // one minute
-
-class TimedOutError extends Error {
-  constructor(message: string) {
-    super(message);
-    this.name = this.constructor.name;
-    if (typeof Error.captureStackTrace === 'function') {
-      Error.captureStackTrace(this, this.constructor);
-    } else {
-      this.stack = new Error(message).stack;
-    }
-  }
-}
-
-export class WorkerInterface {
-  private readonly timeout: number;
-  private readonly _DEBUG: boolean;
-  private _jobCounter: number;
-  private readonly _jobs: Record<number, any>;
-  private readonly _utilWorker: Worker;
-
-  constructor(path: string, timeout = WORKER_TIMEOUT) {
-    this._utilWorker = new Worker(path);
-    this.timeout = timeout;
-    this._jobs = Object.create(null);
-    this._DEBUG = false;
-    this._jobCounter = 0;
-
-    this._utilWorker.onmessage = e => {
-      const [jobId, errorForDisplay, result] = e.data;
-
-      const job = this._getJob(jobId);
-      if (!job) {
-        throw new Error(
-          `Received worker reply to job ${jobId}, but did not have it in our registry!`
-        );
-      }
-
-      const { resolve, reject, fnName } = job;
-
-      if (errorForDisplay) {
-        return reject(
-          new Error(`Error received from worker job ${jobId} (${fnName}): ${errorForDisplay}`)
-        );
-      }
-
-      return resolve(result);
-    };
-  }
-
-  public async callWorker(fnName: string, ...args: any) {
-    const jobId = this._makeJob(fnName);
-
-    return new Promise((resolve, reject) => {
-      this._utilWorker.postMessage([jobId, fnName, ...args]);
-
-      this._updateJob(jobId, {
-        resolve,
-        reject,
-        args: this._DEBUG ? args : null,
-      });
-
-      setTimeout(() => {
-        reject(new TimedOutError(`Worker job ${jobId} (${fnName}) timed out`));
-      }, this.timeout);
-    });
-  }
-
-  private _makeJob(fnName: string): number {
-    this._jobCounter += 1;
-    const id = this._jobCounter;
-
-    if (this._DEBUG) {
-      window.log.info(`Worker job ${id} (${fnName}) started`);
-    }
-    this._jobs[id] = {
-      fnName,
-      start: Date.now(),
-    };
-
-    return id;
-  }
-
-  private _updateJob(id: number, data: any) {
-    const { resolve, reject } = data;
-    const { fnName, start } = this._jobs[id];
-
-    this._jobs[id] = {
-      ...this._jobs[id],
-      ...data,
-      resolve: (value: any) => {
-        this._removeJob(id);
-        const end = Date.now();
-        if (this._DEBUG) {
-          window.log.info(`Worker job ${id} (${fnName}) succeeded in ${end - start}ms`);
-        }
-        return resolve(value);
-      },
-      reject: (error: any) => {
-        this._removeJob(id);
-        const end = Date.now();
-        window.log.info(`Worker job ${id} (${fnName}) failed in ${end - start}ms`);
-        return reject(error);
-      },
-    };
-  }
-
-  private _removeJob(id: number) {
-    if (this._DEBUG) {
-      this._jobs[id].complete = true;
-    } else {
-      // tslint:disable-next-line: no-dynamic-delete
-      delete this._jobs[id];
-    }
-  }
-
-  private _getJob(id: number) {
-    return this._jobs[id];
-  }
-}
diff --git a/ts/receiver/attachments.ts b/ts/receiver/attachments.ts
index b1a94427a..29b3151fc 100644
--- a/ts/receiver/attachments.ts
+++ b/ts/receiver/attachments.ts
@@ -12,6 +12,7 @@ import { OpenGroupRequestCommonType } from '../session/apis/open_group_api/openg
 import { FSv2 } from '../session/apis/file_server_api';
 import { getUnpaddedAttachment } from '../session/crypto/BufferPadding';
 import { decryptAttachment } from '../util/crypto/attachmentsEncrypter';
+import { callUtilsWorker } from '../webworker/workers/util_worker_interface';
 
 export async function downloadAttachment(attachment: {
   url: string;
@@ -61,11 +62,8 @@ export async function downloadAttachment(attachment: {
       throw new Error('Attachment expected size is 0');
     }
 
-    const keyBuffer = (await window.callWorker('fromBase64ToArrayBuffer', key)) as ArrayBuffer;
-    const digestBuffer = (await window.callWorker(
-      'fromBase64ToArrayBuffer',
-      digest
-    )) as ArrayBuffer;
+    const keyBuffer = (await callUtilsWorker('fromBase64ToArrayBuffer', key)) as ArrayBuffer;
+    const digestBuffer = (await callUtilsWorker('fromBase64ToArrayBuffer', digest)) as ArrayBuffer;
 
     data = await decryptAttachment(data, keyBuffer, digestBuffer);
 
diff --git a/ts/session/apis/file_server_api/FileServerApiV2.ts b/ts/session/apis/file_server_api/FileServerApiV2.ts
index 6bcdde631..3496d20ea 100644
--- a/ts/session/apis/file_server_api/FileServerApiV2.ts
+++ b/ts/session/apis/file_server_api/FileServerApiV2.ts
@@ -1,3 +1,4 @@
+import { callUtilsWorker } from '../../../webworker/workers/util_worker_interface';
 import { OpenGroupV2Request } from '../open_group_api/opengroupV2/ApiUtil';
 import { sendApiV2Request } from '../open_group_api/opengroupV2/OpenGroupAPIV2';
 import { parseStatusCodeFromOnionRequest } from '../open_group_api/opengroupV2/OpenGroupAPIV2Parser';
@@ -35,7 +36,7 @@ export const uploadFileToFsV2 = async (
     return null;
   }
   const queryParams = {
-    file: await window.callWorker('arrayBufferToStringBase64', fileContent),
+    file: await callUtilsWorker('arrayBufferToStringBase64', fileContent),
   };
 
   const request: FileServerV2Request = {
@@ -99,7 +100,7 @@ export const downloadFileFromFSv2 = async (
   if (!base64Data) {
     return null;
   }
-  return window.callWorker('fromBase64ToArrayBuffer', base64Data);
+  return callUtilsWorker('fromBase64ToArrayBuffer', base64Data);
 };
 
 /**
diff --git a/ts/session/apis/open_group_api/opengroupV2/ApiAuth.ts b/ts/session/apis/open_group_api/opengroupV2/ApiAuth.ts
index 86a17a2f4..cd483b4dd 100644
--- a/ts/session/apis/open_group_api/opengroupV2/ApiAuth.ts
+++ b/ts/session/apis/open_group_api/opengroupV2/ApiAuth.ts
@@ -1,4 +1,5 @@
 import { getV2OpenGroupRoomByRoomId, saveV2OpenGroupRoom } from '../../../../data/opengroups';
+import { callUtilsWorker } from '../../../../webworker/workers/util_worker_interface';
 import { allowOnlyOneAtATime } from '../../../utils/Promise';
 import { toHex } from '../../../utils/String';
 import { getIdentityKeyPair, getOurPubKeyStrFromCache } from '../../../utils/User';
@@ -120,22 +121,22 @@ export async function requestNewAuthToken({
     window?.log?.warn('Parsing failed');
     return null;
   }
-  const ciphertext = (await window.callWorker(
+  const ciphertext = (await callUtilsWorker(
     'fromBase64ToArrayBuffer',
     base64EncodedCiphertext
   )) as ArrayBuffer;
-  const ephemeralPublicKey = (await window.callWorker(
+  const ephemeralPublicKey = (await callUtilsWorker(
     'fromBase64ToArrayBuffer',
     base64EncodedEphemeralPublicKey
   )) as ArrayBuffer;
   try {
-    const symmetricKey = (await window.callWorker(
+    const symmetricKey = (await callUtilsWorker(
       'deriveSymmetricKey',
       new Uint8Array(ephemeralPublicKey),
       new Uint8Array(userKeyPair.privKey)
     )) as ArrayBuffer;
 
-    const plaintextBuffer = await window.callWorker(
+    const plaintextBuffer = await callUtilsWorker(
       'DecryptAESGCM',
       new Uint8Array(symmetricKey),
       new Uint8Array(ciphertext)
diff --git a/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts b/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts
index daa3a565e..786377746 100644
--- a/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts
+++ b/ts/session/apis/open_group_api/opengroupV2/ApiUtil.ts
@@ -11,6 +11,7 @@ import { getCompleteUrlFromRoom } from '../utils/OpenGroupUtils';
 import { parseOpenGroupV2 } from './JoinOpenGroupV2';
 import { getAllRoomInfos } from './OpenGroupAPIV2';
 import { OpenGroupMessageV2 } from './OpenGroupMessageV2';
+import { callUtilsWorker } from '../../../../webworker/workers/util_worker_interface';
 
 export type OpenGroupRequestCommonType = {
   serverUrl: string;
@@ -44,15 +45,15 @@ export type OpenGroupV2InfoJoinable = OpenGroupV2Info & {
 };
 
 export const TextToBase64 = async (text: string) => {
-  const arrayBuffer = await window.callWorker('bytesFromString', text);
+  const arrayBuffer = await callUtilsWorker('bytesFromString', text);
 
-  const base64 = await window.callWorker('arrayBufferToStringBase64', arrayBuffer);
+  const base64 = await callUtilsWorker('arrayBufferToStringBase64', arrayBuffer);
 
   return base64;
 };
 
 export const textToArrayBuffer = async (text: string) => {
-  return window.callWorker('bytesFromString', text);
+  return callUtilsWorker('bytesFromString', text);
 };
 
 export const verifyED25519Signature = async (
@@ -60,7 +61,7 @@ export const verifyED25519Signature = async (
   base64EncodedData: string,
   base64EncondedSignature: string
 ): Promise<Boolean> => {
-  return window.callWorker('verifySignature', pubkey, base64EncodedData, base64EncondedSignature);
+  return callUtilsWorker('verifySignature', pubkey, base64EncodedData, base64EncondedSignature);
 };
 
 export const parseMessages = async (
@@ -88,7 +89,7 @@ export const parseMessages = async (
       // Validate the message signature
       const senderPubKey = PubKey.cast(opengroupv2Message.sender).withoutPrefix();
 
-      const signatureValid = (await window.callWorker(
+      const signatureValid = (await callUtilsWorker(
         'verifySignature',
         senderPubKey,
         opengroupv2Message.base64EncodedData,
diff --git a/ts/session/apis/open_group_api/opengroupV2/OpenGroupAPIV2.ts b/ts/session/apis/open_group_api/opengroupV2/OpenGroupAPIV2.ts
index 773c53160..a9b6bb610 100644
--- a/ts/session/apis/open_group_api/opengroupV2/OpenGroupAPIV2.ts
+++ b/ts/session/apis/open_group_api/opengroupV2/OpenGroupAPIV2.ts
@@ -17,6 +17,7 @@ import { OpenGroupMessageV2 } from './OpenGroupMessageV2';
 import { isOpenGroupV2Request } from '../../file_server_api/FileServerApiV2';
 import { getAuthToken } from './ApiAuth';
 import pRetry from 'p-retry';
+import { callUtilsWorker } from '../../../../webworker/workers/util_worker_interface';
 
 // used to be overwritten by testing
 export const getMinTimeout = () => 1000;
@@ -399,7 +400,7 @@ export const downloadFileOpenGroupV2 = async (
   if (!base64Data) {
     return null;
   }
-  return new Uint8Array(await window.callWorker('fromBase64ToArrayBuffer', base64Data));
+  return new Uint8Array(await callUtilsWorker('fromBase64ToArrayBuffer', base64Data));
 };
 
 export const downloadFileOpenGroupV2ByUrl = async (
@@ -426,7 +427,7 @@ export const downloadFileOpenGroupV2ByUrl = async (
   if (!base64Data) {
     return null;
   }
-  return new Uint8Array(await window.callWorker('fromBase64ToArrayBuffer', base64Data));
+  return new Uint8Array(await callUtilsWorker('fromBase64ToArrayBuffer', base64Data));
 };
 
 /**
@@ -472,7 +473,7 @@ export const uploadFileOpenGroupV2 = async (
     return null;
   }
   const queryParams = {
-    file: await window.callWorker('arrayBufferToStringBase64', fileContent),
+    file: await callUtilsWorker('arrayBufferToStringBase64', fileContent),
   };
 
   const filesEndpoint = 'files';
@@ -512,7 +513,7 @@ export const uploadImageForRoomOpenGroupV2 = async (
   }
 
   const queryParams = {
-    file: await window.callWorker('arrayBufferToStringBase64', fileContent),
+    file: await callUtilsWorker('arrayBufferToStringBase64', fileContent),
   };
 
   const imageEndpoint = `rooms/${roomInfos.roomId}/image`;
diff --git a/ts/session/apis/open_group_api/opengroupV2/OpenGroupMessageV2.ts b/ts/session/apis/open_group_api/opengroupV2/OpenGroupMessageV2.ts
index 5d36b3dba..cf75b5be9 100644
--- a/ts/session/apis/open_group_api/opengroupV2/OpenGroupMessageV2.ts
+++ b/ts/session/apis/open_group_api/opengroupV2/OpenGroupMessageV2.ts
@@ -1,3 +1,4 @@
+import { callUtilsWorker } from '../../../../webworker/workers/util_worker_interface';
 import { UserUtils } from '../../../utils';
 import { fromBase64ToArray } from '../../../utils/String';
 
@@ -66,7 +67,7 @@ export class OpenGroupMessageV2 {
     if (!signature || signature.length === 0) {
       throw new Error("Couldn't sign message");
     }
-    const base64Sig = await window.callWorker('arrayBufferToStringBase64', signature);
+    const base64Sig = await callUtilsWorker('arrayBufferToStringBase64', signature);
     return new OpenGroupMessageV2({
       base64EncodedData: this.base64EncodedData,
       sentTimestamp: this.sentTimestamp,
diff --git a/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts b/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts
index 14e438d33..a975918fe 100644
--- a/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts
+++ b/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts
@@ -22,6 +22,7 @@ import { DURATION } from '../../../constants';
 import { processNewAttachment } from '../../../../types/MessageAttachment';
 import { MIME } from '../../../../types';
 import { handleOpenGroupV2Message } from '../../../../receiver/opengroup';
+import { callUtilsWorker } from '../../../../webworker/workers/util_worker_interface';
 
 const pollForEverythingInterval = DURATION.SECONDS * 10;
 const pollForRoomAvatarInterval = DURATION.DAYS * 1;
@@ -493,7 +494,7 @@ const handleBase64AvatarUpdate = async (
 
         const upgradedAttachment = await processNewAttachment({
           isRaw: true,
-          data: await window.callWorker('fromBase64ToArrayBuffer', res.base64),
+          data: await callUtilsWorker('fromBase64ToArrayBuffer', res.base64),
           contentType: MIME.IMAGE_UNKNOWN, // contentType is mostly used to generate previews and screenshot. We do not care for those in this case.          // url: `${serverUrl}/${res.roomId}`,
         });
         // update the hash on the conversationModel
diff --git a/ts/session/apis/push_notification_api/PnServer.ts b/ts/session/apis/push_notification_api/PnServer.ts
index d147563cc..c169c6fb5 100644
--- a/ts/session/apis/push_notification_api/PnServer.ts
+++ b/ts/session/apis/push_notification_api/PnServer.ts
@@ -1,3 +1,4 @@
+import { callUtilsWorker } from '../../../webworker/workers/util_worker_interface';
 import { sendViaOnionToNonSnode } from '../../onions/onionSend';
 
 const pnServerPubkeyHex = '642a6585919742e5a2d4dc51244964fbcd8bcab2b75612407de58b810740d049';
@@ -11,7 +12,7 @@ export async function notifyPnServer(wrappedEnvelope: ArrayBuffer, sentTo: strin
   const options: ServerRequestOptionsType = {
     method: 'post',
     objBody: {
-      data: await window.callWorker('arrayBufferToStringBase64', wrappedEnvelope),
+      data: await callUtilsWorker('arrayBufferToStringBase64', wrappedEnvelope),
       send_to: sentTo,
     },
   };
diff --git a/ts/session/apis/snode_api/onions.ts b/ts/session/apis/snode_api/onions.ts
index cf863e133..f83bcb94e 100644
--- a/ts/session/apis/snode_api/onions.ts
+++ b/ts/session/apis/snode_api/onions.ts
@@ -15,6 +15,7 @@ import { Snode } from '../../../data/data';
 import { ERROR_CODE_NO_CONNECT } from './SNodeAPI';
 import { Onions } from '.';
 import { hrefPnServerDev, hrefPnServerProd } from '../push_notification_api/PnServer';
+import { callUtilsWorker } from '../../../webworker/workers/util_worker_interface';
 
 export const resetSnodeFailureCount = () => {
   snodeFailureCount = {};
@@ -51,7 +52,9 @@ async function encryptForPubKey(pubKeyX25519hex: string, reqObj: any): Promise<D
   const textEncoder = new TextEncoder();
   const plaintext = textEncoder.encode(reqStr);
 
-  return window.callWorker('encryptForPubkey', pubKeyX25519hex, plaintext);
+  return callUtilsWorker('encryptForPubkey', pubKeyX25519hex, plaintext) as Promise<
+    DestinationContext
+  >;
 }
 
 export type DestinationRelayV2 = {
@@ -79,7 +82,7 @@ async function encryptForRelayV2(
   };
 
   const plaintext = encodeCiphertextPlusJson(ctx.ciphertext, reqObj);
-  return window.callWorker('encryptForPubkey', relayX25519hex, plaintext);
+  return callUtilsWorker('encryptForPubkey', relayX25519hex, plaintext);
 }
 
 /// Encode ciphertext as (len || binary) and append payloadJson as utf8
@@ -389,13 +392,13 @@ export async function decodeOnionResult(symmetricKey: ArrayBuffer, ciphertext: s
   } catch (e) {
     // just try to get a json object from what is inside (for PN requests), if it fails, continue ()
   }
-  const ciphertextBuffer = await window.callWorker('fromBase64ToArrayBuffer', parsedCiphertext);
+  const ciphertextBuffer = await callUtilsWorker('fromBase64ToArrayBuffer', parsedCiphertext);
 
-  const plaintextBuffer = await window.callWorker(
+  const plaintextBuffer = (await callUtilsWorker(
     'DecryptAESGCM',
     new Uint8Array(symmetricKey),
     new Uint8Array(ciphertextBuffer)
-  );
+  )) as string;
 
   return { plaintext: new TextDecoder().decode(plaintextBuffer), ciphertextBuffer };
 }
@@ -743,7 +746,11 @@ const sendOnionRequest = async ({
       const bodyEncoded = textEncoder.encode(body);
 
       const plaintext = encodeCiphertextPlusJson(bodyEncoded, options);
-      destCtx = await window.callWorker('encryptForPubkey', destX25519hex, plaintext);
+      destCtx = (await callUtilsWorker(
+        'encryptForPubkey',
+        destX25519hex,
+        plaintext
+      )) as DestinationContext;
     } else {
       destCtx = await encryptForPubKey(destX25519hex, options);
     }
diff --git a/ts/session/crypto/DecryptedAttachmentsManager.ts b/ts/session/crypto/DecryptedAttachmentsManager.ts
index 4b69d1d67..d4f6072a0 100644
--- a/ts/session/crypto/DecryptedAttachmentsManager.ts
+++ b/ts/session/crypto/DecryptedAttachmentsManager.ts
@@ -10,7 +10,7 @@ import * as fse from 'fs-extra';
 import { DURATION } from '../constants';
 import { makeObjectUrl, urlToBlob } from '../../types/attachments/VisualAttachment';
 import { getAttachmentPath } from '../../types/MessageAttachment';
-import { decryptAttachmentBuffer } from '../../node/local_attachments_encrypter';
+import { decryptAttachmentBuffer } from '../../util/local_attachments_encrypter';
 
 const urlToDecryptedBlobMap = new Map<
   string,
diff --git a/ts/node/local_attachments_encrypter.ts b/ts/util/local_attachments_encrypter.ts
similarity index 63%
rename from ts/node/local_attachments_encrypter.ts
rename to ts/util/local_attachments_encrypter.ts
index 8f1511d75..7a5b0f6a3 100644
--- a/ts/node/local_attachments_encrypter.ts
+++ b/ts/util/local_attachments_encrypter.ts
@@ -1,31 +1,32 @@
 import { isArrayBuffer } from 'lodash';
 import { fromHexToArray } from '../session/utils/String';
-import { sqlNode } from './sql';
+// import { callUtilsWorker } from '../webworker/workers/util_worker_interface';
+import { getItemById } from '../data/channelsItem';
 
 export const encryptAttachmentBuffer = async (bufferIn: ArrayBuffer) => {
   if (!isArrayBuffer(bufferIn)) {
     throw new TypeError("'bufferIn' must be an array buffer");
   }
-  const key = sqlNode.getItemById('local_attachment_encrypted_key')?.value as string | undefined;
+  const key = (await getItemById('local_attachment_encrypted_key'))?.value as string | undefined;
   if (!key) {
     throw new TypeError(
       "'encryptAttachmentBuffer' needs a key set in local_attachment_encrypted_key"
     );
   }
   const encryptingKey = fromHexToArray(key);
-  return window.callWorker('encryptAttachmentBuffer', encryptingKey, bufferIn);
+  return callUtilsWorker('encryptAttachmentBuffer', encryptingKey, bufferIn);
 };
 
 export const decryptAttachmentBuffer = async (bufferIn: ArrayBuffer): Promise<Uint8Array> => {
   if (!isArrayBuffer(bufferIn)) {
     throw new TypeError("'bufferIn' must be an array buffer");
   }
-  const key = sqlNode.getItemById('local_attachment_encrypted_key')?.value as string;
+  const key = (await getItemById('local_attachment_encrypted_key'))?.value as string;
   if (!key) {
     throw new TypeError(
       "'decryptAttachmentBuffer' needs a key set in local_attachment_encrypted_key"
     );
   }
   const encryptingKey = fromHexToArray(key);
-  return window.callWorker('decryptAttachmentBuffer', encryptingKey, bufferIn);
+  return callUtilsWorker('decryptAttachmentBuffer', encryptingKey, bufferIn);
 };
diff --git a/ts/webworker/master.ts b/ts/webworker/master.ts
deleted file mode 100644
index 3c36d1518..000000000
--- a/ts/webworker/master.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export function yo() {
-  const worker = new Worker('./ts/webworker/workers/auth.worker.js', { type: 'module' });
-  worker.postMessage({
-    question: 'The Answer to the Ultimate Question of Life, The Universe, and Everything.',
-  });
-  worker.onmessage = ({ data: { answer } }) => {
-    console.log(`The Answer to the Ultimate`, answer);
-  };
-  // const hashed = await auth.hashPassword('Super secret password', '1234');
-
-  // console.log('Hashed password:', hashed);
-}
diff --git a/ts/webworker/worker_interface.ts b/ts/webworker/worker_interface.ts
new file mode 100644
index 000000000..9ed0293c1
--- /dev/null
+++ b/ts/webworker/worker_interface.ts
@@ -0,0 +1,120 @@
+const WORKER_TIMEOUT = 60 * 1000; // one minute
+
+class TimedOutError extends Error {
+  constructor(message: string) {
+    super(message);
+    this.name = this.constructor.name;
+    if (typeof Error.captureStackTrace === 'function') {
+      Error.captureStackTrace(this, this.constructor);
+    } else {
+      this.stack = new Error(message).stack;
+    }
+  }
+}
+
+export class WorkerInterface {
+  private readonly timeout: number;
+  private readonly _DEBUG: boolean;
+  private _jobCounter: number;
+  private readonly _jobs: Record<number, any>;
+  private readonly _utilWorker: Worker;
+
+  constructor(path: string, timeout = WORKER_TIMEOUT) {
+    this._utilWorker = new Worker(path);
+    this.timeout = timeout;
+    this._jobs = Object.create(null);
+    this._DEBUG = false;
+    this._jobCounter = 0;
+
+    this._utilWorker.onmessage = e => {
+      const [jobId, errorForDisplay, result] = e.data;
+
+      const job = this._getJob(jobId);
+      if (!job) {
+        throw new Error(
+          `Received worker reply to job ${jobId}, but did not have it in our registry!`
+        );
+      }
+
+      const { resolve, reject, fnName } = job;
+
+      if (errorForDisplay) {
+        return reject(
+          new Error(`Error received from worker job ${jobId} (${fnName}): ${errorForDisplay}`)
+        );
+      }
+
+      return resolve(result);
+    };
+  }
+
+  public async callWorker(fnName: string, ...args: any) {
+    const jobId = this._makeJob(fnName);
+
+    return new Promise((resolve, reject) => {
+      this._utilWorker.postMessage([jobId, fnName, ...args]);
+
+      this._updateJob(jobId, {
+        resolve,
+        reject,
+        args: this._DEBUG ? args : null,
+      });
+
+      setTimeout(() => {
+        reject(new TimedOutError(`Worker job ${jobId} (${fnName}) timed out`));
+      }, this.timeout);
+    });
+  }
+
+  private _makeJob(fnName: string): number {
+    this._jobCounter += 1;
+    const id = this._jobCounter;
+
+    if (this._DEBUG) {
+      window.log.info(`Worker job ${id} (${fnName}) started`);
+    }
+    this._jobs[id] = {
+      fnName,
+      start: Date.now(),
+    };
+
+    return id;
+  }
+
+  private _updateJob(id: number, data: any) {
+    const { resolve, reject } = data;
+    const { fnName, start } = this._jobs[id];
+
+    this._jobs[id] = {
+      ...this._jobs[id],
+      ...data,
+      resolve: (value: any) => {
+        this._removeJob(id);
+        const end = Date.now();
+        if (this._DEBUG) {
+          window.log.info(`Worker job ${id} (${fnName}) succeeded in ${end - start}ms`);
+        }
+        return resolve(value);
+      },
+      reject: (error: any) => {
+        this._removeJob(id);
+        const end = Date.now();
+        window.log.info(`Worker job ${id} (${fnName}) failed in ${end - start}ms`);
+        return reject(error);
+      },
+    };
+  }
+
+  private _removeJob(id: number) {
+    if (this._DEBUG) {
+      this._jobs[id].complete = true;
+    } else {
+      // tslint:disable-next-line: no-dynamic-delete
+      delete this._jobs[id];
+    }
+  }
+
+  private _getJob(id: number) {
+    return this._jobs[id];
+  }
+}
diff --git a/ts/webworker/workers/auth.worker.ts b/ts/webworker/workers/auth.worker.ts
deleted file mode 100644
index 263064f7a..000000000
--- a/ts/webworker/workers/auth.worker.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import * as lodash from 'lodash';
-// import * as _ from 'lodash';
-
-const sleep = async (time: any) => new Promise(r => setTimeout(r, time));
-
-~(async function main() {
-  while (true) {
-    console.log('lodash map exists:', typeof lodash.map);
-    await sleep(1000);
-  }
-})();
-
-self.onmessage = ({ data: { question } }) => {
-  self.postMessage({
-    answer: `PLOP + ${question} + PLOP + 42`,
-  });
-};
diff --git a/ts/webworker/workers/index.ts b/ts/webworker/workers/index.ts
new file mode 100644
index 000000000..e5c972ea7
--- /dev/null
+++ b/ts/webworker/workers/index.ts
@@ -0,0 +1,2 @@
+import * as utilWorkerInterface from './util_worker_interface';
+export { utilWorkerInterface };
diff --git a/js/util_worker_tasks.js b/ts/webworker/workers/util.worker.ts
similarity index 72%
rename from js/util_worker_tasks.js
rename to ts/webworker/workers/util.worker.ts
index bf6f98649..5ee6f14a5 100644
--- a/js/util_worker_tasks.js
+++ b/ts/webworker/workers/util.worker.ts
@@ -1,4 +1,69 @@
-/* global dcodeIO, Internal, libsignal, sodium */
+import ByteBuffer from 'bytebuffer';
+import { generateKeyPair, sharedKey, verify } from 'curve25519-js';
+import { default as sodiumWrappers } from 'libsodium-wrappers-sumo';
+
+async function getSodium() {
+  await sodiumWrappers.ready;
+  return sodiumWrappers;
+}
+
+export async function decryptAttachmentBuffer(encryptingKey: Uint8Array, bufferIn: ArrayBuffer) {
+  const sodium = await getSodium();
+
+  const header = new Uint8Array(
+    bufferIn.slice(0, sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES)
+  );
+
+  const encryptedBuffer = new Uint8Array(
+    bufferIn.slice(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES)
+  );
+  try {
+    /* Decrypt the stream: initializes the state, using the key and a header */
+    const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, encryptingKey);
+    // what if ^ this call fail (? try to load as a unencrypted attachment?)
+
+    const messageTag = sodium.crypto_secretstream_xchacha20poly1305_pull(state, encryptedBuffer);
+    // we expect the final tag to be there. If not, we might have an issue with this file
+    // maybe not encrypted locally?
+    if (messageTag.tag === sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
+      return messageTag.message;
+    }
+  } catch (e) {
+    // tslint:disable: no-console
+    console.error('Failed to load the file as an encrypted one', e);
+  }
+  return new Uint8Array();
+}
+
+export async function encryptAttachmentBuffer(encryptingKey: Uint8Array, bufferIn: ArrayBuffer) {
+  const sodium = await getSodium();
+
+  try {
+    const uintArrayIn = new Uint8Array(bufferIn);
+
+    /* Set up a new stream: initialize the state and create the header */
+    const { state, header } = sodium.crypto_secretstream_xchacha20poly1305_init_push(encryptingKey);
+    /* Now, encrypt the buffer. */
+    const bufferOut = sodium.crypto_secretstream_xchacha20poly1305_push(
+      state,
+      uintArrayIn,
+      null,
+      sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
+    );
+
+    const encryptedBufferWithHeader = new Uint8Array(bufferOut.length + header.length);
+    encryptedBufferWithHeader.set(header);
+    encryptedBufferWithHeader.set(bufferOut, header.length);
+
+    return { encryptedBufferWithHeader, header };
+  } catch (e) {
+    console.error('encryptAttachmentBuffer error: ', e);
+
+    return null;
+  }
+}
+
+/* global dcodeIO, Internal, libsignal */
 /* eslint-disable no-console */
 /* eslint-disable strict */
 
@@ -10,17 +75,17 @@ const functions = {
   DecryptAESGCM,
   deriveSymmetricKey,
   encryptForPubkey,
-  generateEphemeralKeyPair,
   decryptAttachmentBuffer,
   encryptAttachmentBuffer,
   bytesFromString,
 };
+// tslint:disable: function-name
 
 onmessage = async e => {
   const [jobId, fnName, ...args] = e.data;
 
   try {
-    const fn = functions[fnName];
+    const fn = (functions as any)[fnName];
     if (!fn) {
       throw new Error(`Worker: job ${jobId} did not find function ${fnName}`);
     }
@@ -32,7 +97,7 @@ onmessage = async e => {
   }
 };
 
-function prepareErrorForPostMessage(error) {
+function prepareErrorForPostMessage(error: any) {
   if (!error) {
     return null;
   }
@@ -44,28 +109,32 @@ function prepareErrorForPostMessage(error) {
   return error.message;
 }
 
-function arrayBufferToStringBase64(arrayBuffer) {
-  return dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64');
+function arrayBufferToStringBase64(arrayBuffer: ArrayBuffer) {
+  return ByteBuffer.wrap(arrayBuffer).toString('base64');
 }
 
-function fromBase64ToArrayBuffer(base64Str) {
-  return dcodeIO.ByteBuffer.wrap(base64Str, 'base64').toArrayBuffer();
+function fromBase64ToArrayBuffer(base64Str: string) {
+  return ByteBuffer.wrap(base64Str, 'base64').toArrayBuffer();
 }
 
-function fromHexToArray(hexStr) {
-  return new Uint8Array(dcodeIO.ByteBuffer.wrap(hexStr, 'hex').toArrayBuffer());
+function fromHexToArray(hexStr: string) {
+  return new Uint8Array(ByteBuffer.wrap(hexStr, 'hex').toArrayBuffer());
 }
 
-function fromHexToArrayBuffer(hexStr) {
-  return dcodeIO.ByteBuffer.wrap(hexStr, 'hex').toArrayBuffer();
+function fromHexToArrayBuffer(hexStr: string) {
+  return ByteBuffer.wrap(hexStr, 'hex').toArrayBuffer();
 }
 
-function bytesFromString(string) {
-  return dcodeIO.ByteBuffer.wrap(string, 'utf8').toArrayBuffer();
+function bytesFromString(str: string) {
+  return ByteBuffer.wrap(str, 'utf8').toArrayBuffer();
 }
 
 // hexString, base64String, base64String
-async function verifySignature(senderPubKey, messageBase64, signatureBase64) {
+async function verifySignature(
+  senderPubKey: string,
+  messageBase64: string,
+  signatureBase64: string
+) {
   try {
     if (typeof senderPubKey !== 'string') {
       throw new Error('senderPubKey type not correct');
@@ -78,14 +147,11 @@ async function verifySignature(senderPubKey, messageBase64, signatureBase64) {
     }
     const messageData = new Uint8Array(fromBase64ToArrayBuffer(messageBase64));
     const signature = new Uint8Array(fromBase64ToArrayBuffer(signatureBase64));
-
     // verify returns true if the signature is not correct
-    const verifyRet = Internal.curve25519.verify(
-      fromHexToArray(senderPubKey),
-      messageData,
-      signature
-    );
-    if (verifyRet) {
+
+    const verifyRet = verify(fromHexToArray(senderPubKey), messageData, signature);
+
+    if (!verifyRet) {
       console.error('Invalid signature');
       return false;
     }
@@ -99,14 +165,10 @@ async function verifySignature(senderPubKey, messageBase64, signatureBase64) {
 
 const NONCE_LENGTH = 12;
 
-// uint8array, uint8array
-async function deriveSymmetricKey(x25519PublicKey, x25519PrivateKey) {
+async function deriveSymmetricKey(x25519PublicKey: Uint8Array, x25519PrivateKey: Uint8Array) {
   assertArrayBufferView(x25519PublicKey);
   assertArrayBufferView(x25519PrivateKey);
-  const ephemeralSecret = await libsignal.Curve.async.calculateAgreement(
-    x25519PublicKey.buffer,
-    x25519PrivateKey.buffer
-  );
+  const ephemeralSecret = sharedKey(x25519PrivateKey, x25519PublicKey);
 
   const salt = bytesFromString('LOKI');
 
@@ -127,20 +189,22 @@ async function deriveSymmetricKey(x25519PublicKey, x25519PrivateKey) {
 }
 
 async function generateEphemeralKeyPair() {
-  const keys = await libsignal.Curve.async.generateKeyPair();
-  // Signal protocol prepends with "0x05"
-  keys.pubKey = keys.pubKey.slice(1);
+  const ran = (await getSodium()).randombytes_buf(32);
+  const keys = generateKeyPair(ran);
   return keys;
+  // Signal protocol prepends with "0x05"
+  // keys.pubKey = keys.pubKey.slice(1);
+  // return { pubKey: keys.public, privKey: keys.private };
 }
 
-function assertArrayBufferView(val) {
+function assertArrayBufferView(val: any) {
   if (!ArrayBuffer.isView(val)) {
     throw new Error('val type not correct');
   }
 }
 
 // encryptForPubkey: hexString, payloadBytes: Uint8Array
-async function encryptForPubkey(pubkeyX25519str, payloadBytes) {
+async function encryptForPubkey(pubkeyX25519str: string, payloadBytes: Uint8Array) {
   try {
     if (typeof pubkeyX25519str !== 'string') {
       throw new Error('pubkeyX25519str type not correct');
@@ -150,18 +214,18 @@ async function encryptForPubkey(pubkeyX25519str, payloadBytes) {
     const pubkeyX25519Buffer = fromHexToArray(pubkeyX25519str);
     const symmetricKey = await deriveSymmetricKey(
       pubkeyX25519Buffer,
-      new Uint8Array(ephemeral.privKey)
+      new Uint8Array(ephemeral.private)
     );
     const ciphertext = await EncryptAESGCM(symmetricKey, payloadBytes);
 
-    return { ciphertext, symmetricKey, ephemeralKey: ephemeral.pubKey };
+    return { ciphertext, symmetricKey, ephemeralKey: ephemeral.public };
   } catch (e) {
     console.error('encryptForPubkey got an error:', e);
     return null;
   }
 }
 
-async function EncryptAESGCM(symmetricKey, plaintext) {
+async function EncryptAESGCM(symmetricKey: ArrayBuffer, plaintext: ArrayBuffer) {
   const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
 
   const key = await crypto.subtle.importKey('raw', symmetricKey, { name: 'AES-GCM' }, false, [
@@ -174,6 +238,7 @@ async function EncryptAESGCM(symmetricKey, plaintext) {
     plaintext
   );
 
+  // tslint:disable-next-line: restrict-plus-operands
   const ivAndCiphertext = new Uint8Array(NONCE_LENGTH + ciphertext.byteLength);
 
   ivAndCiphertext.set(nonce);
@@ -183,7 +248,7 @@ async function EncryptAESGCM(symmetricKey, plaintext) {
 }
 
 // uint8array, uint8array
-async function DecryptAESGCM(symmetricKey, ivAndCiphertext) {
+async function DecryptAESGCM(symmetricKey: Uint8Array, ivAndCiphertext: Uint8Array) {
   assertArrayBufferView(symmetricKey);
 
   assertArrayBufferView(ivAndCiphertext);
@@ -200,65 +265,3 @@ async function DecryptAESGCM(symmetricKey, ivAndCiphertext) {
 
   return crypto.subtle.decrypt({ name: 'AES-GCM', iv: nonce }, key, ciphertext);
 }
-
-async function getSodium() {
-  await sodium.ready;
-  return sodium;
-}
-
-// Uint8Array, ArrayBuffer
-async function decryptAttachmentBuffer(encryptingKey, bufferIn) {
-  const sodium = await getSodium();
-
-  const header = new Uint8Array(
-    bufferIn.slice(0, sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES)
-  );
-
-  const encryptedBuffer = new Uint8Array(
-    bufferIn.slice(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES)
-  );
-  try {
-    /* Decrypt the stream: initializes the state, using the key and a header */
-    const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, encryptingKey);
-    // what if ^ this call fail (? try to load as a unencrypted attachment?)
-
-    const messageTag = sodium.crypto_secretstream_xchacha20poly1305_pull(state, encryptedBuffer);
-    // we expect the final tag to be there. If not, we might have an issue with this file
-    // maybe not encrypted locally?
-    if (messageTag.tag === sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
-      return messageTag.message;
-    }
-  } catch (e) {
-    console.error('Failed to load the file as an encrypted one', e);
-  }
-  return new Uint8Array();
-}
-
-// Uint8Array, ArrayBuffer
-async function encryptAttachmentBuffer(encryptingKey, bufferIn) {
-  const sodium = await getSodium();
-
-  try {
-    const uintArrayIn = new Uint8Array(bufferIn);
-
-    /* Set up a new stream: initialize the state and create the header */
-    const { state, header } = sodium.crypto_secretstream_xchacha20poly1305_init_push(encryptingKey);
-    /* Now, encrypt the buffer. */
-    const bufferOut = sodium.crypto_secretstream_xchacha20poly1305_push(
-      state,
-      uintArrayIn,
-      null,
-      sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
-    );
-
-    const encryptedBufferWithHeader = new Uint8Array(bufferOut.length + header.length);
-    encryptedBufferWithHeader.set(header);
-    encryptedBufferWithHeader.set(bufferOut, header.length);
-
-    return { encryptedBufferWithHeader, header };
-  } catch (e) {
-    console.error('encryptAttachmentBuffer error: ', e);
-
-    return null;
-  }
-}
diff --git a/ts/webworker/workers/util_worker_interface.ts b/ts/webworker/workers/util_worker_interface.ts
new file mode 100644
index 000000000..eb2fae0bb
--- /dev/null
+++ b/ts/webworker/workers/util_worker_interface.ts
@@ -0,0 +1,9 @@
+import { WorkerInterface } from '../worker_interface';
+import { join } from 'path';
+
+const utilWorkerPath = join('./', 'ts', 'webworker', 'workers', 'util.worker.js'); //app.getAppPath()
+const utilWorkerInterface = new WorkerInterface(utilWorkerPath, 3 * 60 * 1000); //{ type: 'module' }
+
+export const callUtilsWorker = async (fnName: string, ...args: any): Promise<any> => {
+  return utilWorkerInterface.callWorker(fnName, ...args);
+};
diff --git a/ts/window.d.ts b/ts/window.d.ts
index 1e280ed57..c972b8e6e 100644
--- a/ts/window.d.ts
+++ b/ts/window.d.ts
@@ -91,7 +91,6 @@ declare global {
     LokiPushNotificationServer: any;
     getGlobalOnlineStatus: () => boolean;
     confirmationDialog: any;
-    callWorker: (fnName: string, ...args: any) => Promise<any>;
     setStartInTray: (val: boolean) => Promise<void>;
     getStartInTray: () => Promise<boolean>;
     closeAbout: () => void;