@crypticat/core
Advanced tools
Comparing version
/// <reference types="node" /> | ||
import WebSocket from 'isomorphic-ws'; | ||
import { DiffieHellman, Cipher, Decipher } from 'crypto'; | ||
@@ -10,2 +9,3 @@ import { EventEmitter } from 'events'; | ||
decipher: Decipher; | ||
nick: string | null; | ||
} | ||
@@ -18,19 +18,22 @@ export interface LinkState { | ||
declare interface CrypticatClient { | ||
on(event: 'message', listener: (nick: string, content: string) => void): this; | ||
on(event: 'disconnect', listener: () => void): this; | ||
on(event: 'message', listener: (userUid: string, nick: string | null, content: string) => void): this; | ||
on(event: 'close', listener: () => void): this; | ||
on(event: 'error', listener: (error: Error) => void): this; | ||
on(event: 'connect', listener: (uid: string, nick: string | null) => void): this; | ||
on(event: 'disconnect', listener: (uid: string, nick: string | null) => void): this; | ||
} | ||
declare class CrypticatClient extends EventEmitter { | ||
linkState: LinkState; | ||
room: string | null; | ||
ws?: WebSocket; | ||
private linkState; | ||
private linkedNextYet; | ||
private nick; | ||
private ws?; | ||
constructor(); | ||
connect(address: string): Promise<void>; | ||
sendEncrypted(dir: 'next' | 'prev', message: { | ||
action: string; | ||
payload: object; | ||
}): void; | ||
joinRoom(name: string): Promise<void>; | ||
sendMessage(nick: string, content: string): void; | ||
sendMessage(content: string): void; | ||
setNick(nick: string | null): void; | ||
getNick(): string | null; | ||
private assertWs; | ||
private sendEncrypted; | ||
} | ||
export { CrypticatClient }; |
@@ -29,3 +29,4 @@ "use strict"; | ||
}; | ||
this.room = null; | ||
this.linkedNextYet = false; | ||
this.nick = ''; | ||
} | ||
@@ -53,74 +54,104 @@ connect(address) { | ||
this.ws.addEventListener('message', (message) => { | ||
lib_1.assertDefined(this.ws); | ||
const { action, payload } = JSON.parse(message.data); | ||
switch (action) { | ||
case 'GENERATE_KEY': { | ||
this.linkState.df = crypto_1.default.createDiffieHellman(Buffer.from(payload.prime, 'hex')); | ||
const key = this.linkState.df.generateKeys(); | ||
this.ws.send(JSON.stringify({ | ||
action: 'KEY', | ||
payload: { | ||
key: key.toString('hex') | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j; | ||
try { | ||
lib_1.assertDefined(this.ws); | ||
const { action, payload } = JSON.parse(message.data); | ||
switch (action) { | ||
case 'GENERATE_KEY': { | ||
lib_1.assertDefined(payload.prime); | ||
this.linkState.df = crypto_1.default.createDiffieHellman(Buffer.from(payload.prime, 'hex')); | ||
const key = this.linkState.df.generateKeys(); | ||
this.ws.send(JSON.stringify({ | ||
action: 'KEY', | ||
payload: { | ||
key: key.toString('hex') | ||
} | ||
})); | ||
break; | ||
} | ||
case 'PREV_LINK': | ||
case 'NEXT_LINK': { | ||
lib_1.assertDefined(payload.key); | ||
lib_1.assertDefined(payload.uid); | ||
const dir = action === 'PREV_LINK' ? 'prev' : 'next'; | ||
if (action === 'NEXT_LINK' && !this.linkedNextYet) { | ||
this.linkedNextYet = true; | ||
this.emit('connect', payload.uid, (_a = payload.nick) !== null && _a !== void 0 ? _a : null); | ||
this.sendEncrypted('prev', { | ||
action: 'CONNECT', | ||
payload: { uid: payload.uid, nick: payload.nick } | ||
}); | ||
} | ||
})); | ||
break; | ||
} | ||
case 'PREV_LINK': | ||
case 'NEXT_LINK': { | ||
const dir = action === 'PREV_LINK' ? 'prev' : 'next'; | ||
lib_1.assertDefined(this.linkState.df); | ||
const secret = this.linkState.df.computeSecret(Buffer.from(payload.key, 'hex')); | ||
this.linkState[dir] = { | ||
secret, | ||
cipher: crypto_1.default.createCipheriv('aes-256-gcm', secret, iv), | ||
decipher: crypto_1.default.createDecipheriv('aes-256-gcm', secret, iv), | ||
uid: payload.uid | ||
}; | ||
break; | ||
} | ||
case 'CLEAR_PREV_LINK': | ||
case 'CLEAR_NEXT_LINK': { | ||
const dir = action === 'CLEAR_PREV_LINK' ? 'prev' : 'next'; | ||
delete this.linkState[dir]; | ||
break; | ||
} | ||
case 'ENCRYPTED_PAYLOAD': { | ||
if (payload.dir !== 'prev' && payload.dir !== 'next') | ||
else if (this.linkedNextYet) { | ||
console.log('DISCONNECTED', dir); | ||
lib_1.assertDefined(this.linkState[dir]); | ||
this.emit('disconnect', (_b = this.linkState[dir]) === null || _b === void 0 ? void 0 : _b.uid, (_d = (_c = this.linkState[dir]) === null || _c === void 0 ? void 0 : _c.nick) !== null && _d !== void 0 ? _d : null); | ||
this.sendEncrypted(dir === 'prev' ? 'next' : 'prev', { | ||
action: 'DISCONNECT', | ||
payload: { uid: (_e = this.linkState[dir]) === null || _e === void 0 ? void 0 : _e.uid, nick: (_f = this.linkState[dir]) === null || _f === void 0 ? void 0 : _f.nick } | ||
}); | ||
} | ||
lib_1.assertDefined(this.linkState.df); | ||
const secret = this.linkState.df.computeSecret(Buffer.from(payload.key, 'hex')); | ||
this.linkState[dir] = { | ||
secret, | ||
cipher: crypto_1.default.createCipheriv('aes-256-gcm', secret, iv), | ||
decipher: crypto_1.default.createDecipheriv('aes-256-gcm', secret, iv), | ||
uid: payload.uid, | ||
nick: payload.nick | ||
}; | ||
break; | ||
const inverseLink = this.linkState[payload.dir === 'prev' ? 'next' : 'prev']; | ||
lib_1.assertDefined(inverseLink); | ||
const decryptedMessage = inverseLink.decipher.update(Buffer.from(payload.encryptedMessage, 'hex')); | ||
const parsed = JSON.parse(decryptedMessage.toString()); | ||
{ | ||
const { action, payload } = parsed; | ||
switch (action) { | ||
case 'MESSAGE': { | ||
this.emit('message', payload.nick, payload.content); | ||
break; | ||
} | ||
case 'CLEAR_PREV_LINK': | ||
case 'CLEAR_NEXT_LINK': { | ||
const dir = action === 'CLEAR_PREV_LINK' ? 'prev' : 'next'; | ||
delete this.linkState[dir]; | ||
break; | ||
} | ||
case 'ENCRYPTED_PAYLOAD': { | ||
if (payload.dir !== 'prev' && payload.dir !== 'next') | ||
break; | ||
const inverseLink = this.linkState[payload.dir === 'prev' ? 'next' : 'prev']; | ||
lib_1.assertDefined(inverseLink); | ||
const decryptedMessage = inverseLink.decipher.update(Buffer.from(payload.encryptedMessage, 'hex')); | ||
const parsed = JSON.parse(decryptedMessage.toString()); | ||
lib_1.assertDefined(payload.directFrom); | ||
const originalFrom = (_g = parsed.from) !== null && _g !== void 0 ? _g : payload.directFrom; | ||
{ | ||
const { action, payload } = parsed.message; | ||
switch (action) { | ||
case 'MESSAGE': { | ||
lib_1.assertDefined(payload.content); | ||
this.emit('message', originalFrom, payload.nick, payload.content); | ||
if (inverseLink.uid === originalFrom) { | ||
inverseLink.nick = payload.nick; | ||
} | ||
break; | ||
} | ||
case 'CONNECT': { | ||
lib_1.assertDefined(payload.uid); | ||
this.emit('connect', payload.uid, (_h = payload.nick) !== null && _h !== void 0 ? _h : null); | ||
break; | ||
} | ||
case 'DISCONNECT': { | ||
lib_1.assertDefined(payload.uid); | ||
this.emit('disconnect', payload.uid, (_j = payload.nick) !== null && _j !== void 0 ? _j : null); | ||
break; | ||
} | ||
} | ||
} | ||
const nextDir = payload.dir; | ||
if (this.linkState[nextDir]) | ||
this.sendEncrypted(nextDir, parsed.message, originalFrom); | ||
break; | ||
} | ||
const nextDir = payload.dir; | ||
if (this.linkState[nextDir]) | ||
this.sendEncrypted(nextDir, parsed); | ||
break; | ||
} | ||
} | ||
catch (error) { | ||
this.emit('error', error); | ||
} | ||
}); | ||
this.ws.addEventListener('close', () => this.emit('disconnect')); | ||
this.ws.addEventListener('close', () => this.emit('close')); | ||
}); | ||
} | ||
sendEncrypted(dir, message) { | ||
this.assertWs(this.ws); | ||
const link = this.linkState[dir]; | ||
lib_1.assertDefined(link); | ||
this.ws.send(JSON.stringify({ | ||
action: 'DISPATCH_ENCRYPTED', | ||
payload: { | ||
recipient: link.uid, | ||
encryptedMessage: link.cipher.update(JSON.stringify(message)).toString('hex'), | ||
dir | ||
} | ||
})); | ||
} | ||
joinRoom(name) { | ||
@@ -131,3 +162,3 @@ return __awaiter(this, void 0, void 0, function* () { | ||
action: 'JOIN_ROOM', | ||
payload: { name } | ||
payload: { name, nick: this.nick } | ||
})); | ||
@@ -137,7 +168,7 @@ this.linkState.df = null; | ||
this.linkState.prev = null; | ||
this.linkedNextYet = false; | ||
yield lib_1.waitFor(this.ws, 'ROOM_READY'); | ||
this.room = name; | ||
}); | ||
} | ||
sendMessage(nick, content) { | ||
sendMessage(content) { | ||
this.assertWs(this.ws); | ||
@@ -147,3 +178,3 @@ if (this.linkState.next) { | ||
action: 'MESSAGE', | ||
payload: { content, nick } | ||
payload: { content, nick: this.nick } | ||
}); | ||
@@ -154,13 +185,37 @@ } | ||
action: 'MESSAGE', | ||
payload: { content, nick } | ||
payload: { content, nick: this.nick } | ||
}); | ||
} | ||
} | ||
setNick(nick) { | ||
this.nick = nick; | ||
} | ||
getNick() { | ||
return this.nick; | ||
} | ||
assertWs(ws) { | ||
if (ws === undefined || ws.readyState !== 1) { | ||
throw new assert_1.AssertionError({ message: 'The websocket is not open!' }); | ||
if ((ws === null || ws === void 0 ? void 0 : ws.readyState) !== 1) { | ||
throw new assert_1.AssertionError({ | ||
message: 'The websocket is not open!', | ||
expected: 1, | ||
actual: ws === null || ws === void 0 ? void 0 : ws.readyState | ||
}); | ||
} | ||
} | ||
sendEncrypted(dir, message, from) { | ||
this.assertWs(this.ws); | ||
const link = this.linkState[dir]; | ||
if (!link) | ||
return; | ||
this.ws.send(JSON.stringify({ | ||
action: 'DISPATCH_ENCRYPTED', | ||
payload: { | ||
recipient: link.uid, | ||
encryptedMessage: link.cipher.update(JSON.stringify({ message, from })).toString('hex'), | ||
dir | ||
} | ||
})); | ||
} | ||
} | ||
exports.CrypticatClient = CrypticatClient; | ||
//# sourceMappingURL=client.js.map |
@@ -16,3 +16,6 @@ "use strict"; | ||
if (thing === null || thing === undefined) { | ||
throw new assert_1.AssertionError({ message: `Expected 'thing' to be defined, but received ${thing}` }); | ||
throw new assert_1.AssertionError({ | ||
message: `Expected thing to be defined`, | ||
actual: thing | ||
}); | ||
} | ||
@@ -19,0 +22,0 @@ } |
/// <reference types="node" /> | ||
import WebSocket from 'isomorphic-ws'; | ||
import { EventEmitter } from 'events'; | ||
@@ -13,3 +12,3 @@ import { Server as HttpServer } from 'http'; | ||
on(event: 'connect', listener: (uid: string) => void): this; | ||
on(event: 'join', listener: (uid: string, room: string) => void): this; | ||
on(event: 'join', listener: (uid: string, room: string, nick: string | null) => void): this; | ||
on(event: 'dispatch', listener: (fromUid: string, toUid: string) => void): this; | ||
@@ -19,7 +18,5 @@ on(event: 'disconnect', listener: (uid: string) => void): this; | ||
declare class CrypticatServer extends EventEmitter { | ||
sockets: { | ||
[key: string]: WebSocket; | ||
}; | ||
rooms: Room[]; | ||
wss?: WebSocket.Server; | ||
private sockets; | ||
private rooms; | ||
private wss?; | ||
constructor(); | ||
@@ -26,0 +23,0 @@ listen(location: HttpServer | HttpsServer | number, path?: string): void; |
@@ -95,9 +95,10 @@ "use strict"; | ||
ws.addEventListener('message', (message) => __awaiter(this, void 0, void 0, function* () { | ||
var _a; | ||
const { action, payload } = JSON.parse(message.data); | ||
switch (action) { | ||
case 'JOIN_ROOM': { | ||
this.emit('join', uid, payload.name); | ||
if (room) { | ||
lib_1.assertDefined(payload.name); | ||
this.emit('join', uid, payload.name, (_a = payload.nick) !== null && _a !== void 0 ? _a : null); | ||
if (room) | ||
yield leaveRoom(); | ||
} | ||
const existingRoom = this.rooms.find(({ name }) => name === payload.name); | ||
@@ -135,3 +136,3 @@ if (!existingRoom) { | ||
action: 'NEXT_LINK', | ||
payload: { key: bobKey, uid } | ||
payload: { key: bobKey, uid, nick: payload.nick } | ||
})); | ||
@@ -147,2 +148,5 @@ existingRoom.links.unshift(uid); | ||
case 'DISPATCH_ENCRYPTED': { | ||
lib_1.assertDefined(payload.recipient); | ||
lib_1.assertDefined(payload.encryptedMessage); | ||
lib_1.assertDefined(payload.dir); | ||
this.emit('dispatch', uid, payload.recipient); | ||
@@ -152,4 +156,4 @@ const recipientWs = this.sockets[payload.recipient]; | ||
action: 'ENCRYPTED_PAYLOAD', | ||
from: uid, | ||
payload: { | ||
directFrom: uid, | ||
encryptedMessage: payload.encryptedMessage, | ||
@@ -156,0 +160,0 @@ dir: payload.dir |
{ | ||
"name": "@crypticat/core", | ||
"version": "0.1.10", | ||
"version": "0.2.0", | ||
"description": "Crypticat's core client and server implementation", | ||
@@ -5,0 +5,0 @@ "author": "Kognise <felix.mattick@gmail.com>", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
36815
21.39%478
14.9%