aumera-on-screen-widget 0.0.14 → 0.0.16

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.
Files changed (2) hide show
  1. package/df-btn.js +372 -60
  2. package/package.json +1 -1
package/df-btn.js CHANGED
@@ -1,30 +1,241 @@
1
- const wrapper = document.querySelector("#df-btn");
2
- const config = {
3
- src: wrapper.getAttribute("src"),
4
- orgId: wrapper.getAttribute("orgId"),
5
- env: wrapper.getAttribute("env"),
6
- width: wrapper.getAttribute("width"),
7
- height: wrapper.getAttribute("height"),
8
- openText: wrapper.getAttribute("openText"),
9
- closeText: wrapper.getAttribute("closeText"),
10
- background: wrapper.getAttribute("background"),
11
- backgroundDark: wrapper.getAttribute("backgroundDark"),
12
- logo: wrapper.getAttribute("logo"),
13
- logoDark: wrapper.getAttribute("logoDark"),
14
- position: wrapper.getAttribute("position") || "right",
15
- animation: wrapper.getAttribute("animation") || "message",
16
- shakeDuration: parseInt(wrapper.getAttribute("shakeDuration")) || 3000,
17
- shakeInterval: parseInt(wrapper.getAttribute("shakeInterval")) || 1000,
18
- shakeAmplitude: parseInt(wrapper.getAttribute("shakeAmplitude")) || 10,
19
- shakeFrequency: parseInt(wrapper.getAttribute("shakeFrequency")) || 1,
20
- showNotification: wrapper.getAttribute("showNotification") === "true",
21
- };
22
-
23
- const origin = config.src.substring(0, config.src.lastIndexOf("/"));
24
-
25
- if (!config.orgId) {
26
- console.warn("Please specify your organization ID in attributes!");
27
- } else {
1
+ (async function () {
2
+ const wrapper = document.querySelector("#df-btn");
3
+
4
+ // Helper function to get GraphQL endpoint based on environment
5
+ function getGraphQLUrl(env) {
6
+ return env === "dev"
7
+ ? "https://admin.dev.aumera.ai/g/"
8
+ : "https://admin.aumera.ai/g/";
9
+ }
10
+
11
+ // Helper function to get language from page
12
+ function getLanguage() {
13
+ // Check HTML lang attribute first
14
+ let lang = document.documentElement.lang || "";
15
+
16
+ // Fallback to browser language
17
+ if (!lang) {
18
+ lang = navigator.language || navigator.languages?.[0] || "en";
19
+ }
20
+
21
+ // Extract language code (e.g., "en" from "en-US")
22
+ lang = lang.split("-")[0].toLowerCase();
23
+
24
+ // Validate against supported languages
25
+ const supportedLangs = ["en", "de", "it", "fr", "es"];
26
+ return supportedLangs.includes(lang) ? lang : "de";
27
+ }
28
+
29
+ // Async function to fetch chatbot settings from GraphQL
30
+ async function fetchChatbotSettings(orgId, env, language) {
31
+ try {
32
+ const graphqlUrl = getGraphQLUrl(env);
33
+ console.log("[OSW Widget] Fetching settings from:", graphqlUrl);
34
+ console.log(
35
+ "[OSW Widget] Request params - orgId:",
36
+ orgId,
37
+ "env:",
38
+ env,
39
+ "language:",
40
+ language
41
+ );
42
+
43
+ const query = `
44
+ query osw {
45
+ oswChatbotSettingsPublic(organizationId: ${orgId}) {
46
+ message
47
+ success
48
+ settings {
49
+ animation
50
+ chatbotMask
51
+ oswColourBackgroundDark
52
+ oswColourBackgroundLight
53
+ oswColourFontDark
54
+ oswColourFontLight
55
+ oswHintMessage
56
+ oswLabel
57
+ position
58
+ showNotification
59
+ }
60
+ }
61
+ }
62
+ `;
63
+
64
+ const response = await fetch(graphqlUrl, {
65
+ method: "POST",
66
+ headers: {
67
+ "Content-Type": "application/json",
68
+ "Accept-Language": language,
69
+ },
70
+ body: JSON.stringify({ query }),
71
+ });
72
+
73
+ console.log(
74
+ "[OSW Widget] Response status:",
75
+ response.status,
76
+ response.statusText
77
+ );
78
+
79
+ if (!response.ok) {
80
+ console.error(
81
+ "[OSW Widget] Failed to fetch - Status:",
82
+ response.status,
83
+ response.statusText
84
+ );
85
+ const errorText = await response
86
+ .text()
87
+ .catch(() => "Unable to read error response");
88
+ console.error("[OSW Widget] Error response:", errorText);
89
+ return null;
90
+ }
91
+
92
+ const result = await response.json();
93
+ console.log("[OSW Widget] GraphQL response:", result);
94
+
95
+ if (result.data?.oswChatbotSettingsPublic?.success) {
96
+ console.log(
97
+ "[OSW Widget] Settings retrieved successfully:",
98
+ result.data.oswChatbotSettingsPublic.settings
99
+ );
100
+ return result.data.oswChatbotSettingsPublic.settings;
101
+ }
102
+
103
+ console.warn(
104
+ "[OSW Widget] GraphQL query succeeded but success=false or missing data"
105
+ );
106
+ return null;
107
+ } catch (error) {
108
+ console.error("[OSW Widget] Exception while fetching settings:", error);
109
+ console.error("[OSW Widget] Error details:", error.message, error.stack);
110
+ return null;
111
+ }
112
+ }
113
+
114
+ // Default configuration with fallback values
115
+ const defaultConfig = {
116
+ env: "prod",
117
+ width: "414px",
118
+ height: "75vh",
119
+ openText: "Chat",
120
+ closeText: "Close",
121
+ background: "#FEFFFF",
122
+ backgroundDark: "#171717",
123
+ fontColor: "#3c4043",
124
+ fontColorDark: "#FFFFFF",
125
+ position: "right",
126
+ animation: "none",
127
+ shakeDuration: 3000,
128
+ shakeInterval: 1000,
129
+ shakeAmplitude: 10,
130
+ shakeFrequency: 1,
131
+ showNotification: false,
132
+ };
133
+
134
+ // Initial config from HTML attributes with fallback to defaults
135
+ const config = {
136
+ src: wrapper.getAttribute("src"),
137
+ orgId: wrapper.getAttribute("orgId"),
138
+ env: wrapper.getAttribute("env") || defaultConfig.env,
139
+ width: wrapper.getAttribute("width") || defaultConfig.width,
140
+ height: wrapper.getAttribute("height") || defaultConfig.height,
141
+ openText: wrapper.getAttribute("openText") || defaultConfig.openText,
142
+ closeText: wrapper.getAttribute("closeText") || defaultConfig.closeText,
143
+ background: wrapper.getAttribute("background") || defaultConfig.background,
144
+ backgroundDark:
145
+ wrapper.getAttribute("backgroundDark") || defaultConfig.backgroundDark,
146
+ fontColor: wrapper.getAttribute("fontColor") || defaultConfig.fontColor,
147
+ fontColorDark:
148
+ wrapper.getAttribute("fontColorDark") || defaultConfig.fontColorDark,
149
+ logo: wrapper.getAttribute("logo"),
150
+ logoDark: wrapper.getAttribute("logoDark"),
151
+ position: wrapper.getAttribute("position") || defaultConfig.position,
152
+ animation: wrapper.getAttribute("animation") || defaultConfig.animation,
153
+ shakeDuration:
154
+ parseInt(wrapper.getAttribute("shakeDuration")) ||
155
+ defaultConfig.shakeDuration,
156
+ shakeInterval:
157
+ parseInt(wrapper.getAttribute("shakeInterval")) ||
158
+ defaultConfig.shakeInterval,
159
+ shakeAmplitude:
160
+ parseInt(wrapper.getAttribute("shakeAmplitude")) ||
161
+ defaultConfig.shakeAmplitude,
162
+ shakeFrequency:
163
+ parseInt(wrapper.getAttribute("shakeFrequency")) ||
164
+ defaultConfig.shakeFrequency,
165
+ showNotification:
166
+ wrapper.getAttribute("showNotification") === "true" ||
167
+ defaultConfig.showNotification,
168
+ };
169
+
170
+ const origin = config.src.substring(0, config.src.lastIndexOf("/"));
171
+
172
+ // Get detected language
173
+ const detectedLang = getLanguage();
174
+ console.log("[OSW Widget] Detected language:", detectedLang);
175
+ console.log("[OSW Widget] Initial config from HTML:", config);
176
+
177
+ // Fetch settings from GraphQL and merge with config
178
+ if (!config.orgId) {
179
+ console.error(
180
+ "[OSW Widget] No organization ID provided! Widget will not be displayed."
181
+ );
182
+ console.error(
183
+ "[OSW Widget] Add orgId attribute to the script tag: <script id='df-btn' orgId='YOUR_ORG_ID' ...>"
184
+ );
185
+ return; // Exit if no orgId
186
+ }
187
+
188
+ const settings = await fetchChatbotSettings(
189
+ config.orgId,
190
+ config.env,
191
+ detectedLang
192
+ );
193
+
194
+ if (!settings) {
195
+ console.warn(
196
+ "[OSW Widget] Failed to fetch chatbot settings. Using fallback configuration."
197
+ );
198
+ console.warn("[OSW Widget] Check:");
199
+ console.warn("[OSW Widget] 1. Network tab for CORS errors");
200
+ console.warn("[OSW Widget] 2. Organization ID is correct");
201
+ console.warn("[OSW Widget] 3. API endpoint is accessible");
202
+ // Continue with default config instead of exiting
203
+ config.hintMessage = "Click to check messages";
204
+ } else {
205
+ console.log("[OSW Widget] Merging GraphQL settings with config...");
206
+
207
+ // Merge GraphQL settings with config (GraphQL takes precedence, but null values fall back to HTML attributes)
208
+ config.animation = settings.animation || config.animation;
209
+ config.openText = settings.oswLabel || config.openText;
210
+ config.position = settings.position || config.position;
211
+ config.background = settings.oswColourBackgroundLight || config.background;
212
+ config.backgroundDark =
213
+ settings.oswColourBackgroundDark || config.backgroundDark;
214
+ config.fontColor = settings.oswColourFontLight || config.fontColor;
215
+ config.fontColorDark = settings.oswColourFontDark || config.fontColorDark;
216
+ config.showNotification =
217
+ settings.showNotification ?? config.showNotification;
218
+ config.hintMessage = settings.oswHintMessage || "Click to check messages";
219
+
220
+ // Use chatbotMask as logo if available
221
+ if (settings.chatbotMask) {
222
+ config.logo = settings.chatbotMask;
223
+ config.logoDark = settings.chatbotMask;
224
+ console.log(
225
+ "[OSW Widget] Using chatbotMask as logo:",
226
+ settings.chatbotMask
227
+ );
228
+ }
229
+ }
230
+
231
+ console.log("[OSW Widget] Final merged config:", config);
232
+ console.log("[OSW Widget] Widget will render with:", {
233
+ animation: config.animation,
234
+ openText: config.openText,
235
+ position: config.position,
236
+ showNotification: config.showNotification,
237
+ logo: config.logo ? "Custom logo" : "Default logo",
238
+ });
28
239
  const style = document.createElement("style");
29
240
  style.innerHTML = `
30
241
  .df-btn {
@@ -53,8 +264,8 @@ if (!config.orgId) {
53
264
  position: absolute;
54
265
  top: -45px;
55
266
  ${config.position === "left" ? "left: 0;" : "right: 0;"}
56
- background: #202124;
57
- color: white;
267
+ background: ${config.background || "#FEFFFF"};
268
+ color: ${config.fontColor || "#3c4043"};
58
269
  padding: 8px 16px;
59
270
  border-radius: 8px;
60
271
  font-size: 13px;
@@ -63,7 +274,10 @@ if (!config.orgId) {
63
274
  transition: all 0.3s ease;
64
275
  white-space: nowrap;
65
276
  box-shadow: 0 2px 6px rgba(0,0,0,0.15);
66
- pointer-events: none;
277
+ pointer-events: auto;
278
+ display: flex;
279
+ align-items: center;
280
+ gap: 8px;
67
281
  }
68
282
 
69
283
  .df-notification:after {
@@ -75,7 +289,7 @@ if (!config.orgId) {
75
289
  height: 0;
76
290
  border-left: 6px solid transparent;
77
291
  border-right: 6px solid transparent;
78
- border-top: 6px solid #202124;
292
+ border-top: 6px solid ${config.background || "#FEFFFF"};
79
293
  }
80
294
 
81
295
  .df-notification.show {
@@ -83,9 +297,29 @@ if (!config.orgId) {
83
297
  transform: translateY(0);
84
298
  }
85
299
 
300
+ .df-notification-close {
301
+ cursor: pointer;
302
+ width: 16px;
303
+ height: 16px;
304
+ display: flex;
305
+ align-items: center;
306
+ justify-content: center;
307
+ flex-shrink: 0;
308
+ opacity: 0.7;
309
+ transition: opacity 0.2s ease;
310
+ }
311
+
312
+ .df-notification-close:hover {
313
+ opacity: 1;
314
+ }
315
+
316
+ .df-notification-text {
317
+ flex: 1;
318
+ }
319
+
86
320
  .df-btn-text {
87
321
  min-width: 56px;
88
- color: #3c4043;
322
+ color: ${config.fontColor || "#3c4043"};
89
323
  display: inline-flex;
90
324
  align-items: center;
91
325
  font-weight: 500;
@@ -106,7 +340,9 @@ if (!config.orgId) {
106
340
  background-position: center;
107
341
  background-repeat: no-repeat;
108
342
  background-size: cover;
109
- background-image: url('${origin + "/assets/chat-agent.jpg"}');
343
+ background-image: url('${
344
+ config.logo || origin + "/assets/chat-agent.jpg"
345
+ }');
110
346
  border-radius: 50%;
111
347
  width: 32px;
112
348
  height: 32px;
@@ -249,16 +485,29 @@ if (!config.orgId) {
249
485
  }
250
486
 
251
487
  .df-btn-text {
252
- color: white
488
+ color: ${config.fontColorDark || "#FFFFFF"}
253
489
  }
254
490
 
255
491
  .df-btn-text:before {
256
- background-image: url('${origin + "/assets/chat-agent.jpg"}')
492
+ background-image: url('${
493
+ config.logoDark ||
494
+ config.logo ||
495
+ origin + "/assets/chat-agent.jpg"
496
+ }')
257
497
  }
258
498
 
259
499
  .df-btn:not(.df-closed) > .df-btn-text:before {
260
500
  background-image: url('${origin}/assets/close_dark.svg')
261
501
  }
502
+
503
+ .df-notification {
504
+ background: ${config.backgroundDark || "#171717"};
505
+ color: ${config.fontColorDark || "#FFFFFF"};
506
+ }
507
+
508
+ .df-notification:after {
509
+ border-top: 6px solid ${config.backgroundDark || "#171717"};
510
+ }
262
511
  }
263
512
 
264
513
  @keyframes shake {
@@ -280,27 +529,6 @@ if (!config.orgId) {
280
529
 
281
530
  document.head.appendChild(style);
282
531
 
283
- // Language detection function
284
- function getLanguage() {
285
- // Check HTML lang attribute first
286
- let lang = document.documentElement.lang || "";
287
-
288
- // Fallback to browser language
289
- if (!lang) {
290
- lang = navigator.language || navigator.languages?.[0] || "en";
291
- }
292
-
293
- // Extract language code (e.g., "en" from "en-US")
294
- lang = lang.split("-")[0].toLowerCase();
295
-
296
- // Validate against supported languages
297
- const supportedLangs = ["en", "de", "it", "fr", "es"];
298
- return supportedLangs.includes(lang) ? lang : "de";
299
- }
300
-
301
- // Get the detected language
302
- const detectedLang = getLanguage();
303
-
304
532
  // Function to get chat URL based on environment
305
533
  function getChatUrl() {
306
534
  const env = config.env;
@@ -316,11 +544,21 @@ if (!config.orgId) {
316
544
  }
317
545
  }
318
546
 
319
- document.write(`
547
+ // Create button HTML
548
+ const buttonHTML = `
320
549
  <button class="df-btn df-closed" onclick="dfToggle()">
321
550
  ${
322
551
  config.showNotification
323
- ? '<div class="df-notification">Click to check messages</div>'
552
+ ? `<div class="df-notification">
553
+ <span class="df-notification-text">${
554
+ config.hintMessage || "Click to check messages"
555
+ }</span>
556
+ <div class="df-notification-close" onclick="dismissNotification(event)">
557
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
558
+ <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
559
+ </svg>
560
+ </div>
561
+ </div>`
324
562
  : ""
325
563
  }
326
564
  <div class="df-btn-header">
@@ -330,13 +568,63 @@ if (!config.orgId) {
330
568
  config.orgId
331
569
  }" allow="microphone *"></iframe>
332
570
  </button>
333
- `);
571
+ `;
572
+
573
+ // Insert button into DOM (works in async context unlike document.write)
574
+ document.body.insertAdjacentHTML("beforeend", buttonHTML);
334
575
 
335
576
  let dfToggled = false;
336
577
  let inactivityTimer = null;
337
578
  let shakeInterval = null;
338
579
 
580
+ // Check if notification was dismissed
581
+ const NOTIFICATION_DISMISSED_KEY = `osw-notification-dismissed-${config.orgId}`;
582
+
583
+ function isNotificationDismissed() {
584
+ try {
585
+ return localStorage.getItem(NOTIFICATION_DISMISSED_KEY) === "true";
586
+ } catch (e) {
587
+ return false;
588
+ }
589
+ }
590
+
591
+ function dismissNotification(event) {
592
+ event.stopPropagation();
593
+ event.preventDefault();
594
+
595
+ const notification = document.querySelector(".df-notification");
596
+ if (notification) {
597
+ notification.classList.remove("show");
598
+ }
599
+
600
+ // Stop any ongoing shake animation
601
+ const button = document.querySelector(".df-btn");
602
+ if (button) {
603
+ button.classList.remove("shake");
604
+ }
605
+
606
+ // Clear timers
607
+ clearInactivityAndShake();
608
+
609
+ // Store dismissed state
610
+ try {
611
+ localStorage.setItem(NOTIFICATION_DISMISSED_KEY, "true");
612
+ } catch (e) {
613
+ console.warn(
614
+ "[OSW Widget] Could not save notification dismissed state:",
615
+ e
616
+ );
617
+ }
618
+ }
619
+
620
+ window.dismissNotification = dismissNotification;
621
+
339
622
  const startInactivityTimer = () => {
623
+ // Don't start timer if notification was dismissed
624
+ if (isNotificationDismissed()) {
625
+ return;
626
+ }
627
+
340
628
  // Only start inactivity timer if button is closed
341
629
  if (!dfToggled) {
342
630
  inactivityTimer = setTimeout(() => {
@@ -518,6 +806,28 @@ if (!config.orgId) {
518
806
  btn.addEventListener("click", () => {
519
807
  // Only toggle if chat is closed (popover mode)
520
808
  if (btn.classList.contains("df-closed")) {
809
+ // Dismiss notification when opening chat
810
+ const notification = document.querySelector(".df-notification");
811
+ if (notification && notification.classList.contains("show")) {
812
+ notification.classList.remove("show");
813
+ }
814
+
815
+ // Stop any ongoing shake animation
816
+ btn.classList.remove("shake");
817
+
818
+ // Clear timers
819
+ clearInactivityAndShake();
820
+
821
+ // Store dismissed state
822
+ try {
823
+ localStorage.setItem(NOTIFICATION_DISMISSED_KEY, "true");
824
+ } catch (e) {
825
+ console.warn(
826
+ "[OSW Widget] Could not save notification dismissed state:",
827
+ e
828
+ );
829
+ }
830
+
521
831
  const isMobile = window.innerWidth <= 768;
522
832
 
523
833
  if (isMobile) {
@@ -587,4 +897,6 @@ if (!config.orgId) {
587
897
  if (!isMobile) {
588
898
  startInactivityTimer();
589
899
  }
590
- }
900
+
901
+ console.log("[OSW Widget] ✓ Widget initialized successfully!");
902
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aumera-on-screen-widget",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "A lightweight, customizable chat widget for websites",
5
5
  "main": "df-btn.js",
6
6
  "scripts": {