From b4d41fd32a81583ba325b56d3d867d8087e4af05 Mon Sep 17 00:00:00 2001 From: Murph Murphy Date: Mon, 10 Jun 2024 19:13:10 -0600 Subject: [PATCH] Don't register the message handler with null --- src/frame/worker/index.ts | 276 +++++++++++++-------------- src/frame/worker/tests/index.test.ts | 6 +- 2 files changed, 133 insertions(+), 149 deletions(-) diff --git a/src/frame/worker/index.ts b/src/frame/worker/index.ts index 6a6671e..4150643 100644 --- a/src/frame/worker/index.ts +++ b/src/frame/worker/index.ts @@ -5,26 +5,18 @@ import * as GroupCrypto from "./GroupCrypto"; import * as SearchCrypto from "./SearchCrypto"; import * as UserCrypto from "./UserCrypto"; -type WorkerMessageCallback = (message: RequestMessage, callback: (response: ResponseMessage, transferList?: Uint8Array[]) => void) => void; - //The postMessage function in a WebWorker has a different signature than the frame postMessage function. The "correct" fix here would be to include //the "webworker" lib in the tsconfig file. But you can't use that in conjunction with the "dom" lib, so everything breaks down. So instead of trying //to do tons of work to hack those together, we're just redefining this method to match the signature we know it has. declare function postMessage(message: any, transfer?: ArrayBuffer[]): void; -class ParentThreadMessenger { - onMessageCallback!: WorkerMessageCallback; +// Be careful using `this` in this file. The index of a WebWorker seems to be special and `this` is overwritten with +// the global webworker context in many places. +export class ParentThreadMessenger { constructor() { self.addEventListener("message", this.processMessageIntoWorker, false); } - /** - * Subscribe a callback to when a new message is posted to this frame - */ - onMessage(callback: WorkerMessageCallback) { - this.onMessageCallback = callback; - } - /** * Post a response message back to the parent window * @param {ResponseMessage} data Response message to post @@ -42,16 +34,139 @@ class ParentThreadMessenger { ); } + /* tslint:disable cyclomatic-complexity */ + onMessageCallback(data: RequestMessage, callback: (message: ResponseMessage, transferList?: Uint8Array[]) => void): void { + const errorHandler = errorResponse.bind(null, callback); + switch (data.type) { + case "USER_DEVICE_KEYGEN": + const {message} = data; + return UserCrypto.generateDeviceAndSigningKeys( + message.jwtToken, + message.passcode, + message.keySalt, + message.encryptedPrivateUserKey, + message.publicUserKey + ).engage(errorHandler, (keys) => callback({type: "USER_DEVICE_KEYGEN_RESPONSE", message: keys})); + case "NEW_USER_AND_DEVICE_KEYGEN": + return UserCrypto.generateNewUserAndDeviceKeys(data.message.passcode).engage(errorHandler, (keys) => + callback({type: "NEW_USER_AND_DEVICE_KEYGEN_RESPONSE", message: keys}) + ); + case "NEW_USER_KEYGEN": + return UserCrypto.generateNewUserKeys(data.message.passcode).engage(errorHandler, (keys) => + callback({type: "NEW_USER_KEYGEN_RESPONSE", message: keys}) + ); + case "DECRYPT_LOCAL_KEYS": + return UserCrypto.decryptDeviceAndSigningKeys( + data.message.encryptedDeviceKey, + data.message.encryptedSigningKey, + data.message.symmetricKey, + data.message.nonce + ).engage(errorHandler, (deviceAndSigningKeys) => callback({type: "DECRYPT_LOCAL_KEYS_RESPONSE", message: deviceAndSigningKeys})); + case "ROTATE_USER_PRIVATE_KEY": + return UserCrypto.rotatePrivateKey(data.message.passcode, data.message.encryptedPrivateUserKey).engage(errorHandler, (userRotationResult) => + callback({type: "ROTATE_USER_PRIVATE_KEY_RESPONSE", message: userRotationResult}) + ); + case "CHANGE_USER_PASSCODE": + return UserCrypto.changeUsersPasscode(data.message.currentPasscode, data.message.newPasscode, data.message.encryptedPrivateUserKey).engage( + errorHandler, + (encryptedPrivateKey) => callback({type: "CHANGE_USER_PASSCODE_RESPONSE", message: encryptedPrivateKey}) + ); + case "SIGNATURE_GENERATION": { + const signature = UserCrypto.signRequestPayload( + data.message.segmentID, + data.message.userID, + data.message.signingKeys, + data.message.method, + data.message.url, + data.message.body + ); + return callback({type: "SIGNATURE_GENERATION_RESPONSE", message: signature}); + } + case "DOCUMENT_ENCRYPT": + return DocumentCrypto.encryptDocument( + data.message.document, + data.message.userKeyList, + data.message.groupKeyList, + data.message.signingKeys + ).engage(errorHandler, (encryptedContent) => + callback({type: "DOCUMENT_ENCRYPT_RESPONSE", message: encryptedContent}, [encryptedContent.encryptedDocument.content]) + ); + case "DOCUMENT_DECRYPT": + return DocumentCrypto.decryptDocument(data.message.document, data.message.encryptedSymmetricKey, data.message.privateKey).engage( + errorHandler, + (decryptedDocument) => callback({type: "DOCUMENT_DECRYPT_RESPONSE", message: {decryptedDocument}}, [decryptedDocument]) + ); + case "DOCUMENT_REENCRYPT": + return DocumentCrypto.reEncryptDocument(data.message.document, data.message.existingDocumentSymmetricKey, data.message.privateKey).engage( + errorHandler, + (encryptedDocument) => callback({type: "DOCUMENT_REENCRYPT_RESPONSE", message: {encryptedDocument}}, [encryptedDocument.content]) + ); + case "DOCUMENT_ENCRYPT_TO_KEYS": + return DocumentCrypto.encryptToKeys( + data.message.symmetricKey, + data.message.userKeyList, + data.message.groupKeyList, + data.message.privateKey, + data.message.signingKeys + ).engage(errorHandler, (keyList) => callback({type: "DOCUMENT_ENCRYPT_TO_KEYS_RESPONSE", message: keyList})); + case "GROUP_CREATE": + return GroupCrypto.createGroup(data.message.signingKeys, data.message.memberList, data.message.adminList).engage(errorHandler, (group) => + callback({type: "GROUP_CREATE_RESPONSE", message: group}) + ); + case "ROTATE_GROUP_PRIVATE_KEY": + return GroupCrypto.rotatePrivateKey( + data.message.encryptedGroupKey, + data.message.adminList, + data.message.userPrivateMasterKey, + data.message.signingKeys + ).engage(errorHandler, (result) => callback({type: "ROTATE_GROUP_PRIVATE_KEY_RESPONSE", message: result})); + case "GROUP_ADD_ADMINS": + return GroupCrypto.addAdminsToGroup( + data.message.encryptedGroupKey, + data.message.groupPublicKey, + data.message.groupID, + data.message.userKeyList, + data.message.adminPrivateKey, + data.message.signingKeys + ).engage(errorHandler, (result) => callback({type: "GROUP_ADD_ADMINS_RESPONSE", message: result})); + case "GROUP_ADD_MEMBERS": + return GroupCrypto.addMembersToGroup( + data.message.encryptedGroupKey, + data.message.groupPublicKey, + data.message.groupID, + data.message.userKeyList, + data.message.adminPrivateKey, + data.message.signingKeys + ).engage(errorHandler, (result) => callback({type: "GROUP_ADD_MEMBERS_RESPONSE", message: result})); + case "SEARCH_TOKENIZE_DATA": { + const message = SearchCrypto.tokenizeData(data.message.value, data.message.salt, data.message.partitionId); + return callback({type: "SEARCH_TOKENIZE_STRING_RESPONSE", message}); + } + case "SEARCH_TOKENIZE_QUERY": { + const message = SearchCrypto.tokenizeQuery(data.message.value, data.message.salt, data.message.partitionId); + return callback({type: "SEARCH_TOKENIZE_STRING_RESPONSE", message}); + } + case "SEARCH_TRANSLITERATE_STRING": { + const message = SearchCrypto.transliterateString(data.message); + return callback({type: "SEARCH_TRANSLITERATE_STRING_RESPONSE", message}); + } + default: + //Force TS to tell us if we ever create a new request type that we don't handle here + const exhaustiveCheck: never = data; + return exhaustiveCheck; + } + } + /** * Process a received message into the iFrame * @param {MessageEvent} event Frame postMessage event object */ - processMessageIntoWorker = (event: MessageEvent) => { + processMessageIntoWorker(event: MessageEvent) { const message: WorkerEvent = event.data; - this.onMessageCallback(message.data, (responseData: ResponseMessage, transferList?: Uint8Array[]) => { - this.postMessageToParent(responseData, message.replyID, transferList); + ParentThreadMessenger.prototype.onMessageCallback(message.data, (responseData: ResponseMessage, transferList?: Uint8Array[]) => { + ParentThreadMessenger.prototype.postMessageToParent(responseData, message.replyID, transferList); }); - }; + } } export const messenger = new ParentThreadMessenger(); @@ -68,134 +183,3 @@ function errorResponse(callback: (response: ErrorResponse) => void, error: SDKEr }, }); } - -/* tslint:disable cyclomatic-complexity */ -messenger.onMessage((data: RequestMessage, callback: (message: ResponseMessage, transferList?: Uint8Array[]) => void): void => { - const errorHandler = errorResponse.bind(null, callback); - switch (data.type) { - case "USER_DEVICE_KEYGEN": - const {message} = data; - return UserCrypto.generateDeviceAndSigningKeys( - message.jwtToken, - message.passcode, - message.keySalt, - message.encryptedPrivateUserKey, - message.publicUserKey - ).engage(errorHandler, (keys) => callback({type: "USER_DEVICE_KEYGEN_RESPONSE", message: keys})); - case "NEW_USER_AND_DEVICE_KEYGEN": - return UserCrypto.generateNewUserAndDeviceKeys(data.message.passcode).engage(errorHandler, (keys) => - callback({type: "NEW_USER_AND_DEVICE_KEYGEN_RESPONSE", message: keys}) - ); - case "NEW_USER_KEYGEN": - return UserCrypto.generateNewUserKeys(data.message.passcode).engage(errorHandler, (keys) => - callback({type: "NEW_USER_KEYGEN_RESPONSE", message: keys}) - ); - case "DECRYPT_LOCAL_KEYS": - return UserCrypto.decryptDeviceAndSigningKeys( - data.message.encryptedDeviceKey, - data.message.encryptedSigningKey, - data.message.symmetricKey, - data.message.nonce - ).engage(errorHandler, (deviceAndSigningKeys) => callback({type: "DECRYPT_LOCAL_KEYS_RESPONSE", message: deviceAndSigningKeys})); - case "ROTATE_USER_PRIVATE_KEY": - return UserCrypto.rotatePrivateKey(data.message.passcode, data.message.encryptedPrivateUserKey).engage(errorHandler, (userRotationResult) => - callback({type: "ROTATE_USER_PRIVATE_KEY_RESPONSE", message: userRotationResult}) - ); - case "CHANGE_USER_PASSCODE": - return UserCrypto.changeUsersPasscode( - data.message.currentPasscode, - data.message.newPasscode, - data.message.encryptedPrivateUserKey - ).engage(errorHandler, (encryptedPrivateKey) => callback({type: "CHANGE_USER_PASSCODE_RESPONSE", message: encryptedPrivateKey})); - case "SIGNATURE_GENERATION": - { - const signature = UserCrypto.signRequestPayload( - data.message.segmentID, - data.message.userID, - data.message.signingKeys, - data.message.method, - data.message.url, - data.message.body - ); - return callback({type: "SIGNATURE_GENERATION_RESPONSE", message: signature}); - } - case "DOCUMENT_ENCRYPT": - return DocumentCrypto.encryptDocument( - data.message.document, - data.message.userKeyList, - data.message.groupKeyList, - data.message.signingKeys - ).engage(errorHandler, (encryptedContent) => - callback({type: "DOCUMENT_ENCRYPT_RESPONSE", message: encryptedContent}, [encryptedContent.encryptedDocument.content]) - ); - case "DOCUMENT_DECRYPT": - return DocumentCrypto.decryptDocument(data.message.document, data.message.encryptedSymmetricKey, data.message.privateKey).engage( - errorHandler, - (decryptedDocument) => callback({type: "DOCUMENT_DECRYPT_RESPONSE", message: {decryptedDocument}}, [decryptedDocument]) - ); - case "DOCUMENT_REENCRYPT": - return DocumentCrypto.reEncryptDocument( - data.message.document, - data.message.existingDocumentSymmetricKey, - data.message.privateKey - ).engage(errorHandler, (encryptedDocument) => - callback({type: "DOCUMENT_REENCRYPT_RESPONSE", message: {encryptedDocument}}, [encryptedDocument.content]) - ); - case "DOCUMENT_ENCRYPT_TO_KEYS": - return DocumentCrypto.encryptToKeys( - data.message.symmetricKey, - data.message.userKeyList, - data.message.groupKeyList, - data.message.privateKey, - data.message.signingKeys - ).engage(errorHandler, (keyList) => callback({type: "DOCUMENT_ENCRYPT_TO_KEYS_RESPONSE", message: keyList})); - case "GROUP_CREATE": - return GroupCrypto.createGroup(data.message.signingKeys, data.message.memberList, data.message.adminList).engage(errorHandler, (group) => - callback({type: "GROUP_CREATE_RESPONSE", message: group}) - ); - case "ROTATE_GROUP_PRIVATE_KEY": - return GroupCrypto.rotatePrivateKey( - data.message.encryptedGroupKey, - data.message.adminList, - data.message.userPrivateMasterKey, - data.message.signingKeys - ).engage(errorHandler, (result) => callback({type: "ROTATE_GROUP_PRIVATE_KEY_RESPONSE", message: result})); - case "GROUP_ADD_ADMINS": - return GroupCrypto.addAdminsToGroup( - data.message.encryptedGroupKey, - data.message.groupPublicKey, - data.message.groupID, - data.message.userKeyList, - data.message.adminPrivateKey, - data.message.signingKeys - ).engage(errorHandler, (result) => callback({type: "GROUP_ADD_ADMINS_RESPONSE", message: result})); - case "GROUP_ADD_MEMBERS": - return GroupCrypto.addMembersToGroup( - data.message.encryptedGroupKey, - data.message.groupPublicKey, - data.message.groupID, - data.message.userKeyList, - data.message.adminPrivateKey, - data.message.signingKeys - ).engage(errorHandler, (result) => callback({type: "GROUP_ADD_MEMBERS_RESPONSE", message: result})); - case "SEARCH_TOKENIZE_DATA": - { - const message = SearchCrypto.tokenizeData(data.message.value, data.message.salt, data.message.partitionId); - return callback({type: "SEARCH_TOKENIZE_STRING_RESPONSE", message}); - } - case "SEARCH_TOKENIZE_QUERY": - { - const message = SearchCrypto.tokenizeQuery(data.message.value, data.message.salt, data.message.partitionId); - return callback({type: "SEARCH_TOKENIZE_STRING_RESPONSE", message}); - } - case "SEARCH_TRANSLITERATE_STRING": - { - const message = SearchCrypto.transliterateString(data.message); - return callback({type: "SEARCH_TRANSLITERATE_STRING_RESPONSE", message}); - } - default: - //Force TS to tell us if we ever create a new request type that we don't handle here - const exhaustiveCheck: never = data; - return exhaustiveCheck; - } -}); diff --git a/src/frame/worker/tests/index.test.ts b/src/frame/worker/tests/index.test.ts index 406bf7e..56e90b1 100644 --- a/src/frame/worker/tests/index.test.ts +++ b/src/frame/worker/tests/index.test.ts @@ -1,5 +1,5 @@ import Future from "futurejs"; -import {messenger} from "../"; +import {messenger, ParentThreadMessenger} from "../"; import SDKError from "../../../lib/SDKError"; import * as DocumentCrypto from "../DocumentCrypto"; import * as GroupCrypto from "../GroupCrypto"; @@ -40,11 +40,11 @@ describe("worker index", () => { it("invokes message callback with event data when processing", () => { jest.spyOn(window, "postMessage").mockImplementation(); const bytes = new Uint8Array(3); - jest.spyOn(messenger, "onMessageCallback"); + jest.spyOn(ParentThreadMessenger.prototype, "onMessageCallback"); messenger.processMessageIntoWorker({data: {data: {foo: "bar"}, replyID: 38}} as MessageEvent); - expect(messenger.onMessageCallback).toHaveBeenCalledWith({foo: "bar"}, expect.any(Function)); + expect(ParentThreadMessenger.prototype.onMessageCallback).toHaveBeenCalledWith({foo: "bar"}, expect.any(Function)); const callback = (messenger.onMessageCallback as unknown as jest.SpyInstance).mock.calls[0][1]; callback({response: "data"}, [bytes]);