@product7/product7-js 0.3.5 → 0.3.7

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@product7/product7-js",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "JavaScript SDK for integrating Product7 feedback widgets into any website",
5
5
  "main": "dist/product7-js.js",
6
6
  "module": "src/index.js",
@@ -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 () => {
@@ -37,29 +37,7 @@ export class APIService extends BaseAPIService {
37
37
  }
38
38
 
39
39
  async checkAgentsOnline() {
40
- if (!this.isSessionValid()) {
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
- if (!this.isSessionValid()) {
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
- if (!this.isSessionValid()) {
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
- if (!this.isSessionValid()) {
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) {
@@ -242,7 +98,7 @@ export class APIService extends BaseAPIService {
242
98
  return mockResponse;
243
99
  }
244
100
 
245
- const response = await this._makeRequest('/widget/messenger/identify', {
101
+ const response = await this._makeRequest('/widget/identify', {
246
102
  method: 'POST',
247
103
  headers: {
248
104
  'Content-Type': 'application/json',
@@ -325,65 +181,4 @@ export class APIService extends BaseAPIService {
325
181
  async getChangelogs(options) {
326
182
  return this.changelog.getChangelogs(options);
327
183
  }
328
-
329
- _loadStoredSession() {
330
- if (typeof localStorage === 'undefined') return false;
331
-
332
- try {
333
- const stored = localStorage.getItem('product7_session');
334
- if (!stored) return false;
335
-
336
- const sessionData = JSON.parse(stored);
337
-
338
- // Invalidate mock tokens when not in mock mode (and vice versa)
339
- const isMockToken =
340
- sessionData.token && sessionData.token.startsWith('mock_');
341
- if (isMockToken !== this.mock) {
342
- localStorage.removeItem('product7_session');
343
- return false;
344
- }
345
-
346
- this.sessionToken = sessionData.token;
347
- this.sessionExpiry = new Date(sessionData.expiry);
348
-
349
- return this.isSessionValid();
350
- } catch (error) {
351
- return false;
352
- }
353
- }
354
-
355
- async _makeRequest(endpoint, options = {}) {
356
- const url = `${this.baseURL}${endpoint}`;
357
-
358
- try {
359
- const response = await fetch(url, options);
360
-
361
- if (!response.ok) {
362
- let errorMessage = `HTTP ${response.status}`;
363
- let responseData = null;
364
-
365
- try {
366
- responseData = await response.json();
367
- errorMessage =
368
- responseData.message || responseData.error || errorMessage;
369
- } catch (e) {
370
- errorMessage = (await response.text()) || errorMessage;
371
- }
372
-
373
- throw new APIError(response.status, errorMessage, responseData);
374
- }
375
-
376
- const contentType = response.headers.get('content-type');
377
- if (contentType && contentType.includes('application/json')) {
378
- return await response.json();
379
- }
380
-
381
- return await response.text();
382
- } catch (error) {
383
- if (error instanceof APIError) {
384
- throw error;
385
- }
386
- throw new APIError(0, error.message, null);
387
- }
388
- }
389
184
  }
@@ -275,6 +275,15 @@ export class BaseAPIService {
275
275
  if (!stored) return false;
276
276
 
277
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
+
278
287
  if (
279
288
  this.workspace &&
280
289
  sessionData.workspace &&
@@ -283,6 +292,7 @@ export class BaseAPIService {
283
292
  localStorage.removeItem('product7_session');
284
293
  return false;
285
294
  }
295
+
286
296
  this.sessionToken = sessionData.token;
287
297
  this.sessionExpiry = new Date(sessionData.expiry);
288
298