import {EventManager} from './services/events.ts';
-import * as httpInstance from './services/http';
+import {HttpManager} from './services/http.ts';
import Translations from './services/translations';
import * as componentMap from './components';
import {ComponentStore} from './services/components.ts';
};
// Set events and http services on window
-window.$http = httpInstance;
+window.$http = new HttpManager();
window.$events = new EventManager();
// Translation setup
import {ComponentStore} from "./services/components";
import {EventManager} from "./services/events";
+import {HttpManager} from "./services/http";
declare global {
interface Window {
$components: ComponentStore,
$events: EventManager,
+ $http: HttpManager,
+ baseUrl: (path: string) => string;
}
}
\ No newline at end of file
// Docs: https://www.diagrams.net/doc/faq/embed-mode
import * as store from './store';
import {ConfirmDialog} from "../components";
+import {HttpError} from "./http";
type DrawioExportEventResponse = {
action: 'export',
export async function load(drawingId: string): Promise<string> {
try {
const resp = await window.$http.get(window.baseUrl(`/images/drawio/base64/${drawingId}`));
- return `data:image/png;base64,${resp.data.content}`;
+ const data = resp.data as {content: string};
+ return `data:image/png;base64,${data.content}`;
} catch (error) {
- if (error instanceof window.$http.HttpError) {
+ if (error instanceof HttpError) {
window.$events.showResponseError(error);
}
close();
+import {HttpError} from "./http";
+
export class EventManager {
protected listeners: Record<string, ((data: {}) => void)[]> = {};
protected stack: {name: string, data: {}}[] = [];
/**
* Notify standard server-provided error messages.
*/
- showResponseError(responseErr: {status?: number, data?: {message?: string}}): void {
+ showResponseError(responseErr: {status?: number, data?: Record<any, any>}|HttpError): void {
if (!responseErr.status) return;
- if (responseErr.status >= 400 && responseErr.data && responseErr.data.message) {
+ if (responseErr.status >= 400 && typeof responseErr.data === 'object' && responseErr.data.message) {
this.error(responseErr.data.message);
}
}
+++ /dev/null
-/**
- * @typedef FormattedResponse
- * @property {Headers} headers
- * @property {Response} original
- * @property {Object|String} data
- * @property {Boolean} redirected
- * @property {Number} status
- * @property {string} statusText
- * @property {string} url
- */
-
-/**
- * Get the content from a fetch response.
- * Checks the content-type header to determine the format.
- * @param {Response} response
- * @returns {Promise<Object|String>}
- */
-async function getResponseContent(response) {
- if (response.status === 204) {
- return null;
- }
-
- const responseContentType = response.headers.get('Content-Type') || '';
- const subType = responseContentType.split(';')[0].split('/').pop();
-
- if (subType === 'javascript' || subType === 'json') {
- return response.json();
- }
-
- return response.text();
-}
-
-export class HttpError extends Error {
-
- constructor(response, content) {
- super(response.statusText);
- this.data = content;
- this.headers = response.headers;
- this.redirected = response.redirected;
- this.status = response.status;
- this.statusText = response.statusText;
- this.url = response.url;
- this.original = response;
- }
-
-}
-
-/**
- * @param {String} method
- * @param {String} url
- * @param {Object} events
- * @return {XMLHttpRequest}
- */
-export function createXMLHttpRequest(method, url, events = {}) {
- const csrfToken = document.querySelector('meta[name=token]').getAttribute('content');
- const req = new XMLHttpRequest();
-
- for (const [eventName, callback] of Object.entries(events)) {
- req.addEventListener(eventName, callback.bind(req));
- }
-
- req.open(method, url);
- req.withCredentials = true;
- req.setRequestHeader('X-CSRF-TOKEN', csrfToken);
-
- return req;
-}
-
-/**
- * Create a new HTTP request, setting the required CSRF information
- * to communicate with the back-end. Parses & formats the response.
- * @param {String} url
- * @param {Object} options
- * @returns {Promise<FormattedResponse>}
- */
-async function request(url, options = {}) {
- let requestUrl = url;
-
- if (!requestUrl.startsWith('http')) {
- requestUrl = window.baseUrl(requestUrl);
- }
-
- if (options.params) {
- const urlObj = new URL(requestUrl);
- for (const paramName of Object.keys(options.params)) {
- const value = options.params[paramName];
- if (typeof value !== 'undefined' && value !== null) {
- urlObj.searchParams.set(paramName, value);
- }
- }
- requestUrl = urlObj.toString();
- }
-
- const csrfToken = document.querySelector('meta[name=token]').getAttribute('content');
- const requestOptions = {...options, credentials: 'same-origin'};
- requestOptions.headers = {
- ...requestOptions.headers || {},
- baseURL: window.baseUrl(''),
- 'X-CSRF-TOKEN': csrfToken,
- };
-
- const response = await fetch(requestUrl, requestOptions);
- const content = await getResponseContent(response);
- const returnData = {
- data: content,
- headers: response.headers,
- redirected: response.redirected,
- status: response.status,
- statusText: response.statusText,
- url: response.url,
- original: response,
- };
-
- if (!response.ok) {
- throw new HttpError(response, content);
- }
-
- return returnData;
-}
-
-/**
- * Perform a HTTP request to the back-end that includes data in the body.
- * Parses the body to JSON if an object, setting the correct headers.
- * @param {String} method
- * @param {String} url
- * @param {Object} data
- * @returns {Promise<FormattedResponse>}
- */
-async function dataRequest(method, url, data = null) {
- const options = {
- method,
- body: data,
- };
-
- // Send data as JSON if a plain object
- if (typeof data === 'object' && !(data instanceof FormData)) {
- options.headers = {
- 'Content-Type': 'application/json',
- 'X-Requested-With': 'XMLHttpRequest',
- };
- options.body = JSON.stringify(data);
- }
-
- // Ensure FormData instances are sent over POST
- // Since Laravel does not read multipart/form-data from other types
- // of request. Hence the addition of the magic _method value.
- if (data instanceof FormData && method !== 'post') {
- data.append('_method', method);
- options.method = 'post';
- }
-
- return request(url, options);
-}
-
-/**
- * Perform a HTTP GET request.
- * Can easily pass query parameters as the second parameter.
- * @param {String} url
- * @param {Object} params
- * @returns {Promise<FormattedResponse>}
- */
-export async function get(url, params = {}) {
- return request(url, {
- method: 'GET',
- params,
- });
-}
-
-/**
- * Perform a HTTP POST request.
- * @param {String} url
- * @param {Object} data
- * @returns {Promise<FormattedResponse>}
- */
-export async function post(url, data = null) {
- return dataRequest('POST', url, data);
-}
-
-/**
- * Perform a HTTP PUT request.
- * @param {String} url
- * @param {Object} data
- * @returns {Promise<FormattedResponse>}
- */
-export async function put(url, data = null) {
- return dataRequest('PUT', url, data);
-}
-
-/**
- * Perform a HTTP PATCH request.
- * @param {String} url
- * @param {Object} data
- * @returns {Promise<FormattedResponse>}
- */
-export async function patch(url, data = null) {
- return dataRequest('PATCH', url, data);
-}
-
-/**
- * Perform a HTTP DELETE request.
- * @param {String} url
- * @param {Object} data
- * @returns {Promise<FormattedResponse>}
- */
-async function performDelete(url, data = null) {
- return dataRequest('DELETE', url, data);
-}
-
-export {performDelete as delete};
-
-/**
- * Parse the response text for an error response to a user
- * presentable string. Handles a range of errors responses including
- * validation responses & server response text.
- * @param {String} text
- * @returns {String}
- */
-export function formatErrorResponseText(text) {
- const data = text.startsWith('{') ? JSON.parse(text) : {message: text};
- if (!data) {
- return text;
- }
-
- if (data.message || data.error) {
- return data.message || data.error;
- }
-
- const values = Object.values(data);
- const isValidation = values.every(val => {
- return Array.isArray(val) || val.every(x => typeof x === 'string');
- });
-
- if (isValidation) {
- return values.flat().join(' ');
- }
-
- return text;
-}
--- /dev/null
+type ResponseData = Record<any, any>|string;
+
+type RequestOptions = {
+ params?: Record<string, string>,
+ headers?: Record<string, string>
+};
+
+type FormattedResponse = {
+ headers: Headers;
+ original: Response;
+ data: ResponseData;
+ redirected: boolean;
+ status: number;
+ statusText: string;
+ url: string;
+};
+
+export class HttpError extends Error implements FormattedResponse {
+
+ data: ResponseData;
+ headers: Headers;
+ original: Response;
+ redirected: boolean;
+ status: number;
+ statusText: string;
+ url: string;
+
+ constructor(response: Response, content: ResponseData) {
+ super(response.statusText);
+ this.data = content;
+ this.headers = response.headers;
+ this.redirected = response.redirected;
+ this.status = response.status;
+ this.statusText = response.statusText;
+ this.url = response.url;
+ this.original = response;
+ }
+}
+
+export class HttpManager {
+
+ /**
+ * Get the content from a fetch response.
+ * Checks the content-type header to determine the format.
+ */
+ protected async getResponseContent(response: Response): Promise<ResponseData|null> {
+ if (response.status === 204) {
+ return null;
+ }
+
+ const responseContentType = response.headers.get('Content-Type') || '';
+ const subType = responseContentType.split(';')[0].split('/').pop();
+
+ if (subType === 'javascript' || subType === 'json') {
+ return response.json();
+ }
+
+ return response.text();
+ }
+
+ createXMLHttpRequest(method: string, url: string, events: Record<string, (e: Event) => void> = {}): XMLHttpRequest {
+ const csrfToken = document.querySelector('meta[name=token]')?.getAttribute('content');
+ const req = new XMLHttpRequest();
+
+ for (const [eventName, callback] of Object.entries(events)) {
+ req.addEventListener(eventName, callback.bind(req));
+ }
+
+ req.open(method, url);
+ req.withCredentials = true;
+ req.setRequestHeader('X-CSRF-TOKEN', csrfToken || '');
+
+ return req;
+ }
+
+ /**
+ * Create a new HTTP request, setting the required CSRF information
+ * to communicate with the back-end. Parses & formats the response.
+ */
+ protected async request(url: string, options: RequestOptions & RequestInit = {}): Promise<FormattedResponse> {
+ let requestUrl = url;
+
+ if (!requestUrl.startsWith('http')) {
+ requestUrl = window.baseUrl(requestUrl);
+ }
+
+ if (options.params) {
+ const urlObj = new URL(requestUrl);
+ for (const paramName of Object.keys(options.params)) {
+ const value = options.params[paramName];
+ if (typeof value !== 'undefined' && value !== null) {
+ urlObj.searchParams.set(paramName, value);
+ }
+ }
+ requestUrl = urlObj.toString();
+ }
+
+ const csrfToken = document.querySelector('meta[name=token]')?.getAttribute('content') || '';
+ const requestOptions: RequestInit = {...options, credentials: 'same-origin'};
+ requestOptions.headers = {
+ ...requestOptions.headers || {},
+ baseURL: window.baseUrl(''),
+ 'X-CSRF-TOKEN': csrfToken,
+ };
+
+ const response = await fetch(requestUrl, requestOptions);
+ const content = await this.getResponseContent(response) || '';
+ const returnData: FormattedResponse = {
+ data: content,
+ headers: response.headers,
+ redirected: response.redirected,
+ status: response.status,
+ statusText: response.statusText,
+ url: response.url,
+ original: response,
+ };
+
+ if (!response.ok) {
+ throw new HttpError(response, content);
+ }
+
+ return returnData;
+ }
+
+ /**
+ * Perform a HTTP request to the back-end that includes data in the body.
+ * Parses the body to JSON if an object, setting the correct headers.
+ */
+ protected async dataRequest(method: string, url: string, data: Record<string, any>|null): Promise<FormattedResponse> {
+ const options: RequestInit & RequestOptions = {
+ method,
+ body: data as BodyInit,
+ };
+
+ // Send data as JSON if a plain object
+ if (typeof data === 'object' && !(data instanceof FormData)) {
+ options.headers = {
+ 'Content-Type': 'application/json',
+ 'X-Requested-With': 'XMLHttpRequest',
+ };
+ options.body = JSON.stringify(data);
+ }
+
+ // Ensure FormData instances are sent over POST
+ // Since Laravel does not read multipart/form-data from other types
+ // of request, hence the addition of the magic _method value.
+ if (data instanceof FormData && method !== 'post') {
+ data.append('_method', method);
+ options.method = 'post';
+ }
+
+ return this.request(url, options);
+ }
+
+ /**
+ * Perform a HTTP GET request.
+ * Can easily pass query parameters as the second parameter.
+ */
+ async get(url: string, params: {} = {}): Promise<FormattedResponse> {
+ return this.request(url, {
+ method: 'GET',
+ params,
+ });
+ }
+
+ /**
+ * Perform a HTTP POST request.
+ */
+ async post(url: string, data: null|Record<string, any> = null): Promise<FormattedResponse> {
+ return this.dataRequest('POST', url, data);
+ }
+
+ /**
+ * Perform a HTTP PUT request.
+ */
+ async put(url: string, data: null|Record<string, any> = null): Promise<FormattedResponse> {
+ return this.dataRequest('PUT', url, data);
+ }
+
+ /**
+ * Perform a HTTP PATCH request.
+ */
+ async patch(url: string, data: null|Record<string, any> = null): Promise<FormattedResponse> {
+ return this.dataRequest('PATCH', url, data);
+ }
+
+ /**
+ * Perform a HTTP DELETE request.
+ */
+ async delete(url: string, data: null|Record<string, any> = null): Promise<FormattedResponse> {
+ return this.dataRequest('DELETE', url, data);
+ }
+
+ /**
+ * Parse the response text for an error response to a user
+ * presentable string. Handles a range of errors responses including
+ * validation responses & server response text.
+ */
+ protected formatErrorResponseText(text: string): string {
+ const data = text.startsWith('{') ? JSON.parse(text) : {message: text};
+ if (!data) {
+ return text;
+ }
+
+ if (data.message || data.error) {
+ return data.message || data.error;
+ }
+
+ const values = Object.values(data);
+ const isValidation = values.every(val => {
+ return Array.isArray(val) && val.every(x => typeof x === 'string');
+ });
+
+ if (isValidation) {
+ return values.flat().join(' ');
+ }
+
+ return text;
+ }
+
+}