uapi-json 1.18.0 → 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/docs/Terminal.md +57 -2
- package/package.json +1 -1
- package/src/Services/Terminal/Terminal.js +192 -50
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
|
@@ -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
|
|
|
@@ -209,50 +233,151 @@ module.exports = function (settings) {
|
|
|
209
233
|
const getTerminalId = (sessionToken) => getHashSubstr(sessionToken);
|
|
210
234
|
const isClosed = () => state.terminalState === TERMINAL_STATE_CLOSED;
|
|
211
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
|
+
}
|
|
212
261
|
|
|
213
|
-
|
|
214
|
-
getToken: async () => {
|
|
215
|
-
await getSessionToken();
|
|
216
|
-
// Needed here as getSessionToken marks terminal as busy
|
|
217
|
-
state.terminalState = TERMINAL_STATE_READY;
|
|
218
|
-
return state.sessionToken;
|
|
219
|
-
},
|
|
220
|
-
executeCommand: async (command, stopMD = defaultStopMD) => {
|
|
221
|
-
try {
|
|
222
|
-
const sessionToken = await getSessionToken();
|
|
223
|
-
const terminalId = getTerminalId(sessionToken);
|
|
262
|
+
const timeToWait = sleepInterval - (Date.now() - state.lastCommandFinishedAt);
|
|
224
263
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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);
|
|
228
272
|
|
|
229
|
-
|
|
230
|
-
const response = await processResponse(screen, stopMD);
|
|
273
|
+
await sleepBeforeCommand(sleepInterval);
|
|
231
274
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
275
|
+
if (debug) {
|
|
276
|
+
log(`[${terminalId}] Terminal request:\n${command}`);
|
|
277
|
+
}
|
|
235
278
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
});
|
|
279
|
+
const screen = await executeCommandWithRetry(command);
|
|
280
|
+
const response = await processResponse(screen, stopMD);
|
|
239
281
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
: new TerminalRuntimeError.TerminalUnexpectedError({ screen: response });
|
|
282
|
+
if (debug) {
|
|
283
|
+
log(`[${terminalId}] Terminal response:\n${response}`);
|
|
284
|
+
}
|
|
244
285
|
|
|
245
|
-
|
|
246
|
-
|
|
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
|
+
}
|
|
321
|
+
|
|
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();
|
|
247
347
|
|
|
248
|
-
|
|
348
|
+
try {
|
|
349
|
+
await getSessionToken();
|
|
350
|
+
setTerminalReady();
|
|
351
|
+
return state.sessionToken;
|
|
249
352
|
} catch (err) {
|
|
250
|
-
|
|
251
|
-
terminalState: TERMINAL_STATE_ERROR,
|
|
252
|
-
});
|
|
353
|
+
setTerminalError(err);
|
|
253
354
|
throw err;
|
|
254
355
|
}
|
|
255
356
|
},
|
|
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,
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
setTerminalBusy();
|
|
379
|
+
return executeAndDrainCommands(command, commandOptions);
|
|
380
|
+
},
|
|
256
381
|
isClosed,
|
|
257
382
|
isInitialized,
|
|
258
383
|
closeSessionSafe: async () => {
|
|
@@ -267,19 +392,36 @@ module.exports = function (settings) {
|
|
|
267
392
|
|
|
268
393
|
await terminal.closeSession().catch(console.error);
|
|
269
394
|
},
|
|
270
|
-
closeSession: () =>
|
|
271
|
-
.
|
|
272
|
-
(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
+
},
|
|
283
425
|
};
|
|
284
426
|
|
|
285
427
|
if (autoClose) {
|