/* * Copyright (C) 2019 Intel Corporation * SPDX-License-Identifier: MIT */ /* global require:false */ (() => { const FormData = require('form-data'); const { ServerError, } = require('./exceptions'); const store = require('store'); const config = require('./config'); function generateError(errorData) { if (errorData.response) { const message = `${errorData.message}. ${JSON.stringify(errorData.response.data) || ''}.`; return new ServerError(message, errorData.response.status); } // Server is unavailable (no any response) const message = `${errorData.message}.`; // usually is "Error Network" return new ServerError(message, 0); } class ServerProxy { constructor() { const Axios = require('axios'); Axios.defaults.withCredentials = true; Axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN'; Axios.defaults.xsrfCookieName = 'csrftoken'; let token = store.get('token'); if (token) { Axios.defaults.headers.common.Authorization = `Token ${token}`; } async function about() { const { backendAPI } = config; let response = null; try { response = await Axios.get(`${backendAPI}/server/about`, { proxy: config.proxy, }); } catch (errorData) { throw generateError(errorData); } return response.data; } async function share(directory) { const { backendAPI } = config; directory = encodeURIComponent(directory); let response = null; try { response = await Axios.get(`${backendAPI}/server/share?directory=${directory}`, { proxy: config.proxy, }); } catch (errorData) { throw generateError(errorData); } return response.data; } async function exception(exceptionObject) { const { backendAPI } = config; try { await Axios.post(`${backendAPI}/server/exception`, JSON.stringify(exceptionObject), { proxy: config.proxy, headers: { 'Content-Type': 'application/json', }, }); } catch (errorData) { throw generateError(errorData); } } async function formats() { const { backendAPI } = config; let response = null; try { response = await Axios.get(`${backendAPI}/server/annotation/formats`, { proxy: config.proxy, }); } catch (errorData) { throw generateError(errorData); } return response.data; } async function datasetFormats() { const { backendAPI } = config; let response = null; try { response = await Axios.get(`${backendAPI}/server/dataset/formats`, { proxy: config.proxy, }); response = JSON.parse(response.data); } catch (errorData) { throw generateError(errorData); } return response; } async function register(username, firstName, lastName, email, password1, password2) { let response = null; try { const data = JSON.stringify({ username, first_name: firstName, last_name: lastName, email, password1, password2, }); response = await Axios.post(`${config.backendAPI}/auth/register`, data, { proxy: config.proxy, headers: { 'Content-Type': 'application/json', }, }); } catch (errorData) { throw generateError(errorData); } return response.data; } async function login(username, password) { const authenticationData = ([ `${encodeURIComponent('username')}=${encodeURIComponent(username)}`, `${encodeURIComponent('password')}=${encodeURIComponent(password)}`, ]).join('&').replace(/%20/g, '+'); Axios.defaults.headers.common.Authorization = ''; let authenticationResponse = null; try { authenticationResponse = await Axios.post( `${config.backendAPI}/auth/login`, authenticationData, { proxy: config.proxy, }, ); } catch (errorData) { throw generateError(errorData); } if (authenticationResponse.headers['set-cookie']) { // Browser itself setup cookie and header is none // In NodeJS we need do it manually const cookies = authenticationResponse.headers['set-cookie'].join(';'); Axios.defaults.headers.common.Cookie = cookies; } token = authenticationResponse.data.key; store.set('token', token); Axios.defaults.headers.common.Authorization = `Token ${token}`; } async function logout() { try { await Axios.post(`${config.backendAPI}/auth/logout`, { proxy: config.proxy, }); } catch (errorData) { throw generateError(errorData); } store.remove('token'); Axios.defaults.headers.common.Authorization = ''; } async function authorized() { try { await module.exports.users.getSelf(); } catch (serverError) { if (serverError.code === 401) { return false; } throw serverError; } return true; } async function serverRequest(url, data) { try { return (await Axios({ url, ...data, })).data; } catch (errorData) { throw generateError(errorData); } } async function getTasks(filter = '') { const { backendAPI } = config; let response = null; try { response = await Axios.get(`${backendAPI}/tasks?page_size=10&${filter}`, { proxy: config.proxy, }); } catch (errorData) { throw generateError(errorData); } response.data.results.count = response.data.count; return response.data.results; } async function saveTask(id, taskData) { const { backendAPI } = config; try { await Axios.patch(`${backendAPI}/tasks/${id}`, JSON.stringify(taskData), { proxy: config.proxy, headers: { 'Content-Type': 'application/json', }, }); } catch (errorData) { throw generateError(errorData); } } async function deleteTask(id) { const { backendAPI } = config; try { await Axios.delete(`${backendAPI}/tasks/${id}`); } catch (errorData) { throw generateError(errorData); } } async function exportDataset(id, format) { const { backendAPI } = config; let url = `${backendAPI}/tasks/${id}/dataset?format=${format}`; return new Promise((resolve, reject) => { async function request() { try { const response = await Axios .get(`${url}`, { proxy: config.proxy, }); if (response.status === 202) { setTimeout(request, 3000); } else { url = `${url}&action=download`; resolve(url); } } catch (errorData) { reject(generateError(errorData)); } } setTimeout(request); }); } async function createTask(taskData, files, onUpdate) { const { backendAPI } = config; async function wait(id) { return new Promise((resolve, reject) => { async function checkStatus() { try { const response = await Axios.get(`${backendAPI}/tasks/${id}/status`); if (['Queued', 'Started'].includes(response.data.state)) { if (response.data.message !== '') { onUpdate(response.data.message); } setTimeout(checkStatus, 1000); } else if (response.data.state === 'Finished') { resolve(); } else if (response.data.state === 'Failed') { // If request has been successful, but task hasn't been created // Then passed data is wrong and we can pass code 400 const message = 'Could not create the task on the server. ' + `${response.data.message}.`; reject(new ServerError(message, 400)); } else { // If server has another status, it is unexpected // Therefore it is server error and we can pass code 500 reject(new ServerError( `Unknown task state has been received: ${response.data.state}`, 500, )); } } catch (errorData) { reject( generateError(errorData), ); } } setTimeout(checkStatus, 1000); }); } const batchOfFiles = new FormData(); for (const key in files) { if (Object.prototype.hasOwnProperty.call(files, key)) { for (let i = 0; i < files[key].length; i++) { batchOfFiles.append(`${key}[${i}]`, files[key][i]); } } } let response = null; onUpdate('The task is being created on the server..'); try { response = await Axios.post(`${backendAPI}/tasks`, JSON.stringify(taskData), { proxy: config.proxy, headers: { 'Content-Type': 'application/json', }, }); } catch (errorData) { throw generateError(errorData); } onUpdate('The data is being uploaded to the server..'); try { await Axios.post(`${backendAPI}/tasks/${response.data.id}/data`, batchOfFiles, { proxy: config.proxy, }); } catch (errorData) { try { await deleteTask(response.data.id); } catch (_) { // ignore } throw generateError(errorData); } try { await wait(response.data.id); } catch (createException) { await deleteTask(response.data.id); throw createException; } const createdTask = await getTasks(`?id=${response.id}`); return createdTask[0]; } async function getJob(jobID) { const { backendAPI } = config; let response = null; try { response = await Axios.get(`${backendAPI}/jobs/${jobID}`, { proxy: config.proxy, }); } catch (errorData) { throw generateError(errorData); } return response.data; } async function saveJob(id, jobData) { const { backendAPI } = config; try { await Axios.patch(`${backendAPI}/jobs/${id}`, JSON.stringify(jobData), { proxy: config.proxy, headers: { 'Content-Type': 'application/json', }, }); } catch (errorData) { throw generateError(errorData); } } async function getUsers(id = null) { const { backendAPI } = config; let response = null; try { if (id === null) { response = await Axios.get(`${backendAPI}/users?page_size=all`, { proxy: config.proxy, }); } else { response = await Axios.get(`${backendAPI}/users/${id}`, { proxy: config.proxy, }); } } catch (errorData) { throw generateError(errorData); } return response.data.results; } async function getSelf() { const { backendAPI } = config; let response = null; try { response = await Axios.get(`${backendAPI}/users/self`, { proxy: config.proxy, }); } catch (errorData) { throw generateError(errorData); } return response.data; } async function getPreview(tid) { const { backendAPI } = config; let response = null; try { // TODO: change 0 frame to preview response = await Axios.get(`${backendAPI}/tasks/${tid}/frames/0`, { proxy: config.proxy, responseType: 'blob', }); } catch (errorData) { const code = errorData.response ? errorData.response.status : errorData.code; throw new ServerError( `Could not get preview frame for the task ${tid} from the server`, code, ); } return response.data; } async function getData(tid, frame) { const { backendAPI } = config; let response = null; try { response = await Axios.get(`${backendAPI}/tasks/${tid}/frames/${frame}`, { proxy: config.proxy, responseType: 'blob', }); } catch (errorData) { throw generateError(errorData); } return response.data; } async function getMeta(tid) { const { backendAPI } = config; let response = null; try { response = await Axios.get(`${backendAPI}/tasks/${tid}/frames/meta`, { proxy: config.proxy, }); } catch (errorData) { throw generateError(errorData); } return response.data; } // Session is 'task' or 'job' async function getAnnotations(session, id) { const { backendAPI } = config; let response = null; try { response = await Axios.get(`${backendAPI}/${session}s/${id}/annotations`, { proxy: config.proxy, }); } catch (errorData) { throw generateError(errorData); } return response.data; } // Session is 'task' or 'job' async function updateAnnotations(session, id, data, action) { const { backendAPI } = config; let requestFunc = null; let url = null; if (action.toUpperCase() === 'PUT') { requestFunc = Axios.put.bind(Axios); url = `${backendAPI}/${session}s/${id}/annotations`; } else { requestFunc = Axios.patch.bind(Axios); url = `${backendAPI}/${session}s/${id}/annotations?action=${action}`; } let response = null; try { response = await requestFunc(url, JSON.stringify(data), { proxy: config.proxy, headers: { 'Content-Type': 'application/json', }, }); } catch (errorData) { throw generateError(errorData); } return response.data; } // Session is 'task' or 'job' async function uploadAnnotations(session, id, file, format) { const { backendAPI } = config; let annotationData = new FormData(); annotationData.append('annotation_file', file); return new Promise((resolve, reject) => { async function request() { try { const response = await Axios .put(`${backendAPI}/${session}s/${id}/annotations?format=${format}`, annotationData, { proxy: config.proxy, }); if (response.status === 202) { annotationData = new FormData(); setTimeout(request, 3000); } else { resolve(); } } catch (errorData) { reject(generateError(errorData)); } } setTimeout(request); }); } // Session is 'task' or 'job' async function dumpAnnotations(id, name, format) { const { backendAPI } = config; const filename = name.replace(/\//g, '_'); const baseURL = `${backendAPI}/tasks/${id}/annotations/${encodeURIComponent(filename)}`; let query = `format=${encodeURIComponent(format)}`; let url = `${baseURL}?${query}`; return new Promise((resolve, reject) => { async function request() { Axios.get(`${url}`, { proxy: config.proxy, }).then((response) => { if (response.status === 202) { setTimeout(request, 3000); } else { query = `${query}&action=download`; url = `${baseURL}?${query}`; resolve(url); } }).catch((errorData) => { reject(generateError(errorData)); }); } setTimeout(request); }); } async function saveLogs(logs) { const { backendAPI } = config; try { await Axios.post(`${backendAPI}/server/logs`, JSON.stringify(logs), { proxy: config.proxy, headers: { 'Content-Type': 'application/json', }, }); } catch (errorData) { throw generateError(errorData); } } Object.defineProperties(this, Object.freeze({ server: { value: Object.freeze({ about, share, formats, datasetFormats, exception, login, logout, authorized, register, request: serverRequest, }), writable: false, }, tasks: { value: Object.freeze({ getTasks, saveTask, createTask, deleteTask, exportDataset, }), writable: false, }, jobs: { value: Object.freeze({ getJob, saveJob, }), writable: false, }, users: { value: Object.freeze({ getUsers, getSelf, }), writable: false, }, frames: { value: Object.freeze({ getData, getMeta, getPreview, }), writable: false, }, annotations: { value: Object.freeze({ updateAnnotations, getAnnotations, dumpAnnotations, uploadAnnotations, }), writable: false, }, logs: { value: Object.freeze({ save: saveLogs, }), writable: false, }, })); } } const serverProxy = new ServerProxy(); module.exports = serverProxy; })();