@elastic/ems-client
Version:
JavaScript client library for the Elastic Maps Service
327 lines (319 loc) • 11.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.LATEST_API_URL_PATH = exports.EMSClient = void 0;
var _lodash = _interopRequireDefault(require("lodash"));
var _tms_service = require("./tms_service");
var _file_layer = require("./file_layer");
var _coerce = _interopRequireDefault(require("semver/functions/coerce"));
var _valid = _interopRequireDefault(require("semver/functions/valid"));
var _major = _interopRequireDefault(require("semver/functions/major"));
var _minor = _interopRequireDefault(require("semver/functions/minor"));
var _url = require("url");
var _utils = require("./utils");
var _lruCache = require("lru-cache");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
const REST_API_REGEX = /\d{4}-\d{2}-\d{2}/;
const LATEST_API_URL_PATH = exports.LATEST_API_URL_PATH = 'latest';
/**
* plugins cannot have upstream dependencies on core/*-kibana.
* Work-around by copy-pasting modifyUrl routine here.
* @param url
* @param block
*/
function modifyUrlLocal(url, block) {
const parsed = (0, _url.parse)(url, true);
// copy over the most specific version of each
// property. By default, the parsed url includes
// several conflicting properties (like path and
// pathname + search, or search and query) and keeping
// track of which property is actually used when they
// are formatted is harder than necessary
const meaningfulParts = {
protocol: parsed.protocol,
slashes: parsed.slashes,
auth: parsed.auth,
hostname: parsed.hostname,
port: parsed.port,
pathname: parsed.pathname,
query: parsed.query || {},
hash: parsed.hash
};
// the block modifies the meaningfulParts object, or returns a new one
const modifiedParts = block(meaningfulParts) || meaningfulParts;
// format the modified/replaced meaningfulParts back into a url
return (0, _url.format)({
protocol: modifiedParts.protocol,
slashes: modifiedParts.slashes,
auth: modifiedParts.auth,
hostname: modifiedParts.hostname,
port: modifiedParts.port,
pathname: modifiedParts.pathname,
query: modifiedParts.query,
hash: modifiedParts.hash
});
}
const extendUrl = (url, props) => modifyUrlLocal(url, parsed => _lodash.default.merge(parsed, props));
/**
* Unescape a url template that was escaped by encodeURI() so leaflet
* will be able to correctly locate the variables in the template
* @param {String} url
* @return {String}
*/
const unescapeTemplateVars = url => {
const ENCODED_TEMPLATE_VARS_RE = /%7B(\w+?)%7D/g;
return url.replace(ENCODED_TEMPLATE_VARS_RE, (total, varName) => `{${varName}}`);
};
//this is not the default locale from Kibana, but the default locale supported by the Elastic Maps Service
const DEFAULT_LANGUAGE = 'en';
class EMSClient {
constructor(config) {
_defineProperty(this, "EMS_LOAD_TIMEOUT", 32000);
_defineProperty(this, "_queryParams", void 0);
_defineProperty(this, "_appVersion", void 0);
_defineProperty(this, "_fetchFunction", void 0);
_defineProperty(this, "_sanitizer", void 0);
_defineProperty(this, "_fileApiUrl", void 0);
_defineProperty(this, "_tileApiUrl", void 0);
_defineProperty(this, "_emsVersion", void 0);
_defineProperty(this, "_emsLandingPageUrl", void 0);
_defineProperty(this, "_language", void 0);
_defineProperty(this, "_proxyPath", void 0);
_defineProperty(this, "_cache", void 0);
_defineProperty(this, "_isRestApi", false);
/**
* these methods are assigned outside the constructor
*/
_defineProperty(this, "_getMainCatalog", void 0);
_defineProperty(this, "_getDefaultTMSCatalog", void 0);
_defineProperty(this, "_getDefaultFileCatalog", void 0);
_defineProperty(this, "_loadTMSServices", void 0);
_defineProperty(this, "_loadFileLayers", void 0);
// Remove kbnVersion in 8.0
if ('kbnVersion' in config) {
console.warn('The "kbnVersion" parameter for ems-client is deprecated. Please use "appVersion" instead.');
this._appVersion = config.kbnVersion;
} else {
this._appVersion = config.appVersion;
}
this._queryParams = {
elastic_tile_service_tos: 'agree',
my_app_name: config.appName || 'kibana',
my_app_version: this._appVersion
};
this._sanitizer = config.htmlSanitizer ? config.htmlSanitizer : x => x;
this._tileApiUrl = config.tileApiUrl;
this._fileApiUrl = config.fileApiUrl;
this._emsVersion = this._getEmsVersion(config.emsVersion);
this._isRestApi = this._emsVersion == config.emsVersion;
this._emsLandingPageUrl = config.landingPageUrl || '';
this._language = config.language || DEFAULT_LANGUAGE;
this._fetchFunction = config.fetchFunction;
this._proxyPath = config.proxyPath || '';
this._cache = new _lruCache.LRUCache({
max: config.cacheSize || 10
});
this._invalidateSettings();
}
getDefaultLocale() {
return DEFAULT_LANGUAGE;
}
getLocale() {
return this._language;
}
getValueInLanguage(i18nObject) {
if (!i18nObject) {
return '';
}
return i18nObject[this._language] ? i18nObject[this._language] : i18nObject[DEFAULT_LANGUAGE];
}
/**
* this internal method is overridden by the tests to simulate custom manifest.
*/
async getManifest(endpointUrl) {
try {
const url = extendUrl(endpointUrl, {
query: this._queryParams
});
const response = await this._fetchWithTimeout(url);
if (!response.ok) {
throw new Error(response.statusText);
}
if (response) {
return await response.json();
} else {
throw new Error('Response not found');
}
} catch (e) {
if (e instanceof Error) {
throw e;
} else {
throw new Error('Unknown error', {
cause: e
});
}
}
}
/**
* Add optional query-parameters to all requests
*
* @param additionalQueryParams
*/
addQueryParams(additionalQueryParams) {
for (const key in additionalQueryParams) {
if (Object.prototype.hasOwnProperty.call(additionalQueryParams, key)) {
if (additionalQueryParams[key] !== this._queryParams[key]) {
//changes detected.
this._queryParams = _lodash.default.assign({}, this._queryParams, additionalQueryParams);
this._invalidateSettings();
break;
}
}
}
}
async getMainManifest() {
return await this._getMainCatalog();
}
async getDefaultFileManifest() {
return await this._getDefaultFileCatalog();
}
async getDefaultTMSManifest() {
return await this._getDefaultTMSCatalog();
}
async getFileLayers() {
return await this._loadFileLayers();
}
async getTMSServices() {
return await this._loadTMSServices();
}
cacheGeoJson(layerId, geoJson) {
this._cache.set(layerId, geoJson);
return;
}
getCachedGeoJson(layerId) {
return this._cache.get(layerId);
}
getEmsVersion() {
return this._emsVersion;
}
getTileApiUrl() {
return this._tileApiUrl;
}
getFileApiUrl() {
return this._fileApiUrl;
}
getLandingPageUrl() {
return this._emsLandingPageUrl;
}
sanitizeHtml(html) {
return this._sanitizer(html);
}
extendUrlWithParams(url) {
return unescapeTemplateVars(extendUrl(url, {
query: this._queryParams
}));
}
async findFileLayerById(id) {
const fileLayers = await this.getFileLayers();
for (let i = 0; i < fileLayers.length; i++) {
if (fileLayers[i].hasId(id)) {
return fileLayers[i];
}
}
}
async findTMSServiceById(id) {
const tmsServices = await this.getTMSServices();
for (let i = 0; i < tmsServices.length; i++) {
if (tmsServices[i].hasId(id)) {
return tmsServices[i];
}
}
}
_getEmsVersion(version) {
if (version?.match(REST_API_REGEX)) {
return version;
}
const semverVersion = (0, _valid.default)((0, _coerce.default)(version));
if (semverVersion) {
return `v${(0, _major.default)(semverVersion)}.${(0, _minor.default)(semverVersion)}`;
} else {
throw new Error(`Invalid version: ${version}`);
}
}
_fetchWithTimeout(url) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error(`Request to ${url} timed out`)), this.EMS_LOAD_TIMEOUT);
this._fetchFunction(url).then(response => {
clearTimeout(timer);
resolve(response);
}, err => {
clearTimeout(timer);
reject(err);
});
});
}
_invalidateSettings() {
this._cache.clear();
this._getMainCatalog = _lodash.default.once(async () => {
const services = [];
if (this._tileApiUrl) {
const version = this._isRestApi ? LATEST_API_URL_PATH : this._emsVersion;
services.push({
type: 'tms',
manifest: (0, _utils.toAbsoluteUrl)(this._tileApiUrl, `${version}/manifest`)
});
}
if (this._fileApiUrl) {
const version = this._isRestApi ? LATEST_API_URL_PATH : this._emsVersion;
services.push({
type: 'file',
manifest: (0, _utils.toAbsoluteUrl)(this._fileApiUrl, `${version}/manifest`)
});
}
return {
services: services
};
});
this._getDefaultTMSCatalog = _lodash.default.once(async () => {
const catalogue = await this._getMainCatalog();
const firstService = catalogue.services.find(service => service.type === 'tms');
if (!firstService) {
return {
services: []
};
}
const url = this._proxyPath + firstService.manifest;
return await this.getManifest(url);
});
this._getDefaultFileCatalog = _lodash.default.once(async () => {
const catalogue = await this._getMainCatalog();
const firstService = catalogue.services.find(service => service.type === 'file');
if (!firstService) {
return {
layers: []
};
}
const url = this._proxyPath + firstService.manifest;
return await this.getManifest(url);
});
//Cache the actual instances of TMSService as these in turn cache sub-manifests for the style-files
this._loadTMSServices = _lodash.default.once(async () => {
const tmsManifest = await this._getDefaultTMSCatalog();
return tmsManifest.services.map(serviceConfig => new _tms_service.TMSService(serviceConfig, this, this._proxyPath));
});
this._loadFileLayers = _lodash.default.once(async () => {
const fileManifest = await this._getDefaultFileCatalog();
return fileManifest.layers.map(layerConfig => new _file_layer.FileLayer(layerConfig, this, this._proxyPath));
});
}
}
exports.EMSClient = EMSClient;