ti2-tourplan 1.0.18 → 1.0.20

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,59 @@ 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
+ // can't use proxy because of the static IP thing, darn
89
+ // if (this.xmlProxyUrl) {
90
+ // // use pyfilematch xmlproxy
91
+ // try {
92
+ // replyObj = R.path(['data'], await axios({
93
+ // method: 'post',
94
+ // url: `${this.xmlProxyUrl}/xmlproxy`,
95
+ // data: {
96
+ // url: endpoint,
97
+ // data,
98
+ // headers: getHeaders({ length: `${data.length}` }),
99
+ // },
100
+ // }));
101
+ // } catch (err) {
102
+ // console.log('error in calling pyfilematch xmlproxy', err);
103
+ // errorStr = `error in calling pyfilematch xmlproxy: ${err}`;
104
+ // }
105
+ // }
106
+ if (!replyObj) {
107
+ // in case of error /xmlproxy, fallback to call tourplan directly
108
+ // and then use pyfilematch xml2json to parse the xml
109
+ const reply = R.path(['data'], await axios({
110
+ method: 'post',
111
+ url: endpoint,
112
+ data,
113
+ headers: getHeaders({ length: data.length }),
114
+ }));
115
+ if (this.xmlProxyUrl) {
116
+ try {
117
+ // using raw axios to avoid logging the large xml request
118
+ ({ data: replyObj } = await axiosRaw({
119
+ method: 'post',
120
+ url: `${this.xmlProxyUrl}/xml2json`,
121
+ data: { xml: reply },
122
+ maxContentLength: Infinity,
123
+ maxBodyLength: Infinity,
124
+ }));
125
+ } catch (err) {
126
+ console.log('error in calling pyfilematch xml2json', R.pathOr('Nada', ['response', 'data', 'error'], err));
127
+ errorStr = `error in calling pyfilematch xml2json: ${R.pathOr('Nada', ['response', 'data', 'error'], err)}`;
128
+ }
129
+ }
130
+ // in case of error from /xml2json, fallback to fast-xml-parser
131
+ if (!replyObj) {
132
+ replyObj = fastParser.parse(reply);
133
+ }
134
+ }
135
+ const requestType = R.keys(model)[0];
136
+ if (!replyObj) throw new Error(`${requestType} failed: ${errorStr || 'no reply object'}`);
137
+ const error = replyObj.error || R.path(['Reply', 'ErrorReply', 'Error'], replyObj);
93
138
  if (error) {
94
- const requestType = R.keys(model)[0];
95
139
  if (error.indexOf('2050 SCN Request denied for TEST connecting from') > -1
96
140
  && requestType === 'OptionInfoRequest'
97
141
  && endpoint.indexOf('actour') > -1
@@ -130,7 +174,7 @@ class Plugin {
130
174
  axios,
131
175
  xmlOptions: hostConnectXmlOptions,
132
176
  });
133
- assert(R.path(['AgentInfoReply', 0, 'Currency'], replyObj));
177
+ assert(R.path(['AgentInfoReply', 'Currency'], replyObj));
134
178
  return true;
135
179
  }
136
180
  const model = {
@@ -142,7 +186,7 @@ class Plugin {
142
186
  const replyObj = await this.callTourplan({
143
187
  model, endpoint, axios, xmlOptions: defaultXmlOptions,
144
188
  });
145
- assert(R.path(['AuthenticationReply', 0], replyObj) === '');
189
+ assert(R.path(['AuthenticationReply'], replyObj) === '');
146
190
  return true;
147
191
  } catch (err) {
148
192
  return false;
@@ -293,7 +337,7 @@ class Plugin {
293
337
  : await this.cache.getOrExec({
294
338
  fnParams: [model],
295
339
  fn: () => this.callTourplan(payload),
296
- ttl: 60 * 60 * 12, // 12 hours
340
+ ttl: 60 * 60 * 24, // 24 hours
297
341
  forceRefresh: false,
298
342
  });
299
343
  let products = [];
@@ -303,8 +347,8 @@ class Plugin {
303
347
  products = R.call(R.compose(
304
348
  R.map(optionsGroupedBySupplierId => {
305
349
  const supplierData = {
306
- supplierId: R.path([0, 'OptGeneral', 0, 'SupplierId', 0], optionsGroupedBySupplierId),
307
- supplierName: R.path([0, 'OptGeneral', 0, 'SupplierName', 0], optionsGroupedBySupplierId),
350
+ supplierId: R.path([0, 'OptGeneral', 'SupplierId'], optionsGroupedBySupplierId),
351
+ supplierName: R.path([0, 'OptGeneral', 'SupplierName'], optionsGroupedBySupplierId),
308
352
  };
309
353
  return translateTPOption({
310
354
  supplierData,
@@ -314,20 +358,26 @@ class Plugin {
314
358
  });
315
359
  }),
316
360
  R.values,
317
- R.groupBy(R.path(['OptGeneral', 0, 'SupplierId', 0])),
361
+ R.groupBy(R.path(['OptGeneral', 'SupplierId'])),
318
362
  productName ? R.filter(o => {
319
363
  const str = `${
320
- R.path(['OptGeneral', 0, 'Description', 0], o)
364
+ R.path(['OptGeneral', 'Description'], o)
321
365
  } ${
322
- R.path(['Opt', 0], o)
366
+ R.path(['Opt'], o)
323
367
  } ${
324
- R.path(['OptGeneral', 0, 'SupplierId', 0], o)
368
+ R.path(['OptGeneral', 'SupplierId'], o)
325
369
  } ${
326
- R.path(['OptGeneral', 0, 'SupplierName', 0], o)
370
+ R.path(['OptGeneral', 'SupplierName'], o)
327
371
  }`;
328
372
  return wildcardMatch(productName, str);
329
373
  }) : R.identity,
330
- R.pathOr([], ['OptionInfoReply', 0, 'Option']),
374
+ root => {
375
+ const options = R.pathOr([], ['OptionInfoReply', 'Option'], root);
376
+ // due to the new parser, single option will be returned as an object
377
+ // instead of an array
378
+ if (Array.isArray(options)) return options;
379
+ return [options];
380
+ },
331
381
  ), replyObj);
332
382
  }
333
383
  const productFields = [{
@@ -715,13 +765,13 @@ class Plugin {
715
765
  });
716
766
  console.log('replyObj', replyObj);
717
767
  return {
718
- message: R.path(['AddServiceReply', 0, 'Status', 0], replyObj)
768
+ message: R.path(['AddServiceReply', 'Status'], replyObj)
719
769
  === 'NO' ? 'Service cannot be added to quote' : '',
720
770
  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),
771
+ id: R.path(['AddServiceReply', 'BookingId'], replyObj),
772
+ reference: R.path(['AddServiceReply', 'Ref'], replyObj),
773
+ linePrice: R.path(['AddServiceReply', 'Services', 'Service', 'LinePrice'], replyObj),
774
+ lineId: R.path(['AddServiceReply', 'ServiceLineId'], replyObj),
725
775
  },
726
776
  };
727
777
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ti2-tourplan",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
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
  }