UNPKG

sap-servicelayer

Version:

Biblioteca TypeScript para integração com SAP Service Layer API

501 lines (500 loc) 17.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ServiceLayer = void 0; const axios_1 = __importDefault(require("axios")); const https_1 = __importDefault(require("https")); /** * Agent HTTPS que permite certificados auto-assinados */ const agent = new https_1.default.Agent({ rejectUnauthorized: false, }); /** * Classe para integração com SAP Service Layer */ class ServiceLayer { /** * Construtor da classe ServiceLayer * @param config Configurações do Service Layer */ constructor(config) { this.cookie = ''; this.routerId = ''; this.retries = 0; this.database = config.database; this.url = config.url; this.username = config.username; this.password = config.password; this.language = config.language || '29'; this.timeout = config.timeout || 0; } /** * Realiza login no Service Layer */ async login() { const me = this; async function execute(retries = 0) { me.routerId = ''; me.cookie = ''; // Quando mandamos o cookie antigo no login, ocorre um erro na SL. Erro: name.toLowerCase is not a function const urlParse = `${me.url}/Login`; let response; try { response = await axios_1.default.post(urlParse, { CompanyDB: me.database, UserName: me.username, Password: me.password, Language: me.language, }, { httpsAgent: agent, timeout: me.timeout, }); } catch (ex) { const error = ex; if (!error.response) { error.response = {}; } if (!error.response || !error.response.status) { throw error; } switch (error.response.status) { case 502: console.log(`Nova tentativa, erro 502. ${retries}`); if (retries < 5) { return await execute(++retries); } else { throw error; } default: throw error; } } const headerCookie = response.headers['set-cookie']; if (headerCookie) { headerCookie.forEach((cookie) => { const [key, value] = cookie.split('='); if (cookie.indexOf('ROUTEID') > -1) { me.routerId += `${key}=${value}`; } else { me.cookie += `${key}=${value}`; } }); } } return await execute(); } /** * Realiza logout do Service Layer * Nota: Deve ser feito no lado do cliente apenas apagando o token recebido no login */ async logout() { // Implementação deve ser feita no lado do cliente this.cookie = ''; this.routerId = ''; } /** * Executa uma requisição no Service Layer (método privado) * @param config Configurações da requisição * @returns Dados da resposta */ async execute(config) { const executeRequest = async (retries = 0) => { const customHeaders = { Cookie: this.cookie, }; if (config.header) { for (const prop in config.header) { customHeaders[prop] = config.header[prop]; } } if (config.size) { customHeaders['Prefer'] = `odata.maxpagesize=${config.size}`; } let info = ''; if (config.page !== undefined && config.page !== null) { const skipPage = (config.page || 0) * (config.size || 20); const idx = config.url.indexOf('?'); info = (idx >= 0 ? '&' : '?') + `$skip=${skipPage}`; } const newUrl = `${this.url}/${config.url}${info}`; let response; try { response = await (0, axios_1.default)({ method: config.method, url: newUrl, httpsAgent: agent, headers: customHeaders, data: config.data ? JSON.stringify(config.data) : null, timeout: config.timeout || 0, }); } catch (ex) { const error = ex; if (!error.response) { error.response = {}; } if (!error.response || !error.response.status) { throw error; } switch (error.response.status) { case 401: console.log(`Reconectando na service layer. ${retries}`); await this.login(); if (retries < 5) { return await executeRequest(++retries); } else { throw error; } case 502: console.log(`Nova tentativa, erro 502. ${retries}`); if (retries < 5) { return await executeRequest(++retries); } else { throw error; } default: throw error; } } const responseData = response.data; if (config.page !== undefined && config.page !== null) { responseData.previous = config.page > 0; responseData.next = responseData['odata.nextLink'] !== undefined; } return responseData; }; return await executeRequest(); } /** * Gera um GUID único */ generateGuid() { const S4 = () => { return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); }; return (S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4()); } /** * Cria headers formatados para batch */ createHeader(header) { let data = ''; for (const prop in header) { data += `${prop}: ${header[prop]}\n`; } return data; } /** * Obtém a versão do Service Layer da URL */ getVersionSL(fullUrl) { const match = fullUrl.match(/\/b1s\/v[0-9]+/); const basePath = match ? match[0] : '/b1s/v1'; return basePath.endsWith('/') ? basePath : `${basePath}/`; } /** * Executa operações em batch * @param config Configurações do batch * @returns Array de respostas das operações */ async executeBatch(config) { const executeBatchRequest = async (retries = 0) => { const batchBoundary = 'batch_' + this.generateGuid(); const changeSetBoundary = config.transaction ? 'changeset_' + this.generateGuid() : ''; const customHeaders = { Cookie: this.cookie, 'Content-Type': `multipart/mixed;boundary=${batchBoundary}`, 'OData-Version': '4.0', }; if (config.header) { for (const prop in config.header) { customHeaders[prop] = config.header[prop]; } } const url = `${this.url}/$batch`; const versionSL = this.getVersionSL(this.url); let data = ''; for (let i = 0; i < config.operations.length; i++) { const operation = config.operations[i]; if (i === 0 && config.transaction) { data += `--${batchBoundary}\n`; data += `Content-Type: multipart/mixed;boundary=${changeSetBoundary}\n`; } if (config.transaction) { data += `--${changeSetBoundary}\n`; } else { data += `--${batchBoundary}\n`; } data += 'Content-Type: application/http\n'; data += 'Content-Transfer-Encoding:binary\n'; data += `Content-ID: ${i + 1}\n\n`; data += `${operation.method} ${versionSL}${operation.url}\n`; data += this.createHeader(operation.header || {}); data += '\n'; if (operation.data) { data += JSON.stringify(operation.data) + '\n\n'; } } if (config.transaction) { data += `--${changeSetBoundary}--\n`; } data += `--${batchBoundary}--\n`; let response; try { response = await (0, axios_1.default)({ method: 'post', url, httpsAgent: agent, headers: customHeaders, data, timeout: config.timeout || 0, }); } catch (ex) { const error = ex; if (!error.response) { error.response = {}; } if (!error.response || !error.response.status) { throw error; } switch (error.response.status) { case 401: console.log(`Reconectando na service layer. ${retries}`); await this.login(); if (retries < 5) { return await executeBatchRequest(++retries); } else { throw error; } case 502: console.log(`Nova tentativa, erro 502. ${retries}`); if (retries < 5) { return await executeBatchRequest(++retries); } else { throw error; } default: throw error; } } const responseArray = []; const responseLines = response.data.split(/\r?\n/); const boundaryRex = response.data.match(/\n--changesetresponse/) ? new RegExp('^--changesetresponse') : new RegExp('^--batchresponse'); let seekStatus = 'init'; let contentId = null; let httpCode = null; let httpStatus = null; let json = null; let jsonStr = ''; for (let i = 0; i < responseLines.length; i++) { const line = responseLines[i]; if (seekStatus === 'init') { if (!line.match(boundaryRex)) { continue; } else { seekStatus = 'response'; continue; } } else { if (line.match(boundaryRex)) { const responseItem = { httpCode: httpCode || '', httpStatus: httpStatus || '', contentId, json, }; responseArray.push(responseItem); contentId = null; httpCode = null; httpStatus = null; json = null; jsonStr = ''; seekStatus = 'response'; continue; } } if (seekStatus === 'response') { let m = line.match(/^Content-ID: (\d+)$/); if (m) { contentId = m[1]; continue; } m = line.match(/^HTTP\/\d\.\d (\d+) (.+)$/); if (m) { httpCode = m[1]; httpStatus = m[2]; continue; } if (line.match(/^{$/)) { jsonStr += '{\n'; seekStatus = 'json'; continue; } } if (seekStatus === 'json') { jsonStr += line + '\n'; if (line.match(/^}$/)) { json = JSON.parse(jsonStr); } } } return responseArray; }; return await executeBatchRequest(); } /** * Executa requisições SMLSVC * @param config Configurações da requisição SMLSVC * @returns Dados da resposta */ async executeSmlsvc(config) { const executeRequest = async (retries = 0) => { this.retries = retries; const customHeaders = { Cookie: this.cookie, }; if (config.headers) { for (const prop in config.headers) { customHeaders[prop] = config.headers[prop]; } } const newUrl = `${this.url}/${config.url}`; try { const response = await axios_1.default.get(newUrl, { httpsAgent: agent, headers: customHeaders, timeout: 0, }); return response.data; } catch (ex) { const error = ex; if (!error.response) { error.response = {}; } if (!error.response || !error.response.status) { throw error; } switch (error.response.status) { case 401: console.log(`Reconectando na service layer. ${retries}`); await this.login(); if (retries < 5) { return await executeRequest(++retries); } else { throw error; } case 502: console.log(`Nova tentativa, erro 502. ${retries}`); if (retries < 5) { return await executeRequest(++retries); } else { throw error; } default: throw error; } } }; return await executeRequest(); } /** * Executa uma requisição GET * @param config Configurações da requisição * @returns Dados da resposta */ async get(config) { return this.execute({ url: config.url, method: 'GET', header: config.header, page: config.page, size: config.size, timeout: config.timeout, }); } /** * Executa uma requisição POST * @param config Configurações da requisição * @returns Dados da resposta */ async post(config) { return this.execute({ url: config.url, method: 'POST', header: config.header, data: config.data, timeout: config.timeout, }); } /** * Executa uma requisição PUT * @param config Configurações da requisição * @returns Dados da resposta */ async put(config) { return this.execute({ url: config.url, method: 'PUT', header: config.header, data: config.data, timeout: config.timeout, }); } /** * Executa uma requisição PATCH * @param config Configurações da requisição * @returns Dados da resposta */ async patch(config) { return this.execute({ url: config.url, method: 'PATCH', header: config.header, data: config.data, timeout: config.timeout, }); } /** * Executa uma requisição DELETE * @param config Configurações da requisição * @returns Dados da resposta */ async delete(config) { return this.execute({ url: config.url, method: 'DELETE', header: config.header, timeout: config.timeout, }); } } exports.ServiceLayer = ServiceLayer;