const VnObject = require('./object'); const JsonException = require('./json-exception'); /** * Handler for JSON rest connections. */ module.exports = new Class({ Extends: VnObject, _connected: false, _requestsCount: 0, token: null, /** * Initilizes the connection object. */ initialize() { VnObject.prototype.initialize.call(this); this.fetchToken(); }, fetchToken() { let token = null; if (sessionStorage.getItem('vnToken')) token = sessionStorage.getItem('vnToken'); if (localStorage.getItem('vnToken')) token = localStorage.getItem('vnToken'); this.token = token; }, clearToken() { this.token = null; localStorage.removeItem('vnToken'); sessionStorage.removeItem('vnToken'); }, /** * Opens the connection to the REST service. * * @param {String} user The user name * @param {String} password The user password * @param {Boolean} remember Specifies if the user should be remembered * @return {Promise} Resolved when operation is done */ async open(user, pass, remember) { let params; if (user !== null && user !== undefined) { params = { user: user, password: pass, remember: remember }; } else params = null; try { const json = await this.post('Accounts/login', params); const storage = remember ? localStorage : sessionStorage; storage.setItem('vnToken', json.token); this.token = json.token; this._connected = true; this.emit('openned'); } catch(err) { this._closeClient(); throw err; } }, /** * Closes the connection to the REST service. * * @return {Promise} Resolved when operation is done */ async close() { const token = this.token; this._closeClient(); this.emit('closed'); if (token) { const config = { headers: {'Authorization': token} }; await this.post('Accounts/logout', null, config); } }, _closeClient() { this._connected = false; this.clearToken(); }, /** * Supplants another user. * * @param {String} supplantUser The user name */ async supplantUser(supplantUser) { const json = await this.send('client/supplant', {supplantUser}); this.token = json; }, /** * Ends the user supplanting and restores the last login. */ async supplantEnd() { await this.post('Accounts/logout'); this.fetchToken(); }, /** * Executes the specified REST service with the given params and calls * the callback when response is received. * * @param {String} url The service path * @param {Object} params The params to pass to the service * @return {Object} The parsed JSON response */ async send(url, params) { if (!params) params = {}; params.srv = `json:${url}`; return this.sendWithUrl('POST', '.', params); }, async sendForm(form) { const params = {}; const elements = form.elements; for (var i = 0; i < elements.length; i++) if (elements[i].name) params[elements[i].name] = elements[i].value; return this.sendWithUrl('POST', form.action, params); }, async sendFormMultipart(form) { return this.request({ method: 'POST', url: form.action, data: new FormData(form) }); }, async sendFormData(formData) { return this.request({ method: 'POST', url: '', data: formData }); }, /* * Called when REST response is received. */ async sendWithUrl(method, url, params) { return this.request({ method, url, data: Vn.Url.makeUri(params), headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); }, async get(url, config) { config = Object.assign({}, config, { method: 'GET', url: `api/${url}` }); return this.request(config); }, async post(url, data, config) { return this.requestData('POST', url, data, config); }, async patch(url, data, config) { return this.requestData('PATCH', url, data, config); }, async requestData(method, url, data, config) { config = Object.assign({}, config, { method, url: `api/${url}`, data: data && JSON.stringify(data), }); config.headers = Object.assign({}, config.headers, { 'Content-Type': 'application/json;charset=utf-8' }); return this.request(config); }, async request(config) { const request = new XMLHttpRequest(); request.open(config.method, config.url, true); if (this.token) request.setRequestHeader('Authorization', this.token); const headers = config.headers; if (headers) for (const header in headers) request.setRequestHeader(header, headers[header]); const promise = new Promise((resolve, reject) => { request.onreadystatechange = () => this._onStateChange(request, resolve, reject); }); request.send(config.data); this._requestsCount++; if (this._requestsCount === 1) this.emit('loading-changed', true); return promise; }, _onStateChange(request, resolve, reject) { if (request.readyState !== 4) return; this._requestsCount--; if (this._requestsCount === 0) this.emit('loading-changed', false); let data = null; let error = null; try { if (request.status == 0) { const err = new JsonException(); err.message = _('The server does not respond, please check your Internet connection'); err.statusCode = request.status; throw err; } let contentType = null; try { contentType = request .getResponseHeader('Content-Type') .split(';')[0] .trim(); } catch (err) { console.warn(err); } if (contentType != 'application/json') { const err = new JsonException(); err.message = request.statusText; err.statusCode = request.status; throw err; } let json; let jsData; if (request.responseText) json = JSON.parse(request.responseText); if (json) jsData = json.data || json; if (request.status >= 200 && request.status < 300) { data = jsData; } else { let exception = jsData.exception; let error = jsData.error; let err = new JsonException(); if (exception) { exception = exception .replace(/\\/g, '.') .replace(/Exception$/, '') .replace(/^Vn\.Web\./, ''); err.exception = exception; err.message = jsData.message; err.code = jsData.code; err.file = jsData.file; err.line = jsData.line; err.trace = jsData.trace; err.statusCode = request.status; } else if (error) { err.message = error.message; err.code = error.code; err.statusCode = request.status; } else { err.message = request.statusText; err.statusCode = request.status; } throw err; } } catch (e) { data = null; error = e; } if (error) { if (error.exception == 'SessionExpired') this.clearToken(); this.emit('error', error); reject(error); } else resolve(data); } });