@nextgis/ngw-connector
Version:
A lightweight HTTP client optimized for use with NextGIS Web API
1,529 lines (1,503 loc) • 46.2 kB
JavaScript
/** Bundle of @nextgis/ngw-connector; version: 3.0.1; author: NextGIS */
'use strict';
var utils = require('@nextgis/utils');
var Cache = require('@nextgis/cache');
var events = require('events');
const templateRe = /\{ *([\w_-]+) *\}/g;
function template(str, data) {
return str.replace(templateRe, (s, key) => {
let value = data[key];
if (value === void 0) {
throw new Error("No value provided for letiable " + s);
} else if (typeof value === "function") {
value = value(data);
}
return value;
});
}
async function apiRequest(opt) {
const { params, name, connector, requestOptions } = opt;
const apiItems = await connector.connect();
let apiItem = apiItems && apiItems[name];
if (apiItem) {
apiItem = [...apiItem];
let url = apiItem.shift();
if (apiItem.length) {
const replaceParams = {};
for (let fry = 0; fry < apiItem.length; fry++) {
const arg = apiItem[fry];
replaceParams[fry] = `{${arg}}`;
if (params[arg] === void 0) {
throw new Error(`\`${arg}\` URL API argument is not specified`);
}
}
if (url) {
url = template(url, replaceParams);
}
}
if (params) {
const paramArray = [];
const paramList = params.paramList;
if (Array.isArray(paramList)) {
paramList.forEach(([key, value]) => {
paramArray.push(`${key}=${value}`);
});
}
for (const p in params) {
if (apiItem.indexOf(p) === -1) {
paramArray.push(`${p}=${params[p]}`);
}
}
if (paramArray.length) {
url = `${url}?${paramArray.join("&")}`;
}
}
if (url) {
return connector.makeQuery(url, params, {
cacheName: name,
...requestOptions
});
} else {
throw new Error("Request URL is not set");
}
} else {
return void 0;
}
}
var pkg = {
name: "@nextgis/ngw-connector",
version: "3.0.1",
_priority: 12,
description: "A lightweight HTTP client optimized for use with NextGIS Web API",
main: "index.js",
module: "lib/ngw-connector.esm-bundler.js",
unpkg: "lib/ngw-connector.global.prod.js",
jsdelivr: "lib/ngw-connector.global.prod.js",
types: "lib/index.d.ts",
dependencies: {
"@nextgis/cache": "3.0.0",
"@nextgis/cancelable-promise": "3.0.0",
"@nextgis/utils": "3.0.0",
"@types/events": "^3.0.0",
events: "*",
"form-data": "^4.0.0"
},
devDependencies: {
"@nextgis/build-tools": "3.0.0"
},
scripts: {
clean: "rimraf ./lib",
dev: "node ../build-tools/lib/build.js",
prod: "npm run dev -- --release",
lint: "eslint ./src/**/*.ts --fix --c ../../.eslintrc",
watch: "npm run dev -- --watch",
"gen:types": "node ./scripts/generator"
},
buildOptions: {
name: "NgwConnector",
formats: [
"esm-bundler",
"esm-browser",
"cjs",
"global"
]
},
keywords: [
"NextGIS",
"MAP"
],
author: "NextGIS",
files: [
"index.js",
"lib"
],
license: "MIT",
homepage: "https://github.com/nextgis/nextgis_frontend/tree/master/packages/ngw-connector#readme",
repository: {
type: "git",
url: "git://github.com/nextgis/nextgis_frontend.git"
},
engines: {
node: ">=18.20.4"
},
gitHead: "edc68033f914f6c95c4125b9738bba6d36e990b4"
};
class AbortError extends Error {
constructor(message = "AbortError") {
super(message);
this.name = "AbortError";
}
}
var __defProp$8 = Object.defineProperty;
var __defNormalProp$8 = (obj, key, value) => key in obj ? __defProp$8(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$8 = (obj, key, value) => __defNormalProp$8(obj, typeof key !== "symbol" ? key + "" : key, value);
class NgwError extends Error {
constructor(er) {
super();
__publicField$8(this, "name", "NgwError");
__publicField$8(this, "title");
__publicField$8(this, "message");
__publicField$8(this, "detail");
__publicField$8(this, "exception");
__publicField$8(this, "status_code");
__publicField$8(this, "data");
__publicField$8(this, "guru_meditation");
Object.assign(this, er);
Object.setPrototypeOf(this, NgwError.prototype);
}
}
var __defProp$7 = Object.defineProperty;
var __defNormalProp$7 = (obj, key, value) => key in obj ? __defProp$7(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$7 = (obj, key, value) => __defNormalProp$7(obj, typeof key !== "symbol" ? key + "" : key, value);
class InsufficientPermissionsError extends NgwError {
constructor(obj) {
super(obj);
__publicField$7(this, "name", "InsufficientPermissionsError");
__publicField$7(this, "exception", "nextgisweb.core.exception.InsufficientPermissions");
Object.setPrototypeOf(this, InsufficientPermissionsError.prototype);
}
}
var __defProp$6 = Object.defineProperty;
var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$6 = (obj, key, value) => __defNormalProp$6(obj, typeof key !== "symbol" ? key + "" : key, value);
let NetworksResponseError$1 = class NetworksResponseError extends NgwError {
constructor(obj) {
super(obj);
__publicField$6(this, "message", "There is no response from the server or problem connecting to server.");
__publicField$6(this, "title", "Network error");
__publicField$6(this, "detail", "Check network connectivity and try again later.");
Object.setPrototypeOf(this, NetworksResponseError.prototype);
}
};
function extractError(error) {
if (utils.isObject(error)) {
if (error.name && error.message && error.title) {
return {
title: error.title,
message: error.message,
detail: error.detail || null,
data: error.data && error.data.data ? error.data.data : null
};
} else if (error.exception) {
if (error.status === void 0 || error.status === 0 || error.data === void 0) {
return new NetworksResponseError$1({
title: error.title,
status_code: error.status_code,
exception: error.exception
});
}
}
return {
title: typeof error.title === "string" ? error.title : "Unexpected error",
message: typeof error.message === "string" ? error.message : "Something went wrong."
};
}
}
function isError(error) {
if (utils.isObject(error)) {
return error.status_code && error.exception && error.title;
}
return false;
}
var __defProp$5 = Object.defineProperty;
var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$5 = (obj, key, value) => __defNormalProp$5(obj, key + "" , value);
class NetworkError extends Error {
constructor(url) {
super();
__publicField$5(this, "name", "NetworkError");
Object.setPrototypeOf(this, NetworkError.prototype);
this.message = `Unable to request ${url}.
Possibly invalid NGW URL entered or CORS not configured to get request from ${location.origin}`;
}
}
var __defProp$4 = Object.defineProperty;
var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
class ResourceNotFoundError extends NgwError {
constructor(obj) {
super(obj);
__publicField$4(this, "name", "ResourceNotFoundError");
__publicField$4(this, "exception", "nextgisweb.resource.exception.ResourceNotFound");
Object.setPrototypeOf(this, ResourceNotFoundError.prototype);
}
}
var errors = /*#__PURE__*/Object.freeze({
__proto__: null,
AbortError: AbortError,
InsufficientPermissionsError: InsufficientPermissionsError,
NetworkError: NetworkError,
NetworksResponseError: NetworksResponseError$1,
NgwError: NgwError,
ResourceNotFoundError: ResourceNotFoundError,
extractError: extractError,
isError: isError
});
var __defProp$3 = Object.defineProperty;
var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
class BaseAPIError extends Error {
constructor(message) {
super(message || "Something went wrong.");
__publicField$3(this, "title");
this.name = "BaseAPIError";
this.title = "Unknown API error";
if (Error.captureStackTrace) {
Error.captureStackTrace(this, BaseAPIError);
}
}
}
class NetworksResponseError extends BaseAPIError {
// prettier-ignore
constructor(message) {
super(message || "There is no response from the server or problem connecting to server.");
__publicField$3(this, "detail");
this.title = "Network error";
this.detail = "Check network connectivity and try again later.";
}
}
class InvalidResponseError extends BaseAPIError {
constructor(message) {
super(message || "Something went wrong.");
this.title = "Unexpected server response";
}
}
class ServerResponseError extends BaseAPIError {
constructor(data) {
super(data.message);
__publicField$3(this, "detail");
__publicField$3(this, "data");
this.title = data.title || this.title;
this.detail = data.detail || null;
this.data = data;
}
}
class LunkwillError extends Error {
// prettier-ignore
constructor(message, data = {}) {
super(message || "Unexpected error while processing long-running request.");
__publicField$3(this, "title");
__publicField$3(this, "data");
this.name = "LunkwillError";
this.title = "Long-running request error";
this.data = data;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, LunkwillError);
}
}
}
class LunkwillRequestCancelled extends LunkwillError {
constructor(data) {
super("Long-running request was cancelled.", data);
}
}
class LunkwillRequestFailed extends LunkwillError {
constructor(data) {
super(void 0, data);
}
}
function lunkwillCheckResponse(lwResp) {
const ct = lwResp.headers.get("content-type");
return ct !== void 0 && ct !== null && ct.includes("application/vnd.lunkwill.request-summary+json");
}
async function responseJson(response) {
try {
return response.json();
} catch {
throw new Error();
}
}
async function lunkwillResponseUrl(lwResp) {
const lwData = await responseJson(lwResp);
let delay = lwData.delay_ms;
const retry = lwData.retry_ms !== void 0 ? lwData.retry_ms : 2e3;
const sum = `/api/lunkwill/${lwData.id}/summary`;
const res = `/api/lunkwill/${lwData.id}/response`;
const sleep = (msec) => new Promise((resolve) => setTimeout(resolve, msec));
let failed = false;
let ready = false;
while (!ready) {
await sleep(failed ? retry : delay);
failed = false;
let lwResp2;
let lwData2;
try {
lwResp2 = await fetch(sum, { credentials: "same-origin" });
lwData2 = await lwResp2.json();
} catch {
failed = true;
continue;
}
switch (lwData2.status) {
case void 0:
throw new LunkwillError(void 0, lwData2);
case "ready":
ready = true;
break;
case "cancelled":
throw new LunkwillRequestCancelled(lwData2);
case "failed":
throw new LunkwillRequestFailed(lwData2);
case "spooled":
case "processing":
case "buffering":
delay = lwData2.delay_ms;
break;
default:
throw new LunkwillError(void 0, lwData2);
}
}
return res;
}
async function lunkwillFetch(lwRespUrl) {
try {
return await window.fetch(lwRespUrl, { credentials: "same-origin" });
} catch {
throw new NetworksResponseError();
}
}
const mediaContentTypes = [
"application/pdf",
"image/png",
"image/jpeg",
"image/tiff",
"text/csv"
];
const mediaContentTypesRegex = new RegExp(mediaContentTypes.join("|"));
function isMediaContentType(contentType) {
return mediaContentTypesRegex.test(contentType);
}
function encodeQueryParams(value) {
const result = [];
for (const [k, v] of Object.entries(value)) {
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
result.push(`${k}=${encodeURIComponent(v)}`);
} else if (Array.isArray(v)) {
result.push(`${k}=${v.map(encodeURIComponent).join(",")}`);
} else {
for (const [sk, sv] of Object.entries(v)) {
const ske = `${k}[${encodeURIComponent(sk)}]`;
if (typeof sv === "string" || typeof sv === "number" || typeof sv === "boolean") {
result.push(`${ske}=${encodeURIComponent(sv)}`);
} else if (Array.isArray(sv)) {
const sve = sv.map(encodeURIComponent);
result.push(`${ske}=${sve.join(",")}`);
}
}
}
}
return result.join("&");
}
function generateUrl(path, query) {
let urlParams = "";
if (query !== void 0) {
urlParams = "?" + encodeQueryParams(query);
}
return path + urlParams;
}
async function request(path, options, cacheEngine) {
var _a, _b;
const defaults = {
method: "GET",
credentials: "same-origin",
headers: {}
};
const {
withCredentials,
responseType,
cacheProps,
cacheName,
lunkwill,
cache,
query,
json,
...opt
} = {
...defaults,
...options
};
opt.method = (_a = opt.method) == null ? void 0 : _a.toUpperCase();
if (withCredentials) {
opt.credentials = "include";
}
let useLunkwill = false;
if (lunkwill !== void 0) {
lunkwill.toHeaders(opt.headers || {});
useLunkwill = true;
}
const lunkwillReturnUrl = !!opt.lunkwillReturnUrl;
delete opt.lunkwillReturnUrl;
if (json !== void 0) {
opt.body = JSON.stringify(json);
const headers = opt.headers || {};
headers["Content-Type"] = "application/json";
opt.headers = headers;
}
const url = generateUrl(path, query);
const makeRequest = async () => {
let response;
try {
response = await fetch(url, opt);
} catch (e) {
if (opt.signal && opt.signal.aborted) {
throw e;
}
throw new NetworksResponseError();
}
if (useLunkwill && lunkwillCheckResponse(response)) {
const lwRespUrl = await lunkwillResponseUrl(response);
if (lunkwillReturnUrl) {
return lwRespUrl;
}
response = await lunkwillFetch(lwRespUrl);
}
const respCType = response.headers.get("content-type");
const respJSON = respCType && (respCType.includes("application/json") || respCType.includes("application/vnd.lunkwill.request-summary+json"));
let body;
try {
const respMedia = respCType && isMediaContentType(respCType);
if (responseType === "blob" || respMedia) {
body = await response.blob();
} else if (respJSON) {
body = await response.json();
} else {
throw new InvalidResponseError();
}
} catch (e) {
if (e.name === "AbortError" || e instanceof InvalidResponseError) {
throw e;
}
throw new InvalidResponseError();
}
if (400 <= response.status && response.status <= 599) {
throw new ServerResponseError(body);
}
return body;
};
if (cacheEngine) {
if (((_b = opt.method) == null ? void 0 : _b.toUpperCase()) === "GET") {
if (cache !== false) {
const props = cacheProps ? cacheProps : {
...utils.objectRemoveEmpty({
withCredentials,
responseType
})
};
return cacheEngine.add(cacheName || url, makeRequest, {
props,
expirationTime: cache ? void 0 : 500
});
}
} else {
const ignoredForClean = ["api/feature_layer/identify"];
if (ignoredForClean.every((part) => !path.includes(part))) {
cacheEngine.clean();
}
}
}
return makeRequest();
}
function routeURL(name, baseUrl, routeData, ...rest) {
const [template, ...params] = routeData[name];
const first = rest[0];
let sub;
if (first === void 0) {
sub = [];
} else if (typeof first === "object" && first !== null) {
if (rest.length > 1) {
throw new Error("Too many arguments for route(name, object)!");
}
sub = [];
for (const [p, v] of Object.entries(first)) {
sub[params.indexOf(p)] = String(v);
}
} else {
sub = rest.map((v) => String(v));
}
return utils.fixUrlStr(
baseUrl + template.replace(/\{(\w+)\}/g, function(m, a) {
const idx = parseInt(a);
const value = sub[idx];
if (value === void 0) {
const msg = `Undefined parameter ${idx} in "${template}".`;
throw new Error(msg);
}
return String(value);
})
);
}
function route(name, connector, ...rest) {
const result = {
url: async (opt) => {
var _a;
const routeData = await connector.connect();
const template = routeURL(
name,
(_a = connector.options.baseUrl) != null ? _a : "",
routeData,
...rest
);
return generateUrl(template, opt == null ? void 0 : opt.query);
}
};
const methods = ["get", "post", "put", "delete", "patch"];
for (const method of methods) {
const methodResp = (requestOptions) => {
var _a;
const { headers: optHeaders, ...restOpt } = requestOptions || {};
if ((_a = requestOptions == null ? void 0 : requestOptions.signal) == null ? void 0 : _a.aborted) {
throw new AbortError();
}
return connector.connect().then((routeData) => {
var _a2;
const template = routeURL(
name,
(_a2 = connector.options.baseUrl) != null ? _a2 : "",
routeData,
...rest
);
const headers = utils.objectRemoveEmpty({
...connector.getAuthorizationHeaders(),
...optHeaders != null ? optHeaders : {}
});
return request(
template,
{
headers,
...restOpt,
method
},
connector.cache
);
});
};
result[method] = methodResp;
}
return result;
}
function isObject(val) {
return Object.prototype.toString.call(val) === "[object Object]";
}
let loadData;
{
const url = require("url");
const http = require("http");
const https = require("https");
const FormData2 = require("form-data");
const adapterFor = (inputUrl) => {
const adapters = {
"http:": http,
"https:": https
};
const protocol = url.parse(inputUrl).protocol || "https:";
return adapters[protocol];
};
loadData = (url2, callback, options = {}, error, onCancel) => {
const { file, headers, method, data, responseType } = options;
const request = new Promise((resolve, reject) => {
const adapter = adapterFor(url2);
if (adapter) {
const requestOpt = {
headers: headers || {},
method
};
const body = typeof data === "string" ? data : JSON.stringify(data);
let form;
let uploadedFile = file;
if (file) {
const fileMeta = {};
if (isObject(file) && "file" in file && ("filename" in file || "name" in file)) {
const {
file: file_,
name,
...fileMeta_
} = file;
if (name && !fileMeta_.filename) {
fileMeta_.filename = name;
}
Object.assign(fileMeta, fileMeta_);
uploadedFile = file_;
}
form = new FormData2();
form.append("file", uploadedFile, fileMeta);
if (data) {
for (const d in data) {
form.append(d, data[d]);
}
}
Object.assign(requestOpt.headers, {
// 'content-length': form.getLengthSync(),
...form.getHeaders()
});
}
if (body !== void 0) {
Object.assign(requestOpt.headers, {
"content-type": "application/json",
"content-length": Buffer.byteLength(body)
});
}
const req = adapter.request(url2, requestOpt, (resp) => {
let data2 = "";
resp.on("data", (chunk) => {
data2 += chunk;
});
resp.on("end", () => {
if (data2) {
if (responseType === "blob") {
resolve(data2);
} else {
let json;
try {
json = JSON.parse(data2);
if (json && json.status_code && json.status_code) {
reject(json.message);
}
} catch (er) {
reject(er);
}
if (json !== void 0) {
if (isError(json)) {
reject("extractError(json)");
} else {
resolve(json);
}
}
}
}
reject("no data");
});
});
if (form) {
form.pipe(req);
}
req.on("error", (err) => {
reject(err);
});
if (body) {
req.write(body);
}
onCancel(() => {
req.abort();
});
req.end();
} else {
throw new Error(`Given URL '${url2}' is not correct`);
}
});
return request.then((data2) => {
if (callback) {
callback(data2);
}
return data2;
}).catch((er) => {
if (error) {
error(er);
} else {
throw new Error(er);
}
});
};
}
const CONNECTORS = [];
function addConnector(connector) {
CONNECTORS.push(connector);
}
function findConnector(options) {
return CONNECTORS.find((x) => {
if (x.options.baseUrl === options.baseUrl) {
if (options.auth) {
if (x.options.auth) {
return utils.objectDeepEqual(x.options.auth, options.auth);
}
} else {
return true;
}
}
});
}
function removeConnector(connector) {
const index = CONNECTORS.indexOf(connector);
if (index !== -1) {
CONNECTORS.splice(index, 1);
}
}
var __defProp$2 = Object.defineProperty;
var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
let ID = 0;
let REQUEST_ID = 0;
class NgwConnector {
constructor(options) {
this.options = options;
__publicField$2(this, "id", ID++);
__publicField$2(this, "emitter", new events.EventEmitter());
__publicField$2(this, "user");
__publicField$2(this, "cache");
__publicField$2(this, "withCredentials");
// Dedicated cache for route requests, as these are executed before any other requests.
// This cache does not need to be cleared during the session.
__publicField$2(this, "routeCache");
__publicField$2(this, "client", `NextGIS-NGW-Connector/${pkg.version}`);
__publicField$2(this, "routeStr", "/api/component/pyramid/route");
__publicField$2(this, "activeRequests", {});
__publicField$2(this, "requestTransform");
const exist = findConnector(options);
this.cache = new Cache({ namespace: options.cacheId });
this.routeCache = new Cache({ namespace: "routecache" });
if (exist) {
return exist;
} else {
const { route: route2, requestTransform, withCredentials } = this.options;
if (route2) {
this.routeStr = route2;
}
if (requestTransform) {
this.requestTransform = requestTransform;
}
if (withCredentials !== void 0) {
this.withCredentials = withCredentials;
}
addConnector(this);
}
}
/**
* Clear the cache.
*/
clearCache() {
this.cache.clean();
}
setRequestTransform(requestTransform) {
this.requestTransform = requestTransform;
}
/**
* Fast way to specify the connection address to NextGIS Web.
* The current connection will be severed.
* @param baseUrl - NGW url
*/
setNgw(baseUrl) {
this.logout();
this.options.baseUrl = baseUrl;
addConnector(this);
}
/**
* Establishing a connection with NextGIS Web to fulfill all other requests.
* @remarks
* This method need not be called manually as it is used when forming a request in {@link apiRequest | apiRequest}.
* Can be used to check connection.
* @example
* ```javascript
* const connector = new NgwConnector({ baseUrl: 'https://demo.nextgis.com' });
* connector.connect()
* .then(() => console.log('Ok'))
* .catch((er) => console.log('Connection problem', er));
* ```
*/
async connect({
signal
} = {}) {
const auth = this.options.auth;
if (auth) {
const { login, password } = auth;
if (login && password) {
await this._login({ login, password });
}
}
const routeUrl = `${this.routeStr}?client=${this.client}`;
return this.routeCache.add(
this.options.baseUrl || String(this.id),
() => this.makeQuery(routeUrl, null, { signal, cache: false })
);
}
/**
* Quick way to change NextGIS Web user.
* @param credentials - New user credentials
*/
login(credentials, options) {
this.logout();
addConnector(this);
return this._login(credentials, options);
}
/**
* Disconnecting a user. Aborting all current requests
*/
logout() {
this.abort();
removeConnector(this);
this.options.auth = void 0;
this.user = void 0;
this.routeCache.clean();
this.clearCache();
this.emitter.emit("logout");
}
async getUserInfo(credentials, options) {
if (this.user && this.user.id) {
return this.user;
}
if (credentials) {
this.options.auth = credentials;
}
const options_ = {
headers: this.getAuthorizationHeaders(credentials),
cache: true,
...options
};
return this.makeQuery(
"/api/component/auth/current_user",
{},
options_
);
}
/**
* Obtaining the required Headers for authentication of requests in the NGW.
*/
getAuthorizationHeaders(credentials) {
const client = this.makeClientId(credentials);
if (client) {
return {
Authorization: `Basic ${client}`
};
}
return {};
}
makeClientId(credentials) {
credentials = credentials || this.options.auth;
if (credentials) {
const { login, password } = credentials;
const encodedStr = `${login}:${password}`;
{
return Buffer.from(encodedStr).toString("base64");
}
}
}
/** Stop all api requests */
abort() {
for (const abortController of Object.values(this.activeRequests)) {
abortController.abort();
}
this.activeRequests = {};
}
getActiveApiRequests() {
return { ...this.activeRequests };
}
route(name, ...rest) {
return route(name, this, ...rest);
}
/**
* Send request to NGW.
* @param url - URL address to NGW
* @param params - Query params
* @param options - Request options
*/
async makeQuery(url, params, options = {}) {
var _a;
url = (this.options.baseUrl ? this.options.baseUrl : "") + url;
if (!url) {
throw new Error("Empty `url` not allowed");
}
if (params) {
const { paramList, ...restParams } = params;
url = template(url, restParams);
}
url = encodeURI(utils.fixUrlStr(url));
options = { withCredentials: this.withCredentials, ...options };
const {
cache,
signal: externalSignal,
method = "GET",
headers,
cacheName,
cacheProps,
responseType,
withCredentials
} = options;
const internalAbortController = new AbortController();
const internalSignal = internalAbortController.signal;
if (externalSignal) {
if (externalSignal.aborted) {
throw new AbortError();
}
externalSignal.addEventListener("abort", () => {
internalAbortController.abort();
});
}
options.signal = internalSignal;
const createPromise = async () => {
const id = REQUEST_ID++;
this.activeRequests[id] = internalAbortController;
try {
return this._loadData(url, options);
} finally {
this._cleanActiveRequest(id);
}
};
if (method === "GET" && cache !== false) {
const cacheOptions = cacheProps ? cacheProps : {
...utils.objectRemoveEmpty({
headers,
withCredentials,
responseType,
baseUrl: this.options.baseUrl,
userId: (_a = this.user) == null ? void 0 : _a.id
}),
params
};
return this.cache.add(cacheName || url, createPromise, {
props: cacheOptions,
expirationTime: cache ? void 0 : 500
});
}
return createPromise();
}
_loadData(url, options) {
options.responseType = options.responseType || "json";
return new Promise((resolve, reject) => {
var _a;
if (this.user) {
options = options || {};
options.headers = {
...this.getAuthorizationHeaders(),
...options.headers
};
}
if (this.requestTransform) {
const [transUrl, transOptions] = this.requestTransform(url, options);
url = transUrl;
options = transOptions;
}
let runOnAbort = void 0;
loadData(url, resolve, options, reject, (handler) => {
runOnAbort = handler;
});
(_a = options.signal) == null ? void 0 : _a.addEventListener("abort", () => {
if (runOnAbort !== void 0) {
runOnAbort();
}
reject(new AbortError());
});
}).catch((httpError) => {
if (httpError.name !== "AbortError") {
{
console.warn("DEV WARN", httpError);
}
const er = this._handleHttpError(httpError);
if (er) {
throw er;
}
}
throw httpError;
});
}
async _login(credentials, options) {
try {
const data = await this.getUserInfo(credentials, options);
this.user = data;
this.emitter.emit("login", data);
return data;
} catch (er) {
this.emitter.emit("login:error", er);
throw er;
}
}
_cleanActiveRequest(requestId) {
delete this.activeRequests[requestId];
}
_handleHttpError(er) {
if (er) {
if (er instanceof NgwError) {
if (er.exception === "nextgisweb.resource.exception.ResourceNotFound") {
throw new ResourceNotFoundError(er);
} else if (er.exception === "nextgisweb.core.exception.InsufficientPermissions") {
throw new InsufficientPermissionsError(er);
}
}
}
return er;
}
}
__publicField$2(NgwConnector, "errors", errors);
function resourceCompare(res1, res2) {
return utils.objectDeepEqual(res1, res2);
}
const exclude = ["description"];
function resourceToQuery(resource, prefix = "") {
prefix = prefix ? prefix + "__" : "";
const query = {};
for (const [key, value] of Object.entries(resource)) {
if (exclude.indexOf(key) === -1) {
if (isObject(value)) {
if (key === "owner_user") {
const children = resourceToQuery(
value,
key
);
Object.assign(query, children);
} else if (key === "parent" && "id" in value) {
query.parent_id = value.id;
}
} else if (utils.defined(value)) {
query[prefix + key] = value;
}
}
}
return query;
}
var __defProp$1 = Object.defineProperty;
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
class ResourcesControl {
constructor({
connector,
cacheId
}) {
__publicField$1(this, "cache");
__publicField$1(this, "connector");
this.connector = connector;
this.cache = new Cache({ namespace: cacheId });
}
// -------------------------------------------------------------------------
// Resource Methods
// -------------------------------------------------------------------------
/**
* Receive resource from NGW by id, keyname or search-object parameter.
* @param resource - Resource id, keyname or search-object
*
* @remarks
* Fetching resource would be cached to speed up next call
*/
getOne(resource, requestOptions) {
const opt = { ...requestOptions };
if (typeof resource === "string") ; else if (typeof resource === "number") ; else if (isObject(resource)) {
if (resource.id !== void 0) {
resource.id;
} else {
if (resource.keyname) {
resource.keyname;
}
if (resource.display_name) {
resource.display_name;
}
}
}
if (typeof resource === "string") {
return this._fetchResourceBy({ keyname: resource }, opt);
} else if (typeof resource === "number") {
return this._fetchResourceById(resource, opt);
} else if (isObject(resource)) {
return this._fetchResourceBy(resource, opt);
}
return Promise.resolve(void 0);
}
getOneOrFail(resource, requestOptions) {
return this.getOne(resource, requestOptions).then((res) => {
if (res) {
return res;
}
throw new ResourceNotFoundError();
});
}
/**
* A fast way to retrieve resource ID for any resource definition.
* @param resource - Any available resource definition
*
* @remarks
* There are situations when exactly the resource id is needed
* (for example, to compose the correct request to the api)
* then this method will come in handy to facilitate the extraction of the identifier
* if the resource is specified through a keyname or other parameters.
*/
getId(resource, requestOptions) {
if (typeof resource === "number") {
return Promise.resolve(resource);
} else if (typeof resource === "string" || isObject(resource)) {
return this.getOne(resource, requestOptions).then((res) => {
if (res) {
return res.resource.id;
}
});
}
return Promise.resolve(void 0);
}
/**
* A fast way to retrieve resource ID for any resource definition.
* @param resource - Any available resource definition
*
* @remarks
* Similar with {@link NgwConnector.getResourceId | getResourceId} but rise error if resource is not exist.
* To not make one more checks if the resource is definitely exists
*/
getIdOrFail(resource, requestOptions) {
return this.getId(resource, requestOptions).then((resp) => {
if (resp === void 0) {
throw new Error();
}
return resp;
});
}
getMany(resource, requestOptions) {
return this._resourceCacheFilter(resource).then((items) => {
if (!items.length) {
const query = {};
if (resource.keyname) {
query.keyname = resource.keyname;
} else {
Object.assign(query, resourceToQuery(resource));
}
return this.connector.route("resource.search").get({
...requestOptions,
query: {
serialization: "full",
...query
}
}).then((resources) => {
if ((requestOptions == null ? void 0 : requestOptions.cache) && resources) {
for (const x of resources) {
this.cache.add("resource.item", Promise.resolve(x), {
id: x.resource.id
});
}
}
return resources;
});
}
return items;
});
}
getParent(resource, requestOptions) {
return this.getOne(resource, requestOptions).then((child) => {
var _a, _b;
if ((_b = (_a = child == null ? void 0 : child.resource) == null ? void 0 : _a.parent) == null ? void 0 : _b.id) {
return this.getOne(child.resource.parent.id, requestOptions);
}
return Promise.resolve(void 0);
});
}
getChildrenOf(resource, requestOptions) {
return this.getIdOrFail(resource).then(
(parent) => this._getChildrenOf(parent, requestOptions)
);
}
update(resource, data) {
return this.getId(resource).then((id) => {
if (id !== void 0) {
return this.connector.put("resource.item", { data }, { id });
}
});
}
/**
* Fast way to delete resource from NGW and clean cache.
* @param resource - Resource definition
*/
delete(resource) {
return this.getId(resource).then((id) => {
if (id !== void 0) {
return this.connector.delete("resource.item", null, { id }).then(() => {
this._cleanResourceItemCache(id);
return void 0;
});
}
});
}
async _getChildrenOf(parentDef, requestOptions, _items = []) {
let parent = void 0;
if (typeof parentDef === "string") {
parent = await this.getId(parentDef, requestOptions);
} else if (typeof parentDef === "object") {
parent = parentDef.id;
} else {
parent = parentDef;
}
const items = await this.connector.route("resource.collection").get({
...requestOptions,
query: {
parent
}
});
const recursivePromises = [];
for (const item of items) {
if (requestOptions == null ? void 0 : requestOptions.cache) {
this.cache.add("resource.item", Promise.resolve(item), {
id: item.resource.id
});
}
_items.push(item);
if ((requestOptions == null ? void 0 : requestOptions.recursive) && item.resource.children) {
recursivePromises.push(
this._getChildrenOf(item.resource.id, requestOptions, _items)
);
}
}
if (recursivePromises.length) {
return Promise.all(recursivePromises).then(() => {
return _items;
});
}
return _items;
}
async _cleanResourceItemCache(id) {
var _a;
const all = this.cache.all();
const toDelete = [];
for (const c of all) {
const cid = (_a = c.props) == null ? void 0 : _a.id;
if (["resource.item", "resource"].includes(c.key) && cid !== void 0) {
if (typeof cid === "number") {
if (cid === id) {
toDelete.push(c);
}
} else {
const rid = await this.getId(cid);
if (rid === id) {
toDelete.push(c);
}
}
}
}
for (const d of toDelete) {
this.cache.delete(d);
}
}
_fetchResourceById(id, requestOptions) {
return this.connector.route("resource.item", { id }).get(requestOptions);
}
_fetchResourceBy(resource, requestOptions) {
return this.getMany(resource, requestOptions).then((resources) => {
return resources[0];
});
}
_resourceCacheFilter(resource) {
return Promise.all(this.cache.matchAll("resource.item")).then(
(resources) => {
const items = [];
resources.filter((x) => {
if (x) {
if (resource.keyname && x.resource.keyname) {
return resource.keyname === x.resource.keyname;
}
if (utils.defined(resource.id) && utils.defined(x.resource.id)) {
return resource.id === x.resource.id;
}
return resourceCompare(resource, x.resource);
}
});
return items;
}
);
}
}
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
class NgwConnectorExtended extends NgwConnector {
constructor(options) {
super(options);
__publicField(this, "resources");
this.resources = new ResourcesControl({
connector: this,
cacheId: options.cacheId
});
}
static create(options) {
return new this(options);
}
/**
* Clear the cache.
*/
clearCache() {
super.clearCache();
this.resources.cache.clean();
}
/**
* Send request to NGW api router.
* @param name - NGW route name from {@link https://docs.nextgis.com/docs_ngweb_dev/doc/developer/resource.html#routes | routes}
* @param params - Request item params or query params
* @param requestOptions - Request options
*
* @example
* ```javascript
*
* // there is such an NGW route item
* // "feature_layer.feature.item": [
* // "/api/resource/{0}/feature/{1}",
* // "id",
* // "fid"
* // ],
*
* const connector = new NgwConnector({ baseUrl: 'https://example.nextgis.com' });
* connector.apiRequest('feature_layer.feature.item', {
* // request params for {0} and {1}
* 'id': 2011,
* 'fid': 101,
* // query params
* 'srs': 4326,
* 'geom_format': 'geojson',
* }, { method: 'GET' });
* // send get-request to 'https://example.nextgis.com/api/resource/2011/feature/101?srs=4326&geom_format=geojson'
*
* ```
*/
apiRequest(name, params_ = {}, requestOptions = {}) {
var _a;
params_ = (_a = requestOptions.params) != null ? _a : params_;
const params = utils.objectRemoveEmpty(params_);
return apiRequest({ name, params, requestOptions, connector: this });
}
/**
* Shortcut method for send POST request to NGW.
* @param name - NGW route name from {@link https://docs.nextgis.com/docs_ngweb_dev/doc/developer/resource.html#routes | routes}
* @param options - Request options
* @param params - Request item params or query params
*
* @example
* ```javascript
* connector.post('resource.collection', { data: POST_PAYLOAD })
* .then((newResource) => console.log(newResource))
* .catch((error) => console.warn(error));
* ```
*/
post(name, options, params) {
options = options || {};
options.method = "POST";
return this.apiRequest(
name,
params,
options
);
}
/**
* Shortcut method for send GET request to NGW.
* @param name - NGW route name from {@link https://docs.nextgis.com/docs_ngweb_dev/doc/developer/resource.html#routes | routes}
* @param options - Request options
* @param params - Request item params or query params
*/
get(name, options, params) {
options = options || {};
options.method = "GET";
return this.apiRequest(
name,
params,
options
);
}
/**
* Shortcut method for send PATCH request to NGW.
* @param name - NGW route name from {@link https://docs.nextgis.com/docs_ngweb_dev/doc/developer/resource.html#routes | routes}
* @param options - Request options
* @param params - Request item params or query params
*/
patch(name, options, params) {
options = options || {};
options.method = "PATCH";
return this.apiRequest(
name,
params,
options
);
}
/**
* Shortcut method for send PUT request to NGW.
* @param name - NGW route name from {@link https://docs.nextgis.com/docs_ngweb_dev/doc/developer/resource.html#routes | routes}
* @param options - Request options
* @param params - Request item params or query params
*/
put(name, options, params) {
options = options || {};
options.method = "PUT";
return this.apiRequest(
name,
params,
options
);
}
/**
* Shortcut method for send DELETE request to NGW.
* @param name - NGW route name from {@link https://docs.nextgis.com/docs_ngweb_dev/doc/developer/resource.html#routes | routes}
* @param options - Request options
* @param params - Request item params or query params
*/
delete(name, options, params) {
options = options || {};
options.method = "DELETE";
return this.apiRequest(
name,
params,
options
);
}
// -------------------------------------------------------------------------
// Resource Methods
// -------------------------------------------------------------------------
/**
* {@link ResourcesControl.getOne}
*/
getResource(resource, requestOptions) {
return this.resources.getOne(resource, requestOptions);
}
/**
* {@link ResourcesControl.getOneOrFail}
*/
getResourceOrFail(resource, requestOptions) {
return this.resources.getOneOrFail(resource, requestOptions);
}
/**
* @deprecated - use {@link getResource}
*/
getResourceBy(resource) {
return this.resources.getOne(resource);
}
/**
* @deprecated - use {@link getResource}
*/
getResourceByKeyname(keyname) {
return this.resources.getOne(keyname);
}
/**
* @deprecated - use {@link getResource}
*/
getResourceById(id) {
return this.resources.getOne(id);
}
/**
* {@link ResourcesControl.getId}
*/
getResourceId(resource, requestOptions) {
return this.resources.getId(resource, requestOptions);
}
/**
* {@link ResourcesControl.getIdOrFail}
*/
getResourceIdOrFail(resource, requestOptions) {
return this.resources.getIdOrFail(resource, requestOptions);
}
/**
* {@link ResourcesControl.getMany}
*/
getResourcesBy(resource, requestOptions) {
return this.resources.getMany(resource, requestOptions);
}
/**
* {@link ResourcesControl.getParent}
*/
getResourceParent(resource, requestOptions) {
return this.resources.getParent(resource, requestOptions);
}
/**
* {@link ResourcesControl.getChildrenOf}
*/
getResourceChildren(resource, requestOptions) {
return this.resources.getChildrenOf(resource, requestOptions);
}
/**
* {@link ResourcesControl.update}
*/
updateResource(resource, data) {
return this.resources.update(resource, data);
}
/**
* {@link ResourcesControl.delete}
*/
deleteResource(resource) {
return this.resources.delete(resource);
}
}
module.exports = NgwConnectorExtended;
//# sourceMappingURL=ngw-connector.cjs.js.map