@yousolution/node-red-contrib-you-sap-service-layer 0.2.12 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ # [0.2.14] - 2025-12-24
6
+
7
+ - Bug Fix Auth Protocall
8
+
9
+
10
+ # [0.2.13] - 2025-11-21
11
+
12
+ - Add Manage Static Additional Header on Nodes from Login Configuration
13
+
14
+
5
15
  # [0.2.12] - 2025-08-05
6
16
 
7
17
  - Bug Fix on DeleteSAP node for manage Drafts cancelation
@@ -1,12 +1,98 @@
1
1
  <script type="text/javascript">
2
+ const headerTypes = [
3
+ { value: "Accept", label: "Accept", hasValue: false },
4
+ { value: "Accept-Encoding", label: "Accept-Encoding", hasValue: false },
5
+ { value: "Accept-Language", label: "Accept-Language", hasValue: false },
6
+ { value: "Authorization", label: "Authorization", hasValue: false },
7
+ { value: "Content-Type", label: "Content-Type", hasValue: false },
8
+ { value: "Cache-Control", label: "Cache-Control", hasValue: false },
9
+ { value: "User-Agent", label: "User-Agent", hasValue: false },
10
+ { value: "Location", label: "Location", hasValue: false },
11
+ { value: "x-tunnel-id", label: "TunnelID", hasValue: false },
12
+ { value: "other", label: "Other", hasValue: true, icon: "red/images/typedInput/az.svg" }
13
+ //{ value: "msg", label: "msg.", hasValue: true },
14
+ ]
15
+ const headerOptions = {};
16
+ const defaultOptions = [
17
+ { value: "other", label: "Other", hasValue: true, icon: "red/images/typedInput/az.svg" },
18
+ // { value: "msg", label: "msg.", hasValue: true },
19
+ ];
20
+ headerOptions["accept"] = [
21
+ { value: "text/plain", label: "text/plain", hasValue: false },
22
+ { value: "text/html", label: "text/html", hasValue: false },
23
+ { value: "application/json", label: "application/json", hasValue: false },
24
+ { value: "application/xml", label: "application/xml", hasValue: false },
25
+ ...defaultOptions,
26
+ ];
27
+ headerOptions["accept-encoding"] = [
28
+ { value: "gzip", label: "gzip", hasValue: false },
29
+ { value: "deflate", label: "deflate", hasValue: false },
30
+ { value: "compress", label: "compress", hasValue: false },
31
+ { value: "br", label: "br", hasValue: false },
32
+ { value: "gzip, deflate", label: "gzip, deflate", hasValue: false },
33
+ { value: "gzip, deflate, br", label: "gzip, deflate, br", hasValue: false },
34
+ ...defaultOptions,
35
+ ];
36
+ headerOptions["accept-language"] = [
37
+ { value: "*", label: "*", hasValue: false },
38
+ { value: "en-GB, en-US, en;q=0.9", label: "en-GB, en-US, en;q=0.9", hasValue: false },
39
+ { value: "de-AT, de-DE;q=0.9, en;q=0.5", label: "de-AT, de-DE;q=0.9, en;q=0.5", hasValue: false },
40
+ { value: "es-mx,es,en;q=0.5", label: "es-mx,es,en;q=0.5", hasValue: false },
41
+ { value: "fr-CH, fr;q=0.9, en;q=0.8", label: "fr-CH, fr;q=0.9, en;q=0.8", hasValue: false },
42
+ { value: "zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6", label: "zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6", hasValue: false },
43
+ { value: "ja-JP, jp", label: "ja-JP, jp", hasValue: false },
44
+ ...defaultOptions,
45
+ ];
46
+ headerOptions["content-type"] = [
47
+ { value: "text/css", label: "text/css", hasValue: false },
48
+ { value: "text/plain", label: "text/plain", hasValue: false },
49
+ { value: "text/html", label: "text/html", hasValue: false },
50
+ { value: "application/json", label: "application/json", hasValue: false },
51
+ { value: "application/octet-stream", label: "application/octet-stream", hasValue: false },
52
+ { value: "application/pdf", label: "application/pdf", hasValue: false },
53
+ { value: "application/xml", label: "application/xml", hasValue: false },
54
+ { value: "application/zip", label: "application/zip", hasValue: false },
55
+ { value: "multipart/form-data", label: "multipart/form-data", hasValue: false },
56
+ { value: "audio/aac", label: "audio/aac", hasValue: false },
57
+ { value: "audio/ac3", label: "audio/ac3", hasValue: false },
58
+ { value: "audio/basic", label: "audio/basic", hasValue: false },
59
+ { value: "audio/mp4", label: "audio/mp4", hasValue: false },
60
+ { value: "audio/ogg", label: "audio/ogg", hasValue: false },
61
+ { value: "image/bmp", label: "image/bmp", hasValue: false },
62
+ { value: "image/gif", label: "image/gif", hasValue: false },
63
+ { value: "image/jpeg", label: "image/jpeg", hasValue: false },
64
+ { value: "image/png", label: "image/png", hasValue: false },
65
+ { value: "image/tiff", label: "image/tiff", hasValue: false },
66
+ ...defaultOptions,
67
+ ];
68
+ headerOptions["cache-control"] = [
69
+ { value: "max-age=0", label: "max-age=0", hasValue: false },
70
+ { value: "max-age=86400", label: "max-age=86400", hasValue: false },
71
+ { value: "no-cache", label: "no-cache", hasValue: false },
72
+ ...defaultOptions,
73
+ ];
74
+
75
+ headerOptions["user-agent"] = [
76
+ { value: "Mozilla/5.0", label: "Mozilla/5.0", hasValue: false },
77
+ ...defaultOptions,
78
+ ];
79
+
80
+ function getHeaderOptions(headerName) {
81
+ const lc = (headerName || "").toLowerCase();
82
+ let opts = headerOptions[lc];
83
+ return opts || defaultOptions;
84
+ }
85
+
2
86
  RED.nodes.registerType('authenticateSap',{
3
87
  category: 'Sap',
4
88
  color: '#FFC300',
5
89
  defaults: {
6
90
  name: {value: ''},
91
+ protocall: {value: 'https'},
7
92
  host: {value: ''},
8
93
  port: {value: ''},
9
- version: {value: ''}
94
+ version: {value: ''},
95
+ headers: { value: [] }
10
96
  },
11
97
  credentials: {
12
98
  company: {type: "string"},
@@ -33,7 +119,105 @@
33
119
  types:["msg", "str"],
34
120
  typeField: "#node-input-userType"
35
121
  });
36
- }
122
+
123
+
124
+ const node = this;
125
+ const hasMatch = function (arr, value) {
126
+ return arr.some(function (ht) {
127
+ return ht.value === value
128
+ });
129
+ }
130
+ const headerList = $("#node-input-headers-container").css('min-height', '150px').css('min-width', '450px').editableList({
131
+ addItem: function (container, i, header) {
132
+ const row = $('<div/>').css({
133
+ overflow: 'hidden',
134
+ whiteSpace: 'nowrap',
135
+ display: 'flex'
136
+ }).appendTo(container);
137
+ const propertNameCell = $('<div/>').css({ 'flex-grow': 1 }).appendTo(row);
138
+ const propertyName = $('<input/>', { class: "node-input-header-name", type: "text", style: "width: 100%" })
139
+ .appendTo(propertNameCell)
140
+ .typedInput({ types: headerTypes });
141
+
142
+ const propertyValueCell = $('<div/>').css({ 'flex-grow': 1, 'margin-left': '10px' }).appendTo(row);
143
+ const propertyValue = $('<input/>', { class: "node-input-header-value", type: "text", style: "width: 100%" })
144
+ .appendTo(propertyValueCell)
145
+ .typedInput({
146
+ types: getHeaderOptions(header.keyType)
147
+ });
148
+
149
+ const setup = function(_header) {
150
+ const headerTypeIsAPreset = function(h) {return hasMatch(headerTypes, h) };
151
+ const headerValueIsAPreset = function(h, v) {return hasMatch(getHeaderOptions(h), v) };
152
+ const {keyType, keyValue, valueType, valueValue} = header;
153
+ if(keyType == "msg" || keyType == "other") {
154
+ propertyName.typedInput('type', keyType);
155
+ propertyName.typedInput('value', keyValue);
156
+ } else if (headerTypeIsAPreset(keyType)) {
157
+ propertyName.typedInput('type', keyType);
158
+ } else {
159
+ propertyName.typedInput('type', "other");
160
+ propertyName.typedInput('value', keyValue);
161
+ }
162
+ if(valueType == "msg" || valueType == "other") {
163
+ propertyValue.typedInput('type', valueType);
164
+ propertyValue.typedInput('value', valueValue);
165
+ } else if (headerValueIsAPreset(propertyName.typedInput('type'), valueType)) {
166
+ propertyValue.typedInput('type', valueType);
167
+ } else {
168
+ propertyValue.typedInput('type', "other");
169
+ propertyValue.typedInput('value', valueValue);
170
+ }
171
+ }
172
+ setup(header);
173
+
174
+ propertyName.on('change', function (event) {
175
+ propertyValue.typedInput('types', getHeaderOptions(propertyName.typedInput('type')));
176
+ });
177
+
178
+ },
179
+ sortable: true,
180
+ removable: true
181
+ });
182
+ if (node.headers) {
183
+ for (let index = 0; index < node.headers.length; index++) {
184
+ const element = node.headers[index];
185
+ headerList.editableList('addItem', node.headers[index]);
186
+ }
187
+ }
188
+
189
+ },
190
+ oneditsave: function() {
191
+ const node = this;
192
+ const headers = $("#node-input-headers-container").editableList('items');
193
+ node.headers = [];
194
+ headers.each(function(i) {
195
+ const header = $(this);
196
+ const keyType = header.find(".node-input-header-name").typedInput('type');
197
+ const keyValue = header.find(".node-input-header-name").typedInput('value');
198
+ const valueType = header.find(".node-input-header-value").typedInput('type');
199
+ const valueValue = header.find(".node-input-header-value").typedInput('value');
200
+ if (keyType !== '' || keyType === 'other' || keyType === 'msg') {
201
+ node.headers.push({
202
+ keyType, keyValue, valueType, valueValue
203
+ })
204
+ }
205
+ });
206
+ },
207
+ oneditresize: function(size) {
208
+ const dlg = $("#dialog-form");
209
+ const expandRow = dlg.find('.node-input-headers-container-row');
210
+ let height = dlg.height() - 5;
211
+ if(expandRow && expandRow.length){
212
+ const siblingRows = dlg.find('> .form-row:not(.node-input-headers-container-row)');
213
+ for (let i = 0; i < siblingRows.size(); i++) {
214
+ const cr = $(siblingRows[i]);
215
+ if(cr.is(":visible"))
216
+ height -= cr.outerHeight(true);
217
+ }
218
+ $("#node-input-headers-container").editableList('height',height);
219
+ }
220
+ }
37
221
  });
