releasebird-javascript-sdk 1.0.38 → 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
  }
package/src/index.js CHANGED
@@ -2,6 +2,7 @@ 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
 
@@ -81,6 +82,12 @@ class Rbird {
81
82
  if (showButton) {
82
83
  RbirdWebsiteWidget.getInstance().showButton(showButton);
83
84
  }
85
+ // Initialize banner manager and display banners
86
+ runFunctionWhenDomIsReady(() => {
87
+ const bannerManager = RbirdBannerManager.getInstance();
88
+ bannerManager.injectStyles();
89
+ bannerManager.init(apiKey);
90
+ });
84
91
  })
85
92
 
86
93
  } catch (e) {