@product7/product7-js 0.3.4 → 0.3.6
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/dist/product7-js.js +159 -241
- package/dist/product7-js.js.map +1 -1
- package/dist/product7-js.min.js +1 -1
- package/dist/product7-js.min.js.map +1 -1
- package/package.json +1 -1
- package/src/api/services/FeedbackService.js +7 -0
- package/src/api/services/MessengerService.js +42 -0
- package/src/api/services/SurveyService.js +6 -0
- package/src/core/APIService.js +58 -211
- package/src/core/BaseAPIService.js +14 -0
- package/src/core/Product7.js +1 -1
- package/src/widgets/MessengerWidget.js +3 -1
package/package.json
CHANGED
|
@@ -21,12 +21,19 @@ export class FeedbackService {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
const contact = this.api.getContactIdentity();
|
|
25
|
+
|
|
24
26
|
const payload = {
|
|
25
27
|
board:
|
|
26
28
|
feedbackData.board_id || feedbackData.board || feedbackData.boardName,
|
|
27
29
|
title: feedbackData.title,
|
|
28
30
|
content: feedbackData.content,
|
|
29
31
|
attachments: feedbackData.attachments || [],
|
|
32
|
+
...(contact?.contactId && {
|
|
33
|
+
contact_id: contact.contactId,
|
|
34
|
+
contact_email: contact.contactEmail,
|
|
35
|
+
contact_name: contact.contactName,
|
|
36
|
+
}),
|
|
30
37
|
};
|
|
31
38
|
|
|
32
39
|
return this.api._handleAuthRetry(async () => {
|
|
@@ -37,6 +37,10 @@ export class MessengerService {
|
|
|
37
37
|
agents_online: true,
|
|
38
38
|
online_count: 2,
|
|
39
39
|
response_time: 'Usually replies within a few minutes',
|
|
40
|
+
available_agents: [
|
|
41
|
+
{ full_name: 'Sarah', picture: '' },
|
|
42
|
+
{ full_name: 'Tom', picture: '' },
|
|
43
|
+
],
|
|
40
44
|
},
|
|
41
45
|
};
|
|
42
46
|
}
|
|
@@ -224,10 +228,48 @@ export class MessengerService {
|
|
|
224
228
|
});
|
|
225
229
|
}
|
|
226
230
|
|
|
231
|
+
async uploadFile(base64Data, filename) {
|
|
232
|
+
await this.api._ensureSession();
|
|
233
|
+
|
|
234
|
+
if (this.api.mock) {
|
|
235
|
+
await delay(300);
|
|
236
|
+
return { status: true, url: `https://mock-cdn.example.com/${filename}` };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return this.api._makeRequest('/widget/messenger/upload', {
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers: {
|
|
242
|
+
'Content-Type': 'application/json',
|
|
243
|
+
Authorization: `Bearer ${this.api.sessionToken}`,
|
|
244
|
+
},
|
|
245
|
+
body: JSON.stringify({ file: base64Data, filename }),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
227
249
|
async identifyContact(data) {
|
|
228
250
|
return this.api.identify(data);
|
|
229
251
|
}
|
|
230
252
|
|
|
253
|
+
async submitRating(conversationId, data) {
|
|
254
|
+
await this.api._ensureSession();
|
|
255
|
+
|
|
256
|
+
if (this.api.mock) {
|
|
257
|
+
return { status: true, data: { rated: true } };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return this.api._makeRequest(
|
|
261
|
+
`/widget/messenger/conversations/${conversationId}/rating`,
|
|
262
|
+
{
|
|
263
|
+
method: 'POST',
|
|
264
|
+
headers: {
|
|
265
|
+
'Content-Type': 'application/json',
|
|
266
|
+
Authorization: `Bearer ${this.api.sessionToken}`,
|
|
267
|
+
},
|
|
268
|
+
body: JSON.stringify(data),
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
231
273
|
async sendTypingIndicator(conversationId, isTyping) {
|
|
232
274
|
await this.api._ensureSession();
|
|
233
275
|
|
|
@@ -78,6 +78,7 @@ export class SurveyService {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
const respondent = this._getRespondentContext(responseData);
|
|
81
|
+
const contact = this.api.getContactIdentity?.() || null;
|
|
81
82
|
|
|
82
83
|
const payload = {
|
|
83
84
|
rating: responseData.rating,
|
|
@@ -87,6 +88,11 @@ export class SurveyService {
|
|
|
87
88
|
respondent_id: respondent.respondent_id,
|
|
88
89
|
}),
|
|
89
90
|
...(respondent.email && { email: respondent.email }),
|
|
91
|
+
...(contact?.contactId && {
|
|
92
|
+
contact_id: contact.contactId,
|
|
93
|
+
contact_email: contact.contactEmail,
|
|
94
|
+
contact_name: contact.contactName,
|
|
95
|
+
}),
|
|
90
96
|
};
|
|
91
97
|
|
|
92
98
|
return this.api._handleAuthRetry(async () => {
|
package/src/core/APIService.js
CHANGED
|
@@ -37,29 +37,7 @@ export class APIService extends BaseAPIService {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
async checkAgentsOnline() {
|
|
40
|
-
|
|
41
|
-
await this.init();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (this.mock) {
|
|
45
|
-
return {
|
|
46
|
-
status: true,
|
|
47
|
-
data: {
|
|
48
|
-
agents_online: true,
|
|
49
|
-
online_count: 2,
|
|
50
|
-
response_time: 'Usually replies within a few minutes',
|
|
51
|
-
available_agents: [
|
|
52
|
-
{ full_name: 'Sarah', picture: '' },
|
|
53
|
-
{ full_name: 'Tom', picture: '' },
|
|
54
|
-
],
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return this._makeRequest('/widget/messenger/agents/online', {
|
|
60
|
-
method: 'GET',
|
|
61
|
-
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
62
|
-
});
|
|
40
|
+
return this.messenger.checkAgentsOnline();
|
|
63
41
|
}
|
|
64
42
|
|
|
65
43
|
async getConversations(options) {
|
|
@@ -75,137 +53,15 @@ export class APIService extends BaseAPIService {
|
|
|
75
53
|
}
|
|
76
54
|
|
|
77
55
|
async startConversation(data) {
|
|
78
|
-
|
|
79
|
-
console.log(
|
|
80
|
-
'[APIService] startConversation: session invalid, calling init...'
|
|
81
|
-
);
|
|
82
|
-
try {
|
|
83
|
-
await this.init();
|
|
84
|
-
console.log(
|
|
85
|
-
'[APIService] startConversation: init result, token:',
|
|
86
|
-
this.sessionToken ? 'set' : 'null'
|
|
87
|
-
);
|
|
88
|
-
} catch (initError) {
|
|
89
|
-
console.error(
|
|
90
|
-
'[APIService] startConversation: init failed:',
|
|
91
|
-
initError.message
|
|
92
|
-
);
|
|
93
|
-
throw initError;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (!this.sessionToken) {
|
|
98
|
-
console.error(
|
|
99
|
-
'[APIService] startConversation: no session token after init'
|
|
100
|
-
);
|
|
101
|
-
throw new APIError(401, 'No valid session token available');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
console.log(
|
|
105
|
-
'[APIService] startConversation: sending to',
|
|
106
|
-
`${this.baseURL}/widget/messenger/conversations`,
|
|
107
|
-
'mock:',
|
|
108
|
-
this.mock
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
if (this.mock) {
|
|
112
|
-
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
113
|
-
const newConv = {
|
|
114
|
-
id: 'conv_' + Date.now(),
|
|
115
|
-
subject: data.subject || 'New conversation',
|
|
116
|
-
status: 'open',
|
|
117
|
-
last_message_at: new Date().toISOString(),
|
|
118
|
-
created_at: new Date().toISOString(),
|
|
119
|
-
messages: [
|
|
120
|
-
{
|
|
121
|
-
id: 'msg_' + Date.now(),
|
|
122
|
-
content: data.message,
|
|
123
|
-
sender_type: 'customer',
|
|
124
|
-
created_at: new Date().toISOString(),
|
|
125
|
-
},
|
|
126
|
-
],
|
|
127
|
-
};
|
|
128
|
-
MOCK_CONVERSATIONS.unshift(newConv);
|
|
129
|
-
MOCK_MESSAGES[newConv.id] = newConv.messages;
|
|
130
|
-
return { status: true, data: newConv };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return this._makeRequest('/widget/messenger/conversations', {
|
|
134
|
-
method: 'POST',
|
|
135
|
-
headers: {
|
|
136
|
-
'Content-Type': 'application/json',
|
|
137
|
-
Authorization: `Bearer ${this.sessionToken}`,
|
|
138
|
-
},
|
|
139
|
-
body: JSON.stringify({
|
|
140
|
-
message: data.message,
|
|
141
|
-
subject: data.subject || '',
|
|
142
|
-
}),
|
|
143
|
-
});
|
|
56
|
+
return this.messenger.startConversation(data);
|
|
144
57
|
}
|
|
145
58
|
|
|
146
59
|
async sendMessage(conversationId, data) {
|
|
147
|
-
|
|
148
|
-
await this.init();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (this.mock) {
|
|
152
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
153
|
-
const newMessage = {
|
|
154
|
-
id: 'msg_' + Date.now(),
|
|
155
|
-
content: data.content,
|
|
156
|
-
attachments: data.attachments || [],
|
|
157
|
-
sender_type: 'customer',
|
|
158
|
-
created_at: new Date().toISOString(),
|
|
159
|
-
};
|
|
160
|
-
if (!MOCK_MESSAGES[conversationId]) {
|
|
161
|
-
MOCK_MESSAGES[conversationId] = [];
|
|
162
|
-
}
|
|
163
|
-
MOCK_MESSAGES[conversationId].push(newMessage);
|
|
164
|
-
return { status: true, data: newMessage };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const payload = { content: data.content };
|
|
168
|
-
if (data.attachments && data.attachments.length > 0) {
|
|
169
|
-
payload.attachments = data.attachments;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return this._makeRequest(
|
|
173
|
-
`/widget/messenger/conversations/${conversationId}/messages`,
|
|
174
|
-
{
|
|
175
|
-
method: 'POST',
|
|
176
|
-
headers: {
|
|
177
|
-
'Content-Type': 'application/json',
|
|
178
|
-
Authorization: `Bearer ${this.sessionToken}`,
|
|
179
|
-
},
|
|
180
|
-
body: JSON.stringify(payload),
|
|
181
|
-
}
|
|
182
|
-
);
|
|
60
|
+
return this.messenger.sendMessage(conversationId, data);
|
|
183
61
|
}
|
|
184
62
|
|
|
185
|
-
/**
|
|
186
|
-
* Upload a file to CDN via widget endpoint
|
|
187
|
-
* @param {string} base64Data - Base64 encoded file data (with or without data URI prefix)
|
|
188
|
-
* @param {string} filename - Original filename
|
|
189
|
-
* @returns {Promise<Object>} Response with url
|
|
190
|
-
*/
|
|
191
63
|
async uploadFile(base64Data, filename) {
|
|
192
|
-
|
|
193
|
-
await this.init();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (this.mock) {
|
|
197
|
-
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
198
|
-
return { status: true, url: `https://mock-cdn.example.com/${filename}` };
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return this._makeRequest('/widget/messenger/upload', {
|
|
202
|
-
method: 'POST',
|
|
203
|
-
headers: {
|
|
204
|
-
'Content-Type': 'application/json',
|
|
205
|
-
Authorization: `Bearer ${this.sessionToken}`,
|
|
206
|
-
},
|
|
207
|
-
body: JSON.stringify({ file: base64Data, filename }),
|
|
208
|
-
});
|
|
64
|
+
return this.messenger.uploadFile(base64Data, filename);
|
|
209
65
|
}
|
|
210
66
|
|
|
211
67
|
async sendTypingIndicator(conversationId, isTyping) {
|
|
@@ -229,7 +85,7 @@ export class APIService extends BaseAPIService {
|
|
|
229
85
|
|
|
230
86
|
if (this.mock) {
|
|
231
87
|
await new Promise((r) => setTimeout(r, 300));
|
|
232
|
-
|
|
88
|
+
const mockResponse = {
|
|
233
89
|
status: true,
|
|
234
90
|
data: {
|
|
235
91
|
contact_id: 'mock_contact_' + Date.now(),
|
|
@@ -238,9 +94,11 @@ export class APIService extends BaseAPIService {
|
|
|
238
94
|
is_new: true,
|
|
239
95
|
},
|
|
240
96
|
};
|
|
97
|
+
this._storeContactIdentity(mockResponse.data, metadata);
|
|
98
|
+
return mockResponse;
|
|
241
99
|
}
|
|
242
100
|
|
|
243
|
-
|
|
101
|
+
const response = await this._makeRequest('/widget/messenger/identify', {
|
|
244
102
|
method: 'POST',
|
|
245
103
|
headers: {
|
|
246
104
|
'Content-Type': 'application/json',
|
|
@@ -256,82 +114,71 @@ export class APIService extends BaseAPIService {
|
|
|
256
114
|
metadata: metadata.custom_fields || {},
|
|
257
115
|
}),
|
|
258
116
|
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
async identifyContact(data) {
|
|
262
|
-
return this.identify(data);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
async getHelpCollections(options) {
|
|
266
|
-
return this.help.getHelpCollections(options);
|
|
267
|
-
}
|
|
268
117
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
118
|
+
if (response?.status && response?.data) {
|
|
119
|
+
this._storeContactIdentity(response.data, metadata);
|
|
120
|
+
}
|
|
272
121
|
|
|
273
|
-
|
|
274
|
-
return this.changelog.getChangelogs(options);
|
|
122
|
+
return response;
|
|
275
123
|
}
|
|
276
124
|
|
|
277
|
-
|
|
278
|
-
|
|
125
|
+
_storeContactIdentity(data, metadata = {}) {
|
|
126
|
+
this.contactId = data.contact_id || null;
|
|
127
|
+
this.contactEmail = data.email || metadata.email || null;
|
|
128
|
+
this.contactName = data.name || metadata.name || null;
|
|
279
129
|
|
|
280
130
|
try {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
this.sessionToken = sessionData.token;
|
|
295
|
-
this.sessionExpiry = new Date(sessionData.expiry);
|
|
296
|
-
|
|
297
|
-
return this.isSessionValid();
|
|
298
|
-
} catch (error) {
|
|
299
|
-
return false;
|
|
131
|
+
localStorage.setItem(
|
|
132
|
+
'product7_contact',
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
contactId: this.contactId,
|
|
135
|
+
contactEmail: this.contactEmail,
|
|
136
|
+
contactName: this.contactName,
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
/* silent */
|
|
300
141
|
}
|
|
301
142
|
}
|
|
302
143
|
|
|
303
|
-
|
|
304
|
-
|
|
144
|
+
getContactIdentity() {
|
|
145
|
+
if (this.contactId) {
|
|
146
|
+
return {
|
|
147
|
+
contactId: this.contactId,
|
|
148
|
+
contactEmail: this.contactEmail,
|
|
149
|
+
contactName: this.contactName,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
305
152
|
|
|
306
153
|
try {
|
|
307
|
-
const
|
|
154
|
+
const stored = localStorage.getItem('product7_contact');
|
|
155
|
+
if (stored) {
|
|
156
|
+
const parsed = JSON.parse(stored);
|
|
157
|
+
this.contactId = parsed.contactId;
|
|
158
|
+
this.contactEmail = parsed.contactEmail;
|
|
159
|
+
this.contactName = parsed.contactName;
|
|
160
|
+
return parsed;
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
/* silent */
|
|
164
|
+
}
|
|
308
165
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
let responseData = null;
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
312
168
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
responseData.message || responseData.error || errorMessage;
|
|
317
|
-
} catch (e) {
|
|
318
|
-
errorMessage = (await response.text()) || errorMessage;
|
|
319
|
-
}
|
|
169
|
+
async identifyContact(data) {
|
|
170
|
+
return this.identify(data);
|
|
171
|
+
}
|
|
320
172
|
|
|
321
|
-
|
|
322
|
-
|
|
173
|
+
async getHelpCollections(options) {
|
|
174
|
+
return this.help.getHelpCollections(options);
|
|
175
|
+
}
|
|
323
176
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
177
|
+
async searchHelpArticles(query, options) {
|
|
178
|
+
return this.help.searchHelpArticles(query, options);
|
|
179
|
+
}
|
|
328
180
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (error instanceof APIError) {
|
|
332
|
-
throw error;
|
|
333
|
-
}
|
|
334
|
-
throw new APIError(0, error.message, null);
|
|
335
|
-
}
|
|
181
|
+
async getChangelogs(options) {
|
|
182
|
+
return this.changelog.getChangelogs(options);
|
|
336
183
|
}
|
|
337
184
|
}
|
|
@@ -246,8 +246,12 @@ export class BaseAPIService {
|
|
|
246
246
|
this.sessionToken = null;
|
|
247
247
|
this.sessionExpiry = null;
|
|
248
248
|
this.identitySyncedToken = null;
|
|
249
|
+
this.contactId = null;
|
|
250
|
+
this.contactEmail = null;
|
|
251
|
+
this.contactName = null;
|
|
249
252
|
this._removeData('product7_session');
|
|
250
253
|
this._removeData('product7_metadata');
|
|
254
|
+
this._removeData('product7_contact');
|
|
251
255
|
}
|
|
252
256
|
|
|
253
257
|
_storeSession() {
|
|
@@ -271,6 +275,15 @@ export class BaseAPIService {
|
|
|
271
275
|
if (!stored) return false;
|
|
272
276
|
|
|
273
277
|
const sessionData = JSON.parse(stored);
|
|
278
|
+
|
|
279
|
+
// Invalidate mock tokens when not in mock mode (and vice versa)
|
|
280
|
+
const isMockToken =
|
|
281
|
+
sessionData.token && sessionData.token.startsWith('mock_');
|
|
282
|
+
if (isMockToken !== this.mock) {
|
|
283
|
+
localStorage.removeItem('product7_session');
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
274
287
|
if (
|
|
275
288
|
this.workspace &&
|
|
276
289
|
sessionData.workspace &&
|
|
@@ -279,6 +292,7 @@ export class BaseAPIService {
|
|
|
279
292
|
localStorage.removeItem('product7_session');
|
|
280
293
|
return false;
|
|
281
294
|
}
|
|
295
|
+
|
|
282
296
|
this.sessionToken = sessionData.token;
|
|
283
297
|
this.sessionExpiry = new Date(sessionData.expiry);
|
|
284
298
|
|
package/src/core/Product7.js
CHANGED
|
@@ -58,6 +58,7 @@ export class MessengerWidget extends BaseWidget {
|
|
|
58
58
|
initialView: options.initialView || 'home',
|
|
59
59
|
previewData: options.previewData || null,
|
|
60
60
|
featuredContent: options.featuredContent || null,
|
|
61
|
+
feedbackBoardName: options.feedbackBoardName || null,
|
|
61
62
|
feedbackUrl: options.feedbackUrl || null,
|
|
62
63
|
changelogUrl: options.changelogUrl || null,
|
|
63
64
|
helpUrl: options.helpUrl || null,
|
|
@@ -109,7 +110,8 @@ export class MessengerWidget extends BaseWidget {
|
|
|
109
110
|
const widget = this.sdk.createWidget('button', {
|
|
110
111
|
trigger: false,
|
|
111
112
|
displayMode: 'modal',
|
|
112
|
-
boardName:
|
|
113
|
+
boardName:
|
|
114
|
+
this.messengerOptions.feedbackBoardName || this.sdk.config.boardName,
|
|
113
115
|
primaryColor: this.messengerOptions.primaryColor,
|
|
114
116
|
theme: this.messengerOptions.theme,
|
|
115
117
|
});
|