38
222
  </script>
39
223
 
@@ -43,6 +227,14 @@
43
227
  <input type="text" id="node-input-name" placeholder="Name">
44
228
  </div>
45
229
 
230
+ <div class="form-row">
231
+ <label for="node-input-protocall"><i class="fa fa-cog"></i> Protocall</label>
232
+ <select name="node-input-protocall" id="node-input-protocall">
233
+ <option value="http">HTTP</option>
234
+ <option value="https">HTTPS</option>
235
+ </select>
236
+ </div>
237
+
46
238
  <div class="form-row">
47
239
  <label for="node-input-host"><i class="fa fa-cogs"></i> Host</label>
48
240
  <input type="text" id="node-input-host" placeholder="Host">
@@ -77,6 +269,15 @@
77
269
  <label for="node-input-password"><i class="fa fa-lock"></i> Password</label>
78
270
  <input type="password" id="node-input-password" placeholder="password">
79
271
  </div>
272
+
273
+ <div>
274
+ <div class="form-row" style="margin-bottom:0;">
275
+ <label><i class="fa fa-list"></i> <span>Headers</span></label>
276
+ </div>
277
+ <div class="form-row node-input-headers-container-row">
278
+ <ol id="node-input-headers-container"></ol>
279
+ </div>
280
+ </div>
80
281
  </script>
