diff --git a/package.json b/package.json index 08c1b69..6e943bb 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ ], "scripts": { "prepare": "npm run build", - "test": "mocha --timeout 30000 -r ts-node/register src/*/Tests.ts", + "test": "mocha --timeout 60000 -r ts-node/register src/*/Tests.ts", "lint": "eslint '*/*.ts' --quiet --fix", "build": "tsc", "example": "npx ts-node Example/example.ts", diff --git a/src/Binary/Decoder.ts b/src/Binary/Decoder.ts index d2aa484..3d481db 100644 --- a/src/Binary/Decoder.ts +++ b/src/Binary/Decoder.ts @@ -6,7 +6,7 @@ export default class Decoder { checkEOS(length: number) { if (this.index + length > this.buffer.length) { - throw 'end of stream' + throw new Error('end of stream') } } next() { @@ -48,7 +48,7 @@ export default class Decoder { if (value >= 0 && value < 16) { return value < 10 ? '0'.charCodeAt(0) + value : 'A'.charCodeAt(0) + value - 10 } - throw 'invalid hex: ' + value + throw new Error('invalid hex: ' + value) } unpackNibble(value: number) { if (value >= 0 && value <= 9) { @@ -62,7 +62,7 @@ export default class Decoder { case 15: return '\0'.charCodeAt(0) default: - throw 'invalid nibble: ' + value + throw new Error('invalid nibble: ' + value) } } unpackByte(tag: number, value: number) { @@ -71,7 +71,7 @@ export default class Decoder { } else if (tag === WA.Tags.HEX_8) { return this.unpackHex(value) } else { - throw 'unknown tag: ' + tag + throw new Error('unknown tag: ' + tag) } } readPacked8(tag: number) { @@ -90,7 +90,7 @@ export default class Decoder { } readRangedVarInt(min, max, description = 'unknown') { // value = - throw 'WTF; should not be called' + throw new Error('WTF; should not be called') } isListTag(tag: number) { return tag === WA.Tags.LIST_EMPTY || tag === WA.Tags.LIST_8 || tag === WA.Tags.LIST_16 @@ -104,7 +104,7 @@ export default class Decoder { case WA.Tags.LIST_16: return this.readInt(2) default: - throw 'invalid tag for list size: ' + tag + throw new Error('invalid tag for list size: ' + tag) } } @@ -134,12 +134,12 @@ export default class Decoder { if (i && j) { return i + '@' + j } - throw 'invalid jid pair: ' + i + ', ' + j + throw new Error('invalid jid pair: ' + i + ', ' + j) case WA.Tags.HEX_8: case WA.Tags.NIBBLE_8: return this.readPacked8(tag) default: - throw 'invalid string with tag: ' + tag + throw new Error('invalid string with tag: ' + tag) } } readAttributes(n: number) { @@ -161,14 +161,14 @@ export default class Decoder { } getToken(index: number) { if (index < 3 || index >= WA.SingleByteTokens.length) { - throw 'invalid token index: ' + index + throw new Error('invalid token index: ' + index) } return WA.SingleByteTokens[index] } getTokenDouble(index1, index2): string { const n = 256 * index1 + index2 if (n < 0 || n > WA.DoubleByteTokens.length) { - throw 'invalid double token index: ' + n + throw new Error('invalid double token index: ' + n) } return WA.DoubleByteTokens[n] } @@ -176,12 +176,12 @@ export default class Decoder { const listSize = this.readListSize(this.readByte()) const descrTag = this.readByte() if (descrTag === WA.Tags.STREAM_END) { - throw 'unexpected stream end' + throw new Error('unexpected stream end') } const descr = this.readString(descrTag) if (listSize === 0 || !descr) { - throw 'invalid node' + throw new Error('invalid node') } const attrs = this.readAttributes((listSize - 1) >> 1) diff --git a/src/Binary/Encoder.ts b/src/Binary/Encoder.ts index 756265c..98ab4a0 100644 --- a/src/Binary/Encoder.ts +++ b/src/Binary/Encoder.ts @@ -25,7 +25,7 @@ export default class Encoder { } writeByteLength(length: number) { if (length >= 4294967296) { - throw 'string too large to encode: ' + length + throw new Error('string too large to encode: ' + length) } if (length >= 1 << 20) { this.pushByte(WA.Tags.BINARY_32) @@ -51,7 +51,7 @@ export default class Encoder { if (token < 245) { this.pushByte(token) } else if (token <= 500) { - throw 'invalid token' + throw new Error('invalid token') } } writeString(token: string, i: boolean = null) { @@ -69,7 +69,7 @@ export default class Encoder { const overflow = tokenIndex - WA.Tags.SINGLE_BYTE_MAX const dictionaryIndex = overflow >> 8 if (dictionaryIndex < 0 || dictionaryIndex > 3) { - throw 'double byte dict token out of range: ' + token + ', ' + tokenIndex + throw new Error('double byte dict token out of range: ' + token + ', ' + tokenIndex) } this.writeToken(WA.Tags.DICTIONARY_0 + dictionaryIndex) this.writeToken(overflow % 256) @@ -118,7 +118,7 @@ export default class Encoder { this.writeByteLength(buffer.length) this.pushBytes(buffer) } else { - throw 'invalid children: ' + children + ' (' + typeof children + ')' + throw new Error('invalid children: ' + children + ' (' + typeof children + ')') } } getValidKeys(obj: Object) { @@ -128,7 +128,7 @@ export default class Encoder { if (!node) { return } else if (node.length !== 3) { - throw 'invalid node given: ' + node + throw new Error('invalid node given: ' + node) } const validAttributes = this.getValidKeys(node[1]) diff --git a/src/WAClient/Tests.ts b/src/WAClient/Tests.ts index baace08..2c6b8c9 100644 --- a/src/WAClient/Tests.ts +++ b/src/WAClient/Tests.ts @@ -186,7 +186,7 @@ WAClientTest('Events', (client) => { const response = await client.sendMessage(testJid, 'My Name Jeff', MessageType.text) await promiseTimeout(10000, waitForUpdate()) }) - /* it ('should update me on presence', async () => { + /*it ('should update me on presence', async () => { //client.logUnhandledMessages = true client.setOnPresenceUpdate (presence => { console.log (presence) diff --git a/src/WAConnection/Base.ts b/src/WAConnection/Base.ts index e0b1395..7bd0b73 100644 --- a/src/WAConnection/Base.ts +++ b/src/WAConnection/Base.ts @@ -75,7 +75,7 @@ export default class WAConnectionBase { */ loadAuthInfoFromBase64(authInfo: AuthenticationCredentialsBase64 | string) { if (!authInfo) { - throw 'given authInfo is null' + throw new Error('given authInfo is null') } if (typeof authInfo === 'string') { this.log(`loading authentication credentials from ${authInfo}`) @@ -96,7 +96,7 @@ export default class WAConnectionBase { */ loadAuthInfoFromBrowser(authInfo: AuthenticationCredentialsBrowser | string) { if (!authInfo) { - throw 'given authInfo is null' + throw new Error('given authInfo is null') } if (typeof authInfo === 'string') { this.log(`loading authentication credentials from ${authInfo}`) @@ -130,7 +130,7 @@ export default class WAConnectionBase { return this.registerCallback([parameters, null, null], callback) } if (!Array.isArray(parameters)) { - throw 'parameters (' + parameters + ') must be a string or array' + throw new Error('parameters (' + parameters + ') must be a string or array') } const func = 'function:' + parameters[0] const key = parameters[1] || '' @@ -152,7 +152,7 @@ export default class WAConnectionBase { return this.deregisterCallback([parameters]) } if (!Array.isArray(parameters)) { - throw 'parameters (' + parameters + ') must be a string or array' + throw new Error('parameters (' + parameters + ') must be a string or array') } const func = 'function:' + parameters[0] const key = parameters[1] || '' @@ -250,7 +250,7 @@ export default class WAConnectionBase { /** Send some message to the WhatsApp servers */ protected send(m) { if (!this.conn) { - throw 'cannot send message, disconnected from WhatsApp' + throw new Error('cannot send message, disconnected from WhatsApp') } this.msgCount += 1 // increment message count, it makes the 'epoch' field when sending binary messages this.conn.send(m) @@ -261,7 +261,7 @@ export default class WAConnectionBase { */ async logout() { if (!this.conn) { - throw "You're not even connected, you can't log out" + throw new Error("You're not even connected, you can't log out") } await new Promise((resolve) => { this.conn.send('goodbye,["admin","Conn","disconnect"]', null, () => { diff --git a/src/WAConnection/Connect.ts b/src/WAConnection/Connect.ts index 0bc307e..eb476ec 100644 --- a/src/WAConnection/Connect.ts +++ b/src/WAConnection/Connect.ts @@ -25,7 +25,7 @@ export default class WAConnectionConnector extends WAConnectionValidator { async connectSlim(authInfo: AuthenticationCredentialsBase64 | string = null, timeoutMs: number = null) { // if we're already connected, throw an error if (this.conn) { - throw [1, 'already connected or connecting'] + throw new Error('already connected or connecting') } // set authentication credentials if required try { diff --git a/src/WAConnection/Tests.ts b/src/WAConnection/Tests.ts index 5726649..ceab536 100644 --- a/src/WAConnection/Tests.ts +++ b/src/WAConnection/Tests.ts @@ -58,7 +58,7 @@ describe('Test Connect', () => { assert.ok(chats) if (chats.length > 0) { assert.ok(chats[0].jid) - assert.ok(chats[0].count) + assert.ok(chats[0].count !== null) } assert.ok(contacts) if (contacts.length > 0) { diff --git a/src/WAConnection/Utils.ts b/src/WAConnection/Utils.ts index b3e1ea8..bb57f60 100644 --- a/src/WAConnection/Utils.ts +++ b/src/WAConnection/Utils.ts @@ -80,7 +80,7 @@ export function decryptWA (message: any, macKey: Buffer, encKey: Buffer, decoder const commaIndex = message.indexOf(',') // all whatsapp messages have a tag and a comma, followed by the actual message if (commaIndex < 0) { // if there was no comma, then this message must be not be valid - throw [2, 'invalid message', message] + throw Error ('invalid message: ' + message) } let data = message.slice(commaIndex+1, message.length) // get the message tag. @@ -99,7 +99,7 @@ export function decryptWA (message: any, macKey: Buffer, encKey: Buffer, decoder } else { if (!macKey || !encKey) { // if we recieved a message that was encrypted but we don't have the keys, then there must be an error - throw [3, 'recieved encrypted message when auth creds not available', data] + throw new Error ('recieved encrypted buffer when auth creds unavailable') } /* If the data recieved was not a JSON, then it must be an encrypted message. @@ -115,9 +115,8 @@ export function decryptWA (message: any, macKey: Buffer, encKey: Buffer, decoder const computedChecksum = hmacSign(data, macKey) // compute the sign of the message we recieved using our macKey if (!checksum.equals(computedChecksum)) { - throw [7, "checksums don't match"] + throw new Error (`Checksums don't match:\nog: ${checksum.toString('hex')}\ncomputed: ${computedChecksum.toString('hex')}`) } - // the checksum the server sent, must match the one we computed for the message to be valid const decrypted = aesDecrypt(data, encKey) // decrypt using AES json = decoder.read(decrypted) // decode the binary message into a JSON array diff --git a/src/WAConnection/Validation.ts b/src/WAConnection/Validation.ts index f61e568..614bd76 100644 --- a/src/WAConnection/Validation.ts +++ b/src/WAConnection/Validation.ts @@ -2,6 +2,8 @@ import * as Curve from 'curve25519-js' import * as Utils from './Utils' import WAConnectionBase from './Base' +const StatusError = (message: any, description: string='unknown error') => new Error (`unexpected status: ${message.status} on JSON: ${JSON.stringify(message)}`) + export default class WAConnectionValidator extends WAConnectionBase { /** Authenticate the connection */ protected async authenticate() { @@ -37,31 +39,30 @@ export default class WAConnectionValidator extends WAConnectionBase { return this.generateKeysForAuth(json.ref) } default: - throw [json.status, 'unknown error', json] + throw StatusError (json) } }) .then((json) => { if ('status' in json) { switch (json.status) { case 401: // if the phone was unpaired - throw [json.status, 'unpaired from phone', json] + throw StatusError (json, 'unpaired from phone') case 429: // request to login was denied, don't know why it happens - throw [json.status, 'request denied, try reconnecting', json] - case 304: // request to generate a new key for a QR code was denied - throw [json.status, 'request for new key denied', json] + throw StatusError (json, 'request denied, try reconnecting') default: - throw [json.status, 'unknown error status', json] + throw StatusError (json) } } if (json[1] && json[1].challenge) { // if its a challenge request (we get it when logging in) - return this.respondToChallenge(json[1].challenge).then((json) => { - if (json.status !== 200) { - // throw an error if the challenge failed - throw [json.status, 'unknown error', json] - } - return this.waitForMessage('s2', []) // otherwise wait for the validation message - }) + return this.respondToChallenge(json[1].challenge) + .then((json) => { + if (json.status !== 200) { + // throw an error if the challenge failed + throw StatusError (json) + } + return this.waitForMessage('s2', []) // otherwise wait for the validation message + }) } else { // otherwise just chain the promise further return json @@ -107,7 +108,7 @@ export default class WAConnectionValidator extends WAConnectionBase { } const secret = Buffer.from(json.secret, 'base64') if (secret.length !== 144) { - throw [4, 'incorrect secret length: ' + secret.length] + throw new Error ('incorrect secret length received: ' + secret.length) } // generate shared key from our private key & the secret shared by the server const sharedKey = Curve.sharedKey(this.curveKeys.private, secret.slice(0, 32)) @@ -140,11 +141,11 @@ export default class WAConnectionValidator extends WAConnectionBase { return onValidationSuccess() } else { // if the checksums didn't match - throw [5, 'HMAC validation failed'] + throw new Error ('HMAC validation failed') } } else { // if we didn't get the connected field (usually we get this message when one opens WhatsApp on their phone) - throw [6, 'json connection failed', json] + throw new Error (`incorrect JSON: ${JSON.stringify(json)}`) } } /**