@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 +3 -2
- package/src/constructorio.js +3 -0
- package/src/modules/quizzes.js +240 -0
- package/src/utils/helpers.js +1 -1
- package/src/.DS_Store +0 -0
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructor-io/constructorio-node",
|
|
3
|
-
"version": "4.
|
|
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"
|
package/src/constructorio.js
CHANGED
|
@@ -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;
|
package/src/utils/helpers.js
CHANGED
|
@@ -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 =
|
|
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
|