81
282
 
82
283
 
@@ -9,12 +9,24 @@ module.exports = function (RED) {
9
9
  // reset status
10
10
  node.status({});
11
11
 
12
+ let ConfigsHeaders = config.headers ? config.headers.reduce((acc, header) => {
13
+ // Se keyValue è vuoto, usa keyType come chiave
14
+ const key = header.keyValue === "" ? header.keyType : header.keyValue;
15
+ const value = header.valueValue;
16
+
17
+ acc[key] = value;
18
+ return acc;
19
+ }, {}) : {};
20
+
21
+
12
22
  const globalContext = node.context().global;
13
23
 
14
24
  globalContext.set(`_YOU_SapServiceLayer_${node.id}`, {
15
25
  host: config.host,
26
+ protocall : config.protocall,
16
27
  port: config.port,
17
28
  version: config.version,
29
+ staticHeaders: ConfigsHeaders,
18
30
  credentials: {
19
31
  CompanyDB: node.credentials.company,
20
32
  UserName: node.credentials.user,
@@ -50,17 +62,14 @@ module.exports = function (RED) {
50
62
 
51
63
  }
52
64
 
53
-
54
65
  //If User setted from msg
55
66
  if (node.credentials.userType == 'msg') {
56
67
  const user = msg[node.credentials.user];
57
68
  let currentUser = globalContext.get(`_YOU_SapServiceLayer_${node.id}.credentials.UserName`);
58
69
 
59
70
  if(user !== currentUser) {
60
- console.log('Reset User');
61
71
  globalContext.set(`_YOU_SapServiceLayer_${node.id}.headers`, null);
62
72
  }
63
-
64
73
  globalContext.set(`_YOU_SapServiceLayer_${node.id}.credentials.UserName`, user);
65
74
  }
66
75
 
@@ -89,7 +98,6 @@ module.exports = function (RED) {
89
98
  validToken = minutesDifference > 25 ? false : true;
90
99
  }
91
100
 
92
-
93
101
  if (!headers || !validToken) {
94
102
  try {
95
103
  const result = await Support.login(node, node.id);
@@ -132,6 +140,7 @@ module.exports = function (RED) {
132
140
  user: { type: 'text' },
133
141
  userType: { type: 'text' },
134
142
  password: { type: 'password' },
143
+ headers: {},
135
144
  },
136
145
  });
137
146
  };
package/nodes/support.js CHANGED
@@ -66,12 +66,13 @@ async function login(node, idAuth) {
66
66
  const globalContext = node.context().global;
67
67
 
68
68
  const host = globalContext.get(`_YOU_SapServiceLayer_${idAuth}.host`);
69
+ const protocall = globalContext.get(`_YOU_SapServiceLayer_${idAuth}.protocall`);
69
70
  const port = globalContext.get(`_YOU_SapServiceLayer_${idAuth}.port`);
70
71
  const version = globalContext.get(`_YOU_SapServiceLayer_${idAuth}.version`);
71
72
 
72
- const url = `https://${host}:${port}/b1s/${version}/Login`;
73
-
73
+ const url = `${protocall}://${host}:${port}/b1s/${version}/Login`;
74
74
  const credentials = globalContext.get(`_YOU_SapServiceLayer_${idAuth}.credentials`);
75
+ const staticHeaders = globalContext.get(`_YOU_SapServiceLayer_${idAuth}.staticHeaders`);
75
76
  const dataString = JSON.stringify(credentials);
76
77
 
77
78
  const options = {
@@ -80,6 +81,7 @@ async function login(node, idAuth) {
80
81
  rejectUnauthorized: false,
81
82
  data: credentials,
82
83
  headers: {
84
+ ...staticHeaders,
83
85
  'Content-Type': 'application/json',
84
86
  'Content-Length': dataString.length,
85
87
  },
@@ -181,7 +183,7 @@ function generateRequest(node, msg, config, options) {
181
183
  options.service = options.service || null;
182
184
  options.manipulateMethod = options.manipulateMethod || null;
183
185
 
184
- const { idAuthNode, host, port, version, cookies } = getSapParams(node, msg, config);
186
+ const { idAuthNode, host, protocall, port, version, cookies, staticHeaders } = getSapParams(node, msg, config);
185
187
 
186
188
  let rawQuery = null;
187
189
  let url;
@@ -216,28 +218,28 @@ function generateRequest(node, msg, config, options) {
216
218
  if (entity == 'script') {
217
219
  const partnerName = config.partnerName;
218
220
  const scriptName = config.scriptName;
219
- url = `https://${host}:${port}/b1s/${version}/${entity}/${partnerName}/${scriptName}`;
221
+ url = `${protocall}://${host}:${port}/b1s/${version}/${entity}/${partnerName}/${scriptName}`;
220
222
  }
221
223
 
222
224
  const odataNextLink = msg[config.nextLink];
223
225
 
224
226
  if (!odataNextLink) {
225
- url = `https://${host}:${port}/b1s/${version}/${entity}`;
227
+ url = `${protocall}://${host}:${port}/b1s/${version}/${entity}`;
226
228
  }
227
229
 
228
230
  if (options.isCrossJoin) {
229
- url = `https://${host}:${port}/b1s/${version}/$crossjoin(${entity})`;
231
+ url = `${protocall}://${host}:${port}/b1s/${version}/$crossjoin(${entity})`;
230
232
  }
231
233
 
232
234
  if (options.isSQLQuery) {
233
235
  if (!config.sqlCode) {
234
236
  throw new Error('Missing sqlCode');
235
237
  }
236
- url = `https://${host}:${port}/b1s/${version}/SQLQueries('${msg[config.sqlCode]}')/List`;
238
+ url = `${protocall}://${host}:${port}/b1s/${version}/SQLQueries('${msg[config.sqlCode]}')/List`;
237
239
  }
238
240
 
239
241
  if (odataNextLink) {
240
- url = `https://${host}:${port}/b1s/${version}/${odataNextLink}`;
242
+ url = `${protocall}://${host}:${port}/b1s/${version}/${odataNextLink}`;
241
243
  }
242
244
  if (options.isClose && !options.hasEntityId) {
243
245
  throw new Error(`The options are not correct. If 'isClose' is true then 'hasEntityId' must be true.`);
@@ -274,18 +276,18 @@ function generateRequest(node, msg, config, options) {
274
276
  let ItemCode = msg[config.AlternateCatNumItemId];
275
277
  let CardCode = msg[config.AlternateCatNumCardCodeId];
276
278
  let Substitute = msg[config.AlternateCatNumSubstituteId]
277
- url = `https://${host}:${port}/b1s/${version}/${entity}(ItemCode='${ItemCode}', CardCode='${CardCode}', Substitute='${Substitute}')`;
279
+ url = `${protocall}://${host}:${port}/b1s/${version}/${entity}(ItemCode='${ItemCode}', CardCode='${CardCode}', Substitute='${Substitute}')`;
278
280
 
279
281
  }
280
282
  else if(Number.isInteger(entityId)){
281
- url = `https://${host}:${port}/b1s/${version}/${entity}(${entityId})`;
283
+ url = `${protocall}://${host}:${port}/b1s/${version}/${entity}(${entityId})`;
282
284
  }
283
285
  else {
284
- url = `https://${host}:${port}/b1s/${version}/${entity}('${entityId}')`;
286
+ url = `${protocall}://${host}:${port}/b1s/${version}/${entity}('${entityId}')`;
285
287
  }
286
288
 
287
289
  } else {
288
- url = `https://${host}:${port}/b1s/${version}/${entity}(${entityId})`;
290
+ url = `${protocall}://${host}:${port}/b1s/${version}/${entity}(${entityId})`;
289
291
  }
290
292
 
291
293
  if (options.isClose) {
@@ -297,15 +299,15 @@ function generateRequest(node, msg, config, options) {
297
299
  throw new Error('Missing method');
298
300
  }
299
301
  if (thickIdApi.includes(entity)) {
300
- url = `https://${host}:${port}/b1s/${version}/${entity}('${entityId}')/${config.manipulateMethod}`;
302
+ url = `${protocall}://${host}:${port}/b1s/${version}/${entity}('${entityId}')/${config.manipulateMethod}`;
301
303
  } else {
302
- url = `https://${host}:${port}/b1s/${version}/${entity}(${entityId})/${config.manipulateMethod}`;
304
+ url = `${protocall}://${host}:${port}/b1s/${version}/${entity}(${entityId})/${config.manipulateMethod}`;
303
305
  }
304
306
  }
305
307
  }
306
308
 
307
309
  if (config.service) {
308
- url = `https://${host}:${port}/b1s/${version}/${config.service}`;
310
+ url = `${protocall}://${host}:${port}/b1s/${version}/${config.service}`;
309
311
  }
310
312
 
311
313
  if (options.isCreateSQLQuery) {
@@ -318,7 +320,7 @@ function generateRequest(node, msg, config, options) {
318
320
  if (!config.sqlText) {
319
321
  throw new Error('Missing sqlText');
320
322
  }
321
- url = `https://${host}:${port}/b1s/${version}/SQLQueries`;
323
+ url = `${protocall}://${host}:${port}/b1s/${version}/SQLQueries`;
322
324
  }
323
325
 
324
326
  if (rawQuery && !odataNextLink) {
@@ -328,7 +330,7 @@ function generateRequest(node, msg, config, options) {
328
330
  }
329
331
 
330
332
  // const cookies = flowContext.get(`_YOU_SapServiceLayer_${idAuthNode}.headers`).join(';');
331
- const headers = { ...msg[config.headers], Cookie: cookies };
333
+ const headers = { ...staticHeaders , ...msg[config.headers], Cookie: cookies };
332
334
 
333
335
  let axiosOptions = {
334
336
  method: options.method,
@@ -354,15 +356,16 @@ function getSapParams(node, msg) {
354
356
 
355
357
  const idAuthNode = msg._YOU_SapServiceLayer.idAuth;
356
358
  const host = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.host`);
359
+ const protocall = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.protocall`);
357
360
  const port = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.port`);
358
361
  const version = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.version`);
359
-
362
+ const staticHeaders = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.staticHeaders`);
360
363
  // if (!flowContext.get(`_YOU_SapServiceLayer_${idAuthNode}.headers`)) {
361
364
  // throw new Error('Authentication failed');
362
365
  // }
363
366
  const cookies = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.headers`).join(';');
364
367
 
365
- return { idAuthNode: idAuthNode, host: host, port: port, version: version, cookies: cookies };
368
+ return { idAuthNode: idAuthNode, host: host, protocall: protocall, port: port, version: version, cookies: cookies, staticHeaders: staticHeaders };
366
369
  } catch (error) {
367
370
  throw new Error('Authentication failed');
368
371
  }
@@ -374,12 +377,4 @@ module.exports = {
374
377
  sendRequest: sendRequest,
375
378
  thickIdApi: thickIdApi,
376
379
  };
377
- // if (process.env.NODE_ENV === 'test') {
378
- // console.log('TEST');
379
- // module.exports = {
380
- // login: login,
381
- // generateRequest: generateRequest,
382
- // sendRequest: sendRequest,
383
- // thickIdApi: thickIdApi,
384
- // };
385
- // }
380
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yousolution/node-red-contrib-you-sap-service-layer",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "Unofficial module SAP Service Layer for NODE-RED",
5
5
  "license": "MIT",
6
6
  "scripts": {