ultravisor 1.0.2 → 1.0.4

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.
Files changed (121) hide show
  1. package/.claude/launch.json +11 -0
  2. package/.claude/ultravisor-dev-config.json +3 -0
  3. package/.ultravisor.json +426 -0
  4. package/docs/README.md +63 -0
  5. package/package.json +12 -8
  6. package/source/Ultravisor.cjs +22 -3
  7. package/source/cli/Ultravisor-CLIProgram.cjs +35 -23
  8. package/source/cli/commands/Ultravisor-Command-SingleOperation.cjs +29 -18
  9. package/source/cli/commands/Ultravisor-Command-SingleTask.cjs +62 -19
  10. package/source/cli/commands/Ultravisor-Command-UpdateTask.cjs +27 -15
  11. package/source/config/Ultravisor-Default-Command-Configuration.cjs +5 -3
  12. package/source/services/Ultravisor-ExecutionEngine.cjs +1039 -0
  13. package/source/services/Ultravisor-ExecutionManifest.cjs +399 -0
  14. package/source/services/Ultravisor-Hypervisor-State.cjs +270 -97
  15. package/source/services/Ultravisor-Hypervisor.cjs +38 -83
  16. package/source/services/Ultravisor-StateManager.cjs +241 -0
  17. package/source/services/Ultravisor-TaskTypeRegistry.cjs +143 -0
  18. package/source/services/tasks/Ultravisor-TaskType-Base.cjs +105 -0
  19. package/source/services/tasks/control/Ultravisor-TaskType-IfConditional.cjs +148 -0
  20. package/source/services/tasks/control/Ultravisor-TaskType-LaunchOperation.cjs +187 -0
  21. package/source/services/tasks/control/Ultravisor-TaskType-SplitExecute.cjs +184 -0
  22. package/source/services/tasks/data/Ultravisor-TaskType-ReplaceString.cjs +82 -0
  23. package/source/services/tasks/data/Ultravisor-TaskType-SetValues.cjs +81 -0
  24. package/source/services/tasks/data/Ultravisor-TaskType-StringAppender.cjs +101 -0
  25. package/source/services/tasks/file-io/Ultravisor-TaskType-ReadFile.cjs +103 -0
  26. package/source/services/tasks/file-io/Ultravisor-TaskType-WriteFile.cjs +117 -0
  27. package/source/services/tasks/interaction/Ultravisor-TaskType-ErrorMessage.cjs +54 -0
  28. package/source/services/tasks/interaction/Ultravisor-TaskType-ValueInput.cjs +62 -0
  29. package/source/web_server/Ultravisor-API-Server.cjs +237 -124
  30. package/test/Ultravisor_browser_tests.js +2226 -0
  31. package/test/Ultravisor_tests.js +1143 -5830
  32. package/webinterface/css/ultravisor.css +23 -0
  33. package/webinterface/package.json +6 -3
  34. package/webinterface/source/Pict-Application-Ultravisor.js +93 -73
  35. package/webinterface/source/cards/FlowCard-CSVTransform.js +43 -0
  36. package/webinterface/source/cards/FlowCard-Command.js +86 -0
  37. package/webinterface/source/cards/FlowCard-ComprehensionIntersect.js +40 -0
  38. package/webinterface/source/cards/FlowCard-Conditional.js +87 -0
  39. package/webinterface/source/cards/FlowCard-CopyFile.js +55 -0
  40. package/webinterface/source/cards/FlowCard-End.js +29 -0
  41. package/webinterface/source/cards/FlowCard-GetJSON.js +55 -0
  42. package/webinterface/source/cards/FlowCard-GetText.js +54 -0
  43. package/webinterface/source/cards/FlowCard-Histogram.js +176 -0
  44. package/webinterface/source/cards/FlowCard-LaunchOperation.js +82 -0
  45. package/webinterface/source/cards/FlowCard-ListFiles.js +55 -0
  46. package/webinterface/source/cards/FlowCard-MeadowCount.js +44 -0
  47. package/webinterface/source/cards/FlowCard-MeadowCreate.js +44 -0
  48. package/webinterface/source/cards/FlowCard-MeadowDelete.js +45 -0
  49. package/webinterface/source/cards/FlowCard-MeadowRead.js +46 -0
  50. package/webinterface/source/cards/FlowCard-MeadowReads.js +46 -0
  51. package/webinterface/source/cards/FlowCard-MeadowUpdate.js +44 -0
  52. package/webinterface/source/cards/FlowCard-ParseCSV.js +85 -0
  53. package/webinterface/source/cards/FlowCard-ReadJSON.js +54 -0
  54. package/webinterface/source/cards/FlowCard-ReadText.js +54 -0
  55. package/webinterface/source/cards/FlowCard-RestRequest.js +59 -0
  56. package/webinterface/source/cards/FlowCard-SendJSON.js +57 -0
  57. package/webinterface/source/cards/FlowCard-Solver.js +77 -0
  58. package/webinterface/source/cards/FlowCard-Start.js +29 -0
  59. package/webinterface/source/cards/FlowCard-TemplateString.js +77 -0
  60. package/webinterface/source/cards/FlowCard-WriteJSON.js +54 -0
  61. package/webinterface/source/cards/FlowCard-WriteText.js +54 -0
  62. package/webinterface/source/data/ExampleFlow-CSVPipeline.js +231 -0
  63. package/webinterface/source/data/ExampleFlow-FileProcessor.js +315 -0
  64. package/webinterface/source/data/ExampleFlow-MeadowPipeline.js +328 -0
  65. package/webinterface/source/providers/PictRouter-Ultravisor-Configuration.json +8 -8
  66. package/webinterface/source/views/PictView-Ultravisor-Dashboard.js +6 -6
  67. package/webinterface/source/views/PictView-Ultravisor-FlowEditor.js +436 -0
  68. package/webinterface/source/views/PictView-Ultravisor-ManifestList.js +45 -43
  69. package/webinterface/source/views/PictView-Ultravisor-OperationEdit.js +34 -89
  70. package/webinterface/source/views/PictView-Ultravisor-OperationList.js +128 -13
  71. package/webinterface/source/views/PictView-Ultravisor-PendingInput.js +314 -0
  72. package/webinterface/source/views/PictView-Ultravisor-Schedule.js +18 -53
  73. package/webinterface/source/views/PictView-Ultravisor-TimingView.js +27 -14
  74. package/webinterface/source/views/PictView-Ultravisor-TopBar.js +2 -1
  75. package/.babelrc +0 -6
  76. package/.browserslistrc +0 -1
  77. package/.browserslistrc-BACKUP +0 -1
  78. package/.gulpfile-quackage-config.json +0 -7
  79. package/.gulpfile-quackage.js +0 -2
  80. package/debug/Harness.js +0 -5
  81. package/source/services/Ultravisor-Operation-Manifest.cjs +0 -160
  82. package/source/services/Ultravisor-Operation.cjs +0 -200
  83. package/source/services/Ultravisor-Task.cjs +0 -349
  84. package/source/services/events/Ultravisor-Hypervisor-Event-Solver.cjs +0 -11
  85. package/source/services/tasks/Ultravisor-Task-Base.cjs +0 -264
  86. package/source/services/tasks/Ultravisor-Task-CollectValues.cjs +0 -188
  87. package/source/services/tasks/Ultravisor-Task-Command.cjs +0 -65
  88. package/source/services/tasks/Ultravisor-Task-CommandEach.cjs +0 -190
  89. package/source/services/tasks/Ultravisor-Task-Conditional.cjs +0 -104
  90. package/source/services/tasks/Ultravisor-Task-DateWindow.cjs +0 -72
  91. package/source/services/tasks/Ultravisor-Task-GeneratePagedOperation.cjs +0 -336
  92. package/source/services/tasks/Ultravisor-Task-LaunchOperation.cjs +0 -143
  93. package/source/services/tasks/Ultravisor-Task-LaunchTask.cjs +0 -146
  94. package/source/services/tasks/Ultravisor-Task-LineMatch.cjs +0 -158
  95. package/source/services/tasks/Ultravisor-Task-Request.cjs +0 -56
  96. package/source/services/tasks/Ultravisor-Task-Solver.cjs +0 -89
  97. package/source/services/tasks/Ultravisor-Task-TemplateString.cjs +0 -93
  98. package/source/services/tasks/rest/Ultravisor-Task-GetBinary.cjs +0 -127
  99. package/source/services/tasks/rest/Ultravisor-Task-GetJSON.cjs +0 -119
  100. package/source/services/tasks/rest/Ultravisor-Task-GetText.cjs +0 -109
  101. package/source/services/tasks/rest/Ultravisor-Task-GetXML.cjs +0 -112
  102. package/source/services/tasks/rest/Ultravisor-Task-RestRequest.cjs +0 -499
  103. package/source/services/tasks/rest/Ultravisor-Task-SendJSON.cjs +0 -150
  104. package/source/services/tasks/stagingfiles/Ultravisor-Task-CopyFile.cjs +0 -110
  105. package/source/services/tasks/stagingfiles/Ultravisor-Task-ListFiles.cjs +0 -89
  106. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadBinary.cjs +0 -87
  107. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadJSON.cjs +0 -67
  108. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadText.cjs +0 -66
  109. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadXML.cjs +0 -69
  110. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteBinary.cjs +0 -95
  111. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteJSON.cjs +0 -96
  112. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteText.cjs +0 -99
  113. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteXML.cjs +0 -102
  114. package/webinterface/.babelrc +0 -6
  115. package/webinterface/.browserslistrc +0 -1
  116. package/webinterface/.browserslistrc-BACKUP +0 -1
  117. package/webinterface/.gulpfile-quackage-config.json +0 -7
  118. package/webinterface/.gulpfile-quackage.js +0 -2
  119. package/webinterface/source/views/PictView-Ultravisor-TaskEdit.js +0 -220
  120. package/webinterface/source/views/PictView-Ultravisor-TaskList.js +0 -248
  121. /package/docs/{cover.md → _cover.md} +0 -0
