@yousolution/node-red-contrib-you-sap-service-layer 0.2.11 → 0.2.13

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,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ # [0.2.13] - 2025-11-21
6
+
7
+ - Add Manage Static Additional Header on Nodes from Login Configuration
8
+
9
+
10
+ # [0.2.12] - 2025-08-05
11
+
12
+ - Bug Fix on DeleteSAP node for manage Drafts cancelation
13
+
5
14
  # [0.2.11] - 2025-07-28
6
15
 
7
16
  - Add Manage NextLink on ServiceNode
@@ -1,4 +1,88 @@
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',
@@ -6,7 +90,8 @@
6
90
  name: {value: ''},
7
91
  host: {value: ''},
8
92
  port: {value: ''},
9
- version: {value: ''}
93
+ version: {value: ''},
94
+ headers: { value: [] }
10
95
  },
11
96
  credentials: {
12
97
  company: {type: "string"},
@@ -33,7 +118,105 @@
33
118
  types:["msg", "str"],
34
119
  typeField: "#node-input-userType"
35
120
  });
36
- }
121
+
122
+
123
+ const node = this;
124
+ const hasMatch = function (arr, value) {
125
+ return arr.some(function (ht) {
126
+ return ht.value === value
127
+ });
128
+ }
129
+ const headerList = $("#node-input-headers-container").css('min-height', '150px').css('min-width', '450px').editableList({
130
+ addItem: function (container, i, header) {
131
+ const row = $('<div/>').css({
132
+ overflow: 'hidden',
133
+ whiteSpace: 'nowrap',
134
+ display: 'flex'
135
+ }).appendTo(container);
136
+ const propertNameCell = $('<div/>').css({ 'flex-grow': 1 }).appendTo(row);
137
+ const propertyName = $('<input/>', { class: "node-input-header-name", type: "text", style: "width: 100%" })
138
+ .appendTo(propertNameCell)
139
+ .typedInput({ types: headerTypes });
140
+
141
+ const propertyValueCell = $('<div/>').css({ 'flex-grow': 1, 'margin-left': '10px' }).appendTo(row);
142
+ const propertyValue = $('<input/>', { class: "node-input-header-value", type: "text", style: "width: 100%" })
143
+ .appendTo(propertyValueCell)
144
+ .typedInput({
145
+ types: getHeaderOptions(header.keyType)
146
+ });
147
+
148
+ const setup = function(_header) {
149
+ const headerTypeIsAPreset = function(h) {return hasMatch(headerTypes, h) };
150
+ const headerValueIsAPreset = function(h, v) {return hasMatch(getHeaderOptions(h), v) };
151
+ const {keyType, keyValue, valueType, valueValue} = header;
152
+ if(keyType == "msg" || keyType == "other") {
153
+ propertyName.typedInput('type', keyType);
154
+ propertyName.typedInput('value', keyValue);
155
+ } else if (headerTypeIsAPreset(keyType)) {
156
+ propertyName.typedInput('type', keyType);
157
+ } else {
158
+ propertyName.typedInput('type', "other");
159
+ propertyName.typedInput('value', keyValue);
160
+ }
161
+ if(valueType == "msg" || valueType == "other") {
162
+ propertyValue.typedInput('type', valueType);
163
+ propertyValue.typedInput('value', valueValue);
164
+ } else if (headerValueIsAPreset(propertyName.typedInput('type'), valueType)) {
165
+ propertyValue.typedInput('type', valueType);
166
+ } else {
167
+ propertyValue.typedInput('type', "other");
168
+ propertyValue.typedInput('value', valueValue);
169
+ }
170
+ }
171
+ setup(header);
172
+
173
+ propertyName.on('change', function (event) {
174
+ propertyValue.typedInput('types', getHeaderOptions(propertyName.typedInput('type')));
175
+ });
176
+
177
+ },
178
+ sortable: true,
179
+ removable: true
180
+ });
181
+ if (node.headers) {
182
+ for (let index = 0; index < node.headers.length; index++) {
183
+ const element = node.headers[index];
184
+ headerList.editableList('addItem', node.headers[index]);
185
+ }
186
+ }
187
+
188
+ },
189
+ oneditsave: function() {
190
+ const node = this;
191
+ const headers = $("#node-input-headers-container").editableList('items');
192
+ node.headers = [];
193
+ headers.each(function(i) {
194
+ const header = $(this);
195
+ const keyType = header.find(".node-input-header-name").typedInput('type');
196
+ const keyValue = header.find(".node-input-header-name").typedInput('value');
197
+ const valueType = header.find(".node-input-header-value").typedInput('type');
198
+ const valueValue = header.find(".node-input-header-value").typedInput('value');
199
+ if (keyType !== '' || keyType === 'other' || keyType === 'msg') {
200
+ node.headers.push({
201
+ keyType, keyValue, valueType, valueValue
202
+ })
203
+ }
204
+ });
205
+ },
206
+ oneditresize: function(size) {
207
+ const dlg = $("#dialog-form");
208
+ const expandRow = dlg.find('.node-input-headers-container-row');
209
+ let height = dlg.height() - 5;
210
+ if(expandRow && expandRow.length){
211
+ const siblingRows = dlg.find('> .form-row:not(.node-input-headers-container-row)');
212
+ for (let i = 0; i < siblingRows.size(); i++) {
213
+ const cr = $(siblingRows[i]);
214
+ if(cr.is(":visible"))
215
+ height -= cr.outerHeight(true);
216
+ }
217
+ $("#node-input-headers-container").editableList('height',height);
218
+ }
219
+ }
37
220
  });
