releasebird-javascript-sdk 1.0.37 → 1.0.39

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.
@@ -0,0 +1,376 @@
1
+ import {API} from './RbirdUtils';
2
+ import {RbirdSessionManager} from './RbirdSessionManager';
3
+
4
+ export class RbirdBannerManager {
5
+ static instance;
6
+
7
+ constructor() {
8
+ this.banners = [];
9
+ this.displayedBanners = new Set();
10
+ this.apiKey = null;
11
+ }
12
+
13
+ static getInstance() {
14
+ if (!RbirdBannerManager.instance) {
15
+ RbirdBannerManager.instance = new RbirdBannerManager();
16
+ }
17
+ return RbirdBannerManager.instance;
18
+ }
19
+
20
+ init(apiKey) {
21
+ if (typeof window === 'undefined') return;
22
+ this.apiKey = apiKey;
23
+ this.fetchAndDisplayBanners();
24
+ }
25
+
26
+ fetchAndDisplayBanners() {
27
+ const sessionManager = RbirdSessionManager.getInstance();
28
+ const state = sessionManager.getState();
29
+
30
+ const http = new XMLHttpRequest();
31
+ http.open("GET", `${API}/ewidget/banners/active`);
32
+ http.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
33
+ http.setRequestHeader("apiKey", this.apiKey);
34
+
35
+ // Send user identification for filtering dismissed banners
36
+ if (state?.identify?.people) {
37
+ http.setRequestHeader("peopleId", state.identify.people);
38
+ }
39
+ if (sessionManager.anonymousIdentifier) {
40
+ http.setRequestHeader("anonymousId", sessionManager.anonymousIdentifier);
41
+ }
42
+
43
+ const self = this;
44
+ http.onreadystatechange = function () {
45
+ if (http.readyState === 4) {
46
+ if (http.status === 200) {
47
+ try {
48
+ const response = JSON.parse(http.responseText);
49
+ self.banners = response;
50
+ self.renderBanners();
51
+ } catch (exp) {
52
+ console.error('Error parsing banners response:', exp);
53
+ }
54
+ }
55
+ }
56
+ };
57
+ http.send();
58
+ }
59
+
60
+ renderBanners() {
61
+ if (!this.banners || this.banners.length === 0) return;
62
+
63
+ this.banners.forEach(banner => {
64
+ if (banner.type === 'INLINE') {
65
+ this.renderInlineBanner(banner);
66
+ } else if (banner.type === 'FLOATING') {
67
+ this.renderFloatingBanner(banner);
68
+ }
69
+ });
70
+ }
71
+
72
+ renderInlineBanner(banner) {
73
+ // Find all elements with data-rbird-banner attribute matching the banner's placement
74
+ const targetElements = document.querySelectorAll(`[data-rbird-banner="${banner.placement || 'default'}"]`);
75
+
76
+ targetElements.forEach(targetElement => {
77
+ const bannerElement = this.createBannerElement(banner, false);
78
+ targetElement.appendChild(bannerElement);
79
+ this.displayedBanners.add(banner.id);
80
+ this.trackImpression(banner.id);
81
+ });
82
+ }
83
+
84
+ renderFloatingBanner(banner) {
85
+ const bannerElement = this.createBannerElement(banner, true);
86
+ document.body.appendChild(bannerElement);
87
+ this.displayedBanners.add(banner.id);
88
+ this.trackImpression(banner.id);
89
+ }
90
+
91
+ createBannerElement(banner, isFloating) {
92
+ const container = document.createElement('div');
93
+ container.className = isFloating ? 'rbird-banner-floating' : 'rbird-banner-inline';
94
+ container.id = `rbird-banner-${banner.id}`;
95
+ container.setAttribute('data-banner-id', banner.id);
96
+
97
+ // Get user language
98
+ const userLang = navigator.language?.split('-')[0] || 'de';
99
+ const title = banner.title?.[userLang] || banner.title?.['de'] || '';
100
+ const message = banner.message?.[userLang] || banner.message?.['de'] || '';
101
+ const buttonText = banner.buttonText?.[userLang] || banner.buttonText?.['de'] || '';
102
+
103
+ // Create banner HTML structure
104
+ const bannerHTML = `
105
+ <div class="rbird-banner-content" style="${this.getBannerStyles(banner, isFloating)}">
106
+ <button class="rbird-banner-close" data-banner-id="${banner.id}" style="${this.getCloseButtonStyles()}">
107
+ ×
108
+ </button>
109
+ <div class="rbird-banner-body">
110
+ ${title ? `<h3 class="rbird-banner-title" style="${this.getTitleStyles()}">${this.escapeHtml(title)}</h3>` : ''}
111
+ ${message ? `<p class="rbird-banner-message" style="${this.getMessageStyles()}">${this.escapeHtml(message)}</p>` : ''}
112
+ ${banner.showButton && buttonText ? `
113
+ <button class="rbird-banner-cta" data-banner-id="${banner.id}" style="${this.getCtaButtonStyles(banner)}">
114
+ ${this.escapeHtml(buttonText)}
115
+ </button>
116
+ ` : ''}
117
+ </div>
118
+ </div>
119
+ `;
120
+
121
+ container.innerHTML = bannerHTML;
122
+
123
+ // Add position styles for floating banners
124
+ if (isFloating) {
125
+ this.applyFloatingPosition(container, banner.position);
126
+ }
127
+
128
+ // Add event listeners
129
+ const closeButton = container.querySelector('.rbird-banner-close');
130
+ if (closeButton) {
131
+ closeButton.addEventListener('click', (e) => {
132
+ e.preventDefault();
133
+ this.closeBanner(banner.id);
134
+ });
135
+ }
136
+
137
+ const ctaButton = container.querySelector('.rbird-banner-cta');
138
+ if (ctaButton) {
139
+ ctaButton.addEventListener('click', (e) => {
140
+ e.preventDefault();
141
+ this.handleCtaClick(banner);
142
+ });
143
+ }
144
+
145
+ return container;
146
+ }
147
+
148
+ getBannerStyles(banner, isFloating) {
149
+ const baseStyles = `
150
+ position: relative;
151
+ background-color: ${banner.backgroundColor || '#ffffff'};
152
+ color: ${banner.textColor || '#000000'};
153
+ padding: 20px;
154
+ border-radius: 8px;
155
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
156
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
157
+ max-width: ${isFloating ? '400px' : '100%'};
158
+ box-sizing: border-box;
159
+ z-index: 10000;
160
+ `;
161
+
162
+ return baseStyles;
163
+ }
164
+
165
+ getCloseButtonStyles() {
166
+ return `
167
+ position: absolute;
168
+ top: 10px;
169
+ right: 10px;
170
+ background: transparent;
171
+ border: none;
172
+ font-size: 24px;
173
+ line-height: 1;
174
+ cursor: pointer;
175
+ color: inherit;
176
+ opacity: 0.6;
177
+ padding: 0;
178
+ width: 24px;
179
+ height: 24px;
180
+ display: flex;
181
+ align-items: center;
182
+ justify-content: center;
183
+ transition: opacity 0.2s;
184
+ `;
185
+ }
186
+
187
+ getTitleStyles() {
188
+ return `
189
+ margin: 0 0 10px 0;
190
+ font-size: 18px;
191
+ font-weight: 600;
192
+ line-height: 1.4;
193
+ `;
194
+ }
195
+
196
+ getMessageStyles() {
197
+ return `
198
+ margin: 0 0 15px 0;
199
+ font-size: 14px;
200
+ line-height: 1.6;
201
+ `;
202
+ }
203
+
204
+ getCtaButtonStyles(banner) {
205
+ return `
206
+ background-color: ${banner.buttonColor || '#667eea'};
207
+ color: ${banner.buttonTextColor || '#ffffff'};
208
+ border: none;
209
+ padding: 10px 20px;
210
+ border-radius: 6px;
211
+ font-size: 14px;
212
+ font-weight: 600;
213
+ cursor: pointer;
214
+ transition: opacity 0.2s;
215
+ display: inline-block;
216
+ `;
217
+ }
218
+
219
+ applyFloatingPosition(element, position) {
220
+ element.style.position = 'fixed';
221
+ element.style.margin = '20px';
222
+ element.style.animation = 'rbirdBannerSlideIn 0.3s ease-out';
223
+
224
+ switch (position) {
225
+ case 'TOP':
226
+ element.style.top = '0';
227
+ element.style.left = '50%';
228
+ element.style.transform = 'translateX(-50%)';
229
+ break;
230
+ case 'BOTTOM':
231
+ element.style.bottom = '0';
232
+ element.style.left = '50%';
233
+ element.style.transform = 'translateX(-50%)';
234
+ break;
235
+ case 'TOP_LEFT':
236
+ element.style.top = '0';
237
+ element.style.left = '0';
238
+ break;
239
+ case 'TOP_RIGHT':
240
+ element.style.top = '0';
241
+ element.style.right = '0';
242
+ break;
243
+ case 'BOTTOM_LEFT':
244
+ element.style.bottom = '0';
245
+ element.style.left = '0';
246
+ break;
247
+ case 'BOTTOM_RIGHT':
248
+ element.style.bottom = '0';
249
+ element.style.right = '0';
250
+ break;
251
+ default:
252
+ element.style.bottom = '0';
253
+ element.style.right = '0';
254
+ }
255
+ }
256
+
257
+ handleCtaClick(banner) {
258
+ this.trackClick(banner.id);
259
+
260
+ if (banner.buttonAction === 'LINK' && banner.buttonLink) {
261
+ window.open(banner.buttonLink, '_blank');
262
+ } else if (banner.buttonAction === 'OPEN_WIDGET') {
263
+ // Open the Releasebird widget
264
+ if (window.Rbird && window.Rbird.showWidget) {
265
+ window.Rbird.showWidget();
266
+ }
267
+ }
268
+
269
+ // Close banner after CTA click if configured
270
+ if (banner.closeAfterAction) {
271
+ this.closeBanner(banner.id);
272
+ }
273
+ }
274
+
275
+ closeBanner(bannerId) {
276
+ const bannerElement = document.getElementById(`rbird-banner-${bannerId}`);
277
+ if (bannerElement) {
278
+ bannerElement.style.animation = 'rbirdBannerSlideOut 0.3s ease-in';
279
+ setTimeout(() => {
280
+ bannerElement.remove();
281
+ }, 300);
282
+ }
283
+
284
+ // Track dismiss in backend
285
+ this.trackDismiss(bannerId);
286
+ this.displayedBanners.delete(bannerId);
287
+ }
288
+
289
+ trackDismiss(bannerId) {
290
+ this.trackInteraction(bannerId, 'DISMISS');
291
+ }
292
+
293
+ trackImpression(bannerId) {
294
+ this.trackInteraction(bannerId, 'IMPRESSION');
295
+ }
296
+
297
+ trackClick(bannerId) {
298
+ this.trackInteraction(bannerId, 'CLICK');
299
+ }
300
+
301
+ trackInteraction(bannerId, interactionType) {
302
+ const sessionManager = RbirdSessionManager.getInstance();
303
+ const state = sessionManager.getState();
304
+
305
+ const http = new XMLHttpRequest();
306
+ http.open("POST", `${API}/ewidget/banners/interaction`);
307
+ http.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
308
+ http.setRequestHeader("apiKey", this.apiKey);
309
+
310
+ // Send user identification
311
+ if (state?.identify?.people) {
312
+ http.setRequestHeader("peopleId", state.identify.people);
313
+ }
314
+ if (sessionManager.anonymousIdentifier) {
315
+ http.setRequestHeader("anonymousId", sessionManager.anonymousIdentifier);
316
+ }
317
+
318
+ const requestData = {
319
+ bannerId: bannerId,
320
+ type: interactionType
321
+ };
322
+
323
+ http.send(JSON.stringify(requestData));
324
+ }
325
+
326
+ escapeHtml(text) {
327
+ const div = document.createElement('div');
328
+ div.textContent = text;
329
+ return div.innerHTML;
330
+ }
331
+
332
+ // Add CSS animations
333
+ injectStyles() {
334
+ if (typeof window === 'undefined' || document.getElementById('rbird-banner-styles')) return;
335
+
336
+ const style = document.createElement('style');
337
+ style.id = 'rbird-banner-styles';
338
+ style.textContent = `
339
+ @keyframes rbirdBannerSlideIn {
340
+ from {
341
+ opacity: 0;
342
+ transform: translateY(-20px);
343
+ }
344
+ to {
345
+ opacity: 1;
346
+ transform: translateY(0);
347
+ }
348
+ }
349
+
350
+ @keyframes rbirdBannerSlideOut {
351
+ from {
352
+ opacity: 1;
353
+ transform: translateY(0);
354
+ }
355
+ to {
356
+ opacity: 0;
357
+ transform: translateY(-20px);
358
+ }
359
+ }
360
+
361
+ .rbird-banner-close:hover {
362
+ opacity: 1 !important;
363
+ }
364
+
365
+ .rbird-banner-cta:hover {
366
+ opacity: 0.9 !important;
367
+ transform: translateY(-1px);
368
+ }
369
+
370
+ .rbird-banner-inline {
371
+ margin: 20px 0;
372
+ }
373
+ `;
374
+ document.head.appendChild(style);
375
+ }
376
+ }
@@ -84,28 +84,41 @@ export default class RbirdScreenshotManager {
84
84
  let that = this;
85
85
  const loader = this.showLoader();
86
86
 
87
- html2canvas(document.body, {
88
- allowTaint: false,
89
- useCORS: false,
90
- logging: false,
91
- foreignObjectRendering: false
92
- }).then(function (canvas) {
93
- that.hideLoader();
94
-
95
- if (RbirdWebsiteWidget.getInstance().iframe) {
96
- RbirdWebsiteWidget.getInstance().iframe.contentWindow?.postMessage({
97
- type: 'screenshot',
98
- screenshot: canvas.toDataURL("image/jpeg")
99
- }, '*');
87
+ // Hide the loader and marker boundary before taking the screenshot
88
+ setTimeout(() => {
89
+ loader.style.display = 'none';
90
+ if (that.boundary) {
91
+ that.boundary.style.display = 'none';
100
92
  }
101
93
 
102
- that.deactivateMarker();
103
-
104
- //document.body.appendChild(canvas);
105
- }).catch((e) => {
106
- that.hideLoader();
107
- console.error('Screenshot error:', e);
108
- });
94
+ html2canvas(document.body, {
95
+ allowTaint: false,
96
+ useCORS: false,
97
+ logging: false,
98
+ foreignObjectRendering: false
99
+ }).then(function (canvas) {
100
+ that.hideLoader();
101
+
102
+ if (RbirdWebsiteWidget.getInstance().iframe) {
103
+ RbirdWebsiteWidget.getInstance().iframe.contentWindow?.postMessage({
104
+ type: 'screenshot',
105
+ screenshot: canvas.toDataURL("image/jpeg")
106
+ }, '*');
107
+ }
108
+
109
+ that.deactivateMarker();
110
+
111
+ //document.body.appendChild(canvas);
112
+ }).catch((e) => {
113
+ // Restore visibility in case of error
114
+ loader.style.display = 'flex';
115
+ if (that.boundary) {
116
+ that.boundary.style.display = 'block';
117
+ }
118
+ that.hideLoader();
119
+ console.error('Screenshot error:', e);
120
+ });
121
+ }, 100); // Small delay to ensure the loader is rendered before we hide it
109
122
  }
110
123
 
111
124
  }
