ti2-tourplan 1.0.18 → 1.0.19

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/index.js CHANGED
@@ -1,15 +1,17 @@
1
- // const axios = require('axios');
2
- const Promise = require('bluebird');
1
+ const axiosRaw = require('axios');
2
+ // const Promise = require('bluebird');
3
3
  const R = require('ramda');
4
4
  const assert = require('assert');
5
5
  const moment = require('moment');
6
6
  const js2xmlparser = require('js2xmlparser');
7
7
  const xml2js = require('xml2js');
8
+ const { XMLParser } = require('fast-xml-parser');
8
9
  const { translateTPOption } = require('./resolvers/product');
9
10
 
10
11
  const Normalizer = require('./normalizer');
11
12
 
12
13
  const xmlParser = new xml2js.Parser();
14
+ const fastParser = new XMLParser();
13
15
 
14
16
  const defaultXmlOptions = {
15
17
  prettyPrinting: { enabled: false },
@@ -25,7 +27,6 @@ const hostConnectXmlOptions = {
25
27
  name: 'hostConnect_4_06_009.dtd',
26
28
  },
27
29
  };
28
-
29
30
  const getHeaders = ({ length }) => ({
30
31
  Accept: 'application/xml',
31
32
  'Content-Type': 'application/xml; charset=utf-8',
@@ -82,16 +83,58 @@ class Plugin {
82
83
  js2xmlparser.parse('Request', model, xmlOptions),
83
84
  );
84
85
  data = data.replace(xmlOptions.dtd.name, `Request SYSTEM "${xmlOptions.dtd.name}"`);
85
- const reply = R.path(['data'], await axios({
86
- method: 'post',
87
- url: endpoint,
88
- data,
89
- headers: getHeaders({ length: data.length }),
90
- }));
91
- const replyObj = await xmlParser.parseStringPromise(reply);
92
- const error = R.path(['Reply', 'ErrorReply', 0, 'Error', 0], replyObj);
86
+ let replyObj;
87
+ let errorStr;
88
+ if (this.xmlProxyUrl) {
89
+ // use pyfilematch xmlproxy
90
+ try {
91
+ replyObj = R.path(['data'], await axios({
92
+ method: 'post',
93
+ url: `${this.xmlProxyUrl}/xmlproxy`,
94
+ data: {
95
+ url: endpoint,
96
+ data,
97
+ headers: getHeaders({ length: `${data.length}` }),
98
+ },
99
+ }));
100
+ } catch (err) {
101
+ console.log('error in calling pyfilematch xmlproxy', err);
102
+ errorStr = `error in calling pyfilematch xmlproxy: ${err}`;
103
+ }
104
+ }
105
+ if (!replyObj) {
106
+ // in case of error /xmlproxy, fallback to call tourplan directly
107
+ // and then use pyfilematch xml2json to parse the xml
108
+ const reply = R.path(['data'], await axios({
109
+ method: 'post',
110
+ url: endpoint,
111
+ data,
112
+ headers: getHeaders({ length: data.length }),
113
+ }));
114
+ if (this.xmlProxyUrl) {
115
+ try {
116
+ // using raw axios to avoid logging the large xml request
117
+ ({ data: replyObj } = await axiosRaw({
118
+ method: 'post',
119
+ url: `${this.xmlProxyUrl}/xml2json`,
120
+ data: { xml: reply },
121
+ maxContentLength: Infinity,
122
+ maxBodyLength: Infinity,
123
+ }));
124
+ } catch (err) {
125
+ console.log('error in calling pyfilematch xml2json', R.pathOr('Nada', ['response', 'data', 'error'], err));
126
+ errorStr = `error in calling pyfilematch xml2json: ${R.pathOr('Nada', ['response', 'data', 'error'], err)}`;
127
+ }
128
+ }
129
+ // in case of error from /xml2json, fallback to fast-xml-parser
130
+ if (!replyObj) {
131
+ replyObj = fastParser.parse(reply);
132
+ }
133
+ }
134
+ const requestType = R.keys(model)[0];
135
+ if (!replyObj) throw new Error(`${requestType} failed: ${errorStr || 'no reply object'}`);
136
+ const error = replyObj.error || R.path(['Reply', 'ErrorReply', 'Error'], replyObj);
93
137
  if (error) {
94
- const requestType = R.keys(model)[0];
95
138
  if (error.indexOf('2050 SCN Request denied for TEST connecting from') > -1
96
139
  && requestType === 'OptionInfoRequest'
97
140
  && endpoint.indexOf('actour') > -1
@@ -130,7 +173,7 @@ class Plugin {
130
173
  axios,
131
174
  xmlOptions: hostConnectXmlOptions,
132
175
  });
133
- assert(R.path(['AgentInfoReply', 0, 'Currency'], replyObj));
176
+ assert(R.path(['AgentInfoReply', 'Currency'], replyObj));
134
177
  return true;
135
178
  }
136
179
  const model = {
@@ -142,7 +185,7 @@ class Plugin {
142
185
  const replyObj = await this.callTourplan({
143
186
  model, endpoint, axios, xmlOptions: defaultXmlOptions,
144
187
  });
145
- assert(R.path(['AuthenticationReply', 0], replyObj) === '');
188
+ assert(R.path(['AuthenticationReply'], replyObj) === '');
146
189
  return true;
147
190
  } catch (err) {
148
191
  return false;
@@ -293,7 +336,7 @@ class Plugin {
293
336
  : await this.cache.getOrExec({
294
337
  fnParams: [model],
295
338
  fn: () => this.callTourplan(payload),
296
- ttl: 60 * 60 * 12, // 12 hours
339
+ ttl: 60 * 60 * 24, // 24 hours
297
340
  forceRefresh: false,
298
341
  });
299
342
  let products = [];
@@ -303,8 +346,8 @@ class Plugin {
303
346
  products = R.call(R.compose(
304
347
  R.map(optionsGroupedBySupplierId => {
305
348
  const supplierData = {
306
- supplierId: R.path([0, 'OptGeneral', 0, 'SupplierId', 0], optionsGroupedBySupplierId),
307
- supplierName: R.path([0, 'OptGeneral', 0, 'SupplierName', 0], optionsGroupedBySupplierId),
349
+ supplierId: R.path([0, 'OptGeneral', 'SupplierId'], optionsGroupedBySupplierId),
350
+ supplierName: R.path([0, 'OptGeneral', 'SupplierName'], optionsGroupedBySupplierId),
308
351
  };
309
352
  return translateTPOption({
310
353
  supplierData,
@@ -314,20 +357,26 @@ class Plugin {
314
357
  });
315
358
  }),
316
359
  R.values,
317
- R.groupBy(R.path(['OptGeneral', 0, 'SupplierId', 0])),
360
+ R.groupBy(R.path(['OptGeneral', 'SupplierId'])),
318
361
  productName ? R.filter(o => {
319
362
  const str = `${
320
- R.path(['OptGeneral', 0, 'Description', 0], o)
363
+ R.path(['OptGeneral', 'Description'], o)
321
364
  } ${
322
- R.path(['Opt', 0], o)
365
+ R.path(['Opt'], o)
323
366
  } ${
324
- R.path(['OptGeneral', 0, 'SupplierId', 0], o)
367
+ R.path(['OptGeneral', 'SupplierId'], o)
325
368
  } ${
326
- R.path(['OptGeneral', 0, 'SupplierName', 0], o)
369
+ R.path(['OptGeneral', 'SupplierName'], o)
327
370
  }`;
328
371
  return wildcardMatch(productName, str);
329
372
  }) : R.identity,
330
- R.pathOr([], ['OptionInfoReply', 0, 'Option']),
373
+ root => {
374
+ const options = R.pathOr([], ['OptionInfoReply', 'Option'], root);
375
+ // due to the new parser, single option will be returned as an object
376
+ // instead of an array
377
+ if (Array.isArray(options)) return options;
378
+ return [options];
379
+ },
331
380
  ), replyObj);
332
381
  }
333
382
  const productFields = [{
@@ -715,13 +764,13 @@ class Plugin {
715
764
  });
716
765
  console.log('replyObj', replyObj);
717
766
  return {
718
- message: R.path(['AddServiceReply', 0, 'Status', 0], replyObj)
767
+ message: R.path(['AddServiceReply', 'Status'], replyObj)
719
768
  === 'NO' ? 'Service cannot be added to quote' : '',
720
769
  quote: {
721
- id: R.path(['AddServiceReply', 0, 'BookingId', 0], replyObj),
722
- reference: R.path(['AddServiceReply', 0, 'Ref', 0], replyObj),
723
- linePrice: R.path(['AddServiceReply', 0, 'Services', 0, 'Service', 0, 'LinePrice', 0], replyObj),
724
- lineId: R.path(['AddServiceReply', 0, 'ServiceLineId', 0], replyObj),
770
+ id: R.path(['AddServiceReply', 'BookingId'], replyObj),
771
+ reference: R.path(['AddServiceReply', 'Ref'], replyObj),
772
+ linePrice: R.path(['AddServiceReply', 'Services', 'Service', 'LinePrice'], replyObj),
773
+ lineId: R.path(['AddServiceReply', 'ServiceLineId'], replyObj),
725
774
  },
726
775
  };
727
776
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ti2-tourplan",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
4
4
  "description": "Tourplan's TI2 Plugin",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -27,14 +27,17 @@
27
27
  "testTimeout": 10000
28
28
  },
29
29
  "dependencies": {
30
+ "axios": "^0.27.2",
30
31
  "@graphql-tools/schema": "^8.5.1",
31
- "graphql": "^16.5.0",
32
32
  "bluebird": "^3.7.2",
33
+ "fast-xml-parser": "^4.3.4",
34
+ "graphql": "^16.5.0",
33
35
  "js2xmlparser": "^4.0.2",
34
36
  "jsonwebtoken": "^8.5.1",
35
37
  "moment": "^2.29.1",
36
38
  "ramda": "^0.27.1",
37
39
  "underscore": "^1.13.4",
40
+ "xml-js": "^1.6.11",
38
41
  "xml2js": "^0.4.23"
39
42
  },
40
43
  "devDependencies": {
@@ -39,12 +39,12 @@ const translateTPOption = ({ optionsGroupedBySupplierId, supplierData }) => {
39
39
  productId: supplierData.supplierId,
40
40
  productName: supplierData.supplierName,
41
41
  // Guides, Accommodation, Transfers, Entrance Fees, Meals, Other
42
- // category: R.path([0, 'OptGeneral', 0, 'ButtonName', 0], optionsGroupedBySupplierId),
42
+ // category: R.path([0, 'OptGeneral', 'ButtonName'], optionsGroupedBySupplierId),
43
43
  options: optionsGroupedBySupplierId.map(option => {
44
- const comment = R.path(['OptGeneral', 0, 'Comment', 0], option);
44
+ const comment = R.path(['OptGeneral', 'Comment'], option);
45
45
  const keyData = {
46
- optionId: R.path(['Opt', 0], option),
47
- optionName: `${R.path(['OptGeneral', 0, 'Description', 0], option)}${
46
+ optionId: R.path(['Opt'], option),
47
+ optionName: `${R.path(['OptGeneral', 'Description'], option)}${
48
48
  comment ? `-${comment}` : ''
49
49
  }`,
50
50
  };
@@ -60,44 +60,44 @@ const translateTPOption = ({ optionsGroupedBySupplierId, supplierData }) => {
60
60
  nights is not specified).
61
61
  */
62
62
  const units = (() => {
63
- if (R.path(['OptGeneral', 0, 'SType', 0], option) === 'N') {
63
+ if (R.path(['OptGeneral', 'SType'], option) === 'N') {
64
64
  return [{
65
65
  unitId: 'Adults',
66
66
  unitName: 'Adults',
67
67
  pricing: [],
68
68
  restrictions: {
69
- paxCount: R.path(['OptGeneral', 0, 'MPFCU', 0], option),
70
- minAge: R.path(['OptGeneral', 0, 'Adult_From', 0], option),
71
- maxAge: R.path(['OptGeneral', 0, 'Adult_To', 0], option),
69
+ paxCount: R.path(['OptGeneral', 'MPFCU'], option),
70
+ minAge: R.path(['OptGeneral', 'Adult_From'], option),
71
+ maxAge: R.path(['OptGeneral', 'Adult_To'], option),
72
72
  },
73
- }, R.path(['OptGeneral', 0, 'ChildrenAllowed', 0], option) === 'Y' ? {
73
+ }, R.path(['OptGeneral', 'ChildrenAllowed'], option) === 'Y' ? {
74
74
  unitId: 'Children',
75
75
  unitName: 'Children',
76
76
  pricing: [],
77
77
  restrictions: {
78
- minAge: R.path(['OptGeneral', 0, 'Child_From', 0], option),
79
- maxAge: R.path(['OptGeneral', 0, 'Child_To', 0], option),
78
+ minAge: R.path(['OptGeneral', 'Child_From'], option),
79
+ maxAge: R.path(['OptGeneral', 'Child_To'], option),
80
80
  },
81
- } : null, R.path(['OptGeneral', 0, 'InfantsAllowed', 0], option) === 'Y' ? {
81
+ } : null, R.path(['OptGeneral', 'InfantsAllowed'], option) === 'Y' ? {
82
82
  unitId: 'Infants',
83
83
  unitName: 'Infants',
84
84
  pricing: [],
85
85
  restrictions: {
86
- minAge: R.path(['OptGeneral', 0, 'Infant_From', 0], option),
87
- maxAge: R.path(['OptGeneral', 0, 'Infant_To', 0], option),
86
+ minAge: R.path(['OptGeneral', 'Infant_From'], option),
87
+ maxAge: R.path(['OptGeneral', 'Infant_To'], option),
88
88
  },
89
89
  } : null].filter(Boolean);
90
90
  }
91
- if (R.path(['OptGeneral', 0, 'SType', 0], option) === 'Y') {
91
+ if (R.path(['OptGeneral', 'SType'], option) === 'Y') {
92
92
  return [['SG', 'Single'], ['TW', 'Twin'], ['DB', 'Double'], ['QD', 'Quad']]
93
- .filter(([_, unitName]) => R.path(['OptGeneral', 0, `${unitName}_Avail`, 0], option) === 'Y')
93
+ .filter(([_, unitName]) => R.path(['OptGeneral', `${unitName}_Avail`], option) === 'Y')
94
94
  .map(([unitId, unitName]) => ({
95
95
  unitId,
96
96
  unitName,
97
97
  pricing: [],
98
98
  restrictions: {
99
- paxCount: R.path(['OptGeneral', 0, `${unitName}_Max`, 0], option)
100
- || R.path(['OptGeneral', 0, `${unitName}_Ad_Max`, 0], option),
99
+ paxCount: R.path(['OptGeneral', `${unitName}_Max`], option)
100
+ || R.path(['OptGeneral', `${unitName}_Ad_Max`], option),
101
101
  },
102
102
  }));
103
103
  }