@obelisk/client
Version:
Typescript client to interact with Obelisk on a higher level than the regular ReST API calls.
202 lines (201 loc) • 8.62 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.InternalUtils = void 0;
const rxjs_1 = require("rxjs");
const ajax_1 = require("rxjs/ajax");
const operators_1 = require("rxjs/operators");
const logger_1 = require("./logger");
const interfaces_1 = require("../interfaces");
/**
* @hidden
*/
let InternalUtils = /** @class */ (() => {
class InternalUtils {
static mapAjaxResponse2TPageResponse(resp, index) {
let result = { data: resp.response };
let linkHeaderValue = resp.xhr.getResponseHeader("link");
if (linkHeaderValue !== null) {
let link = { link: InternalUtils.parseLinkHeader(linkHeaderValue) };
result = Object.assign(Object.assign({}, result), link);
}
return result;
}
static token2header(client, cfg) {
if (!cfg.headers) {
cfg.headers = {};
}
cfg.headers = Object.assign(Object.assign({}, cfg.headers), { 'Authorization': 'Bearer ' + (client.tokens.rpt ? client.tokens.rpt.getToken() : '<no rpt present>') });
return cfg;
}
static parseLinkHeader(linkHeaderValue) {
let links = {};
linkHeaderValue.split(',').forEach(line => {
let parts = line.split(';');
let uri = parts[0].trim().slice(1, -1);
let amount = parts.length;
if (amount > 1) {
for (let i = 1; i < amount; i++) {
let part = parts[i].split('=');
let key = part[0];
let value = part[1];
if ('rel' === key.trim()) {
links[value.trim().slice(1, -1)] = uri;
}
else {
console.log(key.trim() + ' => ' + value.trim());
}
}
}
});
return links;
}
/**
* Divide url in 2 parts: before /full/path and ?querystring as 2 item array.
* @param uri
*/
static part(uri) {
const idx = uri.lastIndexOf('?');
let result;
if (idx === -1) {
result = [uri];
}
else {
result = [uri.slice(0, idx), uri.slice(idx)];
}
// Logger.debug(result, 'PART');
return result;
}
/**
* Normalize the uri to an abstract uri, using the client settings.
* @param client
* @param uri
*/
static norm(client, uri, apiVersion = 'v1') {
const schemePrefix = client.options.host + '/api/' + apiVersion;
const apiPrefix = '/api/' + apiVersion;
// https://idlab-iot.tengu.io/api/v1/context
if (uri.startsWith(schemePrefix)) {
return uri;
}
// /api/v1/context
else if (uri.startsWith(apiPrefix)) {
return uri.replace(apiPrefix, schemePrefix);
}
// /context or context
else {
uri = uri.startsWith('/') ? uri : '/' + uri;
return schemePrefix + uri;
}
}
static isTPageCompatible(client, uri, apiVersion = 'v1') {
const check = InternalUtils.regex.test(InternalUtils.norm(client, InternalUtils.part(uri)[0], apiVersion));
// Logger.debug(InternalUtils.part(uri)[0], 'REGEX');
// Logger.debug(check, 'REGEX');
return check;
}
static isStreamEndpointCompatible(client, uri, apiVersion = 'v1') {
const u = InternalUtils.norm(client, InternalUtils.part(uri)[0], apiVersion);
return u.endsWith('/sse');
}
/**
* Catches the first 40x error, on 401, will redirect once and try to get a new token (refreshing too),
* then retry the caught observable or request. If that doesn't work, the error is thrown anyway.
* @param client ObeliskClient to get token from
* @param request Optional AjaxRequest to retry instead of caught Observable
*/
static catch40xAndRetry(client, request) {
return rxjs_1.pipe(operators_1.catchError((resp, caught) => {
// Logger.debug('E', 'Caught generic error, probably 40x');
// 401 UNAUTHORIZED
if (resp.status && resp.status === 401) {
let wwwAuth;
let retryable;
if (request) {
wwwAuth = resp.xhr.getResponseHeader('www-authenticate');
retryable = (c) => ajax_1.ajax(InternalUtils.token2header(c, request));
}
else {
wwwAuth = resp.headers['www-authenticate'];
retryable = () => caught;
}
return InternalUtils.authAndRetry(client, wwwAuth, retryable);
}
// 403 FORBIDDEN handling
else if (resp.status && resp.status === 403) {
client.events$.next({ type: interfaces_1.ClientEventType.OnForbidden });
return rxjs_1.throwError(resp);
}
// All other cases
else {
return rxjs_1.throwError(resp);
}
}));
}
/**
* Catches a 504 client and retries the original observable.
* <strong>Hint</strong> Use this to work around listening to SSE channels that don't emit data for a long time.
*/
static catch504AndRetry() {
return rxjs_1.pipe(operators_1.catchError((resp, caught) => {
if (resp.status && resp.status === 504) {
logger_1.Logger.debug('Retrying on 504 as requested', 'Error 504');
return caught;
}
// All other cases
else {
return rxjs_1.throwError(resp);
}
}));
}
/**
* Send an authed AjaxRequest and does RPT handling.
* 1. Get rpt, get new one if needed
* 2. Catches errors once, then throws if not succeeded authed request
* @param client
* @param request
*/
static authRequest(client, request) {
return ajax_1.ajax(InternalUtils.token2header(client, request))
.pipe(this.catch40xAndRetry(client, request));
}
static createUUID() {
var s = [];
var hexDigits = '0123456789abcdef';
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = '4';
let t = s[19];
s[19] = hexDigits.substr((t & 0x3) | 0x8, 1);
s[8] = s[13] = s[18] = s[23] = '-';
var uuid = s.join('');
return uuid;
}
}
InternalUtils.regex = new RegExp('(\/events|\/stats|\/anonymous-events|\/latest|\/\d{8}|\/\d{8}\/\d{1,2}|\/\d{8}\/\d{1,2}\/\d{1,2})');
/**
* First authenticates and gets a new PAT token, then requests a new RPT token, then retries.
*/
InternalUtils.authAndRetry = (client, wwwAuth, retryAble) => {
const args = {};
if (wwwAuth && wwwAuth.indexOf('UMA') !== -1) {
const t = wwwAuth.substring('UMA'.length).split(',').forEach(part => {
const sides = part.split('=');
const key = sides[0].trim();
const value = JSON.parse(sides[1]);
args[key] = value;
});
}
// Refresh token if needed
logger_1.Logger.debug('Refreshing token', 'AUTH');
return client.refreshPatToken().pipe(operators_1.flatMap(ok => {
let ticket = undefined;
if (client.options.authMode === 'uma') {
ticket = args.ticket;
}
return client.getNewRpt(ticket);
}), operators_1.flatMap(ok => retryAble(client)), operators_1.catchError(err => rxjs_1.throwError(err)));
};
return InternalUtils;
})();
exports.InternalUtils = InternalUtils;