UNPKG

@nextgis/ngw-connector

Version:

A lightweight HTTP client optimized for use with NextGIS Web API

1,529 lines (1,503 loc) 46.2 kB
/** 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