pocket-messaging
Version:
A small cryptographic messaging library written in TypeScript both for browser and nodejs supporting TCP and WebSockets
125 lines (95 loc) • 3.38 kB
text/typescript
import {box, unbox, init} from "./Crypto";
import {
ClientInterface,
SocketDataCallback,
WrappedClient,
} from "pocket-sockets";
/**
* Wrap an already connected and handshooked socket client as encrypted.
*
*/
export class EncryptedClient extends WrappedClient {
protected handlers: {[type: string]: ((data?: any) => void)[]} = {};
protected incomingData: Buffer;
constructor(client: ClientInterface,
protected outgoingKey: Buffer,
protected outgoingNonce: Buffer,
protected incomingKey: Buffer,
protected incomingNonce: Buffer,
protected peerPublicKey: Buffer) {
super(client);
this.incomingData = Buffer.alloc(0);
}
public async init() {
await super.init();
await init(); // Init sodium
}
public unRead(data: Buffer) { //eslint-disable-line @typescript-eslint/no-unused-vars
throw new Error("unRead function not available in EncryptedClient");
}
public getPeerPublicKey(): Buffer {
return this.peerPublicKey;
}
protected decryptData() {
const ret = unbox(this.incomingData, this.incomingNonce, this.incomingKey);
if (!ret) {
// Not enough data in chunk
return;
}
const [decrypted, nextNonce, bytesConsumed] = ret;
this.incomingNonce = nextNonce;
this.incomingData = this.incomingData.slice(bytesConsumed);
this.triggerEvent("data", decrypted);
if (this.incomingData.length > 0) {
this.decryptData();
}
}
public send(data: Buffer) {
if (Buffer.isBuffer(data)) {
// encrypt data
const [encryptedData, nextNonce] = box(data, this.outgoingNonce, this.outgoingKey);
this.outgoingNonce = nextNonce;
this.client.send(encryptedData);
}
else {
throw new Error("EncryptedClient does not work with text data");
}
}
public onData(fn: SocketDataCallback) {
this.hookEvent("data", fn);
if ((this.handlers["data"] ?? []).length === 1) {
this.client.onData( this.handleOnData );
}
}
public offData(fn: SocketDataCallback) {
this.unhookEvent("data", fn);
if ((this.handlers["data"] ?? []).length === 0) {
this.client.offData( this.handleOnData );
}
}
protected handleOnData = async (data: Buffer | string) => {
if (Buffer.isBuffer(data)) {
this.incomingData = Buffer.concat([this.incomingData, data]);
this.decryptData();
}
else {
throw new Error("EncryptedClient does not work with text data");
}
};
protected hookEvent(type: string, callback: (...args: any[]) => void) {
const cbs = this.handlers[type] || [];
this.handlers[type] = cbs;
cbs.push(callback);
}
protected unhookEvent(type: string, callback: (...args: any[]) => void) {
const cbs = (this.handlers[type] || []).filter( (cb: (data?: any[]) => void) =>
callback !== cb );
this.handlers[type] = cbs;
}
protected triggerEvent(type: string, ...args: any[]) {
const cbs = this.handlers[type] || [];
cbs.forEach( callback => {
callback(...args);
});
}
}