diff --git a/README.md b/README.md index 41011c4..f116ae6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Disclaimer In general, I would suggest using cookie sessions for web applications. -But still, you may have a case when you have to use JWT tokens and store them in the local storage, +But still, you may have a case when you have to use JWT/Non-JWT tokens and store them in the local storage, here this library might be useful. React Native is also a good reason for using such a library. @@ -32,7 +32,7 @@ is valid. This library has typings for TypeScript code, but nothing prevents you from just remove types from the examples and use as a JavaScript code. -### Access and Refresh tokens +### Access and Refresh JWT tokens Let's assume you have a backend that uses `accessToken` and `refreshToken` to auth users and provides services `/login`, `/register`, and `/update-token`. All services @@ -50,7 +50,10 @@ The first step you need to do is to create an instance of an `authProvider`. ```typescript import { createAuthProvider } from 'react-token-auth'; -type Session = { accessToken: string; refreshToken: string }; +type Session = { + accessToken: string + refreshToken: string +}; export const { useAuth, authFetch, login, logout } = createAuthProvider({ getAccessToken: session => session.accessToken, @@ -144,6 +147,35 @@ export const getUser = (userId: string) => (dispatch: Dispatch) => { Also if the token already saved in the `localStorage`, it will be restored after refreshing the page. + +### Non-JWT tokens +By default, the access_token expiration time is determined by the JWT token, but you can provide the expiration time yourself. + +For example, the session expiration time can be stored in the session object. + +To do this, you just need to override `getExpirationTime` in the configuration: +```typescript +import { createAuthProvider } from 'react-token-auth'; + +type Session = { + accessToken: string + refreshToken: string + expirationTime: number +}; + +export const { useAuth, authFetch, login, logout } = createAuthProvider({ + getAccessToken: session => session.accessToken, + getExpirationTime: session => session.expirationTime, + storage: localStorage, + onUpdateToken: token => + fetch('/update-token', { + method: 'POST', + body: token.refreshToken, + }).then(r => r.json()), +}); +``` + + ## API ### `createAuthProvider(config: IAuthProviderConfig)` -> `IAuthProvider` @@ -151,6 +183,7 @@ will be restored after refreshing the page. #### `IAuthProviderConfig` - `getAccessToken?: (session: Session) => TokenString` - function which allows to extract access token from the whole session object +- `getExpirationTime?: (session: Session) => Maybe` - function which allows to extract expiration time from the whole session object. Default time from JWT token. - `storageKey?: string = 'REACT_TOKEN_AUTH_KEY'` - key that will be used to store value in local storage - `onUpdateToken?: (session: Session) => Promise>` - function to update access token when it is expired - `onHydratation?: (session: Maybe) => void` - function to process your tokens when `useAuth` is called. @@ -172,6 +205,7 @@ will be restored after refreshing the page. #### `IAsyncAuthProviderConfig` - `getAccessToken?: (session: Session) => TokenString` - function which allows to extract access token from the whole session object +- `getExpirationTime?: (session: Session) => Maybe` - function which allows to extract expiration time from the whole session object. Default time from JWT token. - `storageKey?: string = 'REACT_TOKEN_AUTH_KEY'` - key that will be used to store value in local storage - `onUpdateToken?: (session: Session) => Promise>` - function to update access token when it is expired - `onHydratation?: (session: Maybe) => void` - function to process your tokens when `useAuth` is called. diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 91a2d2c..0000000 --- a/jest.config.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', -}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e5b35d4..0150367 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,18 @@ { "name": "react-token-auth", - "version": "1.1.9", + "version": "2.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "react-token-auth", - "version": "1.1.9", + "version": "2.4.0", "license": "ISC", + "dependencies": { + "@types/buffer-from": "^1.1.0", + "buffer-from": "^1.1.2", + "tslog": "^4.7.1" + }, "devDependencies": { "@types/jest": "^26.0.24", "@types/react": "^16.9.17", @@ -1261,6 +1266,14 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/buffer-from": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/buffer-from/-/buffer-from-1.1.0.tgz", + "integrity": "sha512-BLFpLBcN+RPKUsFxqRkMiwqTOOdi+TrKr5OpLJ9qCnUdSxS6S80+QRX/mIhfR66u0Ykc4QTkReaejOM2ILh+9Q==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -1307,8 +1320,7 @@ "node_modules/@types/node": { "version": "16.11.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.4.tgz", - "integrity": "sha512-TMgXmy0v2xWyuCSCJM6NCna2snndD8yvQF67J29ipdzMcsPa9u+o0tjF5+EQNdhcuZplYuouYqpc4zcd5I6amQ==", - "dev": true + "integrity": "sha512-TMgXmy0v2xWyuCSCJM6NCna2snndD8yvQF67J29ipdzMcsPa9u+o0tjF5+EQNdhcuZplYuouYqpc4zcd5I6amQ==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -1837,10 +1849,9 @@ } }, "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "node_modules/builtin-modules": { "version": "1.1.1", @@ -7294,6 +7305,17 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/tslog": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/tslog/-/tslog-4.7.1.tgz", + "integrity": "sha512-Ez90j4FKCUp9bBlgPq96aYDUbXRIOxz6Vxn/4Iw2/IiVxLB5wsUVkWfeK4oqdRMoW8qBVJz9oIT+ysjfyIRufw==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/fullstack-build/tslog?sponsor=1" + } + }, "node_modules/tsutils": { "version": "2.29.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", @@ -8713,6 +8735,14 @@ "@babel/types": "^7.3.0" } }, + "@types/buffer-from": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/buffer-from/-/buffer-from-1.1.0.tgz", + "integrity": "sha512-BLFpLBcN+RPKUsFxqRkMiwqTOOdi+TrKr5OpLJ9qCnUdSxS6S80+QRX/mIhfR66u0Ykc4QTkReaejOM2ILh+9Q==", + "requires": { + "@types/node": "*" + } + }, "@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -8759,8 +8789,7 @@ "@types/node": { "version": "16.11.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.4.tgz", - "integrity": "sha512-TMgXmy0v2xWyuCSCJM6NCna2snndD8yvQF67J29ipdzMcsPa9u+o0tjF5+EQNdhcuZplYuouYqpc4zcd5I6amQ==", - "dev": true + "integrity": "sha512-TMgXmy0v2xWyuCSCJM6NCna2snndD8yvQF67J29ipdzMcsPa9u+o0tjF5+EQNdhcuZplYuouYqpc4zcd5I6amQ==" }, "@types/normalize-package-data": { "version": "2.4.1", @@ -9173,10 +9202,9 @@ } }, "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "builtin-modules": { "version": "1.1.1", @@ -13392,6 +13420,11 @@ "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==", "dev": true }, + "tslog": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/tslog/-/tslog-4.7.1.tgz", + "integrity": "sha512-Ez90j4FKCUp9bBlgPq96aYDUbXRIOxz6Vxn/4Iw2/IiVxLB5wsUVkWfeK4oqdRMoW8qBVJz9oIT+ysjfyIRufw==" + }, "tsutils": { "version": "2.29.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", diff --git a/package.json b/package.json index 4684659..170d273 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-token-auth", - "version": "2.3.8", + "version": "2.4.0", "description": "React Token Auth", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -40,5 +40,10 @@ }, "files": [ "lib/**/*" - ] + ], + "dependencies": { + "@types/buffer-from": "^1.1.0", + "buffer-from": "^1.1.2", + "tslog": "^4.7.1" + } } diff --git a/src/__tests__/isTokenExpired.test.ts b/src/__tests__/isTokenExpired.test.ts index 799d1a8..429c8f5 100644 --- a/src/__tests__/isTokenExpired.test.ts +++ b/src/__tests__/isTokenExpired.test.ts @@ -1,34 +1,52 @@ -import { isTokenExpired } from '../isTokenExpired'; +import { isTokenExpired, jwtExp } from '../isTokenExpired'; import { createJWTTokenWithExp, getExpiredJWTToken, getNonExpiredJWTToken } from '../test-utils/jwt'; -describe('isTokenExpired', () => { +describe('isTokenExpired-jwt', () => { it('expired token', () => { const token = getExpiredJWTToken(); - expect(isTokenExpired(token)).toBeTruthy(); + expect(isTokenExpired(jwtExp(token))).toBeTruthy(); }); it('valid token', () => { const token = getNonExpiredJWTToken(); - expect(isTokenExpired(token)).toBeFalsy(); + expect(isTokenExpired(jwtExp(token))).toBeFalsy(); }); it('token expires in 1 second', () => { const token = createJWTTokenWithExp(Math.floor(Date.now() / 1000) + 1); - expect(isTokenExpired(token, 5000)).toBeTruthy(); + expect(isTokenExpired(jwtExp(token), 5000)).toBeTruthy(); }); it('token expired 1 second ago', () => { const token = createJWTTokenWithExp(Math.floor(Date.now() / 1000) - 1); - expect(isTokenExpired(token, 5000)).toBeTruthy(); + expect(isTokenExpired(jwtExp(token), 5000)).toBeTruthy(); }); it('hardcoded token is expired', () => { const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6IkpzYWRrZmphZGpraGZAcXdlcXdlcyIsInN1YiI6IjE1IiwiaWF0IjoxNjM3NzUxMjM5LCJleHAiOjE2Mzc3NTEyOTl9.ZjcVgbVvoZrIZHzjIckYgFwY5rnlyxlHGvGNHg_CRRk'; - expect(isTokenExpired(token, 5000)).toBeTruthy(); + expect(isTokenExpired(jwtExp(token), 5000)).toBeTruthy(); + }); +}); + +describe('isTokenExpired-custom-time', () => { + it('expired token', () => { + expect(isTokenExpired(1000)).toBeTruthy(); + }); + + it('valid token', () => { + expect(isTokenExpired(Date.now() + 5000 * 1000)).toBeFalsy(); + }); + + it('token expires in 1 second', () => { + expect(isTokenExpired(Date.now() + 1, 5000)).toBeTruthy(); + }); + + it('token expired 1 second ago', () => { + expect(isTokenExpired(Date.now() - 1, 5000)).toBeTruthy(); }); }); diff --git a/src/createAsyncAuthProvider.ts b/src/createAsyncAuthProvider.ts index be38003..9860543 100644 --- a/src/createAsyncAuthProvider.ts +++ b/src/createAsyncAuthProvider.ts @@ -1,6 +1,6 @@ import { createListenersContainer } from './createListenersContainer'; import { createAsyncTokenProvider } from './createTokenProvider'; -import { isTokenExpired } from './isTokenExpired'; +import { isTokenExpired, jwtExp } from './isTokenExpired'; import { createLogger } from './logger'; import { createTokenUpdater } from './tokenUpdater'; import { Getter, IAsyncAuthStorage, Maybe, TokenString } from './types'; @@ -11,6 +11,7 @@ import { extractAccessToken } from './utils/extractAccessToken'; export interface IAsyncAuthProviderConfig { getAccessToken?: (session: Session) => TokenString; + getExpirationTime?: (session: Session) => Maybe; storageKey?: string; onUpdateToken?: (session: Session) => Promise>; onHydratation?: (session: Maybe) => void; @@ -39,6 +40,7 @@ export const createAsyncAuthProvider = ({ getAccessToken, expirationThresholdMillisec = 5000, debug = false, + getExpirationTime, }: IAsyncAuthProviderConfig): IAsyncAuthProvider => { const logger = createLogger(debug); const listenersContainer = createListenersContainer(); @@ -52,7 +54,7 @@ export const createAsyncAuthProvider = ({ let _session: Maybe = null; const updateSession = async (session: Maybe) => { - logger.log('updateSession', 'session', session); + logger?.debug('updateSession', 'session', session); await tokenProvider.setToken(session); _session = session; listenersContainer.notify(); @@ -64,8 +66,7 @@ export const createAsyncAuthProvider = ({ .then(() => { initiationPromise = null; }) - // tslint:disable-next-line:no-console - .catch(console.error); + .catch(logger?.warn); const waitInit = () => initiationPromise; @@ -77,20 +78,23 @@ export const createAsyncAuthProvider = ({ const getSession = async () => { const accessToken = extractAccessToken(getSessionState(), getAccessToken); - logger.log('getSession', 'accessToken', accessToken); - logger.log('getSession', 'tokenUpdater', tokenUpdater); - if (accessToken) { - logger.log( + logger?.debug('getSession', 'accessToken', accessToken); + logger?.debug('getSession', 'tokenUpdater', tokenUpdater); + if (_session && accessToken) { + const getExpTime = getExpirationTime || (() => jwtExp(accessToken)); + logger?.debug( 'getSession', - 'isTokenExpired(accessToken, expirationThresholdMillisec)', - isTokenExpired(accessToken, expirationThresholdMillisec, logger), + 'isTokenExpired(getExpTime(_session), expirationThresholdMillisec)', + isTokenExpired(getExpTime(_session), expirationThresholdMillisec, logger), ); - } - if (_session && tokenUpdater && accessToken && isTokenExpired(accessToken, expirationThresholdMillisec)) { - const updatedSession = await tokenUpdater.updateToken(_session); - logger.log('getSession', 'updatedSession', accessToken); - await updateSession(updatedSession); + if (tokenUpdater) { + if (isTokenExpired(getExpTime(_session), expirationThresholdMillisec)) { + const updatedSession = await tokenUpdater.updateToken(_session); + logger?.debug('getSession', 'updatedSession', accessToken); + await updateSession(updatedSession); + } + } } return getSessionState(); diff --git a/src/createAuthProvider.ts b/src/createAuthProvider.ts index dc8629b..25411ca 100644 --- a/src/createAuthProvider.ts +++ b/src/createAuthProvider.ts @@ -1,6 +1,6 @@ import { createListenersContainer } from './createListenersContainer'; import { createTokenProvider } from './createTokenProvider'; -import { isTokenExpired } from './isTokenExpired'; +import { isTokenExpired, jwtExp } from './isTokenExpired'; import { createTokenUpdater } from './tokenUpdater'; import { Getter, IAuthStorage, Maybe, TokenString } from './types'; import { createUseAuth } from './useAuth'; @@ -10,6 +10,7 @@ import { extractAccessToken } from './utils/extractAccessToken'; export interface IAuthProviderConfig { getAccessToken?: (session: Session) => TokenString; + getExpirationTime?: (session: Session) => Maybe; storageKey?: string; onUpdateToken?: (session: Session) => Promise>; onHydratation?: (session: Maybe) => void; @@ -35,6 +36,7 @@ export const createAuthProvider = ({ fetchFunction = fetch, getAccessToken, expirationThresholdMillisec = 5000, + getExpirationTime, }: IAuthProviderConfig): IAuthProvider => { const listenersContainer = createListenersContainer(); const tokenProvider = createTokenProvider({ @@ -60,9 +62,12 @@ export const createAuthProvider = ({ const getSession = async () => { const accessToken = extractAccessToken(getSessionState(), getAccessToken); - if (_session && tokenUpdater && accessToken && isTokenExpired(accessToken, expirationThresholdMillisec)) { - const updatedSession = await tokenUpdater.updateToken(_session); - updateSession(updatedSession); + if (_session && tokenUpdater && accessToken) { + const getExpTime = getExpirationTime || (() => jwtExp(accessToken)); + if (isTokenExpired(getExpTime(_session), expirationThresholdMillisec)) { + const updatedSession = await tokenUpdater.updateToken(_session); + updateSession(updatedSession); + } } return getSessionState(); diff --git a/src/isTokenExpired.ts b/src/isTokenExpired.ts index 3246545..60fdf52 100644 --- a/src/isTokenExpired.ts +++ b/src/isTokenExpired.ts @@ -1,42 +1,38 @@ -import { SimpleLogger } from './logger'; -import { Maybe, TokenString } from './types'; -import { Base64 } from './utils/base64'; +import { Maybe } from './types'; +import {Logger} from "tslog"; +import bufferFrom from 'buffer-from'; -export const isTokenExpired = (token: TokenString, thresholdMillisec?: number, logger?: SimpleLogger) => - isTimestampExpired(jwtExp(token, logger), thresholdMillisec, logger); +export const isTokenExpired = (exp: Maybe, thresholdMillisec?: number, logger?: Logger) => { + logger?.debug('isTokenExpired', 'exp', exp); + if (!exp) { + return false; + } -const jwtExp = (token: string, logger?: SimpleLogger): number | null => { + logger?.debug('isTokenExpired', 'Date.now()', Date.now()); + logger?.debug('isTokenExpired', '(thresholdMillisec ?? 0', thresholdMillisec ?? 0); + return Date.now() > exp - (thresholdMillisec ?? 0); +}; + +export const jwtExp = (token: string, logger?: Logger): number | null => { const split = token.split('.'); - logger?.log('jwtExp', 'split', split); + logger?.debug('jwtExp', 'split', split); if (split.length < 2) { return null; } try { - const middlePart = Base64.decode(token.split('.')[1]); - logger?.log('jwtExp', 'middlePart', middlePart); + const middlePart = bufferFrom(token.split('.')[1], 'base64').toString(); + logger?.debug('jwtExp', 'middlePart', middlePart); const jwt = JSON.parse(middlePart); - logger?.log('jwtExp', 'jwt', jwt); + logger?.debug('jwtExp', 'jwt', jwt); if (jwt && jwt.exp && Number.isFinite(jwt.exp)) { return jwt.exp * 1000; } else { return null; } } catch (e) { - // tslint:disable-next-line:no-console - console.warn(e); + logger?.warn(e); return null; } }; - -const isTimestampExpired = (exp: Maybe, thresholdMillisec?: number, logger?: SimpleLogger) => { - logger?.log('isTimestampExpired', 'exp', exp); - if (!exp) { - return false; - } - - logger?.log('isTimestampExpired', 'Date.now()', Date.now()); - logger?.log('isTimestampExpired', '(thresholdMillisec ?? 0', thresholdMillisec ?? 0); - return Date.now() > exp - (thresholdMillisec ?? 0); -}; diff --git a/src/logger.ts b/src/logger.ts index ca59a79..ce221f0 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,15 +1,11 @@ -export interface SimpleLogger { - log: (name: string, message: string, ...objs: any[]) => void; -} +import {Logger} from "tslog"; -export const createLogger = (debug?: boolean): SimpleLogger => { - const log = (name: string, message: string, ...objs: any[]) => { - if (debug) { - // tslint:disable-next-line:no-console - console.log(`[react-token-auth]${name}::${message}`, ...objs.map((it) => JSON.stringify(it))); - } - }; - return { - log, - }; +export const createLogger = (debug?: boolean): Logger|undefined => { + if(debug) { + return new Logger({ + name: "react-token-auth" + }); + } + + return undefined }; diff --git a/src/utils/base64.d.ts b/src/utils/base64.d.ts deleted file mode 100644 index 514fc31..0000000 --- a/src/utils/base64.d.ts +++ /dev/null @@ -1 +0,0 @@ -export const Base64: { decode: (input: string) => string }; diff --git a/src/utils/base64.js b/src/utils/base64.js deleted file mode 100644 index 15f4c1e..0000000 --- a/src/utils/base64.js +++ /dev/null @@ -1,132 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -/** - * Base64 encode / decode - * http://www.webtoolkit.info/ - **/ -var Base64 = { - // private property - _keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', - - // public method for encoding - encode: function (input) { - var output = ''; - var chr1, chr2, chr3, enc1, enc2, enc3, enc4; - var i = 0; - - input = Base64._utf8_encode(input); - - while (i < input.length) { - chr1 = input.charCodeAt(i++); - chr2 = input.charCodeAt(i++); - chr3 = input.charCodeAt(i++); - - enc1 = chr1 >> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; - - if (isNaN(chr2)) { - enc3 = enc4 = 64; - } else if (isNaN(chr3)) { - enc4 = 64; - } - - output = - output + - this._keyStr.charAt(enc1) + - this._keyStr.charAt(enc2) + - this._keyStr.charAt(enc3) + - this._keyStr.charAt(enc4); - } - return output; - }, - - // public method for decoding - decode: function (input) { - var output = ''; - var chr1, chr2, chr3; - var enc1, enc2, enc3, enc4; - var i = 0; - - input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ''); - - while (i < input.length) { - enc1 = this._keyStr.indexOf(input.charAt(i++)); - enc2 = this._keyStr.indexOf(input.charAt(i++)); - enc3 = this._keyStr.indexOf(input.charAt(i++)); - enc4 = this._keyStr.indexOf(input.charAt(i++)); - - chr1 = (enc1 << 2) | (enc2 >> 4); - chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); - chr3 = ((enc3 & 3) << 6) | enc4; - - output = output + String.fromCharCode(chr1); - - if (enc3 != 64) { - output = output + String.fromCharCode(chr2); - } - if (enc4 != 64) { - output = output + String.fromCharCode(chr3); - } - } - - output = Base64._utf8_decode(output); - - return output; - }, - - // private method for UTF-8 encoding - _utf8_encode: function (string) { - string = string.replace(/\r\n/g, '\n'); - var utftext = ''; - - for (var n = 0; n < string.length; n++) { - var c = string.charCodeAt(n); - - if (c < 128) { - utftext += String.fromCharCode(c); - } else if (c > 127 && c < 2048) { - utftext += String.fromCharCode((c >> 6) | 192); - utftext += String.fromCharCode((c & 63) | 128); - } else { - utftext += String.fromCharCode((c >> 12) | 224); - utftext += String.fromCharCode(((c >> 6) & 63) | 128); - utftext += String.fromCharCode((c & 63) | 128); - } - } - return utftext; - }, - - // private method for UTF-8 decoding - _utf8_decode: function (utftext) { - var string = ''; - var i = 0; - var c1 = 0; - var c2 = 0; - var c = 0; - - while (i < utftext.length) { - c = utftext.charCodeAt(i); - - if (c < 128) { - string += String.fromCharCode(c); - i++; - } else if (c > 191 && c < 224) { - c2 = utftext.charCodeAt(i + 1); - string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); - i += 2; - } else { - c2 = utftext.charCodeAt(i + 1); - c3 = utftext.charCodeAt(i + 2); - string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); - i += 3; - } - } - return string; - }, -}; - -module.exports = { Base64 }; diff --git a/tsconfig.json b/tsconfig.json index 235aefc..272363e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "outDir": "./lib", "strict": true, "allowJs": true, + "esModuleInterop": true, "lib": ["dom", "es6"] }, "include": ["src"], diff --git a/tslint.json b/tslint.json index a8571f3..2e64f96 100644 --- a/tslint.json +++ b/tslint.json @@ -1,3 +1,6 @@ { - "extends": ["tslint:recommended", "tslint-config-prettier"] + "extends": ["tslint:recommended", "tslint-config-prettier"], + "rules": { + "no-console": true + } }