@@ -30,6 +30,9 @@ export default class RbirdSessionManager {
30
30
  };
31
31
 
32
32
  init(apiKey) {
33
+ if (typeof window === 'undefined') {
34
+ return Promise.resolve(null);
35
+ }
33
36
  this.apiKey = apiKey;
34
37
  this.identify = this.getState()?.identify;
35
38
 
@@ -96,6 +99,7 @@ export default class RbirdSessionManager {
96
99
  }
97
100
 
98
101
  getState() {
102
+ if (typeof window === 'undefined') return null;
99
103
  let state = window.localStorage.getItem('rbird_state');
100
104
  if (state) {
101
105
  return JSON.parse(state);
@@ -110,6 +114,7 @@ export default class RbirdSessionManager {
110
114
  }
111
115
 
112
116
  logout() {
117
+ if (typeof window === 'undefined') return;
113
118
  window.localStorage.removeItem('rbird_state');
114
119
  const i = this.generateRandomString();
115
120
  window.localStorage.setItem('Rbird_I', i);
@@ -117,6 +122,7 @@ export default class RbirdSessionManager {
117
122
  }
118
123
 
119
124
  trackEvent(name, data) {
125
+ if (typeof window === 'undefined') return;
120
126
  if (window.localStorage.getItem('rbird_state')) {
121
127
  let state = JSON.parse(window.localStorage.getItem('rbird_state'));
122
128
 
@@ -151,6 +157,7 @@ export default class RbirdSessionManager {
151
157
  }
152
158
 
153
159
  identifyUser(identify, hash) {
160
+ if (typeof window === 'undefined') return;
154
161
  let state = this.getState();
155
162
  state.identify.properties = identify;
156
163
  state.identify.hash = hash;
@@ -44,12 +44,18 @@ export default class RbirdWebsiteWidget {
44
44
  }
45
45
 
46
46
  isMobileDevice = () => {
47
+ if (typeof window === 'undefined') return false;
47
48
  const mobileMaxWidth = 768; // Definiert die maximale Breite für mobile Geräte, z.B. Tablets und Smartphones
48
49
  return window.innerWidth <= mobileMaxWidth;
49
50
  }
50
51
 
51
52
  renderWebsiteWidget() {
52
53
  return new Promise((resolve, reject) => {
54
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
55
+ resolve();
56
+ return;
57
+ }
58
+
53
59
  // Überprüfen, ob das Widget bereits sichtbar ist
54
60
  if (this.websiteWidgetVisible) {
55
61
  resolve(); // Wenn das Widget bereits sichtbar ist, wird das Promise sofort aufgelöst
@@ -100,6 +106,7 @@ export default class RbirdWebsiteWidget {
100
106
  }
101
107
 
102
108
  openWebsiteWidget() {
109
+ if (typeof window === 'undefined' || typeof document === 'undefined') return;
103
110
  RbirdUtils.addClass(this.widgetContent, "cta__modal--visible");
104
111
  RbirdUtils.addClass(this.backdrop, "cta__modal-dimmer--visible");
105
112
 
@@ -157,6 +164,7 @@ export default class RbirdWebsiteWidget {
157
164
  }
158
165
 
159
166
  countNotifications() {
167
+ if (typeof window === 'undefined') return;
160
168
  if (RbirdSessionManager.getInstance().identify?.people || RbirdSessionManager.getInstance().anonymousIdentifier) {
161
169
  const http = new XMLHttpRequest();
162
170
  http.open("GET", `${API}/ewidget/unread`);
@@ -213,6 +221,7 @@ export default class RbirdWebsiteWidget {
213
221
  }
214
222
 
215
223
  showButton(showButton) {
224
+ if (typeof window === 'undefined') return;
216
225
  if (showButton && !this.noButton) {
217
226
  this.websiteWidget.style.display = 'block';
218
227
  if (this.hideWidgetButton && !this.noButton) {
@@ -227,6 +236,7 @@ export default class RbirdWebsiteWidget {
227
236
  }
228
237
 
229
238
  closeWebsiteWidget() {
239
+ if (typeof window === 'undefined' || typeof document === 'undefined') return;
230
240
  if (this.iframe) {
231
241
  this.iframe.contentWindow?.postMessage({
232
242
  type: 'close',
@@ -304,6 +314,7 @@ export default class RbirdWebsiteWidget {
304
314
  }
305
315
 
306
316
  styleWidget() {
317
+ if (typeof window === 'undefined' || typeof document === 'undefined') return;
307
318
  this.websiteWidget.className=RbirdSessionManager.getInstance().widgetSettings.launcher === 5 ? 'launcherButton5' : 'launcherButton' ;
308
319
  this.initButton();
309
320
 
package/src/index.js CHANGED
@@ -2,10 +2,12 @@ import { injectStyledCSS } from "./Styles";
2
2
  import RbirdSessionManager from "./RbirdSessionManager";
3
3
  import RbirdWebsiteWidget from "./RbirdWebsiteWidget";
4
4
  import { ReleasebirdConsoleLogger } from "./ReleasebirdConsoleLogger";
5
+ import { RbirdBannerManager } from "./RbirdBannerManager";
5
6
 
6
7
  class Rbird {
7
8
 
8
9
  static isMobileDevice = () => {
10
+ if (typeof window === 'undefined') return false;
9
11
  const mobileMaxWidth = 768; // Definiert die maximale Breite für mobile Geräte, z.B. Tablets und Smartphones
10
12
  return window.innerWidth <= mobileMaxWidth;
11
13
  }
@@ -18,6 +20,7 @@ class Rbird {
18
20
  spaceLeftRight = 30,
19
21
  spaceBottom = 30
20
22
  ) {
23
+ if (typeof window === 'undefined') return;
21
24
  runFunctionWhenDomIsReady(() => {
22
25
  injectStyledCSS(
23
26
  backgroundColor,
@@ -54,6 +57,7 @@ class Rbird {
54
57
  }
55
58
 
56
59
  static showButton(showButton) {
60
+ if (typeof window === 'undefined') return;
57
61
  RbirdWebsiteWidget.getInstance().showButton(showButton);
58
62
  }
59
63
 
@@ -62,6 +66,7 @@ class Rbird {
62
66
  }
63
67
 
64
68
  static initialize(apiKey, showButton) {
69
+ if (typeof window === 'undefined') return;
65
70
  try {
66
71
  if (showButton === undefined) {
67
72
  showButton = true;
@@ -77,6 +82,12 @@ class Rbird {
77
82
  if (showButton) {
78
83
  RbirdWebsiteWidget.getInstance().showButton(showButton);
79
84
  }
85
+ // Initialize banner manager and display banners
86
+ runFunctionWhenDomIsReady(() => {
87
+ const bannerManager = RbirdBannerManager.getInstance();
88
+ bannerManager.injectStyles();
89
+ bannerManager.init(apiKey);
90
+ });
80
91
  })
81
92
 
82
93
  } catch (e) {
@@ -85,6 +96,7 @@ class Rbird {
85
96
  }
86
97
 
87
98
  static showWidget() {
99
+ if (typeof window === 'undefined') return;
88
100
  try {
89
101
  RbirdWebsiteWidget.getInstance().openWebsiteWidget();
90
102
  } catch (e) {
@@ -93,6 +105,7 @@ class Rbird {
93
105
  }
94
106
 
95
107
  static identify(identify, hash) {
108
+ if (typeof window === 'undefined') return;
96
109
  try {
97
110
  RbirdSessionManager.getInstance().identifyUser(identify, hash);
98
111
  } catch (e) {
@@ -101,6 +114,7 @@ class Rbird {
101
114
  }
102
115
 
103
116
  static logout() {
117
+ if (typeof window === 'undefined') return;
104
118
  try {
105
119
  RbirdSessionManager.getInstance().logout();
106
120
  } catch (e) {
@@ -111,6 +125,7 @@ class Rbird {
111
125
  }
112
126
 
113
127
  export const runFunctionWhenDomIsReady = (callback) => {
128
+ if (typeof window === 'undefined' || typeof document === 'undefined') return;
114
129
  if (
115
130
  document.readyState === "complete" ||
116
131
  document.readyState === "loaded" ||