diff --git a/package.json b/package.json index 41d30dcb2..c4ed8e6cd 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "dependencies": { "@babel/runtime": "^7.3.1", "axios": "^0.18.1", + "base64-js": "^1.3.1", "chai-arrays": "^2.0.0", "cross-fetch": "^3.0.0", "form-data": "^2.3.3", diff --git a/src/base64.js b/src/base64.js new file mode 100644 index 000000000..c8a015fe1 --- /dev/null +++ b/src/base64.js @@ -0,0 +1,57 @@ +const base64 = require('base64-js'); + +// source - https://github.com/beatgammit/base64-js/blob/master/test/convert.js#L72 +const map = (arr, callback) => { + const res = []; + let kValue, mappedValue; + + for (let k = 0, len = arr.length; k < len; k++) { + if (typeof arr === 'string' && !!arr.charAt(k)) { + kValue = arr.charAt(k); + mappedValue = callback(kValue, k, arr); + res[k] = mappedValue; + } else if (typeof arr !== 'string' && k in arr) { + kValue = arr[k]; + mappedValue = callback(kValue, k, arr); + res[k] = mappedValue; + } + } + return res; +}; + +export function encodeBase64(data) { + return base64.fromByteArray( + map(data, function(char) { + return char.charCodeAt(0); + }), + ); +} + +// base-64 decoder throws exception if encoded string is not padded by '=' to make string length +// in multiples of 4. So gonna use our own method for this purpose to keep backwards compatibility +// https://github.com/beatgammit/base64-js/blob/master/index.js#L26 +export function decodeBase64(s) { + const e = {}, + w = String.fromCharCode, + L = s.length; + let i, + b = 0, + c, + x, + l = 0, + a, + r = ''; + const A = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + for (i = 0; i < 64; i++) { + e[A.charAt(i)] = i; + } + for (x = 0; x < L; x++) { + c = e[s.charAt(x)]; + b = (b << 6) + c; + l += 6; + while (l >= 8) { + ((a = (b >>> (l -= 8)) & 0xff) || x < L - 2) && (r += w(a)); + } + } + return r; +} diff --git a/src/index.js b/src/index.js index 76585abf3..24bd031d7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,9 @@ export { StreamChat } from './client'; +export * from './client_state'; export { logChatPromiseExecution } from './utils'; +export * from './channel'; +export * from './channel_state'; +export * from './permissions'; +export * from './events'; +export * from './signing'; +export * from './base64.js'; diff --git a/src/signing.js b/src/signing.js index c8e1b5cd9..d9d66097e 100644 --- a/src/signing.js +++ b/src/signing.js @@ -1,5 +1,6 @@ import jwt from 'jsonwebtoken'; import crypto from 'crypto'; +import { encodeBase64, decodeBase64 } from './base64'; /** * Creates the JWT token that can be used for a UserSession @@ -42,32 +43,6 @@ export function JWTServerToken(apiSecret, jwtOptions = {}) { return jwt.sign(payload, apiSecret, opts); } -function decodeBase64(s) { - const e = {}, - w = String.fromCharCode, - L = s.length; - let i, - b = 0, - c, - x, - l = 0, - a, - r = ''; - const A = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - for (i = 0; i < 64; i++) { - e[A.charAt(i)] = i; - } - for (x = 0; x < L; x++) { - c = e[s.charAt(x)]; - b = (b << 6) + c; - l += 6; - while (l >= 8) { - ((a = (b >>> (l -= 8)) & 0xff) || x < L - 2) && (r += w(a)); - } - } - return r; -} - /** * @return {string} */ @@ -82,14 +57,6 @@ export function UserFromToken(token) { return data.user_id; } -function encodeBase64(s) { - if (typeof window !== 'undefined' && window.btoa) { - return window.btoa(s); - } else { - return Buffer.from(s.toString(), 'binary').toString('base64'); - } -} - /** * * @param userId {string} the id of the user diff --git a/test/test.js b/test/test.js index 479a0b590..6db6a85ae 100644 --- a/test/test.js +++ b/test/test.js @@ -4,7 +4,7 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import chaiLike from 'chai-like'; import Immutable from 'seamless-immutable'; -import { StreamChat } from '../src'; +import { StreamChat, decodeBase64, encodeBase64 } from '../src'; import { expectHTTPErrorCode } from './utils'; import fs from 'fs'; import assertArrays from 'chai-arrays'; @@ -586,6 +586,25 @@ describe('Chat', function() { }); }); + describe('base64', function() { + it('encodes correctly', function() { + const testCases = [ + 'vishal', + 'vishy', + 'jaap', + 'eugene', + 'luke=', + 'Marcello!?', + ]; + + testCases.forEach(name => { + const b64str = encodeBase64(name); + const str = decodeBase64(b64str); + expect(str).to.equal(name); + }); + }); + }); + describe('Auth', function() { it('Token based auth', async function() { const token = createUserToken('daenerys'); diff --git a/types/stream-chat/index.d.ts b/types/stream-chat/index.d.ts index e0340dc58..4ec10dac0 100644 --- a/types/stream-chat/index.d.ts +++ b/types/stream-chat/index.d.ts @@ -342,7 +342,7 @@ export class Channel { unbanUser(targetUserID: string): Promise; on(callbackOrString: string, callbackOrNothing: any): void; off(callbackOrString: string, callbackOrNothing: any): void; - hide(userId?: string, clearHistory?: bool): Promise; + hide(userId?: string, clearHistory?: boolean): Promise; show(userId?: string): Promise; } @@ -431,6 +431,10 @@ export function DevToken(userId: string): string; export function CheckSignature(body: any, secret: string, signature: string): boolean; +export function encodeBase64(s: string): string; + +export function decodeBase64(s: string): string; + export function isValidEventType(eventType: string): boolean; export function logChatPromiseExecution(promise: Promise, name: string): void; diff --git a/yarn.lock b/yarn.lock index f071ab1c5..0d058fdf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1351,7 +1351,7 @@ base64-arraybuffer@^0.1.5: resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= -base64-js@^1.1.2: +base64-js@^1.1.2, base64-js@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==