38
221
  </script>
39
222
 
@@ -77,6 +260,15 @@
77
260
  <label for="node-input-password"><i class="fa fa-lock"></i> Password</label>
78
261
  <input type="password" id="node-input-password" placeholder="password">
79
262
  </div>
263
+
264
+ <div>
265
+ <div class="form-row" style="margin-bottom:0;">
266
+ <label><i class="fa fa-list"></i> <span>Headers</span></label>
267
+ </div>
268
+ <div class="form-row node-input-headers-container-row">
269
+ <ol id="node-input-headers-container"></ol>
270
+ </div>
271
+ </div>
80
272
  </script>
81
273
 
82
274
 
@@ -9,12 +9,23 @@ 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,
16
26
  port: config.port,
17
27
  version: config.version,
28
+ staticHeaders: ConfigsHeaders,
18
29
  credentials: {
19
30
  CompanyDB: node.credentials.company,
20
31
  UserName: node.credentials.user,
@@ -50,17 +61,14 @@ module.exports = function (RED) {
50
61
 
51
62
  }
52
63
 
53
-
54
64
  //If User setted from msg
55
65
  if (node.credentials.userType == 'msg') {
56
66
  const user = msg[node.credentials.user];
57
67
  let currentUser = globalContext.get(`_YOU_SapServiceLayer_${node.id}.credentials.UserName`);
58
68
 
59
69
  if(user !== currentUser) {
60
- console.log('Reset User');
61
70
  globalContext.set(`_YOU_SapServiceLayer_${node.id}.headers`, null);
62
71
  }
63
-
64
72
  globalContext.set(`_YOU_SapServiceLayer_${node.id}.credentials.UserName`, user);
65
73
  }
66
74
 
@@ -89,7 +97,6 @@ module.exports = function (RED) {
89
97
  validToken = minutesDifference > 25 ? false : true;
90
98
  }
91
99
 
92
-
93
100
  if (!headers || !validToken) {
94
101
  try {
95
102
  const result = await Support.login(node, node.id);
@@ -132,6 +139,7 @@ module.exports = function (RED) {
132
139
  user: { type: 'text' },
133
140
  userType: { type: 'text' },
134
141
  password: { type: 'password' },
142
+ headers: {},
135
143
  },
136
144
  });
137
145
  };
@@ -90,6 +90,11 @@
90
90
  ) {
91
91
  jQuery('#container-entityId').hide();
92
92
  }
93
+
94
+ if(jQuery(this).val() === 'Drafts'){
95
+ jQuery('#container-entityId').show();
96
+ }
97
+
93
98
  });
94
99
  },
95
100
  });
package/nodes/support.js CHANGED
@@ -72,6 +72,7 @@ async function login(node, idAuth) {
72
72
  const url = `https://${host}:${port}/b1s/${version}/Login`;
73
73
 
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, port, version, cookies, staticHeaders } = getSapParams(node, msg, config);
185
187
 
186
188
  let rawQuery = null;
187
189
  let url;
@@ -251,7 +253,9 @@ function generateRequest(node, msg, config, options) {
251
253
  if (!entityId && config.entity != 'UDO' && config.entity != 'UDT') {
252
254
  throw new Error('Missing entityId');
253
255
  }
256
+
254
257
  const docEntry = msg[config.docEntry];
258
+
255
259
  if (config.entity == 'UDO') {
256
260
  if (!docEntry) {
257
261
  throw new Error('Missing docEntry');
@@ -326,8 +330,9 @@ function generateRequest(node, msg, config, options) {
326
330
  }
327
331
 
328
332
  // const cookies = flowContext.get(`_YOU_SapServiceLayer_${idAuthNode}.headers`).join(';');
329
- const headers = { ...msg[config.headers], Cookie: cookies };
333
+ const headers = { ...staticHeaders , ...msg[config.headers], Cookie: cookies };
330
334
 
335
+ console.log('Headers:', headers);
331
336
  let axiosOptions = {
332
337
  method: options.method,
333
338
  url: url,
@@ -354,13 +359,13 @@ function getSapParams(node, msg) {
354
359
  const host = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.host`);
355
360
  const port = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.port`);
356
361
  const version = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.version`);
357
-
362
+ const staticHeaders = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.staticHeaders`);
358
363
  // if (!flowContext.get(`_YOU_SapServiceLayer_${idAuthNode}.headers`)) {
359
364
  // throw new Error('Authentication failed');
360
365
  // }
361
366
  const cookies = globalContext.get(`_YOU_SapServiceLayer_${idAuthNode}.headers`).join(';');
362
367
 
363
- return { idAuthNode: idAuthNode, host: host, port: port, version: version, cookies: cookies };
368
+ return { idAuthNode: idAuthNode, host: host, port: port, version: version, cookies: cookies, staticHeaders: staticHeaders };
364
369
  } catch (error) {
365
370
  throw new Error('Authentication failed');
366
371
  }
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.11",
3
+ "version": "0.2.13",
4
4
  "description": "Unofficial module SAP Service Layer for NODE-RED",
5
5
  "license": "MIT",
6
6
  "scripts": {