sap-servicelayer
Version:
Biblioteca TypeScript para integração com SAP Service Layer API
501 lines (500 loc) • 17.5 kB
JavaScript
;
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;