Skip to content

Commit

Permalink
Don't register the message handler with null
Browse files Browse the repository at this point in the history
  • Loading branch information
skeet70 committed Jun 11, 2024
1 parent 56f2a7b commit b4d41fd
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 149 deletions.
276 changes: 130 additions & 146 deletions src/frame/worker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<RequestMessage> = 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();
Expand All @@ -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;
}
});
6 changes: 3 additions & 3 deletions src/frame/worker/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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]);

Expand Down

0 comments on commit b4d41fd

Please sign in to comment.