uapi-json 1.17.6 → 1.19.0
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/README.md +18 -0
- package/docs/Terminal.md +57 -2
- package/package.json +20 -17
- package/src/Request/uapi-parser.js +3 -2
- package/src/Request/uapi-request.js +23 -4
- package/src/Services/Air/AirFormat.js +1 -1
- package/src/Services/Air/AirParser.js +2 -2
- package/src/Services/Terminal/Terminal.js +207 -49
package/README.md
CHANGED
|
@@ -89,6 +89,24 @@ It also has several useful helpers to handle errors.
|
|
|
89
89
|
|
|
90
90
|
`logFunction` - set custom logging function that should match next shape `(...args) => {}`. Will receive all requests and responses from uapi/terminal.
|
|
91
91
|
|
|
92
|
+
`httpsAgent` - set custom HTTPS agent for all requests created by the service. If the agent has `options.timeout`, that value is used as the request timeout.
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
const https = require('https');
|
|
96
|
+
|
|
97
|
+
const TerminalService = uAPI.createTerminalService({
|
|
98
|
+
auth,
|
|
99
|
+
options: {
|
|
100
|
+
httpsAgent: new https.Agent({
|
|
101
|
+
keepAlive: true,
|
|
102
|
+
maxSockets: 1,
|
|
103
|
+
maxFreeSockets: 1,
|
|
104
|
+
timeout: 90000,
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
92
110
|
### Auth object
|
|
93
111
|
<a name="auth"></a>
|
|
94
112
|
|
package/docs/Terminal.md
CHANGED
|
@@ -49,11 +49,12 @@ see [close_session](#close_session) method.
|
|
|
49
49
|
# API
|
|
50
50
|
|
|
51
51
|
**TerminalService**
|
|
52
|
-
* [`.executeCommand(command,
|
|
52
|
+
* [`.executeCommand(command, options)`](#execute_command)
|
|
53
|
+
* [`.executeStatelessCommandWhenIdle(command, options)`](#execute_stateless_command_when_idle)
|
|
53
54
|
* [`.closeSession()`](#close_session)
|
|
54
55
|
* [`.getToken()`](#get_token)
|
|
55
56
|
|
|
56
|
-
## .executeCommand(command,
|
|
57
|
+
## .executeCommand(command, options)
|
|
57
58
|
<a name="execute_command"></a>
|
|
58
59
|
Executes a command in terminal and returns its terminal response
|
|
59
60
|
|
|
@@ -62,7 +63,61 @@ Executes a command in terminal and returns its terminal response
|
|
|
62
63
|
| Param | Type | Description |
|
|
63
64
|
| --- | --- | --- |
|
|
64
65
|
| command | `String` | String representation of the command you want to execute |
|
|
66
|
+
| options | `Object` | Optional command execution options. |
|
|
67
|
+
|
|
68
|
+
**Options**
|
|
69
|
+
|
|
70
|
+
| Param | Type | Description |
|
|
71
|
+
| --- | --- | --- |
|
|
65
72
|
| stopMD | `(screens) => boolean` | Function which gets all previous screens concatenated and detects if more `MD` command needed. |
|
|
73
|
+
| sleepInterval | `Number` | Minimum delay in milliseconds from the last successful full terminal response to the next command send. |
|
|
74
|
+
|
|
75
|
+
`stopMD` must be passed inside `options`; passing it directly as the second argument
|
|
76
|
+
is not supported.
|
|
77
|
+
|
|
78
|
+
## .executeStatelessCommandWhenIdle(command, options)
|
|
79
|
+
<a name="execute_stateless_command_when_idle"></a>
|
|
80
|
+
Executes a stateless command when terminal is idle and returns its terminal response.
|
|
81
|
+
|
|
82
|
+
If terminal is busy, command is added to the stateless commands queue and executed
|
|
83
|
+
after the current command and previously queued stateless commands are finished.
|
|
84
|
+
Queued commands are executed one by one in FIFO order.
|
|
85
|
+
If terminal is idle, regular `executeCommand` calls execute before queued stateless
|
|
86
|
+
commands.
|
|
87
|
+
|
|
88
|
+
This method is promise based. Every caller receives the result or error from its own
|
|
89
|
+
command execution. If one queued command fails, later queued commands are still executed.
|
|
90
|
+
If the active command fails before the queue is drained, queued stateless commands are
|
|
91
|
+
skipped and rejected with the same terminal error.
|
|
92
|
+
|
|
93
|
+
Stateless means command is independent of terminal context, such as an open booking,
|
|
94
|
+
fare search, or other GDS working state. This method does not detect or enforce
|
|
95
|
+
statelessness; caller is responsible for using it only for commands safe in any
|
|
96
|
+
terminal context.
|
|
97
|
+
|
|
98
|
+
**Returns**: `Promise` that returns terminal command response in `String` format
|
|
99
|
+
|
|
100
|
+
| Param | Type | Description |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| command | `String` | String representation of the stateless command you want to execute |
|
|
103
|
+
| options | `Object` | Optional command execution options. |
|
|
104
|
+
|
|
105
|
+
**Options**
|
|
106
|
+
|
|
107
|
+
| Param | Type | Description |
|
|
108
|
+
| --- | --- | --- |
|
|
109
|
+
| stopMD | `(screens) => boolean` | Function which gets all previous screens concatenated and detects if more `MD` command needed. |
|
|
110
|
+
| sleepInterval | `Number` | Minimum delay in milliseconds from the last successful full terminal response to the next command send. |
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
await TerminalService.executeCommand('TE', {
|
|
114
|
+
stopMD: (screens) => screens.includes('END OF DISPLAY'),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await TerminalService.executeStatelessCommandWhenIdle('.CDIEV', {
|
|
118
|
+
sleepInterval: 1000,
|
|
119
|
+
});
|
|
120
|
+
```
|
|
66
121
|
|
|
67
122
|
## .closeSession()
|
|
68
123
|
<a name="close_session"></a>
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uapi-json",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "Travelport Universal API",
|
|
5
5
|
"main": "src/",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20"
|
|
8
|
+
},
|
|
6
9
|
"files": [
|
|
7
10
|
"src/",
|
|
8
11
|
"README.md",
|
|
@@ -27,36 +30,36 @@
|
|
|
27
30
|
"node",
|
|
28
31
|
"travelport"
|
|
29
32
|
],
|
|
30
|
-
"author": "Mark Orel <mail.ormark@gmail.com>",
|
|
31
33
|
"contributors": [
|
|
32
|
-
"Dmytro Chertousov <d.chertousov@gmail.com>",
|
|
33
34
|
"Artem Pylypchuk <articicejuice@gmail.com>",
|
|
34
|
-
"
|
|
35
|
+
"Dmytro Chertousov <d.chertousov@gmail.com>",
|
|
36
|
+
"Mark Orel <mail.ormark@gmail.com>",
|
|
35
37
|
"Mark Omarov <dev.mark.omarov@gmail.com>",
|
|
36
|
-
"Oleksii Duvanov <colden.aid@gmail.com>"
|
|
38
|
+
"Oleksii Duvanov <colden.aid@gmail.com>",
|
|
39
|
+
"Yevhenii Huselietov <d46k16@gmail.com>"
|
|
37
40
|
],
|
|
38
41
|
"license": "MIT",
|
|
39
42
|
"dependencies": {
|
|
40
|
-
"axios": "
|
|
43
|
+
"axios": "~1.15.0",
|
|
41
44
|
"galileo-screen": "1.0.5",
|
|
42
|
-
"handlebars": "
|
|
43
|
-
"handlebars-helper-equal": "
|
|
44
|
-
"joi": "
|
|
45
|
-
"moment": "
|
|
46
|
-
"node-errors-helpers": "
|
|
47
|
-
"pretty-data": "
|
|
48
|
-
"promise-retry": "
|
|
49
|
-
"xml2js": "
|
|
45
|
+
"handlebars": "~4.7.9",
|
|
46
|
+
"handlebars-helper-equal": "~1.0.0",
|
|
47
|
+
"joi": "~18.1.2",
|
|
48
|
+
"moment": "~2.30.1",
|
|
49
|
+
"node-errors-helpers": "~1.0.0",
|
|
50
|
+
"pretty-data": "~0.40.0",
|
|
51
|
+
"promise-retry": "~2.0.1",
|
|
52
|
+
"xml2js": "~0.6.2"
|
|
50
53
|
},
|
|
51
54
|
"devDependencies": {
|
|
52
55
|
"chai": "^4.5.0",
|
|
53
56
|
"eslint": "^8.39.0",
|
|
54
57
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
55
58
|
"eslint-plugin-import": "^2.32.0",
|
|
56
|
-
"mocha": "^11.7.
|
|
59
|
+
"mocha": "^11.7.5",
|
|
57
60
|
"nyc": "^17.1.0",
|
|
58
|
-
"proxyquire": "
|
|
59
|
-
"readline2": "
|
|
61
|
+
"proxyquire": "~2.1.3",
|
|
62
|
+
"readline2": "~1.0.1",
|
|
60
63
|
"sinon": "^21.0.0",
|
|
61
64
|
"sinon-chai": "^3.7.0"
|
|
62
65
|
}
|
|
@@ -36,8 +36,9 @@ function mergeLeaf(item) {
|
|
|
36
36
|
return { ...item, ...leaf };
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
function Parser(root, uapiVersion, env, debug, config, provider) {
|
|
39
|
+
function Parser(root, uapiVersion, env, debug, config, provider, log) {
|
|
40
40
|
this.debug = debug;
|
|
41
|
+
this.log = log || console.info;
|
|
41
42
|
if (!config) {
|
|
42
43
|
this.config = defaultConfig(uapiVersion);
|
|
43
44
|
} else {
|
|
@@ -212,7 +213,7 @@ Parser.prototype.parse = function (xml) {
|
|
|
212
213
|
|
|
213
214
|
const end = new Date() - start;
|
|
214
215
|
if (this.debug > 1) {
|
|
215
|
-
|
|
216
|
+
this.log('uAPI_Parse execution time: %dms', end);
|
|
216
217
|
}
|
|
217
218
|
|
|
218
219
|
return data[self.rootObject];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const handlebars = require('handlebars');
|
|
2
2
|
const axios = require('axios');
|
|
3
|
+
const https = require('https');
|
|
3
4
|
const { pd } = require('pretty-data');
|
|
4
5
|
const {
|
|
5
6
|
RequestValidationError,
|
|
@@ -13,6 +14,14 @@ const configInit = require('../config');
|
|
|
13
14
|
|
|
14
15
|
handlebars.registerHelper('equal', require('handlebars-helper-equal'));
|
|
15
16
|
|
|
17
|
+
const REQUEST_AGENT_OPTIONS = {
|
|
18
|
+
keepAlive: true,
|
|
19
|
+
keepAliveMsecs: 5000,
|
|
20
|
+
maxSockets: 20,
|
|
21
|
+
maxFreeSockets: 20,
|
|
22
|
+
};
|
|
23
|
+
const httpsAgent = new https.Agent(REQUEST_AGENT_OPTIONS);
|
|
24
|
+
|
|
16
25
|
/**
|
|
17
26
|
* basic function for requests/responses
|
|
18
27
|
* @param {string} service service url for current response (gateway)
|
|
@@ -43,6 +52,15 @@ module.exports = function uapiRequest(
|
|
|
43
52
|
|
|
44
53
|
const config = configInit(auth.region);
|
|
45
54
|
const log = options.logFunction || console.log;
|
|
55
|
+
const customHttpsAgent = options.httpsAgent;
|
|
56
|
+
const requestHttpsAgent = customHttpsAgent || httpsAgent;
|
|
57
|
+
const requestTimeout = (
|
|
58
|
+
customHttpsAgent
|
|
59
|
+
&& customHttpsAgent.options
|
|
60
|
+
&& typeof customHttpsAgent.options.timeout === 'number'
|
|
61
|
+
)
|
|
62
|
+
? customHttpsAgent.options.timeout
|
|
63
|
+
: config.timeout || 5000;
|
|
46
64
|
|
|
47
65
|
// Performing checks
|
|
48
66
|
if (!service || service.length <= 0) {
|
|
@@ -64,7 +82,7 @@ module.exports = function uapiRequest(
|
|
|
64
82
|
}
|
|
65
83
|
|
|
66
84
|
// create a v52 uAPI parser with default params and request data in env
|
|
67
|
-
const uParser = new Parser(rootObject, 'v52_0', params, debugMode, null, auth.provider);
|
|
85
|
+
const uParser = new Parser(rootObject, 'v52_0', params, debugMode, null, auth.provider, log);
|
|
68
86
|
|
|
69
87
|
const validateInput = () => (
|
|
70
88
|
Promise.resolve(params)
|
|
@@ -85,7 +103,8 @@ module.exports = function uapiRequest(
|
|
|
85
103
|
const response = await axios.request({
|
|
86
104
|
url: service,
|
|
87
105
|
method: 'POST',
|
|
88
|
-
timeout:
|
|
106
|
+
timeout: requestTimeout,
|
|
107
|
+
httpsAgent: requestHttpsAgent,
|
|
89
108
|
auth: {
|
|
90
109
|
username: auth.username,
|
|
91
110
|
password: auth.password,
|
|
@@ -156,7 +175,6 @@ module.exports = function uapiRequest(
|
|
|
156
175
|
};
|
|
157
176
|
|
|
158
177
|
const validateSOAP = function (parsedXML) {
|
|
159
|
-
console.log(parsedXML);
|
|
160
178
|
if (parsedXML['SOAP:Fault']) {
|
|
161
179
|
if (debugMode > 2) {
|
|
162
180
|
log('Parsed error response', pd.json(parsedXML));
|
|
@@ -169,7 +187,8 @@ module.exports = function uapiRequest(
|
|
|
169
187
|
params,
|
|
170
188
|
debugMode,
|
|
171
189
|
errParserConfig,
|
|
172
|
-
auth.provider
|
|
190
|
+
auth.provider,
|
|
191
|
+
log
|
|
173
192
|
);
|
|
174
193
|
const errData = errParser.mergeLeafRecursive(parsedXML['SOAP:Fault'][0]); // parse error data
|
|
175
194
|
return errorHandler.call(errParser, errData);
|
|
@@ -197,7 +197,7 @@ function formatPassengerCategories(pricingInfo) {
|
|
|
197
197
|
|
|
198
198
|
[code] = list;
|
|
199
199
|
if (!list[0] || list.length !== 1) { // TODO throw error
|
|
200
|
-
console.
|
|
200
|
+
console.warn('Warning: different categories '
|
|
201
201
|
+ list.join() + ' in single fare calculation ' + key + ' in fare ' + key);
|
|
202
202
|
}
|
|
203
203
|
passengerCounts[code] = count;
|
|
@@ -218,7 +218,7 @@ function airPrice(obj) {
|
|
|
218
218
|
|
|
219
219
|
let pricingSolution = 0;
|
|
220
220
|
if (priceKeys.length > 1) {
|
|
221
|
-
console.
|
|
221
|
+
console.warn('More than one solution found in booking. Resolving the cheapest one.');
|
|
222
222
|
const solutions = priceKeys.map((key) => pricingSolutions[key]);
|
|
223
223
|
|
|
224
224
|
[pricingSolution] = solutions.sort(
|
|
@@ -347,7 +347,7 @@ function airPriceRspPricingSolutionXML(obj) {
|
|
|
347
347
|
let pricingSolution = 0;
|
|
348
348
|
if (pricingSolutions.length > 1) {
|
|
349
349
|
// TODO: Check result for multiple passenger type results.
|
|
350
|
-
console.
|
|
350
|
+
console.warn('More than one solution found in booking. Resolving the cheapest one.');
|
|
351
351
|
[pricingSolution] = pricingSolutions.sort(
|
|
352
352
|
(a, b) => parseFloat(a.$.TotalPrice.slice(3)) - parseFloat(b.$.TotalPrice.slice(3))
|
|
353
353
|
);
|
|
@@ -101,6 +101,17 @@ const isErrorRetriable = (e) => {
|
|
|
101
101
|
return (errorCode && RETRY_CODES_LIST.includes(errorCode));
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
+
const defaultStopMD = (screens) => !screenFunctions.hasMore(screens);
|
|
105
|
+
|
|
106
|
+
const getCommandOptions = (options = {}) => ({
|
|
107
|
+
stopMD: options.stopMD || defaultStopMD,
|
|
108
|
+
sleepInterval: options.sleepInterval || 0,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const wait = (ms) => new Promise((resolve) => {
|
|
112
|
+
setTimeout(resolve, ms);
|
|
113
|
+
});
|
|
114
|
+
|
|
104
115
|
module.exports = function (settings) {
|
|
105
116
|
const {
|
|
106
117
|
timeout = false, debug = 0, autoClose = true,
|
|
@@ -109,25 +120,37 @@ module.exports = function (settings) {
|
|
|
109
120
|
} = settings;
|
|
110
121
|
|
|
111
122
|
const service = terminalService(validateServiceSettings(settings));
|
|
112
|
-
const defaultStopMD = (screens) => !screenFunctions.hasMore(screens);
|
|
113
123
|
|
|
114
124
|
const state = {
|
|
115
125
|
terminalState: TERMINAL_STATE_NONE,
|
|
116
126
|
sessionToken: token,
|
|
127
|
+
terminalError: null,
|
|
128
|
+
lastCommandFinishedAt: null,
|
|
129
|
+
statelessCommandsQueue: [],
|
|
117
130
|
};
|
|
118
131
|
|
|
119
132
|
// Getting session token
|
|
120
|
-
const
|
|
121
|
-
// Return error if not in correct state
|
|
133
|
+
const throwIfTerminalUnavailable = () => {
|
|
122
134
|
if (state.terminalState === TERMINAL_STATE_BUSY) {
|
|
123
135
|
throw new TerminalRuntimeError.TerminalIsBusy();
|
|
124
136
|
}
|
|
125
137
|
if (state.terminalState === TERMINAL_STATE_CLOSED) {
|
|
126
138
|
throw new TerminalRuntimeError.TerminalIsClosed();
|
|
127
139
|
}
|
|
140
|
+
if (state.terminalState === TERMINAL_STATE_ERROR) {
|
|
141
|
+
throw state.terminalError;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
128
144
|
|
|
129
|
-
|
|
145
|
+
const setTerminalBusy = () => {
|
|
146
|
+
throwIfTerminalUnavailable();
|
|
147
|
+
Object.assign(state, {
|
|
148
|
+
terminalState: TERMINAL_STATE_BUSY,
|
|
149
|
+
terminalError: null,
|
|
150
|
+
});
|
|
151
|
+
};
|
|
130
152
|
|
|
153
|
+
const getSessionToken = async () => {
|
|
131
154
|
// Return token if already obtained
|
|
132
155
|
if (state.sessionToken !== null) {
|
|
133
156
|
return state.sessionToken;
|
|
@@ -137,7 +160,6 @@ module.exports = function (settings) {
|
|
|
137
160
|
state.sessionToken = sessionToken;
|
|
138
161
|
|
|
139
162
|
if (!emulatePcc) {
|
|
140
|
-
state.terminalState = TERMINAL_STATE_READY;
|
|
141
163
|
return state.sessionToken;
|
|
142
164
|
}
|
|
143
165
|
|
|
@@ -158,6 +180,8 @@ module.exports = function (settings) {
|
|
|
158
180
|
throw new TerminalRuntimeError.TerminalEmulationFailed(response);
|
|
159
181
|
}
|
|
160
182
|
|
|
183
|
+
state.lastCommandFinishedAt = Date.now();
|
|
184
|
+
|
|
161
185
|
return sessionToken;
|
|
162
186
|
};
|
|
163
187
|
|
|
@@ -207,63 +231,197 @@ module.exports = function (settings) {
|
|
|
207
231
|
|
|
208
232
|
// Get terminal ID
|
|
209
233
|
const getTerminalId = (sessionToken) => getHashSubstr(sessionToken);
|
|
234
|
+
const isClosed = () => state.terminalState === TERMINAL_STATE_CLOSED;
|
|
235
|
+
const isInitialized = () => state.terminalState !== TERMINAL_STATE_NONE;
|
|
236
|
+
const hasStatelessCommands = () => (
|
|
237
|
+
state.statelessCommandsQueue.length > 0
|
|
238
|
+
);
|
|
239
|
+
const setTerminalReady = () => {
|
|
240
|
+
Object.assign(state, {
|
|
241
|
+
terminalState: TERMINAL_STATE_READY,
|
|
242
|
+
terminalError: null,
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
const setTerminalError = (err) => {
|
|
246
|
+
Object.assign(state, {
|
|
247
|
+
terminalState: TERMINAL_STATE_ERROR,
|
|
248
|
+
terminalError: err,
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
const rejectStatelessCommandsQueue = (err) => {
|
|
252
|
+
while (state.statelessCommandsQueue.length > 0) {
|
|
253
|
+
const { reject } = state.statelessCommandsQueue.shift();
|
|
254
|
+
reject(err);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
const sleepBeforeCommand = async (sleepInterval) => {
|
|
258
|
+
if (state.lastCommandFinishedAt === null || sleepInterval <= 0) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
210
261
|
|
|
211
|
-
|
|
212
|
-
getToken: async () => {
|
|
213
|
-
await getSessionToken();
|
|
214
|
-
// Needed here as getSessionToken marks terminal as busy
|
|
215
|
-
state.terminalState = TERMINAL_STATE_READY;
|
|
216
|
-
return state.sessionToken;
|
|
217
|
-
},
|
|
218
|
-
executeCommand: async (command, stopMD = defaultStopMD) => {
|
|
219
|
-
try {
|
|
220
|
-
const sessionToken = await getSessionToken();
|
|
221
|
-
const terminalId = getTerminalId(sessionToken);
|
|
262
|
+
const timeToWait = sleepInterval - (Date.now() - state.lastCommandFinishedAt);
|
|
222
263
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
264
|
+
if (timeToWait > 0) {
|
|
265
|
+
await wait(timeToWait);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const executeCommandInternal = async (command, options) => {
|
|
269
|
+
const { stopMD, sleepInterval } = options;
|
|
270
|
+
const sessionToken = await getSessionToken();
|
|
271
|
+
const terminalId = getTerminalId(sessionToken);
|
|
226
272
|
|
|
227
|
-
|
|
228
|
-
const response = await processResponse(screen, stopMD);
|
|
273
|
+
await sleepBeforeCommand(sleepInterval);
|
|
229
274
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
275
|
+
if (debug) {
|
|
276
|
+
log(`[${terminalId}] Terminal request:\n${command}`);
|
|
277
|
+
}
|
|
233
278
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
});
|
|
279
|
+
const screen = await executeCommandWithRetry(command);
|
|
280
|
+
const response = await processResponse(screen, stopMD);
|
|
237
281
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
: new TerminalRuntimeError.TerminalUnexpectedError({ screen: response });
|
|
282
|
+
if (debug) {
|
|
283
|
+
log(`[${terminalId}] Terminal response:\n${response}`);
|
|
284
|
+
}
|
|
242
285
|
|
|
243
|
-
|
|
244
|
-
|
|
286
|
+
if (UNEXPECTED_TERMINAL_ERRORS.some((e) => response.includes(e))) {
|
|
287
|
+
const errorObject = isFinancialCommand(command)
|
|
288
|
+
? new TerminalRuntimeError.TerminalUnexpectedFinancialError({ screen: response })
|
|
289
|
+
: new TerminalRuntimeError.TerminalUnexpectedError({ screen: response });
|
|
290
|
+
|
|
291
|
+
throw errorObject;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
state.lastCommandFinishedAt = Date.now();
|
|
295
|
+
|
|
296
|
+
return response;
|
|
297
|
+
};
|
|
298
|
+
const drainStatelessCommandsQueue = async () => {
|
|
299
|
+
if (state.statelessCommandsQueue.length === 0) {
|
|
300
|
+
setTerminalReady();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const {
|
|
305
|
+
command, options, resolve, reject,
|
|
306
|
+
} = state.statelessCommandsQueue.shift();
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const response = await executeCommandInternal(command, options);
|
|
310
|
+
|
|
311
|
+
if (!hasStatelessCommands()) {
|
|
312
|
+
setTerminalReady();
|
|
313
|
+
}
|
|
314
|
+
resolve(response);
|
|
315
|
+
} catch (err) {
|
|
316
|
+
if (!hasStatelessCommands()) {
|
|
317
|
+
setTerminalReady();
|
|
318
|
+
}
|
|
319
|
+
reject(err);
|
|
320
|
+
}
|
|
245
321
|
|
|
246
|
-
|
|
322
|
+
await drainStatelessCommandsQueue();
|
|
323
|
+
};
|
|
324
|
+
const executeAndDrainCommands = async (command, options) => {
|
|
325
|
+
try {
|
|
326
|
+
const response = await executeCommandInternal(command, options);
|
|
327
|
+
|
|
328
|
+
if (hasStatelessCommands()) {
|
|
329
|
+
drainStatelessCommandsQueue()
|
|
330
|
+
.then(setTerminalReady)
|
|
331
|
+
.catch(setTerminalError);
|
|
332
|
+
} else {
|
|
333
|
+
setTerminalReady();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return response;
|
|
337
|
+
} catch (err) {
|
|
338
|
+
setTerminalError(err);
|
|
339
|
+
rejectStatelessCommandsQueue(err);
|
|
340
|
+
throw err;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const terminal = {
|
|
345
|
+
getToken: async () => {
|
|
346
|
+
setTerminalBusy();
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
await getSessionToken();
|
|
350
|
+
setTerminalReady();
|
|
351
|
+
return state.sessionToken;
|
|
247
352
|
} catch (err) {
|
|
248
|
-
|
|
249
|
-
terminalState: TERMINAL_STATE_ERROR,
|
|
250
|
-
});
|
|
353
|
+
setTerminalError(err);
|
|
251
354
|
throw err;
|
|
252
355
|
}
|
|
253
356
|
},
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
357
|
+
executeCommand: async (command, options) => {
|
|
358
|
+
setTerminalBusy();
|
|
359
|
+
return executeAndDrainCommands(command, getCommandOptions(options));
|
|
360
|
+
},
|
|
361
|
+
executeStatelessCommandWhenIdle: async (command, options) => {
|
|
362
|
+
const commandOptions = getCommandOptions(options);
|
|
363
|
+
|
|
364
|
+
if (state.terminalState === TERMINAL_STATE_CLOSED) {
|
|
365
|
+
throw new TerminalRuntimeError.TerminalIsClosed();
|
|
366
|
+
}
|
|
367
|
+
if (state.terminalState === TERMINAL_STATE_ERROR) {
|
|
368
|
+
throw state.terminalError;
|
|
369
|
+
}
|
|
370
|
+
if (state.terminalState === TERMINAL_STATE_BUSY || hasStatelessCommands()) {
|
|
371
|
+
return new Promise((resolve, reject) => {
|
|
372
|
+
state.statelessCommandsQueue.push({
|
|
373
|
+
command, options: commandOptions, resolve, reject,
|
|
263
374
|
});
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
setTerminalBusy();
|
|
379
|
+
return executeAndDrainCommands(command, commandOptions);
|
|
380
|
+
},
|
|
381
|
+
isClosed,
|
|
382
|
+
isInitialized,
|
|
383
|
+
closeSessionSafe: async () => {
|
|
384
|
+
if (isClosed()) {
|
|
385
|
+
console.warn('UAPI-JSON WARNING: Terminal session is already closed');
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (!isInitialized()) {
|
|
389
|
+
console.warn('UAPI-JSON WARNING: Terminal session is not initialized');
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
await terminal.closeSession().catch(console.error);
|
|
394
|
+
},
|
|
395
|
+
closeSession: () => {
|
|
396
|
+
if (state.terminalState === TERMINAL_STATE_BUSY) {
|
|
397
|
+
return Promise.reject(new TerminalRuntimeError.TerminalIsBusy());
|
|
398
|
+
}
|
|
399
|
+
if (state.terminalState === TERMINAL_STATE_CLOSED) {
|
|
400
|
+
return Promise.reject(new TerminalRuntimeError.TerminalIsClosed());
|
|
401
|
+
}
|
|
402
|
+
if (state.terminalState === TERMINAL_STATE_ERROR && state.sessionToken === null) {
|
|
403
|
+
return Promise.reject(state.terminalError);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
Object.assign(state, {
|
|
407
|
+
terminalState: TERMINAL_STATE_BUSY,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
return getSessionToken()
|
|
411
|
+
.then(
|
|
412
|
+
(sessionToken) => service.closeSession({
|
|
413
|
+
sessionToken,
|
|
414
|
+
})
|
|
415
|
+
).then(
|
|
416
|
+
(response) => {
|
|
417
|
+
Object.assign(state, {
|
|
418
|
+
terminalState: TERMINAL_STATE_CLOSED,
|
|
419
|
+
terminalError: null,
|
|
420
|
+
});
|
|
421
|
+
return response;
|
|
422
|
+
}
|
|
423
|
+
);
|
|
424
|
+
},
|
|
267
425
|
};
|
|
268
426
|
|
|
269
427
|
if (autoClose) {
|