@constructor-io/constructorio-node 4.0.0 → 4.1.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/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "@constructor-io/constructorio-node",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "Constructor.io Node.js client",
5
5
  "main": "src/constructorio.js",
6
6
  "scripts": {
7
7
  "version": "chmod +x ./scripts/verify-node-version.sh && ./scripts/verify-node-version.sh && npm run docs && git add ./docs/*",
8
8
  "check-lisc": "license-checker --production --onlyAllow 'Apache-2.0;BSD-3-Clause;MIT'",
9
9
  "lint": "eslint 'src/**/*.js' 'spec/**/*.js'",
10
+ "test:parallel": "mkdir -p test && cp -rf src/* test && mocha --parallel ./spec/*",
10
11
  "test": "mkdir -p test && cp -rf src/* test && mocha ./spec/*",
11
12
  "precoverage": "rm -rf ./coverage && rm -rf ./.nyc_output",
12
- "coverage": "nyc --all --reporter=html npm run test",
13
+ "coverage": "nyc --all --reporter=html npm run test:parallel",
13
14
  "postcoverage": "open coverage/index.html && rm -rf test",
14
15
  "docs": "jsdoc --configure ./.jsdoc.json ./README.md --recurse ./src --destination ./docs",
15
16
  "prepare": "husky install"
@@ -8,6 +8,7 @@ const Recommendations = require('./modules/recommendations');
8
8
  const Tracker = require('./modules/tracker');
9
9
  const Catalog = require('./modules/catalog');
10
10
  const Tasks = require('./modules/tasks');
11
+ const Quizzes = require('./modules/quizzes');
11
12
  const { version: packageVersion } = require('../package.json');
12
13
 
13
14
  /**
@@ -30,6 +31,7 @@ class ConstructorIO {
30
31
  * @property {object} tracker - Interface to {@link module:tracker}
31
32
  * @property {object} catalog - Interface to {@link module:catalog}
32
33
  * @property {object} tasks - Interface to {@link module:tasks}
34
+ * @property {object} quizzes - Interface to {@link module:quizzes}
33
35
  * @returns {class}
34
36
  */
35
37
  constructor(options = {}) {
@@ -65,6 +67,7 @@ class ConstructorIO {
65
67
  this.tracker = new Tracker(this.options);
66
68
  this.catalog = new Catalog(this.options);
67
69
  this.tasks = new Tasks(this.options);
70
+ this.quizzes = new Quizzes(this.options);
68
71
  }
69
72
  }
70
73
 
@@ -0,0 +1,240 @@
1
+ /* eslint-disable object-curly-newline, no-underscore-dangle */
2
+ const qs = require('qs');
3
+ const nodeFetch = require('node-fetch').default;
4
+ const helpers = require('../utils/helpers');
5
+
6
+ // Create URL from supplied quizId and parameters
7
+ // eslint-disable-next-line max-params
8
+ function createQuizUrl(quizId, parameters, userParameters, options, path) {
9
+ const {
10
+ apiKey,
11
+ version,
12
+ } = options;
13
+ const {
14
+ sessionId,
15
+ clientId,
16
+ userId,
17
+ segments,
18
+ } = userParameters;
19
+ const serviceUrl = 'https://quizzes.cnstrc.com';
20
+ let queryParams = { c: version };
21
+ let answersParamString = '';
22
+
23
+ queryParams.key = apiKey;
24
+ queryParams.i = clientId;
25
+ queryParams.s = sessionId;
26
+
27
+ // Pull user segments from options
28
+ if (segments && segments.length) {
29
+ queryParams.us = segments;
30
+ }
31
+
32
+ // Pull user id from options
33
+ if (userId) {
34
+ queryParams.ui = userId;
35
+ }
36
+
37
+ // Validate quiz id is provided
38
+ if (!quizId || typeof quizId !== 'string') {
39
+ throw new Error('quizId is a required parameter of type string');
40
+ }
41
+
42
+ if (path === 'finalize' && (typeof parameters.answers !== 'object' || !Array.isArray(parameters.answers) || parameters.answers.length === 0)) {
43
+ throw new Error('answers is a required parameter of type array');
44
+ }
45
+
46
+ if (parameters) {
47
+ const { section, answers, versionId } = parameters;
48
+
49
+ // Pull section from parameters
50
+ if (section) {
51
+ queryParams.section = section;
52
+ }
53
+
54
+ // Pull version_id from parameters
55
+ if (versionId) {
56
+ queryParams.version_id = versionId;
57
+ }
58
+
59
+ // Pull answers from parameters and transform
60
+ if (answers) {
61
+ answers.forEach((ans) => {
62
+ answersParamString += `&${qs.stringify({ a: ans }, { arrayFormat: 'comma' })}`;
63
+ });
64
+ }
65
+ }
66
+
67
+ queryParams._dt = Date.now();
68
+ queryParams = helpers.cleanParams(queryParams);
69
+
70
+ const queryString = qs.stringify(queryParams, { indices: false });
71
+
72
+ return `${serviceUrl}/v1/quizzes/${encodeURIComponent(quizId)}/${encodeURIComponent(path)}/?${queryString}${answersParamString}`;
73
+ }
74
+
75
+ /**
76
+ * Interface to quiz related API calls
77
+ *
78
+ * @module quizzes
79
+ * @inner
80
+ * @returns {object}
81
+ */
82
+ class Quizzes {
83
+ constructor(options) {
84
+ this.options = options || {};
85
+ }
86
+
87
+ /**
88
+ * Retrieve quiz question from API
89
+ *
90
+ * @function getQuizNextQuestion
91
+ * @description Retrieve quiz question from Constructor.io API
92
+ * @param {string} id - The identifier of the quiz
93
+ * @param {string} [parameters] - Additional parameters to refine result set
94
+ * @param {string} [parameters.section] - Product catalog section
95
+ * @param {array} [parameters.answers] - An array for answers in the format [[1,2],[1]]
96
+ * @param {string} [parameters.versionId] - Version identifier for the quiz.
97
+ * @param {object} [userParameters] - Parameters relevant to the user request
98
+ * @param {number} [userParameters.sessionId] - Session ID, utilized to personalize results
99
+ * @param {number} [userParameters.clientId] - Client ID, utilized to personalize results
100
+ * @param {string} [userParameters.userId] - User ID, utilized to personalize results
101
+ * @param {string} [userParameters.segments] - User segments
102
+ * @param {string} [userParameters.userIp] - Origin user IP, from client
103
+ * @param {string} [userParameters.userAgent] - Origin user agent, from client
104
+ * @param {object} [networkParameters] - Parameters relevant to the network request
105
+ * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
106
+ * @returns {Promise}
107
+ * @see https://docs.constructor.io/rest_api/quiz/using_quizzes/#answering-a-quiz
108
+ * @example
109
+ * constructorio.quizzes.getQuizNextQuestion('quizId', {
110
+ * answers: [[1,2],[1]],
111
+ * section: '123',
112
+ * versionId: '123'
113
+ * });
114
+ */
115
+ getQuizNextQuestion(quizId, parameters, userParameters = {}, networkParameters = {}) {
116
+ const headers = {};
117
+ let requestUrl;
118
+ const fetch = (this.options && this.options.fetch) || nodeFetch;
119
+ const controller = new AbortController();
120
+ const { signal } = controller;
121
+
122
+ try {
123
+ requestUrl = createQuizUrl(quizId, parameters, userParameters, this.options, 'next');
124
+ } catch (e) {
125
+ return Promise.reject(e);
126
+ }
127
+
128
+ // Append security token as 'x-cnstrc-token' if available
129
+ if (this.options.securityToken && typeof this.options.securityToken === 'string') {
130
+ headers['x-cnstrc-token'] = this.options.securityToken;
131
+ }
132
+
133
+ // Append user IP as 'X-Forwarded-For' if available
134
+ if (userParameters.userIp && typeof userParameters.userIp === 'string') {
135
+ headers['X-Forwarded-For'] = userParameters.userIp;
136
+ }
137
+
138
+ // Append user agent as 'User-Agent' if available
139
+ if (userParameters.userAgent && typeof userParameters.userAgent === 'string') {
140
+ headers['User-Agent'] = userParameters.userAgent;
141
+ }
142
+
143
+ // Handle network timeout if specified
144
+ helpers.applyNetworkTimeout(this.options, networkParameters, controller);
145
+
146
+ return fetch(requestUrl, { headers, signal })
147
+ .then((response) => {
148
+ if (response.ok) {
149
+ return response.json();
150
+ }
151
+
152
+ return helpers.throwHttpErrorFromResponse(new Error(), response);
153
+ })
154
+ .then((json) => {
155
+ if (json.version_id) {
156
+ return json;
157
+ }
158
+
159
+ throw new Error('getQuizNextQuestion response data is malformed');
160
+ });
161
+ }
162
+
163
+ /**
164
+ * Retrieves filter expression and recommendation URL from given answers
165
+ *
166
+ * @function getQuizResults
167
+ * @description Retrieve quiz recommendation and filter expression from Constructor.io API
168
+ * @param {string} id - The identifier of the quiz
169
+ * @param {string} [parameters] - Additional parameters to refine result set
170
+ * @param {string} [parameters.section] - Product catalog section
171
+ * @param {array} [parameters.answers] - An array of answers in the format [[1,2],[1]]
172
+ * @param {string} [parameters.versionId] - Specific version identifier for the quiz
173
+ * @param {object} [userParameters] - Parameters relevant to the user request
174
+ * @param {number} [userParameters.sessionId] - Session ID, utilized to personalize results
175
+ * @param {number} [userParameters.clientId] - Client ID, utilized to personalize results
176
+ * @param {string} [userParameters.userId] - User ID, utilized to personalize results
177
+ * @param {string} [userParameters.segments] - User segments
178
+ * @param {string} [userParameters.userIp] - Origin user IP, from client
179
+ * @param {string} [userParameters.userAgent] - Origin user agent, from client
180
+ * @param {object} [networkParameters] - Parameters relevant to the network request
181
+ * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
182
+ * @returns {Promise}
183
+ * @see https://docs.constructor.io/rest_api/quiz/using_quizzes/#completing-the-quiz
184
+ * @example
185
+ * constructorio.quizzes.getQuizResults('quizId', {
186
+ * answers: [[1,2],[1]],
187
+ * section: '123',
188
+ * versionId: '123'
189
+ * });
190
+ */
191
+ getQuizResults(quizId, parameters, userParameters = {}, networkParameters = {}) {
192
+ let requestUrl;
193
+ const headers = {};
194
+ const fetch = (this.options && this.options.fetch) || nodeFetch;
195
+ const controller = new AbortController();
196
+ const { signal } = controller;
197
+
198
+ try {
199
+ requestUrl = createQuizUrl(quizId, parameters, userParameters, this.options, 'finalize');
200
+ } catch (e) {
201
+ return Promise.reject(e);
202
+ }
203
+
204
+ // Append security token as 'x-cnstrc-token' if available
205
+ if (this.options.securityToken && typeof this.options.securityToken === 'string') {
206
+ headers['x-cnstrc-token'] = this.options.securityToken;
207
+ }
208
+
209
+ // Append user IP as 'X-Forwarded-For' if available
210
+ if (userParameters.userIp && typeof userParameters.userIp === 'string') {
211
+ headers['X-Forwarded-For'] = userParameters.userIp;
212
+ }
213
+
214
+ // Append user agent as 'User-Agent' if available
215
+ if (userParameters.userAgent && typeof userParameters.userAgent === 'string') {
216
+ headers['User-Agent'] = userParameters.userAgent;
217
+ }
218
+
219
+ // Handle network timeout if specified
220
+ helpers.applyNetworkTimeout(this.options, networkParameters, controller);
221
+
222
+ return fetch(requestUrl, { headers, signal })
223
+ .then((response) => {
224
+ if (response.ok) {
225
+ return response.json();
226
+ }
227
+
228
+ return helpers.throwHttpErrorFromResponse(new Error(), response);
229
+ })
230
+ .then((json) => {
231
+ if (json.version_id) {
232
+ return json;
233
+ }
234
+
235
+ throw new Error('getQuizResults response data is malformed');
236
+ });
237
+ }
238
+ }
239
+
240
+ module.exports = Quizzes;
@@ -47,7 +47,7 @@ const utils = {
47
47
  applyNetworkTimeout: (options = {}, networkParameters = {}, controller = undefined) => {
48
48
  const optionsTimeout = options && options.networkParameters && options.networkParameters.timeout;
49
49
  const networkParametersTimeout = networkParameters && networkParameters.timeout;
50
- const timeout = optionsTimeout || networkParametersTimeout;
50
+ const timeout = networkParametersTimeout || optionsTimeout;
51
51
 
52
52
  if (typeof timeout === 'number') {
53
53
  setTimeout(() => controller.abort(), timeout);
package/src/.DS_Store DELETED
Binary file