@blockv/sdk
Version:
Allows web apps to display and interact with vatoms.
197 lines (161 loc) • 6.87 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _fetch = _interopRequireDefault(require("@brillout/fetch"));
var _jwtDecode = _interopRequireDefault(require("jwt-decode"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
//
// BlockV AG. Copyright (c) 2018, all rights reserved.
//
// Licensed under the BlockV SDK License (the "License"); you may not use this file or
// the BlockV SDK except in compliance with the License accompanying it. Unless
// required by applicable law or agreed to in writing, the BlockV SDK distributed under
// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
// ANY KIND, either express or implied. See the License for the specific language
// governing permissions and limitations under the License.
//
/* global FormData */
/** List of known error messages to replace the server-supplied ones */
const ErrorCodes = {
2: 'Blank App ID',
17: 'Invalid App ID',
401: 'Token has Expired',
516: 'Invalid Payload',
517: 'Invalid Payload',
521: 'Token Unavailable',
527: 'Invalid Date Format',
1004: 'Invalid Request Payload',
1701: 'vAtom Unrecognized',
1708: 'vAtom Unvailable',
2030: 'No user found, Please register an account first.',
2031: 'Authentication Failed',
2032: 'Login Failed, Please try again',
2034: 'Invalid Token',
2037: 'Upload Avatar Failed',
2049: 'Refresh Token Expired / Not Whitelisted',
2051: 'Too many login attempts, Please try again later.',
2552: 'Unable To Retrieve Token',
2553: 'Token ID Invalid',
2562: 'Cannot Delete Primary Token',
2563: 'Token Already Confirmed',
2564: 'Invalid Verification Code',
2566: 'Token Already Confirmed',
2567: 'Invalid Verification Code',
2569: 'Invalid Token Type',
2571: 'Invalid Email',
2572: 'Invalid Phone Number'
};
class Client {
/** @private */
constructor(bv) {
this.Blockv = bv;
this.store = bv.store;
}
/**
* Sends a request to the backend.
* @param {string} method The HTTP method, ie. "GET", "POST"
* @param {string} endpoint The backend API endpoint, ie. "/v1/user"
* @param {string|object|FormData} payload The request body. Can be null.
* @param {boolean} auth `true` if this request should contain the current user's access token.
* @param {object} headers Optional extra HTTP headers to add to the request.
* @returns {Promise<object>} The server's response payload.
*/
async request(method, endpoint, payload, auth, headers) {
// Ensure our access token is up to date, if this is an authenticated request
if (auth) await this.checkToken(); // Send request
return this.authRequest(method, endpoint, payload, headers);
}
/** @private */
async authRequest(method, endpoint, payload, extraHeaders) {
// Setup headers
const headers = Object.assign({
'App-Id': this.store.appID,
Authorization: `Bearer ${this.store.token}`
}, extraHeaders); // Check payload type
let body = null;
if (!payload) {
// If no body, make it undefined so that fetch() doesn't complain about having a payload on GET requests
body = undefined;
} else if (typeof FormData !== 'undefined' && payload instanceof FormData) {
// Don't add Content-Type header, fetch() adds it's own, which is required because it specifies the form data boundary
body = payload;
} else if (typeof body === 'object') {
// Convert to JSON
body = JSON.stringify(payload);
headers['Content-Type'] = 'application/json';
} else {
// Unknown payload type, assume application/json content type, unless specified in extra headers
body = payload;
if (!extraHeaders['Content-Type']) headers['Content-Type'] = 'application/json';
} // Send request
const response = await (0, _fetch.default)(this.store.server + endpoint, {
method,
body,
headers
}); // Decode JSON
const json = await response.json(); // Check for server error
if (!json.payload && json.error === 2051) {
// Check for the special login locked error
// We need to pull the timestamp that is in the reponse.message to show when they
// can login agin
// HACK: Pull time from original server error string
const dateString = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/g.exec(response.message);
response.lockedUntil = new Date(dateString); // Replace duration in the error message
let duration = response.lockedUntil.getTime() - Date.now();
if (duration < 2000) duration = 2000;
const seconds = Math.floor(duration / 1000);
const minutes = Math.floor(duration / 1000 / 60);
if (seconds <= 90) {
response.message = response.message.replace('%DURATION%', seconds === 1 ? '1 second' : `${seconds} seconds`);
} else {
response.message = response.message.replace('%DURATION%', minutes === 1 ? '1 minute' : `${minutes} minutes`);
} // Throw error
const error = new Error(`Too many login attempts, Try again at : ${response.lockedUntil}`);
error.code = json.error || response.status || 0;
error.httpStatus = response.status;
throw error;
} else if (!json.payload) {
// Throw the error returned by the server
const error = new Error(ErrorCodes[response.error] || json.message || 'An unknown server error has occurred');
error.code = json.error || response.status || 0;
error.httpStatus = response.status;
throw error;
} // No error, continue
return json.payload;
}
/**
* Uses the refresh token to fetch and store a new access token from the backend.
* @private
*/
async refreshToken() {
// Fetch new access token
const data = await this.request('POST', '/v1/access_token', '', false, {
Authorization: `Bearer ${this.store.refreshToken}`
}); // Store it
this.store.token = data.access_token.token;
}
/**
* Checks if the current access token is still valid and has not expired yet. If it is, it will fetch a new one.
* @private
* @returns {Promise} Resolves when the access token is valid.
*/
async checkToken() {
// define our vars
let decodedToken;
let nowDate;
let expirationTime; // Catch errors with decoding the current access token
try {
decodedToken = (0, _jwtDecode.default)(this.store.token);
expirationTime = decodedToken.exp * 1000;
nowDate = Date.now(); // quick calc to determine if the token has expired
if (nowDate + 5000 > expirationTime) throw new Error('Token expired.');
} catch (e) {
// There was an error with the access token. Fetch a new one.
return this.refreshToken();
} // Done
return true;
}
}
exports.default = Client;