@product7/product7-js 0.3.1 → 0.3.4

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.
@@ -17,6 +17,7 @@ import { PreChatFormView } from './messenger/views/PreChatFormView.js';
17
17
  export class MessengerWidget extends BaseWidget {
18
18
  constructor(options) {
19
19
  super({ ...options, type: 'messenger' });
20
+ this._explicitOptions = options || {};
20
21
  const resolvedTheme = options.theme || 'light';
21
22
  const hasExplicitTextColor = Object.prototype.hasOwnProperty.call(
22
23
  options,
@@ -99,6 +100,10 @@ export class MessengerWidget extends BaseWidget {
99
100
  this._handleConversationClosed = this._handleConversationClosed.bind(this);
100
101
  }
101
102
 
103
+ _hasTrigger() {
104
+ return this.options.trigger === true || this.options.trigger === undefined;
105
+ }
106
+
102
107
  _createInternalFeedbackWidget() {
103
108
  try {
104
109
  const widget = this.sdk.createWidget('button', {
@@ -134,11 +139,13 @@ export class MessengerWidget extends BaseWidget {
134
139
  this._feedbackWidget = this._createInternalFeedbackWidget();
135
140
  }
136
141
 
137
- this.launcher = new MessengerLauncher(this.messengerState, {
138
- position: this.messengerOptions.position,
139
- primaryColor: this.messengerOptions.primaryColor,
140
- });
141
- container.appendChild(this.launcher.render());
142
+ if (this._hasTrigger()) {
143
+ this.launcher = new MessengerLauncher(this.messengerState, {
144
+ position: this.messengerOptions.position,
145
+ primaryColor: this.messengerOptions.primaryColor,
146
+ });
147
+ container.appendChild(this.launcher.render());
148
+ }
142
149
 
143
150
  this.panel = new MessengerPanel(this.messengerState, {
144
151
  position: this.messengerOptions.position,
@@ -273,23 +280,13 @@ export class MessengerWidget extends BaseWidget {
273
280
 
274
281
  async _handleIdentifyContact(contactData) {
275
282
  try {
276
- const response = await this.apiService.identifyContact({
277
- name: contactData.name,
283
+ // Route through sdk.identify() so the SDK-level identity state is updated
284
+ // and applyIdentity() handles the messenger state + WebSocket as a side effect.
285
+ const result = await this.sdk.identify({
278
286
  email: contactData.email,
287
+ name: contactData.name,
279
288
  });
280
-
281
- if (response.status) {
282
- console.log(
283
- '[MessengerWidget] Contact identified:',
284
- response.data.contact_id
285
- );
286
- this.messengerState.setIdentified(true, {
287
- name: contactData.name,
288
- email: contactData.email,
289
- });
290
- }
291
-
292
- return response;
289
+ return result;
293
290
  } catch (error) {
294
291
  console.error('[MessengerWidget] Failed to identify contact:', error);
295
292
  throw error;
@@ -297,8 +294,17 @@ export class MessengerWidget extends BaseWidget {
297
294
  }
298
295
 
299
296
  markAsIdentified(name, email) {
300
- this.messengerState.setIdentified(true, { name, email });
301
- console.log('[MessengerWidget] Marked as identified:', email);
297
+ // Called externally by the app when the user is already known.
298
+ // No API call needed — identity was already established via sdk.identify().
299
+ this.applyIdentity({ name, email });
300
+ }
301
+
302
+ applyIdentity(metadata = {}) {
303
+ this.messengerState.setIdentified(true, metadata);
304
+
305
+ if (this.apiService?.sessionToken && !this.wsService?.isConnected) {
306
+ this._initWebSocket();
307
+ }
302
308
  }
303
309
 
304
310
  async _handleUploadFile(base64Data, filename) {
@@ -809,6 +815,42 @@ export class MessengerWidget extends BaseWidget {
809
815
  }
810
816
  }
811
817
 
818
+ async _fetchAndApplySettings() {
819
+ try {
820
+ const response = await this.apiService.getMessengerSettings();
821
+ if (!response?.status || !response?.data) return;
822
+
823
+ const s = response.data;
824
+
825
+ // Only apply values that were NOT explicitly passed in options
826
+ if (s.team_name && !this._hasExplicitOption('teamName')) {
827
+ this.messengerOptions.teamName = s.team_name;
828
+ this.messengerState.teamName = s.team_name;
829
+ }
830
+ if (s.logo_url && !this._hasExplicitOption('logoUrl')) {
831
+ this.messengerOptions.logoUrl = s.logo_url;
832
+ }
833
+ if (s.greeting_message && !this._hasExplicitOption('greetingMessage')) {
834
+ this.messengerState.greetingMessage = s.greeting_message;
835
+ }
836
+ if (s.response_time && !this._hasExplicitOption('responseTime')) {
837
+ this.messengerState.responseTime = s.response_time;
838
+ }
839
+
840
+ // Notify views to re-render with new values
841
+ this.messengerState._notify('availabilityUpdate', {});
842
+ } catch (e) {
843
+ // non-fatal
844
+ }
845
+ }
846
+
847
+ _hasExplicitOption(key) {
848
+ return Object.prototype.hasOwnProperty.call(
849
+ this._explicitOptions || {},
850
+ key
851
+ );
852
+ }
853
+
812
854
  async checkAgentAvailability() {
813
855
  try {
814
856
  const response = await this.apiService.checkAgentsOnline();
@@ -954,6 +996,9 @@ export class MessengerWidget extends BaseWidget {
954
996
  this._applyPreviewData();
955
997
 
956
998
  if (this.messengerOptions.autoLoadData) {
999
+ // Fetch workspace settings and apply only if not explicitly configured
1000
+ this._fetchAndApplySettings();
1001
+
957
1002
  this.loadInitialData();
958
1003
 
959
1004
  if (this.apiService?.sessionToken) {
@@ -92,6 +92,7 @@ export class SurveyWidget extends BaseWidget {
92
92
 
93
93
  SurveyWidget.removeDanglingElements();
94
94
  this._renderSurvey();
95
+ this.state.isOpen = true;
95
96
  this.surveyState.isVisible = true;
96
97
  this.sdk.eventBus.emit('survey:shown', {
97
98
  widget: this,
@@ -101,6 +102,7 @@ export class SurveyWidget extends BaseWidget {
101
102
  }
102
103
 
103
104
  hide() {
105
+ this.state.isOpen = false;
104
106
  this._closeSurvey();
105
107
  return this;
106
108
  }
@@ -175,6 +177,7 @@ export class SurveyWidget extends BaseWidget {
175
177
  this._attachSurveyEvents();
176
178
 
177
179
  requestAnimationFrame(() => {
180
+ if (!this.surveyElement) return;
178
181
  this.surveyElement.style.opacity = '1';
179
182
  this.surveyElement.style.transform =
180
183
  this.surveyOptions.position === 'center'
@@ -1312,6 +1315,7 @@ export class SurveyWidget extends BaseWidget {
1312
1315
  }
1313
1316
 
1314
1317
  _closeSurvey(resetState = true, immediate = false) {
1318
+ this.state.isOpen = false;
1315
1319
  if (this._escapeHandler) {
1316
1320
  document.removeEventListener('keydown', this._escapeHandler);
1317
1321
  this._escapeHandler = null;
@@ -1374,6 +1378,22 @@ export class SurveyWidget extends BaseWidget {
1374
1378
  this.sdk.eventBus.emit('survey:closed', { widget: this });
1375
1379
  }
1376
1380
 
1381
+ open() {
1382
+ return this.show();
1383
+ }
1384
+
1385
+ close() {
1386
+ return this.hide();
1387
+ }
1388
+
1389
+ toggle() {
1390
+ if (this.surveyState.isVisible) {
1391
+ return this.hide();
1392
+ }
1393
+
1394
+ return this.show();
1395
+ }
1396
+
1377
1397
  destroy() {
1378
1398
  this._closeSurvey(true, true);
1379
1399
  super.destroy();
@@ -21,7 +21,9 @@ export class ChatView {
21
21
 
22
22
  this._unsubscribe = this.state.subscribe((type, data) => {
23
23
  if (type === 'connectionChange') {
24
- const banner = this.element?.querySelector('.messenger-connection-banner');
24
+ const banner = this.element?.querySelector(
25
+ '.messenger-connection-banner'
26
+ );
25
27
  if (banner) {
26
28
  banner.style.display = data.connected ? 'none' : 'flex';
27
29
  }
@@ -71,9 +73,10 @@ export class ChatView {
71
73
  ? this._renderEmptyState(isNewConversation)
72
74
  : this._renderGroupedMessages(messages);
73
75
 
74
- const defaultPlaceholder = this.options.composePlaceholder || 'Write a message...';
76
+ const defaultPlaceholder =
77
+ this.options.composePlaceholder || 'Write a message...';
75
78
  const placeholder = isNewConversation
76
- ? (this.options.composePlaceholder || 'Start typing your message...')
79
+ ? this.options.composePlaceholder || 'Start typing your message...'
77
80
  : isClosed
78
81
  ? 'Conversation closed'
79
82
  : defaultPlaceholder;
@@ -94,7 +97,7 @@ export class ChatView {
94
97
  </div>
95
98
  <div class="messenger-chat-header-info">
96
99
  <span class="messenger-chat-title">${this._escapeHtml(teamName)}</span>
97
- <span class="messenger-chat-subtitle">${isClosed ? 'Conversation resolved' : (this.state.responseTime || 'Typically replies within minutes')}</span>
100
+ <span class="messenger-chat-subtitle">${isClosed ? 'Conversation resolved' : this.state.responseTime || 'Typically replies within minutes'}</span>
98
101
  </div>
99
102
  <div class="messenger-chat-header-actions">
100
103
  <button class="sdk-btn-icon sdk-close-btn messenger-mobile-close-btn" aria-label="Close">
@@ -211,7 +214,9 @@ export class ChatView {
211
214
  const messageClass = isOwn
212
215
  ? 'messenger-message-own'
213
216
  : 'messenger-message-received';
214
- const timeStr = isLastInGroup ? this._formatMessageTime(message.timestamp) : '';
217
+ const timeStr = isLastInGroup
218
+ ? this._formatMessageTime(message.timestamp)
219
+ : '';
215
220
  const attachmentsHtml = this._renderMessageAttachments(message.attachments);
216
221
  const isOptimistic = message.isOptimistic;
217
222
 
@@ -225,9 +230,10 @@ export class ChatView {
225
230
  if (isOwn) {
226
231
  const sentIndicator = isLastInGroup
227
232
  ? `<div class="messenger-message-meta messenger-message-meta-own">
228
- ${isOptimistic
229
- ? `<span class="messenger-message-sent-status">Sending…</span>`
230
- : `<span class="messenger-message-sent-status">Sent</span>`
233
+ ${
234
+ isOptimistic
235
+ ? `<span class="messenger-message-sent-status">Sending…</span>`
236
+ : `<span class="messenger-message-sent-status">Sent</span>`
231
237
  }
232
238
  ${timeStr ? `<span>·</span><span>${timeStr}</span>` : ''}
233
239
  </div>`
@@ -137,7 +137,8 @@ export class HomeView {
137
137
  const title = conversation.title || teamName;
138
138
  const timeAgo = this._formatTimeAgo(conversation.lastMessageTime);
139
139
  const preview = conversation.lastMessage
140
- ? conversation.lastMessage.substring(0, 48) + (conversation.lastMessage.length > 48 ? '...' : '')
140
+ ? conversation.lastMessage.substring(0, 48) +
141
+ (conversation.lastMessage.length > 48 ? '...' : '')
141
142
  : '';
142
143
  const hasUnread = conversation.unread > 0;
143
144
 
@@ -253,7 +254,9 @@ export class HomeView {
253
254
  }
254
255
 
255
256
  _attachEvents() {
256
- const recentCard = this.element.querySelector('.messenger-home-recent-card');
257
+ const recentCard = this.element.querySelector(
258
+ '.messenger-home-recent-card'
259
+ );
257
260
  if (recentCard) {
258
261
  recentCard.addEventListener('click', () => {
259
262
  const convId = recentCard.dataset.conversationId;
package/types/index.d.ts CHANGED
@@ -19,11 +19,13 @@ declare module '@product7/product7-js' {
19
19
  user_id?: string;
20
20
  email?: string;
21
21
  name?: string;
22
+ profile_picture?: string;
22
23
  custom_fields?: Record<string, any>;
23
24
  company?: {
24
25
  id?: string;
25
26
  name?: string;
26
27
  monthly_spend?: number;
28
+ custom_fields?: Record<string, any>;
27
29
  };
28
30
  }
29
31
 
@@ -44,11 +46,15 @@ declare module '@product7/product7-js' {
44
46
  triggerText?: string;
45
47
  label?: string;
46
48
  text?: string;
49
+ headless?: boolean;
47
50
  customStyles?: Record<string, string>;
48
51
  trigger?: boolean | string | Element;
49
52
  }
50
53
 
54
+ export interface FeedbackWidgetOptions extends ButtonWidgetOptions {}
55
+
51
56
  export interface WidgetConfigMap {
57
+ feedback?: Partial<FeedbackWidgetOptions> | Record<string, any>;
52
58
  button?: Partial<ButtonWidgetOptions> | Record<string, any>;
53
59
  survey?: Partial<SurveyWidgetOptions> | Record<string, any>;
54
60
  messenger?: Partial<MessengerWidgetOptions> | Record<string, any>;
@@ -63,6 +69,9 @@ declare module '@product7/product7-js' {
63
69
  destroy(): void;
64
70
  show(): this;
65
71
  hide(): this;
72
+ open(): this;
73
+ close(): this;
74
+ toggle(): this;
66
75
  openPanel(): void;
67
76
  closePanel(): void;
68
77
  openModal(): void;
@@ -73,6 +82,8 @@ declare module '@product7/product7-js' {
73
82
  ): void;
74
83
  }
75
84
 
85
+ export interface FeedbackWidget extends ButtonWidget {}
86
+
76
87
  export class Product7 {
77
88
  constructor(config: FeedbackConfig);
78
89
  static create(config: FeedbackConfig): Product7;
@@ -84,7 +95,21 @@ declare module '@product7/product7-js' {
84
95
  status?: boolean;
85
96
  message?: string | null;
86
97
  configVersion?: number | null;
98
+ identified?: boolean;
99
+ }>;
100
+ identify(metadata: Metadata): Promise<{
101
+ identified: boolean;
102
+ metadata: Metadata | null;
103
+ response: any;
87
104
  }>;
105
+ createFeedbackWidget(options?: FeedbackWidgetOptions): FeedbackWidget;
106
+ createMessengerWidget(options?: MessengerWidgetOptions): MessengerWidget;
107
+ createChangelogWidget(options?: ChangelogWidgetOptions): ChangelogWidget;
108
+ createSurveyWidget(options?: SurveyWidgetOptions): SurveyWidget;
109
+ createWidget(
110
+ type: 'feedback',
111
+ options?: FeedbackWidgetOptions
112
+ ): FeedbackWidget;
88
113
  createWidget(type: 'button', options?: ButtonWidgetOptions): ButtonWidget;
89
114
  createWidget(type: 'survey', options?: SurveyWidgetOptions): SurveyWidget;
90
115
  createWidget(
@@ -182,8 +207,12 @@ declare module '@product7/product7-js' {
182
207
  }
183
208
 
184
209
  export interface SurveyWidget {
210
+ mount(container?: string | HTMLElement): this;
185
211
  show(): this;
186
212
  hide(): this;
213
+ open(): this;
214
+ close(): this;
215
+ toggle(): this;
187
216
  destroy(): void;
188
217
  surveyOptions: SurveyWidgetOptions;
189
218
  surveyState: {
@@ -235,6 +264,7 @@ declare module '@product7/product7-js' {
235
264
  enabled?: boolean;
236
265
  position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
237
266
  theme?: 'light' | 'dark';
267
+ headless?: boolean;
238
268
  showBackdrop?: boolean;
239
269
  triggerText?: string;
240
270
  showBadge?: boolean;
@@ -251,6 +281,9 @@ declare module '@product7/product7-js' {
251
281
  type: 'changelog';
252
282
  mount(container?: string | HTMLElement): this;
253
283
  destroy(): void;
284
+ open(): this;
285
+ close(): this;
286
+ toggle(): this;
254
287
  openModal(): void;
255
288
  closeModal(): void;
256
289
  openSidebar(): void;
@@ -266,6 +299,7 @@ declare module '@product7/product7-js' {
266
299
  enabled?: boolean;
267
300
  position?: 'bottom-right' | 'bottom-left';
268
301
  theme?: 'light' | 'dark';
302
+ headless?: boolean;
269
303
  teamName?: string;
270
304
  teamAvatars?: string[];
271
305
  greetingMessage?: string;