@@ -1,499 +0,0 @@
1
- const libUltravisorTaskBase = require('../Ultravisor-Task-Base.cjs');
2
-
3
- const libHTTP = require('http');
4
- const libHTTPS = require('https');
5
-
6
- class UltravisorTaskRestRequest extends libUltravisorTaskBase
7
- {
8
- constructor(pFable)
9
- {
10
- super(pFable);
11
- }
12
-
13
- /**
14
- * Generic REST request with full control over method, body, headers
15
- * and cookies.
16
- *
17
- * Task definition fields:
18
- * - URL: the endpoint to request (literal string)
19
- * - URLAddress (optional): dot-notation address into GlobalState
20
- * that resolves to the URL string. When set, overrides URL.
21
- * - Method (optional): HTTP method (defaults to "GET")
22
- * - Body (optional): request body -- string or object (serialised
23
- * as JSON when object)
24
- * - ContentType (optional): Content-Type header value (defaults to
25
- * "application/json" when Body is an object, omitted otherwise)
26
- * - Headers (optional): object of request headers
27
- * - Cookies (optional): object of cookies to send (name:value pairs)
28
- * - StoreCookies (optional, default true): whether to capture
29
- * Set-Cookie response headers into pContext.GlobalState.Cookies
30
- * - CaptureToken (optional): extract a value from the JSON response
31
- * body and store it in the shared cookie jar. Can be:
32
- * - A string: dot-notation path into the JSON body (the value
33
- * is stored as a cookie named "Token")
34
- * - An object: { "Address": "JSON.path", "Cookie": "CookieName" }
35
- * - CaptureHeader (optional): object mapping response header names
36
- * to GlobalState addresses. E.g.
37
- * { "X-Auth-Token": "AuthToken" } stores the value of the
38
- * X-Auth-Token response header at GlobalState.AuthToken
39
- * - Destination (optional): manyfest address in GlobalState for the
40
- * response data (defaults to "Output")
41
- * - Persist (optional): where to store the response
42
- *
43
- * Shared cookie jar:
44
- * When the response contains Set-Cookie headers they are parsed and
45
- * stored at pContext.GlobalState.Cookies (an object keyed by cookie
46
- * name). Subsequent RestRequest tasks automatically include any
47
- * cookies found at that location. Task-level Cookies merge on top,
48
- * so explicit values override the jar.
49
- *
50
- * Token capture (CaptureToken):
51
- * Many APIs return session tokens in the JSON body rather than via
52
- * Set-Cookie headers. CaptureToken extracts a value from the parsed
53
- * JSON response and stores it in GlobalState.Cookies so that
54
- * subsequent RestRequest tasks automatically send it.
55
- *
56
- * Retries:
57
- * When Retries is set to a number > 0, the request will be retried
58
- * up to that many times on network errors, timeouts, or non-2xx
59
- * status codes. Each retry waits 1 second before re-attempting.
60
- * All retry attempts are logged in the manifest entry.
61
- */
62
- execute(pTaskDefinition, pContext, pManifestEntry, fCallback)
63
- {
64
- // Resolve URL from GlobalState via URLAddress, or fall back to literal URL
65
- let tmpURL = pTaskDefinition.URL || pTaskDefinition.Parameters || '';
66
-
67
- if (pTaskDefinition.URLAddress && typeof(pTaskDefinition.URLAddress) === 'string')
68
- {
69
- let tmpResolved = this.resolveAddress(pTaskDefinition.URLAddress, pContext);
70
- if (tmpResolved && typeof(tmpResolved) === 'string')
71
- {
72
- tmpURL = tmpResolved;
73
- pManifestEntry.Log.push(`RestRequest: resolved URL from GlobalState address "${pTaskDefinition.URLAddress}".`);
74
- }
75
- else
76
- {
77
- pManifestEntry.Log.push(`RestRequest: WARNING URLAddress "${pTaskDefinition.URLAddress}" could not be resolved, falling back to URL field.`);
78
- }
79
- }
80
-
81
- if (!tmpURL || tmpURL.length === 0)
82
- {
83
- pManifestEntry.StopTime = new Date().toISOString();
84
- pManifestEntry.Status = 'Error';
85
- pManifestEntry.Log.push(`RestRequest: missing URL (neither URL nor URLAddress provided a value).`);
86
- return fCallback(null, pManifestEntry);
87
- }
88
-
89
- let tmpMethod = (pTaskDefinition.Method || 'GET').toUpperCase();
90
-
91
- pManifestEntry.Log.push(`RestRequest: ${tmpMethod} ${tmpURL}`);
92
-
93
- // --- Build headers ---
94
- let tmpHeaders = Object.assign({}, pTaskDefinition.Headers || {});
95
-
96
- // --- Prepare request body ---
97
- let tmpBody = '';
98
- if (pTaskDefinition.hasOwnProperty('Body'))
99
- {
100
- if (typeof(pTaskDefinition.Body) === 'object' && pTaskDefinition.Body !== null)
101
- {
102
- tmpBody = JSON.stringify(pTaskDefinition.Body);
103
- if (!tmpHeaders['Content-Type'] && !pTaskDefinition.ContentType)
104
- {
105
- tmpHeaders['Content-Type'] = 'application/json';
106
- }
107
- }
108
- else if (typeof(pTaskDefinition.Body) === 'string')
109
- {
110
- tmpBody = pTaskDefinition.Body;
111
- }
112
- else
113
- {
114
- tmpBody = String(pTaskDefinition.Body);
115
- }
116
- }
117
-
118
- if (pTaskDefinition.ContentType && typeof(pTaskDefinition.ContentType) === 'string')
119
- {
120
- tmpHeaders['Content-Type'] = pTaskDefinition.ContentType;
121
- }
122
-
123
- if (tmpBody.length > 0)
124
- {
125
- tmpHeaders['Content-Length'] = Buffer.byteLength(tmpBody);
126
- pManifestEntry.Log.push(`RestRequest: body ${tmpBody.length} bytes.`);
127
- }
128
-
129
- // --- Build cookie header ---
130
- // Start with the shared cookie jar from GlobalState
131
- if (!pContext.GlobalState || typeof(pContext.GlobalState) !== 'object')
132
- {
133
- pContext.GlobalState = {};
134
- }
135
-
136
- let tmpCookieJar = pContext.GlobalState.Cookies;
137
- let tmpMergedCookies = {};
138
-
139
- // Merge shared jar cookies first
140
- if (tmpCookieJar && typeof(tmpCookieJar) === 'object')
141
- {
142
- let tmpJarKeys = Object.keys(tmpCookieJar);
143
- for (let i = 0; i < tmpJarKeys.length; i++)
144
- {
145
- tmpMergedCookies[tmpJarKeys[i]] = tmpCookieJar[tmpJarKeys[i]];
146
- }
147
- }
148
-
149
- // Merge task-level cookies on top (override jar values)
150
- if (pTaskDefinition.Cookies && typeof(pTaskDefinition.Cookies) === 'object')
151
- {
152
- let tmpTaskCookieKeys = Object.keys(pTaskDefinition.Cookies);
153
- for (let i = 0; i < tmpTaskCookieKeys.length; i++)
154
- {
155
- tmpMergedCookies[tmpTaskCookieKeys[i]] = pTaskDefinition.Cookies[tmpTaskCookieKeys[i]];
156
- }
157
- }
158
-
159
- let tmpCookieKeys = Object.keys(tmpMergedCookies);
160
- if (tmpCookieKeys.length > 0)
161
- {
162
- let tmpCookieString = tmpCookieKeys
163
- .map((pKey) => { return `${pKey}=${tmpMergedCookies[pKey]}`; })
164
- .join('; ');
165
-
166
- tmpHeaders['Cookie'] = tmpCookieString;
167
- pManifestEntry.Log.push(`RestRequest: sending ${tmpCookieKeys.length} cookie(s).`);
168
- }
169
-
170
- // --- Parse URL ---
171
- let tmpParsedURL;
172
- try
173
- {
174
- tmpParsedURL = new URL(tmpURL);
175
- }
176
- catch (pParseError)
177
- {
178
- pManifestEntry.StopTime = new Date().toISOString();
179
- pManifestEntry.Status = 'Error';
180
- pManifestEntry.Log.push(`RestRequest: invalid URL: ${pParseError.message}`);
181
- return fCallback(null, pManifestEntry);
182
- }
183
-
184
- let tmpTransport = (tmpParsedURL.protocol === 'https:') ? libHTTPS : libHTTP;
185
- let tmpTimeout = (this.fable?.ProgramConfiguration?.UltravisorCommandTimeoutMilliseconds) || 300000;
186
- let tmpMaxRetries = (typeof(pTaskDefinition.Retries) === 'number' && pTaskDefinition.Retries > 0) ? pTaskDefinition.Retries : 0;
187
-
188
- let tmpRequestOptions = {
189
- method: tmpMethod,
190
- headers: tmpHeaders,
191
- timeout: tmpTimeout
192
- };
193
-
194
- // --- Retry wrapper ---
195
- let tmpAttempt = 0;
196
- let tmpSelf = this;
197
-
198
- let fPerformRequest = () =>
199
- {
200
- tmpAttempt++;
201
-
202
- if (tmpAttempt > 1)
203
- {
204
- pManifestEntry.Log.push(`RestRequest: retry attempt ${tmpAttempt - 1} of ${tmpMaxRetries}.`);
205
- }
206
-
207
- let tmpRequest = tmpTransport.request(tmpURL, tmpRequestOptions,
208
- (pResponse) =>
209
- {
210
- let tmpChunks = [];
211
-
212
- pResponse.on('data', (pChunk) => { tmpChunks.push(pChunk); });
213
-
214
- pResponse.on('end', () =>
215
- {
216
- let tmpBuffer = Buffer.concat(tmpChunks);
217
- let tmpResponseText = tmpBuffer.toString('utf8');
218
-
219
- pManifestEntry.Log.push(`RestRequest: received ${tmpBuffer.length} bytes, status ${pResponse.statusCode}.`);
220
-
221
- // --- Check for retryable HTTP status ---
222
- if (pResponse.statusCode >= 300 && tmpAttempt <= tmpMaxRetries)
223
- {
224
- pManifestEntry.Log.push(`RestRequest: non-2xx status ${pResponse.statusCode}, will retry in 1s.`);
225
- return setTimeout(fPerformRequest, 1000);
226
- }
227
-
228
- pManifestEntry.StopTime = new Date().toISOString();
229
-
230
- // --- Store response cookies in the shared jar ---
231
- let tmpStoreCookies = (pTaskDefinition.StoreCookies !== false);
232
- if (tmpStoreCookies)
233
- {
234
- tmpSelf._captureResponseCookies(pResponse, pContext, pManifestEntry);
235
- }
236
-
237
- // --- Build the result object ---
238
- let tmpResult = {
239
- StatusCode: pResponse.statusCode,
240
- Headers: pResponse.headers,
241
- Body: tmpResponseText
242
- };
243
-
244
- // Attempt to parse body as JSON for convenience
245
- try
246
- {
247
- tmpResult.JSON = JSON.parse(tmpResponseText);
248
- }
249
- catch (pIgnore)
250
- {
251
- // Not JSON -- that's fine, raw Body is still available
252
- }
253
-
254
- // --- CaptureToken: extract a token from JSON body into the cookie jar ---
255
- if (pTaskDefinition.CaptureToken && tmpResult.JSON)
256
- {
257
- tmpSelf._captureToken(pTaskDefinition.CaptureToken, tmpResult.JSON, pContext, pManifestEntry);
258
- }
259
-
260
- // --- CaptureHeader: extract response headers into GlobalState ---
261
- if (pTaskDefinition.CaptureHeader && typeof(pTaskDefinition.CaptureHeader) === 'object')
262
- {
263
- tmpSelf._captureHeaders(pTaskDefinition.CaptureHeader, pResponse.headers, pContext, pManifestEntry);
264
- }
265
-
266
- pManifestEntry.Output = JSON.stringify(tmpResult);
267
-
268
- if (pResponse.statusCode >= 300)
269
- {
270
- pManifestEntry.Status = 'Error';
271
- pManifestEntry.Log.push(`RestRequest: ${tmpMethod} failed with status ${pResponse.statusCode} after ${tmpAttempt} attempt(s).`);
272
- }
273
- else
274
- {
275
- pManifestEntry.Status = 'Complete';
276
- pManifestEntry.Success = true;
277
- pManifestEntry.Log.push(`RestRequest: ${tmpMethod} completed successfully.`);
278
- }
279
-
280
- // Store via Destination
281
- tmpSelf.storeDestination(pTaskDefinition, pContext, pManifestEntry, tmpResult);
282
-
283
- // Persist
284
- tmpSelf.storeResult(pTaskDefinition, pContext, pManifestEntry, tmpResult);
285
-
286
- return fCallback(null, pManifestEntry);
287
- });
288
- });
289
-
290
- tmpRequest.on('error', (pError) =>
291
- {
292
- pManifestEntry.Log.push(`RestRequest: request error: ${pError.message}`);
293
-
294
- if (tmpAttempt <= tmpMaxRetries)
295
- {
296
- pManifestEntry.Log.push(`RestRequest: will retry in 1s.`);
297
- return setTimeout(fPerformRequest, 1000);
298
- }
299
-
300
- pManifestEntry.StopTime = new Date().toISOString();
301
- pManifestEntry.Status = 'Error';
302
- pManifestEntry.Log.push(`RestRequest: ${tmpMethod} failed after ${tmpAttempt} attempt(s).`);
303
- return fCallback(null, pManifestEntry);
304
- });
305
-
306
- tmpRequest.on('timeout', () =>
307
- {
308
- tmpRequest.destroy();
309
- pManifestEntry.Log.push(`RestRequest: request timed out after ${tmpTimeout}ms.`);
310
-
311
- if (tmpAttempt <= tmpMaxRetries)
312
- {
313
- pManifestEntry.Log.push(`RestRequest: will retry in 1s.`);
314
- return setTimeout(fPerformRequest, 1000);
315
- }
316
-
317
- pManifestEntry.StopTime = new Date().toISOString();
318
- pManifestEntry.Status = 'Error';
319
- pManifestEntry.Log.push(`RestRequest: ${tmpMethod} failed after ${tmpAttempt} attempt(s).`);
320
- return fCallback(null, pManifestEntry);
321
- });
322
-
323
- if (tmpBody.length > 0)
324
- {
325
- tmpRequest.write(tmpBody);
326
- }
327
-
328
- tmpRequest.end();
329
- };
330
-
331
- fPerformRequest();
332
- }
333
-
334
- /**
335
- * Parse Set-Cookie response headers and merge them into the shared
336
- * cookie jar at pContext.GlobalState.Cookies.
337
- *
338
- * @param {object} pResponse - The HTTP response.
339
- * @param {object} pContext - Execution context.
340
- * @param {object} pManifestEntry - Manifest entry for logging.
341
- */
342
- _captureResponseCookies(pResponse, pContext, pManifestEntry)
343
- {
344
- let tmpSetCookieHeaders = pResponse.headers['set-cookie'];
345
-
346
- if (!tmpSetCookieHeaders)
347
- {
348
- return;
349
- }
350
-
351
- // Normalise to an array (some HTTP libs return a single string)
352
- if (!Array.isArray(tmpSetCookieHeaders))
353
- {
354
- tmpSetCookieHeaders = [tmpSetCookieHeaders];
355
- }
356
-
357
- if (!pContext.GlobalState.Cookies || typeof(pContext.GlobalState.Cookies) !== 'object')
358
- {
359
- pContext.GlobalState.Cookies = {};
360
- }
361
-
362
- let tmpCapturedCount = 0;
363
- for (let i = 0; i < tmpSetCookieHeaders.length; i++)
364
- {
365
- let tmpCookieStr = tmpSetCookieHeaders[i];
366
- // Parse the first name=value pair from the Set-Cookie header
367
- let tmpSemiIndex = tmpCookieStr.indexOf(';');
368
- let tmpPairStr = (tmpSemiIndex !== -1) ? tmpCookieStr.substring(0, tmpSemiIndex) : tmpCookieStr;
369
- let tmpEqualsIndex = tmpPairStr.indexOf('=');
370
-
371
- if (tmpEqualsIndex !== -1)
372
- {
373
- let tmpName = tmpPairStr.substring(0, tmpEqualsIndex).trim();
374
- let tmpValue = tmpPairStr.substring(tmpEqualsIndex + 1).trim();
375
- pContext.GlobalState.Cookies[tmpName] = tmpValue;
376
- tmpCapturedCount++;
377
- }
378
- }
379
-
380
- if (tmpCapturedCount > 0)
381
- {
382
- pManifestEntry.Log.push(`RestRequest: captured ${tmpCapturedCount} cookie(s) into GlobalState.Cookies.`);
383
- }
384
- }
385
-
386
- /**
387
- * Extract a token from the JSON response body and store it in the
388
- * shared cookie jar at pContext.GlobalState.Cookies.
389
- *
390
- * CaptureToken can be:
391
- * - A string: dot-notation path into the JSON body. The resolved
392
- * value is stored as a cookie named "Token".
393
- * - An object with:
394
- * { "Address": "JSON.path", "Cookie": "CookieName" }
395
- *
396
- * @param {string|object} pCaptureToken - The CaptureToken definition.
397
- * @param {object} pJSON - The parsed JSON response body.
398
- * @param {object} pContext - Execution context.
399
- * @param {object} pManifestEntry - Manifest entry for logging.
400
- */
401
- _captureToken(pCaptureToken, pJSON, pContext, pManifestEntry)
402
- {
403
- let tmpAddress;
404
- let tmpCookieName;
405
-
406
- if (typeof(pCaptureToken) === 'string')
407
- {
408
- tmpAddress = pCaptureToken;
409
- tmpCookieName = 'Token';
410
- }
411
- else if (typeof(pCaptureToken) === 'object' && pCaptureToken !== null)
412
- {
413
- tmpAddress = pCaptureToken.Address || '';
414
- tmpCookieName = pCaptureToken.Cookie || 'Token';
415
- }
416
- else
417
- {
418
- pManifestEntry.Log.push(`RestRequest: CaptureToken must be a string or object.`);
419
- return;
420
- }
421
-
422
- if (!tmpAddress || tmpAddress.length === 0)
423
- {
424
- pManifestEntry.Log.push(`RestRequest: CaptureToken address is empty.`);
425
- return;
426
- }
427
-
428
- // Walk the JSON body to resolve the token value
429
- let tmpParts = tmpAddress.split('.');
430
- let tmpValue = pJSON;
431
- for (let i = 0; i < tmpParts.length; i++)
432
- {
433
- if (tmpValue === null || tmpValue === undefined || typeof(tmpValue) !== 'object')
434
- {
435
- pManifestEntry.Log.push(`RestRequest: CaptureToken could not resolve "${tmpAddress}" in response body.`);
436
- return;
437
- }
438
- tmpValue = tmpValue[tmpParts[i]];
439
- }
440
-
441
- if (tmpValue === undefined || tmpValue === null)
442
- {
443
- pManifestEntry.Log.push(`RestRequest: CaptureToken resolved "${tmpAddress}" to null/undefined.`);
444
- return;
445
- }
446
-
447
- if (!pContext.GlobalState.Cookies || typeof(pContext.GlobalState.Cookies) !== 'object')
448
- {
449
- pContext.GlobalState.Cookies = {};
450
- }
451
-
452
- pContext.GlobalState.Cookies[tmpCookieName] = String(tmpValue);
453
- pManifestEntry.Log.push(`RestRequest: CaptureToken stored "${tmpAddress}" as cookie "${tmpCookieName}".`);
454
- }
455
-
456
- /**
457
- * Extract response header values and store them at manyfest
458
- * addresses in pContext.GlobalState.
459
- *
460
- * CaptureHeader is an object mapping response header names to
461
- * GlobalState dot-notation addresses. Header names are matched
462
- * case-insensitively.
463
- *
464
- * @param {object} pCaptureHeader - Mapping of header name → GlobalState address.
465
- * @param {object} pResponseHeaders - The response headers (lowercased by Node).
466
- * @param {object} pContext - Execution context.
467
- * @param {object} pManifestEntry - Manifest entry for logging.
468
- */
469
- _captureHeaders(pCaptureHeader, pResponseHeaders, pContext, pManifestEntry)
470
- {
471
- let tmpKeys = Object.keys(pCaptureHeader);
472
- let tmpCapturedCount = 0;
473
-
474
- for (let i = 0; i < tmpKeys.length; i++)
475
- {
476
- let tmpHeaderName = tmpKeys[i].toLowerCase();
477
- let tmpDestAddress = pCaptureHeader[tmpKeys[i]];
478
-
479
- if (typeof(tmpDestAddress) !== 'string' || tmpDestAddress.length === 0)
480
- {
481
- continue;
482
- }
483
-
484
- let tmpHeaderValue = pResponseHeaders[tmpHeaderName];
485
- if (tmpHeaderValue !== undefined)
486
- {
487
- this._Manyfest.setValueAtAddress(pContext.GlobalState, tmpDestAddress, tmpHeaderValue);
488
- tmpCapturedCount++;
489
- }
490
- }
491
-
492
- if (tmpCapturedCount > 0)
493
- {
494
- pManifestEntry.Log.push(`RestRequest: CaptureHeader stored ${tmpCapturedCount} header value(s) into GlobalState.`);
495
- }
496
- }
497
- }
498
-
499
- module.exports = UltravisorTaskRestRequest;
@@ -1,150 +0,0 @@
1
- const libUltravisorTaskBase = require('../Ultravisor-Task-Base.cjs');
2
-
3
- const libHTTP = require('http');
4
- const libHTTPS = require('https');
5
-
6
- class UltravisorTaskSendJSON extends libUltravisorTaskBase
7
- {
8
- constructor(pFable)
9
- {
10
- super(pFable);
11
- }
12
-
13
- /**
14
- * Send JSON data to a REST URL using any HTTP method.
15
- *
16
- * Task definition fields:
17
- * - URL: the endpoint to request
18
- * - Method (optional): HTTP method (defaults to POST)
19
- * - Data (optional): object to serialise and send as the request body
20
- * - Headers (optional): object of request headers
21
- * - Persist (optional): where to store the response
22
- * string -- manyfest address into GlobalState
23
- * { Address: "..." } -- same as string form
24
- * { File: "..." } -- writes response to a staging file
25
- */
26
- execute(pTaskDefinition, pContext, pManifestEntry, fCallback)
27
- {
28
- let tmpURL = pTaskDefinition.URL || pTaskDefinition.Parameters || '';
29
-
30
- if (!tmpURL || tmpURL.length === 0)
31
- {
32
- pManifestEntry.StopTime = new Date().toISOString();
33
- pManifestEntry.Status = 'Error';
34
- pManifestEntry.Log.push(`SendJSON: missing URL.`);
35
- return fCallback(null, pManifestEntry);
36
- }
37
-
38
- let tmpMethod = (pTaskDefinition.Method || 'POST').toUpperCase();
39
- let tmpBody = pTaskDefinition.hasOwnProperty('Data')
40
- ? JSON.stringify(pTaskDefinition.Data)
41
- : '';
42
-
43
- pManifestEntry.Log.push(`SendJSON: ${tmpMethod} ${tmpURL} (${tmpBody.length} bytes body)`);
44
-
45
- let tmpHeaders = Object.assign(
46
- {
47
- 'Content-Type': 'application/json',
48
- 'Accept': 'application/json'
49
- },
50
- pTaskDefinition.Headers || {});
51
-
52
- if (tmpBody.length > 0)
53
- {
54
- tmpHeaders['Content-Length'] = Buffer.byteLength(tmpBody);
55
- }
56
-
57
- let tmpParsedURL;
58
- try
59
- {
60
- tmpParsedURL = new URL(tmpURL);
61
- }
62
- catch (pParseError)
63
- {
64
- pManifestEntry.StopTime = new Date().toISOString();
65
- pManifestEntry.Status = 'Error';
66
- pManifestEntry.Log.push(`SendJSON: invalid URL: ${pParseError.message}`);
67
- return fCallback(null, pManifestEntry);
68
- }
69
-
70
- let tmpTransport = (tmpParsedURL.protocol === 'https:') ? libHTTPS : libHTTP;
71
- let tmpTimeout = (this.fable?.ProgramConfiguration?.UltravisorCommandTimeoutMilliseconds) || 300000;
72
-
73
- let tmpRequestOptions = {
74
- method: tmpMethod,
75
- headers: tmpHeaders,
76
- timeout: tmpTimeout
77
- };
78
-
79
- let tmpRequest = tmpTransport.request(tmpURL, tmpRequestOptions,
80
- (pResponse) =>
81
- {
82
- let tmpData = '';
83
-
84
- pResponse.on('data', (pChunk) => { tmpData += pChunk; });
85
-
86
- pResponse.on('end', () =>
87
- {
88
- pManifestEntry.StopTime = new Date().toISOString();
89
- pManifestEntry.Log.push(`SendJSON: received ${tmpData.length} bytes, status ${pResponse.statusCode}.`);
90
-
91
- try
92
- {
93
- let tmpParsed = JSON.parse(tmpData);
94
- pManifestEntry.Output = JSON.stringify(tmpParsed);
95
- }
96
- catch (pJsonError)
97
- {
98
- pManifestEntry.Output = tmpData.substring(0, 2000);
99
- }
100
-
101
- pManifestEntry.Status = 'Complete';
102
- pManifestEntry.Success = true;
103
- pManifestEntry.Log.push(`SendJSON: ${tmpMethod} completed successfully.`);
104
-
105
- // Persist the response (parsed JSON if possible, raw string otherwise)
106
- if (pTaskDefinition.Persist)
107
- {
108
- let tmpPersistData;
109
- try
110
- {
111
- tmpPersistData = JSON.parse(pManifestEntry.Output);
112
- }
113
- catch (pIgnore)
114
- {
115
- tmpPersistData = pManifestEntry.Output;
116
- }
117
- this.storeResult(pTaskDefinition, pContext, pManifestEntry, tmpPersistData);
118
- }
119
-
120
- return fCallback(null, pManifestEntry);
121
- });
122
- });
123
-
124
- tmpRequest.on('error', (pError) =>
125
- {
126
- pManifestEntry.StopTime = new Date().toISOString();
127
- pManifestEntry.Status = 'Error';
128
- pManifestEntry.Log.push(`SendJSON: request error: ${pError.message}`);
129
- return fCallback(null, pManifestEntry);
130
- });
131
-
132
- tmpRequest.on('timeout', () =>
133
- {
134
- tmpRequest.destroy();
135
- pManifestEntry.StopTime = new Date().toISOString();
136
- pManifestEntry.Status = 'Error';
137
- pManifestEntry.Log.push(`SendJSON: request timed out after ${tmpTimeout}ms.`);
138
- return fCallback(null, pManifestEntry);
139
- });
140
-
141
- if (tmpBody.length > 0)
142
- {
143
- tmpRequest.write(tmpBody);
144
- }
145
-
146
- tmpRequest.end();
147
- }
148
- }
149
-
150
- module.exports = UltravisorTaskSendJSON;