@product7/feedback-sdk 1.1.8 → 1.2.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/README.md +456 -0
- package/dist/README.md +456 -0
- package/dist/feedback-sdk.js +5255 -941
- package/dist/feedback-sdk.js.map +1 -1
- package/dist/feedback-sdk.min.js +1 -1
- package/dist/feedback-sdk.min.js.map +1 -1
- package/package.json +1 -1
- package/src/core/APIService.js +272 -0
- package/src/core/FeedbackSDK.js +70 -1
- package/src/index.js +5 -1
- package/src/styles/messengerStyles.js +1657 -0
- package/src/styles/styles.js +96 -1
- package/src/widgets/BaseWidget.js +1 -1
- package/src/widgets/ButtonWidget.js +84 -49
- package/src/widgets/MessengerWidget.js +441 -0
- package/src/widgets/SurveyWidget.js +24 -8
- package/src/widgets/WidgetFactory.js +2 -0
- package/src/widgets/messenger/MessengerState.js +222 -0
- package/src/widgets/messenger/components/MessengerLauncher.js +119 -0
- package/src/widgets/messenger/components/MessengerPanel.js +130 -0
- package/src/widgets/messenger/components/NavigationTabs.js +134 -0
- package/src/widgets/messenger/views/ChangelogView.js +198 -0
- package/src/widgets/messenger/views/ChatView.js +284 -0
- package/src/widgets/messenger/views/ConversationsView.js +223 -0
- package/src/widgets/messenger/views/HelpView.js +191 -0
- package/src/widgets/messenger/views/HomeView.js +224 -0
- package/types/index.d.ts +129 -67
package/package.json
CHANGED
package/src/core/APIService.js
CHANGED
|
@@ -9,6 +9,39 @@ const MOCK_CONFIG = {
|
|
|
9
9
|
displayMode: 'modal',
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// Mock surveys for development
|
|
13
|
+
const MOCK_SURVEYS = [
|
|
14
|
+
{
|
|
15
|
+
id: 'mock_nps_survey',
|
|
16
|
+
type: 'nps',
|
|
17
|
+
title: 'How likely are you to recommend us?',
|
|
18
|
+
description: 'Your feedback helps us improve',
|
|
19
|
+
low_label: 'Not likely',
|
|
20
|
+
high_label: 'Very likely',
|
|
21
|
+
trigger: 'manual',
|
|
22
|
+
status: 'active',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'mock_csat_survey',
|
|
26
|
+
type: 'csat',
|
|
27
|
+
title: 'How satisfied are you?',
|
|
28
|
+
description: 'Rate your experience with our product',
|
|
29
|
+
trigger: 'manual',
|
|
30
|
+
status: 'active',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'mock_ces_survey',
|
|
34
|
+
type: 'ces',
|
|
35
|
+
title: 'How easy was it?',
|
|
36
|
+
description: 'Rate the ease of completing your task',
|
|
37
|
+
low_label: 'Very difficult',
|
|
38
|
+
high_label: 'Very easy',
|
|
39
|
+
trigger: 'manual',
|
|
40
|
+
status: 'active',
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// Environment URLs
|
|
12
45
|
const ENV_URLS = {
|
|
13
46
|
production: {
|
|
14
47
|
base: 'https://api.product7.io/api/v1',
|
|
@@ -158,6 +191,245 @@ export class APIService {
|
|
|
158
191
|
}
|
|
159
192
|
}
|
|
160
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Get active surveys for the current user/context
|
|
196
|
+
* @param {Object} context - Optional context overrides for targeting
|
|
197
|
+
* @param {string} context.url - Override current URL
|
|
198
|
+
* @param {Object} context.userProperties - Additional user properties
|
|
199
|
+
* @returns {Promise<Array>} Array of active surveys matching targeting rules
|
|
200
|
+
*/
|
|
201
|
+
async getActiveSurveys(context = {}) {
|
|
202
|
+
if (!this.isSessionValid()) {
|
|
203
|
+
await this.init();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!this.sessionToken) {
|
|
207
|
+
throw new APIError(401, 'No valid session token available');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Mock mode - return mock surveys
|
|
211
|
+
if (this.mock) {
|
|
212
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
213
|
+
return {
|
|
214
|
+
success: true,
|
|
215
|
+
data: MOCK_SURVEYS,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const queryParams = new URLSearchParams();
|
|
221
|
+
|
|
222
|
+
// Auto-detect current URL or use provided override
|
|
223
|
+
const currentUrl =
|
|
224
|
+
context.url ||
|
|
225
|
+
(typeof window !== 'undefined' ? window.location.href : '');
|
|
226
|
+
if (currentUrl) {
|
|
227
|
+
queryParams.append('url', currentUrl);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Auto-detect device info
|
|
231
|
+
const deviceInfo = this._getDeviceInfo();
|
|
232
|
+
if (deviceInfo.device) queryParams.append('device', deviceInfo.device);
|
|
233
|
+
if (deviceInfo.browser) queryParams.append('browser', deviceInfo.browser);
|
|
234
|
+
if (deviceInfo.os) queryParams.append('os', deviceInfo.os);
|
|
235
|
+
|
|
236
|
+
// Add user properties if provided
|
|
237
|
+
if (context.userProperties) {
|
|
238
|
+
queryParams.append(
|
|
239
|
+
'user_properties',
|
|
240
|
+
JSON.stringify(context.userProperties)
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const endpoint = `/widget/surveys/active${queryParams.toString() ? '?' + queryParams.toString() : ''}`;
|
|
245
|
+
|
|
246
|
+
const response = await this._makeRequest(endpoint, {
|
|
247
|
+
method: 'GET',
|
|
248
|
+
headers: {
|
|
249
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return response;
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (error.status === 401) {
|
|
256
|
+
this.sessionToken = null;
|
|
257
|
+
this.sessionExpiry = null;
|
|
258
|
+
await this.init();
|
|
259
|
+
return this.getActiveSurveys(context);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
throw new APIError(
|
|
263
|
+
error.status || 500,
|
|
264
|
+
`Failed to get active surveys: ${error.message}`,
|
|
265
|
+
error.response
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Auto-detect device, browser, and OS info
|
|
272
|
+
* @returns {Object} Device info object
|
|
273
|
+
*/
|
|
274
|
+
_getDeviceInfo() {
|
|
275
|
+
if (typeof navigator === 'undefined') {
|
|
276
|
+
return { device: null, browser: null, os: null };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const ua = navigator.userAgent;
|
|
280
|
+
|
|
281
|
+
// Detect device type
|
|
282
|
+
let device = 'desktop';
|
|
283
|
+
if (/Mobi|Android/i.test(ua)) {
|
|
284
|
+
device = /Tablet|iPad/i.test(ua) ? 'tablet' : 'mobile';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Detect browser
|
|
288
|
+
let browser = 'unknown';
|
|
289
|
+
if (ua.includes('Firefox')) browser = 'firefox';
|
|
290
|
+
else if (ua.includes('Edg')) browser = 'edge';
|
|
291
|
+
else if (ua.includes('Chrome')) browser = 'chrome';
|
|
292
|
+
else if (ua.includes('Safari')) browser = 'safari';
|
|
293
|
+
else if (ua.includes('Opera') || ua.includes('OPR')) browser = 'opera';
|
|
294
|
+
|
|
295
|
+
// Detect OS
|
|
296
|
+
let os = 'unknown';
|
|
297
|
+
if (ua.includes('Windows')) os = 'windows';
|
|
298
|
+
else if (ua.includes('Mac')) os = 'macos';
|
|
299
|
+
else if (ua.includes('Linux')) os = 'linux';
|
|
300
|
+
else if (ua.includes('Android')) os = 'android';
|
|
301
|
+
else if (ua.includes('iPhone') || ua.includes('iPad')) os = 'ios';
|
|
302
|
+
|
|
303
|
+
return { device, browser, os };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Submit a survey response
|
|
308
|
+
* @param {string} surveyId - The survey ID
|
|
309
|
+
* @param {Object} responseData - The survey response data
|
|
310
|
+
* @param {number|string} responseData.rating - The rating/score value
|
|
311
|
+
* @param {string} responseData.feedback - Optional text feedback
|
|
312
|
+
* @param {Object} responseData.answers - For custom surveys, key-value pairs
|
|
313
|
+
* @returns {Promise<Object>} Submission result
|
|
314
|
+
*/
|
|
315
|
+
async submitSurveyResponse(surveyId, responseData) {
|
|
316
|
+
if (!this.isSessionValid()) {
|
|
317
|
+
await this.init();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (!this.sessionToken) {
|
|
321
|
+
throw new APIError(401, 'No valid session token available');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!surveyId) {
|
|
325
|
+
throw new APIError(400, 'Survey ID is required');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Mock mode - simulate success
|
|
329
|
+
if (this.mock) {
|
|
330
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
331
|
+
return {
|
|
332
|
+
success: true,
|
|
333
|
+
data: {
|
|
334
|
+
id: 'mock_response_' + Date.now(),
|
|
335
|
+
survey_id: surveyId,
|
|
336
|
+
...responseData,
|
|
337
|
+
},
|
|
338
|
+
message: 'Survey response submitted successfully!',
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const payload = {
|
|
343
|
+
rating: responseData.rating,
|
|
344
|
+
feedback: responseData.feedback || '',
|
|
345
|
+
answers: responseData.answers || {},
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
const response = await this._makeRequest(
|
|
350
|
+
`/widget/surveys/${surveyId}/responses`,
|
|
351
|
+
{
|
|
352
|
+
method: 'POST',
|
|
353
|
+
body: JSON.stringify(payload),
|
|
354
|
+
headers: {
|
|
355
|
+
'Content-Type': 'application/json',
|
|
356
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
357
|
+
},
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
return response;
|
|
362
|
+
} catch (error) {
|
|
363
|
+
if (error.status === 401) {
|
|
364
|
+
this.sessionToken = null;
|
|
365
|
+
this.sessionExpiry = null;
|
|
366
|
+
await this.init();
|
|
367
|
+
return this.submitSurveyResponse(surveyId, responseData);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
throw new APIError(
|
|
371
|
+
error.status || 500,
|
|
372
|
+
`Failed to submit survey response: ${error.message}`,
|
|
373
|
+
error.response
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Dismiss a survey (mark as seen but not completed)
|
|
380
|
+
* @param {string} surveyId - The survey ID to dismiss
|
|
381
|
+
* @returns {Promise<Object>} Dismissal result
|
|
382
|
+
*/
|
|
383
|
+
async dismissSurvey(surveyId) {
|
|
384
|
+
if (!this.isSessionValid()) {
|
|
385
|
+
await this.init();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (!this.sessionToken) {
|
|
389
|
+
throw new APIError(401, 'No valid session token available');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (!surveyId) {
|
|
393
|
+
throw new APIError(400, 'Survey ID is required');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Mock mode - simulate success
|
|
397
|
+
if (this.mock) {
|
|
398
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
399
|
+
return {
|
|
400
|
+
success: true,
|
|
401
|
+
message: 'Survey dismissed successfully',
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
const response = await this._makeRequest(
|
|
407
|
+
`/widget/surveys/${surveyId}/dismiss`,
|
|
408
|
+
{
|
|
409
|
+
method: 'POST',
|
|
410
|
+
headers: {
|
|
411
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
412
|
+
},
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
return response;
|
|
417
|
+
} catch (error) {
|
|
418
|
+
if (error.status === 401) {
|
|
419
|
+
this.sessionToken = null;
|
|
420
|
+
this.sessionExpiry = null;
|
|
421
|
+
await this.init();
|
|
422
|
+
return this.dismissSurvey(surveyId);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
throw new APIError(
|
|
426
|
+
error.status || 500,
|
|
427
|
+
`Failed to dismiss survey: ${error.message}`,
|
|
428
|
+
error.response
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
161
433
|
isSessionValid() {
|
|
162
434
|
return (
|
|
163
435
|
this.sessionToken && this.sessionExpiry && new Date() < this.sessionExpiry
|
package/src/core/FeedbackSDK.js
CHANGED
|
@@ -84,8 +84,76 @@ export class FeedbackSDK {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
87
|
+
* Fetch active surveys from the backend
|
|
88
|
+
* @param {Object} context - Optional context for targeting
|
|
89
|
+
* @param {string} context.page - Current page/route
|
|
90
|
+
* @param {string} context.event - Event trigger name
|
|
91
|
+
* @returns {Promise<Array>} Array of active survey configurations
|
|
92
|
+
*/
|
|
93
|
+
async getActiveSurveys(context = {}) {
|
|
94
|
+
if (!this.initialized) {
|
|
95
|
+
throw new SDKError(
|
|
96
|
+
'SDK must be initialized before fetching surveys. Call init() first.'
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const result = await this.apiService.getActiveSurveys(context);
|
|
102
|
+
return result.data || [];
|
|
103
|
+
} catch (error) {
|
|
104
|
+
this.eventBus.emit('sdk:error', { error });
|
|
105
|
+
throw new SDKError(
|
|
106
|
+
`Failed to fetch active surveys: ${error.message}`,
|
|
107
|
+
error
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Show a survey by its backend ID
|
|
114
|
+
* Fetches survey configuration from the backend and displays it
|
|
115
|
+
* @param {string} surveyId - The backend survey ID
|
|
116
|
+
* @param {Object} options - Additional display options
|
|
117
|
+
* @param {string} options.position - Position override
|
|
118
|
+
* @param {string} options.theme - Theme override
|
|
119
|
+
* @param {Function} options.onSubmit - Callback when survey is submitted
|
|
120
|
+
* @param {Function} options.onDismiss - Callback when survey is dismissed
|
|
121
|
+
* @returns {Promise<SurveyWidget>} The survey widget instance
|
|
122
|
+
*/
|
|
123
|
+
async showSurveyById(surveyId, options = {}) {
|
|
124
|
+
if (!this.initialized) {
|
|
125
|
+
throw new SDKError(
|
|
126
|
+
'SDK must be initialized before showing surveys. Call init() first.'
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Fetch active surveys to find the one with matching ID
|
|
131
|
+
const surveys = await this.getActiveSurveys();
|
|
132
|
+
const surveyConfig = surveys.find((s) => s.id === surveyId);
|
|
133
|
+
|
|
134
|
+
if (!surveyConfig) {
|
|
135
|
+
throw new SDKError(
|
|
136
|
+
`Survey with ID '${surveyId}' not found or not active`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return this.showSurvey({
|
|
141
|
+
surveyId: surveyConfig.id,
|
|
142
|
+
surveyType: surveyConfig.type,
|
|
143
|
+
title: surveyConfig.title,
|
|
144
|
+
description: surveyConfig.description,
|
|
145
|
+
lowLabel: surveyConfig.low_label,
|
|
146
|
+
highLabel: surveyConfig.high_label,
|
|
147
|
+
customQuestions: surveyConfig.questions,
|
|
148
|
+
...options,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Show a survey widget (local/manual mode)
|
|
154
|
+
* For backend-driven surveys, use showSurveyById() instead
|
|
88
155
|
* @param {Object} options - Survey options
|
|
156
|
+
* @param {string} options.surveyId - Backend survey ID (for API tracking)
|
|
89
157
|
* @param {string} options.surveyType - Type of survey: 'nps', 'csat', 'ces', 'custom'
|
|
90
158
|
* @param {string} options.position - Position: 'bottom-right', 'bottom-left', 'center', 'bottom'
|
|
91
159
|
* @param {string} options.theme - Theme: 'light', 'dark'
|
|
@@ -105,6 +173,7 @@ export class FeedbackSDK {
|
|
|
105
173
|
}
|
|
106
174
|
|
|
107
175
|
const surveyWidget = this.createWidget('survey', {
|
|
176
|
+
surveyId: options.surveyId,
|
|
108
177
|
surveyType: options.surveyType || options.type || 'nps',
|
|
109
178
|
position: options.position || 'bottom-right',
|
|
110
179
|
theme: options.theme || this.config.theme || 'light',
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { APIService } from './core/APIService.js';
|
|
2
2
|
import { EventBus } from './core/EventBus.js';
|
|
3
3
|
import { FeedbackSDK } from './core/FeedbackSDK.js';
|
|
4
|
+
import { MESSENGER_STYLES } from './styles/messengerStyles.js';
|
|
4
5
|
import { CSS_STYLES } from './styles/styles.js';
|
|
5
6
|
import {
|
|
6
7
|
APIError,
|
|
@@ -13,6 +14,7 @@ import * as helpers from './utils/helpers.js';
|
|
|
13
14
|
import { BaseWidget } from './widgets/BaseWidget.js';
|
|
14
15
|
import { ButtonWidget } from './widgets/ButtonWidget.js';
|
|
15
16
|
import { InlineWidget } from './widgets/InlineWidget.js';
|
|
17
|
+
import { MessengerWidget } from './widgets/MessengerWidget.js';
|
|
16
18
|
import { SurveyWidget } from './widgets/SurveyWidget.js';
|
|
17
19
|
import { TabWidget } from './widgets/TabWidget.js';
|
|
18
20
|
import { WidgetFactory } from './widgets/WidgetFactory.js';
|
|
@@ -24,7 +26,7 @@ function injectStyles() {
|
|
|
24
26
|
) {
|
|
25
27
|
const style = document.createElement('style');
|
|
26
28
|
style.id = 'feedback-sdk-styles';
|
|
27
|
-
style.textContent = CSS_STYLES;
|
|
29
|
+
style.textContent = CSS_STYLES + MESSENGER_STYLES;
|
|
28
30
|
document.head.appendChild(style);
|
|
29
31
|
}
|
|
30
32
|
}
|
|
@@ -96,6 +98,7 @@ const FeedbackSDKExport = {
|
|
|
96
98
|
TabWidget,
|
|
97
99
|
InlineWidget,
|
|
98
100
|
SurveyWidget,
|
|
101
|
+
MessengerWidget,
|
|
99
102
|
WidgetFactory,
|
|
100
103
|
EventBus,
|
|
101
104
|
APIService,
|
|
@@ -182,6 +185,7 @@ export {
|
|
|
182
185
|
FeedbackSDK,
|
|
183
186
|
helpers,
|
|
184
187
|
InlineWidget,
|
|
188
|
+
MessengerWidget,
|
|
185
189
|
SDKError,
|
|
186
190
|
SurveyWidget,
|
|
187
191
|
TabWidget,
|