@product7/product7-js 0.3.2 → 0.3.5
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 +53 -29
- package/dist/README.md +53 -29
- package/dist/product7-js.js +515 -141
- 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/MessengerService.js +1 -30
- package/src/core/APIService.js +87 -1
- package/src/core/BaseAPIService.js +128 -11
- package/src/core/Product7.js +183 -19
- package/src/docs/api.md +253 -89
- package/src/docs/example.md +203 -153
- package/src/docs/framework-integrations.md +236 -358
- package/src/docs/installation.md +171 -143
- package/src/index.js +48 -41
- package/src/widgets/MessengerWidget.js +30 -30
- package/src/widgets/SurveyWidget.js +20 -0
- package/src/widgets/messenger/views/ChatView.js +14 -8
- package/src/widgets/messenger/views/HomeView.js +5 -2
- package/types/index.d.ts +34 -0
package/package.json
CHANGED
|
@@ -225,36 +225,7 @@ export class MessengerService {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
async identifyContact(data) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (this.api.mock) {
|
|
231
|
-
await delay(300);
|
|
232
|
-
return {
|
|
233
|
-
status: true,
|
|
234
|
-
data: {
|
|
235
|
-
contact_id: 'mock_contact_' + Date.now(),
|
|
236
|
-
email: data.email,
|
|
237
|
-
name: data.name || '',
|
|
238
|
-
is_new: true,
|
|
239
|
-
},
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return this.api._makeRequest('/widget/messenger/identify', {
|
|
244
|
-
method: 'POST',
|
|
245
|
-
headers: {
|
|
246
|
-
'Content-Type': 'application/json',
|
|
247
|
-
Authorization: `Bearer ${this.api.sessionToken}`,
|
|
248
|
-
},
|
|
249
|
-
body: JSON.stringify({
|
|
250
|
-
email: data.email,
|
|
251
|
-
name: data.name || '',
|
|
252
|
-
phone: data.phone || '',
|
|
253
|
-
company: data.company || '',
|
|
254
|
-
avatar_url: data.avatar_url || '',
|
|
255
|
-
metadata: data.metadata || {},
|
|
256
|
-
}),
|
|
257
|
-
});
|
|
228
|
+
return this.api.identify(data);
|
|
258
229
|
}
|
|
259
230
|
|
|
260
231
|
async sendTypingIndicator(conversationId, isTyping) {
|
package/src/core/APIService.js
CHANGED
|
@@ -224,8 +224,94 @@ export class APIService extends BaseAPIService {
|
|
|
224
224
|
return this.messenger.submitRating(conversationId, data);
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
async identify(metadata) {
|
|
228
|
+
await this._ensureSession();
|
|
229
|
+
|
|
230
|
+
if (this.mock) {
|
|
231
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
232
|
+
const mockResponse = {
|
|
233
|
+
status: true,
|
|
234
|
+
data: {
|
|
235
|
+
contact_id: 'mock_contact_' + Date.now(),
|
|
236
|
+
email: metadata.email,
|
|
237
|
+
name: metadata.name || '',
|
|
238
|
+
is_new: true,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
this._storeContactIdentity(mockResponse.data, metadata);
|
|
242
|
+
return mockResponse;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const response = await this._makeRequest('/widget/messenger/identify', {
|
|
246
|
+
method: 'POST',
|
|
247
|
+
headers: {
|
|
248
|
+
'Content-Type': 'application/json',
|
|
249
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
250
|
+
},
|
|
251
|
+
body: JSON.stringify({
|
|
252
|
+
user_id: metadata.user_id || null,
|
|
253
|
+
email: metadata.email || '',
|
|
254
|
+
name: metadata.name || '',
|
|
255
|
+
phone: metadata.phone || '',
|
|
256
|
+
company: metadata.company || '',
|
|
257
|
+
avatar_url: metadata.avatar_url || '',
|
|
258
|
+
metadata: metadata.custom_fields || {},
|
|
259
|
+
}),
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
if (response?.status && response?.data) {
|
|
263
|
+
this._storeContactIdentity(response.data, metadata);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return response;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
_storeContactIdentity(data, metadata = {}) {
|
|
270
|
+
this.contactId = data.contact_id || null;
|
|
271
|
+
this.contactEmail = data.email || metadata.email || null;
|
|
272
|
+
this.contactName = data.name || metadata.name || null;
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
localStorage.setItem(
|
|
276
|
+
'product7_contact',
|
|
277
|
+
JSON.stringify({
|
|
278
|
+
contactId: this.contactId,
|
|
279
|
+
contactEmail: this.contactEmail,
|
|
280
|
+
contactName: this.contactName,
|
|
281
|
+
})
|
|
282
|
+
);
|
|
283
|
+
} catch (e) {
|
|
284
|
+
/* silent */
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
getContactIdentity() {
|
|
289
|
+
if (this.contactId) {
|
|
290
|
+
return {
|
|
291
|
+
contactId: this.contactId,
|
|
292
|
+
contactEmail: this.contactEmail,
|
|
293
|
+
contactName: this.contactName,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
const stored = localStorage.getItem('product7_contact');
|
|
299
|
+
if (stored) {
|
|
300
|
+
const parsed = JSON.parse(stored);
|
|
301
|
+
this.contactId = parsed.contactId;
|
|
302
|
+
this.contactEmail = parsed.contactEmail;
|
|
303
|
+
this.contactName = parsed.contactName;
|
|
304
|
+
return parsed;
|
|
305
|
+
}
|
|
306
|
+
} catch (e) {
|
|
307
|
+
/* silent */
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
|
|
227
313
|
async identifyContact(data) {
|
|
228
|
-
return this.
|
|
314
|
+
return this.identify(data);
|
|
229
315
|
}
|
|
230
316
|
|
|
231
317
|
async getHelpCollections(options) {
|
|
@@ -6,11 +6,16 @@ export class BaseAPIService {
|
|
|
6
6
|
this.sessionToken = null;
|
|
7
7
|
this.sessionExpiry = null;
|
|
8
8
|
this.metadata = config.metadata || null;
|
|
9
|
+
this.identitySyncedToken = null;
|
|
9
10
|
this.mock = config.mock || false;
|
|
10
11
|
this.env = config.env || 'production';
|
|
11
12
|
this.baseURL = this._getBaseURL(config);
|
|
12
13
|
|
|
13
14
|
this._loadStoredSession();
|
|
15
|
+
this._loadStoredMetadata();
|
|
16
|
+
if (this.isSessionValid() && this.metadata) {
|
|
17
|
+
this.identitySyncedToken = this.sessionToken;
|
|
18
|
+
}
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
_getBaseURL(config) {
|
|
@@ -50,20 +55,17 @@ export class BaseAPIService {
|
|
|
50
55
|
: envConfig.base;
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
async init(metadata =
|
|
54
|
-
if (metadata) {
|
|
55
|
-
this.metadata
|
|
58
|
+
async init(metadata = undefined) {
|
|
59
|
+
if (metadata !== undefined) {
|
|
60
|
+
this.setMetadata(metadata);
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
if (this.isSessionValid()) {
|
|
59
64
|
return { sessionToken: this.sessionToken };
|
|
60
65
|
}
|
|
61
66
|
|
|
62
|
-
if (!this.workspace
|
|
63
|
-
throw new APIError(
|
|
64
|
-
400,
|
|
65
|
-
`Missing ${!this.workspace ? 'workspace' : 'user context'} for initialization`
|
|
66
|
-
);
|
|
67
|
+
if (!this.workspace) {
|
|
68
|
+
throw new APIError(400, 'Missing workspace for initialization');
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
if (this.mock) {
|
|
@@ -76,6 +78,7 @@ export class BaseAPIService {
|
|
|
76
78
|
async _initMockSession() {
|
|
77
79
|
this.sessionToken = 'mock_session_' + Date.now();
|
|
78
80
|
this.sessionExpiry = new Date(Date.now() + 3600 * 1000);
|
|
81
|
+
this.identitySyncedToken = null;
|
|
79
82
|
this._storeSession();
|
|
80
83
|
return {
|
|
81
84
|
sessionToken: this.sessionToken,
|
|
@@ -94,7 +97,6 @@ export class BaseAPIService {
|
|
|
94
97
|
async _initRealSession() {
|
|
95
98
|
const payload = {
|
|
96
99
|
workspace: this.workspace,
|
|
97
|
-
user: this.metadata,
|
|
98
100
|
};
|
|
99
101
|
|
|
100
102
|
try {
|
|
@@ -107,6 +109,7 @@ export class BaseAPIService {
|
|
|
107
109
|
|
|
108
110
|
this.sessionToken = initData.sessionToken;
|
|
109
111
|
this.sessionExpiry = new Date(Date.now() + initData.expiresIn * 1000);
|
|
112
|
+
this.identitySyncedToken = null;
|
|
110
113
|
this._storeSession();
|
|
111
114
|
|
|
112
115
|
return {
|
|
@@ -173,12 +176,52 @@ export class BaseAPIService {
|
|
|
173
176
|
this.sessionToken = null;
|
|
174
177
|
this.sessionExpiry = null;
|
|
175
178
|
await this.init();
|
|
179
|
+
await this._restoreIdentity();
|
|
176
180
|
return await method.apply(this, args);
|
|
177
181
|
}
|
|
178
182
|
throw error;
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
185
|
|
|
186
|
+
async identify(metadata = this.metadata) {
|
|
187
|
+
if (metadata !== undefined) {
|
|
188
|
+
this.setMetadata(metadata);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!this.metadata) {
|
|
192
|
+
throw new APIError(400, 'Missing user context for identify');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await this._ensureSession();
|
|
196
|
+
|
|
197
|
+
const payload = this._buildIdentifyPayload(this.metadata);
|
|
198
|
+
|
|
199
|
+
if (this.mock) {
|
|
200
|
+
this.identitySyncedToken = this.sessionToken;
|
|
201
|
+
return {
|
|
202
|
+
status: true,
|
|
203
|
+
identified: true,
|
|
204
|
+
data: {
|
|
205
|
+
user_id: payload.user_id || null,
|
|
206
|
+
email: payload.email || null,
|
|
207
|
+
name: payload.name || null,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const response = await this._makeRequest('/widget/identify', {
|
|
213
|
+
method: 'POST',
|
|
214
|
+
body: JSON.stringify(payload),
|
|
215
|
+
headers: {
|
|
216
|
+
'Content-Type': 'application/json',
|
|
217
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
this.identitySyncedToken = this.sessionToken;
|
|
222
|
+
return response;
|
|
223
|
+
}
|
|
224
|
+
|
|
182
225
|
isSessionValid() {
|
|
183
226
|
return (
|
|
184
227
|
this.sessionToken && this.sessionExpiry && new Date() < this.sessionExpiry
|
|
@@ -186,8 +229,13 @@ export class BaseAPIService {
|
|
|
186
229
|
}
|
|
187
230
|
|
|
188
231
|
setMetadata(metadata) {
|
|
189
|
-
this.metadata = metadata;
|
|
190
|
-
this.
|
|
232
|
+
this.metadata = metadata || null;
|
|
233
|
+
this.identitySyncedToken = null;
|
|
234
|
+
if (this.metadata) {
|
|
235
|
+
this._storeData('product7_metadata', this.metadata);
|
|
236
|
+
} else {
|
|
237
|
+
this._removeData('product7_metadata');
|
|
238
|
+
}
|
|
191
239
|
}
|
|
192
240
|
|
|
193
241
|
getMetadata() {
|
|
@@ -197,8 +245,13 @@ export class BaseAPIService {
|
|
|
197
245
|
clearSession() {
|
|
198
246
|
this.sessionToken = null;
|
|
199
247
|
this.sessionExpiry = null;
|
|
248
|
+
this.identitySyncedToken = null;
|
|
249
|
+
this.contactId = null;
|
|
250
|
+
this.contactEmail = null;
|
|
251
|
+
this.contactName = null;
|
|
200
252
|
this._removeData('product7_session');
|
|
201
253
|
this._removeData('product7_metadata');
|
|
254
|
+
this._removeData('product7_contact');
|
|
202
255
|
}
|
|
203
256
|
|
|
204
257
|
_storeSession() {
|
|
@@ -222,6 +275,14 @@ export class BaseAPIService {
|
|
|
222
275
|
if (!stored) return false;
|
|
223
276
|
|
|
224
277
|
const sessionData = JSON.parse(stored);
|
|
278
|
+
if (
|
|
279
|
+
this.workspace &&
|
|
280
|
+
sessionData.workspace &&
|
|
281
|
+
sessionData.workspace !== this.workspace
|
|
282
|
+
) {
|
|
283
|
+
localStorage.removeItem('product7_session');
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
225
286
|
this.sessionToken = sessionData.token;
|
|
226
287
|
this.sessionExpiry = new Date(sessionData.expiry);
|
|
227
288
|
|
|
@@ -231,6 +292,32 @@ export class BaseAPIService {
|
|
|
231
292
|
}
|
|
232
293
|
}
|
|
233
294
|
|
|
295
|
+
_loadStoredMetadata() {
|
|
296
|
+
if (this.metadata || typeof localStorage === 'undefined') return false;
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const session = localStorage.getItem('product7_session');
|
|
300
|
+
if (session) {
|
|
301
|
+
const sessionData = JSON.parse(session);
|
|
302
|
+
if (
|
|
303
|
+
this.workspace &&
|
|
304
|
+
sessionData.workspace &&
|
|
305
|
+
sessionData.workspace !== this.workspace
|
|
306
|
+
) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const stored = localStorage.getItem('product7_metadata');
|
|
312
|
+
if (!stored) return false;
|
|
313
|
+
|
|
314
|
+
this.metadata = JSON.parse(stored);
|
|
315
|
+
return true;
|
|
316
|
+
} catch (error) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
234
321
|
_storeData(key, value) {
|
|
235
322
|
if (typeof localStorage !== 'undefined') {
|
|
236
323
|
localStorage.setItem(key, JSON.stringify(value));
|
|
@@ -293,4 +380,34 @@ export class BaseAPIService {
|
|
|
293
380
|
const queryString = this._buildQueryParams(params);
|
|
294
381
|
return `${endpoint}${queryString ? '?' + queryString : ''}`;
|
|
295
382
|
}
|
|
383
|
+
|
|
384
|
+
_buildIdentifyPayload(metadata = {}) {
|
|
385
|
+
const payload = {
|
|
386
|
+
user_id: metadata.user_id,
|
|
387
|
+
email: metadata.email,
|
|
388
|
+
name: metadata.name,
|
|
389
|
+
avatar:
|
|
390
|
+
metadata.profile_picture || metadata.avatar_url || metadata.avatar,
|
|
391
|
+
attributes: metadata.custom_fields || {},
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
if (metadata.company) {
|
|
395
|
+
payload.company = metadata.company;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return payload;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async _restoreIdentity() {
|
|
402
|
+
if (
|
|
403
|
+
!this.metadata ||
|
|
404
|
+
!this.sessionToken ||
|
|
405
|
+
!this.identitySyncedToken ||
|
|
406
|
+
this.identitySyncedToken === this.sessionToken
|
|
407
|
+
) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
await this.identify(this.metadata);
|
|
412
|
+
}
|
|
296
413
|
}
|
package/src/core/Product7.js
CHANGED
|
@@ -9,6 +9,7 @@ export class Product7 {
|
|
|
9
9
|
constructor(config = {}) {
|
|
10
10
|
this.config = this._validateAndMergeConfig(config);
|
|
11
11
|
this.initialized = false;
|
|
12
|
+
this.identified = false;
|
|
12
13
|
this.widgets = new Map();
|
|
13
14
|
this.eventBus = new EventBus();
|
|
14
15
|
|
|
@@ -53,13 +54,14 @@ export class Product7 {
|
|
|
53
54
|
this._injectStyles();
|
|
54
55
|
|
|
55
56
|
try {
|
|
56
|
-
const initData = await this.apiService.init(
|
|
57
|
+
const initData = await this.apiService.init();
|
|
57
58
|
|
|
58
59
|
if (initData.config) {
|
|
59
60
|
this.config = deepMerge(initData.config, this.config);
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
this.initialized = true;
|
|
64
|
+
const identifyResult = await this._syncConfiguredMetadataAfterInit();
|
|
63
65
|
this.eventBus.emit('sdk:initialized', {
|
|
64
66
|
config: this.config,
|
|
65
67
|
sessionToken: initData.sessionToken,
|
|
@@ -70,6 +72,7 @@ export class Product7 {
|
|
|
70
72
|
config: initData.config || {},
|
|
71
73
|
sessionToken: initData.sessionToken,
|
|
72
74
|
expiresIn: initData.expiresIn,
|
|
75
|
+
identified: Boolean(identifyResult?.identified),
|
|
73
76
|
};
|
|
74
77
|
} catch (error) {
|
|
75
78
|
this.eventBus.emit('sdk:error', { error });
|
|
@@ -84,10 +87,14 @@ export class Product7 {
|
|
|
84
87
|
);
|
|
85
88
|
}
|
|
86
89
|
|
|
90
|
+
const requestedType = type || 'button';
|
|
91
|
+
const normalizedType = this._normalizeWidgetType(requestedType);
|
|
87
92
|
const widgetId = generateId('widget');
|
|
88
|
-
const widgetConfig = this._getWidgetTypeConfig(
|
|
89
|
-
const explicitOptions = this._omitUndefined(
|
|
90
|
-
|
|
93
|
+
const widgetConfig = this._getWidgetTypeConfig(normalizedType);
|
|
94
|
+
const explicitOptions = this._omitUndefined(
|
|
95
|
+
this._normalizeWidgetOptions(normalizedType, options)
|
|
96
|
+
);
|
|
97
|
+
const widgetEnabled = this._isWidgetEnabled(normalizedType, {
|
|
91
98
|
...widgetConfig,
|
|
92
99
|
...explicitOptions,
|
|
93
100
|
});
|
|
@@ -102,15 +109,35 @@ export class Product7 {
|
|
|
102
109
|
};
|
|
103
110
|
|
|
104
111
|
try {
|
|
105
|
-
const widget = WidgetFactory.create(
|
|
112
|
+
const widget = WidgetFactory.create(normalizedType, widgetOptions);
|
|
106
113
|
this.widgets.set(widgetId, widget);
|
|
107
|
-
this.eventBus.emit('widget:created', {
|
|
114
|
+
this.eventBus.emit('widget:created', {
|
|
115
|
+
widget,
|
|
116
|
+
type: requestedType,
|
|
117
|
+
internalType: normalizedType,
|
|
118
|
+
});
|
|
108
119
|
return widget;
|
|
109
120
|
} catch (error) {
|
|
110
121
|
throw new SDKError(`Failed to create widget: ${error.message}`, error);
|
|
111
122
|
}
|
|
112
123
|
}
|
|
113
124
|
|
|
125
|
+
createFeedbackWidget(options = {}) {
|
|
126
|
+
return this.createWidget('feedback', options);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
createMessengerWidget(options = {}) {
|
|
130
|
+
return this.createWidget('messenger', options);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
createChangelogWidget(options = {}) {
|
|
134
|
+
return this.createWidget('changelog', options);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
createSurveyWidget(options = {}) {
|
|
138
|
+
return this.createWidget('survey', options);
|
|
139
|
+
}
|
|
140
|
+
|
|
114
141
|
getWidget(id) {
|
|
115
142
|
return this.widgets.get(id);
|
|
116
143
|
}
|
|
@@ -229,7 +256,7 @@ export class Product7 {
|
|
|
229
256
|
return null;
|
|
230
257
|
}
|
|
231
258
|
|
|
232
|
-
const surveyWidget = this.
|
|
259
|
+
const surveyWidget = this.createSurveyWidget({
|
|
233
260
|
surveyId: normalizedOptions.surveyId,
|
|
234
261
|
surveyType:
|
|
235
262
|
normalizedOptions.surveyType || normalizedOptions.type || 'nps',
|
|
@@ -463,18 +490,34 @@ export class Product7 {
|
|
|
463
490
|
? this.config.widgets
|
|
464
491
|
: {};
|
|
465
492
|
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
493
|
+
const mergedTypeConfig = this._getWidgetTypeAliases(type).reduce(
|
|
494
|
+
(config, alias) => {
|
|
495
|
+
const legacyTypeConfig = this._isPlainObject(this.config?.[alias])
|
|
496
|
+
? this.config[alias]
|
|
497
|
+
: {};
|
|
469
498
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
499
|
+
const namespacedTypeConfig = this._isPlainObject(widgetsConfig?.[alias])
|
|
500
|
+
? widgetsConfig[alias]
|
|
501
|
+
: {};
|
|
473
502
|
|
|
474
|
-
|
|
503
|
+
return deepMerge(
|
|
504
|
+
config,
|
|
505
|
+
deepMerge(legacyTypeConfig, namespacedTypeConfig)
|
|
506
|
+
);
|
|
507
|
+
},
|
|
508
|
+
{}
|
|
509
|
+
);
|
|
475
510
|
return this._toCamelCaseObject(mergedTypeConfig);
|
|
476
511
|
}
|
|
477
512
|
|
|
513
|
+
_getWidgetTypeAliases(type) {
|
|
514
|
+
if (type === 'button') {
|
|
515
|
+
return ['button', 'feedback'];
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return [type];
|
|
519
|
+
}
|
|
520
|
+
|
|
478
521
|
_isWidgetEnabled(type, options = {}) {
|
|
479
522
|
const typeConfig = this._getWidgetTypeConfig(type);
|
|
480
523
|
if (typeConfig.enabled === false) {
|
|
@@ -517,6 +560,31 @@ export class Product7 {
|
|
|
517
560
|
return normalized;
|
|
518
561
|
}
|
|
519
562
|
|
|
563
|
+
_normalizeWidgetType(type) {
|
|
564
|
+
if (type === 'feedback') {
|
|
565
|
+
return 'button';
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return type;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
_normalizeWidgetOptions(type, options = {}) {
|
|
572
|
+
if (!this._isPlainObject(options)) {
|
|
573
|
+
return options;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const normalizedOptions = { ...options };
|
|
577
|
+
if (
|
|
578
|
+
normalizedOptions.headless === true &&
|
|
579
|
+
normalizedOptions.trigger === undefined &&
|
|
580
|
+
type !== 'survey'
|
|
581
|
+
) {
|
|
582
|
+
normalizedOptions.trigger = false;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return normalizedOptions;
|
|
586
|
+
}
|
|
587
|
+
|
|
520
588
|
_omitUndefined(value) {
|
|
521
589
|
if (!this._isPlainObject(value)) {
|
|
522
590
|
return value;
|
|
@@ -561,7 +629,7 @@ export class Product7 {
|
|
|
561
629
|
return null;
|
|
562
630
|
}
|
|
563
631
|
|
|
564
|
-
const changelogWidget = this.
|
|
632
|
+
const changelogWidget = this.createChangelogWidget({
|
|
565
633
|
...defaults,
|
|
566
634
|
...configDefaults,
|
|
567
635
|
...explicitOptions,
|
|
@@ -628,10 +696,15 @@ export class Product7 {
|
|
|
628
696
|
}
|
|
629
697
|
|
|
630
698
|
setMetadata(metadata) {
|
|
699
|
+
if (metadata) {
|
|
700
|
+
this._validateMetadata(metadata);
|
|
701
|
+
}
|
|
702
|
+
|
|
631
703
|
this.config.metadata = metadata;
|
|
632
704
|
if (this.apiService) {
|
|
633
705
|
this.apiService.setMetadata(metadata);
|
|
634
706
|
}
|
|
707
|
+
this.identified = false;
|
|
635
708
|
this.eventBus.emit('metadata:updated', { metadata });
|
|
636
709
|
}
|
|
637
710
|
|
|
@@ -642,11 +715,53 @@ export class Product7 {
|
|
|
642
715
|
);
|
|
643
716
|
}
|
|
644
717
|
|
|
718
|
+
async identify(metadata = this.config.metadata) {
|
|
719
|
+
if (!this.initialized) {
|
|
720
|
+
throw new SDKError(
|
|
721
|
+
'SDK must be initialized before identifying users. Call init() first.'
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (!metadata) {
|
|
726
|
+
throw new SDKError(
|
|
727
|
+
'Identify requires metadata. Provide at least user_id or email.'
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
this._validateMetadata(metadata);
|
|
732
|
+
this.setMetadata(metadata);
|
|
733
|
+
|
|
734
|
+
try {
|
|
735
|
+
const response = await this.apiService.identify(metadata);
|
|
736
|
+
const configPatch = this._extractIdentifyConfig(response);
|
|
737
|
+
if (Object.keys(configPatch).length > 0) {
|
|
738
|
+
this.updateConfig(configPatch);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
this.identified = true;
|
|
742
|
+
this._applyIdentityToWidgets(metadata);
|
|
743
|
+
|
|
744
|
+
const result = {
|
|
745
|
+
identified: true,
|
|
746
|
+
metadata: this.getMetadata(),
|
|
747
|
+
response,
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
this.eventBus.emit('sdk:identified', result);
|
|
751
|
+
return result;
|
|
752
|
+
} catch (error) {
|
|
753
|
+
this.identified = false;
|
|
754
|
+
this.eventBus.emit('sdk:error', { error, phase: 'identify' });
|
|
755
|
+
throw new SDKError(`Failed to identify user: ${error.message}`, error);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
645
759
|
async reinitialize(newMetadata = null) {
|
|
646
760
|
this.apiService.clearSession();
|
|
647
761
|
this.initialized = false;
|
|
762
|
+
this.identified = false;
|
|
648
763
|
|
|
649
|
-
if (newMetadata) {
|
|
764
|
+
if (newMetadata !== null) {
|
|
650
765
|
this.setMetadata(newMetadata);
|
|
651
766
|
}
|
|
652
767
|
|
|
@@ -675,10 +790,11 @@ export class Product7 {
|
|
|
675
790
|
|
|
676
791
|
destroy() {
|
|
677
792
|
this.destroyAllWidgets();
|
|
678
|
-
this.eventBus.
|
|
793
|
+
this.eventBus.emit('sdk:destroyed');
|
|
794
|
+
this.eventBus.clear();
|
|
679
795
|
this.apiService.clearSession();
|
|
680
796
|
this.initialized = false;
|
|
681
|
-
this.
|
|
797
|
+
this.identified = false;
|
|
682
798
|
}
|
|
683
799
|
|
|
684
800
|
hasFeedbackBeenSubmitted(cooldownDays = 30) {
|
|
@@ -746,7 +862,7 @@ export class Product7 {
|
|
|
746
862
|
metadata: null,
|
|
747
863
|
position: 'right',
|
|
748
864
|
theme: 'light',
|
|
749
|
-
boardName: '
|
|
865
|
+
boardName: 'feature-requests',
|
|
750
866
|
autoShow: true,
|
|
751
867
|
debug: false,
|
|
752
868
|
mock: false,
|
|
@@ -797,7 +913,12 @@ export class Product7 {
|
|
|
797
913
|
|
|
798
914
|
_bindMethods() {
|
|
799
915
|
this.createWidget = this.createWidget.bind(this);
|
|
916
|
+
this.createFeedbackWidget = this.createFeedbackWidget.bind(this);
|
|
917
|
+
this.createMessengerWidget = this.createMessengerWidget.bind(this);
|
|
918
|
+
this.createChangelogWidget = this.createChangelogWidget.bind(this);
|
|
919
|
+
this.createSurveyWidget = this.createSurveyWidget.bind(this);
|
|
800
920
|
this.destroyWidget = this.destroyWidget.bind(this);
|
|
921
|
+
this.identify = this.identify.bind(this);
|
|
801
922
|
this.updateConfig = this.updateConfig.bind(this);
|
|
802
923
|
}
|
|
803
924
|
|
|
@@ -833,4 +954,47 @@ export class Product7 {
|
|
|
833
954
|
: undefined,
|
|
834
955
|
};
|
|
835
956
|
}
|
|
957
|
+
|
|
958
|
+
async _syncConfiguredMetadataAfterInit() {
|
|
959
|
+
if (
|
|
960
|
+
!this.config.metadata ||
|
|
961
|
+
this.apiService.identitySyncedToken === this.apiService.sessionToken
|
|
962
|
+
) {
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
try {
|
|
967
|
+
return await this.identify(this.config.metadata);
|
|
968
|
+
} catch (error) {
|
|
969
|
+
if (this.config.debug) {
|
|
970
|
+
console.warn('[Product7] Initial identify failed:', error);
|
|
971
|
+
}
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
_extractIdentifyConfig(response) {
|
|
977
|
+
const payload = this._isPlainObject(response?.data)
|
|
978
|
+
? response.data
|
|
979
|
+
: response || {};
|
|
980
|
+
const configPatch = this._isPlainObject(payload.config)
|
|
981
|
+
? payload.config
|
|
982
|
+
: {};
|
|
983
|
+
|
|
984
|
+
if (payload.last_feedback_at !== undefined) {
|
|
985
|
+
configPatch.last_feedback_at = payload.last_feedback_at;
|
|
986
|
+
} else if (payload.lastFeedbackAt !== undefined) {
|
|
987
|
+
configPatch.last_feedback_at = payload.lastFeedbackAt;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
return this._omitUndefined(configPatch);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
_applyIdentityToWidgets(metadata) {
|
|
994
|
+
for (const widget of this.widgets.values()) {
|
|
995
|
+
if (typeof widget.applyIdentity === 'function') {
|
|
996
|
+
widget.applyIdentity(metadata);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
836
1000
|
}
|