/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2021, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a // license agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2021 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// import { FetchError } from "./FetchError"; import { statusText } from "./http"; export function json(request: any): any { return request.then((response) => response.json()); } export function text(request: any): any { return request.then((response) => response.text()); } export function arrayBuffer(request: any): any { return request.then((response) => response.arrayBuffer()); } function error400(text: string, _default = "400") { try { return JSON.parse(text).description; } catch { return _default; } } function handleXMLHttpError(xhr: any): Promise { if (xhr.status === 0) { return Promise.reject(new FetchError(0, "Network error")); } if (xhr.status < 200 || xhr.status > 299) { switch (xhr.status) { case 400: { console.error(xhr.responseText); return Promise.reject(new FetchError(400, error400(xhr.responseText))); } case 500: { console.error(error400(xhr.responseText, xhr.responseText)); return Promise.reject(new FetchError(500)); } default: { return Promise.reject(new FetchError(xhr.status)); } } } return Promise.resolve(xhr); } function handleFetchError(response: Response): Promise { if (!response.ok) { switch (response.status) { case 400: { return response.text().then((text) => { console.error(text); return Promise.reject(new FetchError(400, error400(text))); }); } case 500: { return response.text().then((text) => { console.error(error400(text, text)); return Promise.reject(new FetchError(500)); }); } default: return Promise.reject(new FetchError(response.status, statusText(response.status))); } } return Promise.resolve(response); } export function $init( method: "GET" | "POST" | "PUT" | "DELETE", headers?: HeadersInit, body?: ArrayBuffer | Blob | globalThis.File | FormData | object | string | null, signal?: AbortSignal ): RequestInit { headers = { ...headers }; headers["Content-Type"] = "application/json"; if (method === "POST" || method === "PUT") { if (body instanceof FormData) { delete headers["Content-Type"]; } else if (body instanceof File || body instanceof Blob) { const formData = new FormData(); formData.append("file", body); return $init(method, headers, formData, signal); } else if (body instanceof ArrayBuffer) { const blob = new Blob([body]); return $init(method, headers, blob, signal); } else if (body && typeof body === "object") { body = JSON.stringify(body); } else if (typeof body === "string") { headers["Content-Type"] = "text/plain"; } } const result = { method, headers } as RequestInit; // body with type "object", converted to type "string" in the code above if ( body && (body instanceof ArrayBuffer || body instanceof Blob || body instanceof globalThis.File || body instanceof FormData || typeof body === "string") ) { result.body = body; } if (signal) result.signal = signal; return result; } export function $fetch(url: RequestInfo | URL, init?: RequestInit): Promise { return fetch(url, init).then((response) => handleFetchError(response)); } export function $get(url: RequestInfo | URL, headers?: HeadersInit, signal?: AbortSignal): Promise { return $fetch(url, $init("GET", headers, null, signal)); } export function $put( url: RequestInfo | URL, headers?: HeadersInit, body?: ArrayBuffer | Blob | globalThis.File | FormData | object | string | null ) { return $fetch(url, $init("PUT", headers, body)); } export function $post( url: RequestInfo | URL, headers?: HeadersInit, body?: ArrayBuffer | Blob | globalThis.File | FormData | object | string | null ) { return $fetch(url, $init("POST", headers, body)); } export function $delete(url: RequestInfo | URL, headers?: HeadersInit) { return $fetch(url, $init("DELETE", headers)); } export function streamProgress(stream: ReadableStream, onprogress: (progress: number) => void) { const reader = stream.getReader(); let current = 0; function calc(ev) { if (!ev.done) { reader.read().then(calc).catch(console.error); } if (ev.value) current += ev.value.length; onprogress(current); } reader.read().then(calc).catch(console.error); return stream; } export function downloadProgress(response: Response, onprogress?: (progress: number) => void): Response { const contentLength = response.headers.get("Content-Length") ?? ""; const total = parseInt(contentLength, 10); const tee = response.body.tee(); streamProgress(tee[0], (bytesCount: number) => onprogress && onprogress(bytesCount / total)); return new Response(tee[1]); } interface XMLHttpParams { headers: HeadersInit; method: "GET" | "POST" | "PUT" | "DELETE"; body?: XMLHttpRequestBodyInit | Document; uploadProgress?: (progress: number) => void; downloadProgress?: (progress: number) => void; } export function $XMLHttp( url: string, { headers, method, body, uploadProgress, downloadProgress }: XMLHttpParams ): Promise { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open(method, url, true); for (const key in headers) { xhr.setRequestHeader(key, headers[key]); } function calcProgress(event) { return event.lengthComputable ? event.loaded / event.total : 1; } xhr.upload.onprogress = (event) => uploadProgress && uploadProgress(calcProgress(event)); xhr.onprogress = (event) => downloadProgress && downloadProgress(calcProgress(event)); xhr.onloadend = (event) => handleXMLHttpError(event.target).then(resolve, reject); xhr.send(body); }); } function delay(ms: number, signal: AbortSignal) { return new Promise((resolve) => { let timeoutId = 0; const abortHandler = () => { clearTimeout(timeoutId); resolve(true); }; timeoutId = window.setTimeout(() => { signal.removeEventListener("abort", abortHandler); resolve(false); }, ms); signal.addEventListener("abort", abortHandler, { once: true }); }); } export async function waitFor( func: (params: any) => Promise, params: { timeout?: number; interval?: number; signal?: AbortSignal; abortError?: DOMException; timeoutError?: DOMException; result?: any; } = {} ) { const timeout = params.timeout ?? 600000; const interval = params.interval ?? 3000; const signal = params.signal ?? new AbortController().signal; const abortError = params.abortError ?? new DOMException("Aborted", "AbortError"); const timeoutError = params.timeoutError ?? new DOMException("Timeout", "TimeoutError"); const end = performance.now() + timeout; let count = timeout / interval; do { if (await func(params)) return Promise.resolve(params.result); if ((await delay(interval, signal)) || signal.aborted) return Promise.reject(abortError); } while (performance.now() < end && --count > 0); return Promise.reject(timeoutError); } export async function downloadPartOfFile( requestId: number, records: any | null, url: string, defHeaders: HeadersInit, onProgress?: (progress: number, downloaded: Uint8Array, requestId: number) => void, signal?: AbortSignal ): Promise { const headers = { ...defHeaders }; const isMultipleResourceParts = records && records.length; if (records) { const ranges: string[] = []; if (isMultipleResourceParts) { for (let i = 0; i < records.length; i++) { const record = records[i]; ranges.push(`${record.begin}-${record.end}`); } } else { for (let i = 0; i < records.size(); i++) { const record = records.get(i); ranges.push(`${record.begin}-${record.end}`); record.delete(); } } headers["Range"] = "bytes=" + ranges.join(","); } const response = await $fetch(url, $init("GET", headers, null, signal)); const contentLength = response.headers.get("content-length") ?? ""; const total = parseInt(contentLength, 10); const reader = response.body.getReader(); if (isMultipleResourceParts) { let curRecordIndex = 0; let curRecordPos = 0; while (true) { const { done, value } = await reader.read(); if (done) break; let totalLeft = value.byteLength; let loadedPos = 0; while (totalLeft > 0) { const curRecord = records[curRecordIndex]; const recLeft = curRecord.size - curRecordPos; if (totalLeft < recLeft) { const buf = value.subarray(loadedPos, loadedPos + totalLeft); if (typeof onProgress === "function") onProgress(loadedPos / total, buf, curRecord.reqId); curRecordPos += totalLeft; totalLeft = 0; } else { const buf = value.subarray(loadedPos, loadedPos + recLeft); if (typeof onProgress === "function") onProgress(loadedPos / total, buf, curRecord.reqId); curRecordIndex++; curRecordPos = 0; loadedPos += recLeft; totalLeft -= recLeft; } } } } else { let loaded = 0; while (true) { const { done, value } = await reader.read(); if (done) break; loaded += value.byteLength; if (onProgress) onProgress(loaded / total, value, requestId); } } } export function parseArgs(args: string | object): object { if (typeof args === "string") { const firstArg = args.indexOf("--"); if (firstArg !== -1) args = args.slice(firstArg); const argArray = args .split("--") .map((x) => x .split("=") .map((y) => y.split(" ")) .flat() ) .filter((x) => x[0]) .map((x) => x.concat([""])); return Object.fromEntries(argArray); } return args ?? {}; } export function userFullName(firstName: string | any, lastName = "", userName = ""): string { if (firstName && typeof firstName !== "string") { return userFullName(firstName.firstName ?? firstName.name, firstName.lastName, firstName.userName); } return `${firstName ?? ""} ${lastName ?? ""}`.trim() || userName; } export function userInitials(fullName = ""): string { const names = fullName.split(" ").filter((x) => x); return names .reduce((initials, name, index) => { if (index === 0 || index === names.length - 1) initials += name.charAt(0); return initials; }, "") .toUpperCase(); }