releasebird-javascript-sdk 1.0.92 → 1.0.94
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/build/index.js +1 -1
- package/package.json +1 -1
- package/published/1.0.92/index.js +1 -1
- package/published/1.0.93/index.js +1 -0
- package/published/1.0.94/index.js +1 -0
- package/published/latest/index.js +1 -1
- package/src/RbirdAutomationManager.js +91 -31
- package/src/RbirdBookingManager.js +132 -0
- package/src/RbirdScreenshotManager.js +0 -36
- package/src/RbirdWebsiteWidget.js +72 -0
- package/src/index.js +44 -0
|
@@ -31,7 +31,8 @@ export class RbirdAutomationManager {
|
|
|
31
31
|
*/
|
|
32
32
|
async init(apiKey) {
|
|
33
33
|
this.apiKey = apiKey;
|
|
34
|
-
|
|
34
|
+
const { timestamp, isFirstVisit } = this.getFirstSeenTimestamp();
|
|
35
|
+
this.firstSeenTimestamp = timestamp;
|
|
35
36
|
|
|
36
37
|
// Register event handlers
|
|
37
38
|
this.registerEventHandlers();
|
|
@@ -42,12 +43,13 @@ export class RbirdAutomationManager {
|
|
|
42
43
|
// Start command polling (reduced frequency - WebSocket is primary)
|
|
43
44
|
this.startCommandPolling();
|
|
44
45
|
|
|
45
|
-
// Send first_seen event
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// Send first_seen event only if this is truly the first visit
|
|
47
|
+
if (isFirstVisit) {
|
|
48
|
+
await this.sendEvent('first_seen', {
|
|
49
|
+
timeSinceFirstSeen: 0
|
|
50
|
+
});
|
|
51
|
+
}
|
|
49
52
|
|
|
50
|
-
console.log('[Rbird] Automation manager initialized');
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
/**
|
|
@@ -58,7 +60,6 @@ export class RbirdAutomationManager {
|
|
|
58
60
|
window.addEventListener('message', (event) => {
|
|
59
61
|
// Handle automation command notification from widget
|
|
60
62
|
if (event.data === 'automationCommand') {
|
|
61
|
-
console.log('[Rbird] Received WebSocket automation notification');
|
|
62
63
|
// Immediately fetch and execute pending commands
|
|
63
64
|
this.pollCommands();
|
|
64
65
|
}
|
|
@@ -67,15 +68,16 @@ export class RbirdAutomationManager {
|
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
70
|
* Get or set the first_seen timestamp
|
|
71
|
+
* @returns {{ timestamp: number, isFirstVisit: boolean }}
|
|
70
72
|
*/
|
|
71
73
|
getFirstSeenTimestamp() {
|
|
72
74
|
const stored = localStorage.getItem('rbird_first_seen');
|
|
73
75
|
if (stored) {
|
|
74
|
-
return parseInt(stored, 10);
|
|
76
|
+
return { timestamp: parseInt(stored, 10), isFirstVisit: false };
|
|
75
77
|
}
|
|
76
78
|
const now = Date.now();
|
|
77
79
|
localStorage.setItem('rbird_first_seen', now.toString());
|
|
78
|
-
return now;
|
|
80
|
+
return { timestamp: now, isFirstVisit: true };
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
/**
|
|
@@ -162,7 +164,6 @@ export class RbirdAutomationManager {
|
|
|
162
164
|
if (!response.ok) {
|
|
163
165
|
console.warn('[Rbird] Failed to send event:', response.status);
|
|
164
166
|
} else {
|
|
165
|
-
console.log('[Rbird] Event sent:', eventType);
|
|
166
167
|
}
|
|
167
168
|
} catch (error) {
|
|
168
169
|
console.error('[Rbird] Error sending event:', error);
|
|
@@ -177,20 +178,37 @@ export class RbirdAutomationManager {
|
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
/**
|
|
180
|
-
* Get the anonymous ID from session
|
|
181
|
+
* Get the anonymous ID from session.
|
|
182
|
+
* Uses the same anonymousIdentifier as RbirdSessionManager (stored in 'Rbird_I')
|
|
183
|
+
* to ensure consistency between automation and widget.
|
|
181
184
|
*/
|
|
182
185
|
getAnonymousId() {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
+
// First check if there's an identified user with anonymousId
|
|
187
|
+
const identifyAnonymousId = RbirdSessionManager.getInstance().getState()?.identify?.anonymousId;
|
|
188
|
+
if (identifyAnonymousId) {
|
|
189
|
+
return identifyAnonymousId;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Use the same anonymousIdentifier as RbirdSessionManager
|
|
193
|
+
const sessionAnonymousId = RbirdSessionManager.getInstance().anonymousIdentifier;
|
|
194
|
+
if (sessionAnonymousId) {
|
|
195
|
+
return sessionAnonymousId;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Fallback: read from the same localStorage key as RbirdSessionManager
|
|
199
|
+
return localStorage.getItem('Rbird_I') || this.generateAnonymousId();
|
|
186
200
|
}
|
|
187
201
|
|
|
188
202
|
/**
|
|
189
|
-
* Generate and store an anonymous ID
|
|
203
|
+
* Generate and store an anonymous ID (using same key as RbirdSessionManager)
|
|
190
204
|
*/
|
|
191
205
|
generateAnonymousId() {
|
|
192
|
-
const
|
|
193
|
-
|
|
206
|
+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
207
|
+
let id = '';
|
|
208
|
+
for (let i = 0; i < 60; i++) {
|
|
209
|
+
id += characters.charAt(Math.floor(Math.random() * characters.length));
|
|
210
|
+
}
|
|
211
|
+
localStorage.setItem('Rbird_I', id);
|
|
194
212
|
return id;
|
|
195
213
|
}
|
|
196
214
|
|
|
@@ -262,7 +280,6 @@ export class RbirdAutomationManager {
|
|
|
262
280
|
* @param {Object} cmd - The command to execute
|
|
263
281
|
*/
|
|
264
282
|
async executeCommand(cmd) {
|
|
265
|
-
console.log('[Rbird] Executing command:', cmd.type);
|
|
266
283
|
|
|
267
284
|
switch (cmd.type) {
|
|
268
285
|
case 'show_banner':
|
|
@@ -277,6 +294,9 @@ export class RbirdAutomationManager {
|
|
|
277
294
|
case 'send_chat':
|
|
278
295
|
this.executeSendChat(cmd.message, cmd.config);
|
|
279
296
|
break;
|
|
297
|
+
case 'send_booking':
|
|
298
|
+
this.executeSendBooking(cmd.targetId, cmd.message, cmd.config);
|
|
299
|
+
break;
|
|
280
300
|
default:
|
|
281
301
|
console.warn('[Rbird] Unknown command type:', cmd.type);
|
|
282
302
|
}
|
|
@@ -302,25 +322,65 @@ export class RbirdAutomationManager {
|
|
|
302
322
|
|
|
303
323
|
/**
|
|
304
324
|
* Execute send_chat command
|
|
325
|
+
* @param {string} message - The chat message
|
|
326
|
+
* @param {Object} config - Config including chatId, openWidget flag, senderName, senderAvatar
|
|
305
327
|
*/
|
|
306
328
|
executeSendChat(message, config) {
|
|
307
329
|
if (!message) return;
|
|
308
330
|
|
|
309
|
-
|
|
331
|
+
const widget = RbirdWebsiteWidget.getInstance();
|
|
332
|
+
const shouldOpenWidget = config?.openWidget !== false; // Default to true
|
|
333
|
+
|
|
334
|
+
if (shouldOpenWidget) {
|
|
335
|
+
// Open the widget and send the message
|
|
336
|
+
widget.openWebsiteWidget();
|
|
337
|
+
|
|
338
|
+
// Send message to iframe to display automation message
|
|
339
|
+
setTimeout(() => {
|
|
340
|
+
if (widget.iframe) {
|
|
341
|
+
widget.iframe.contentWindow?.postMessage({
|
|
342
|
+
type: 'automationMessage',
|
|
343
|
+
message: message,
|
|
344
|
+
chatId: config?.chatId
|
|
345
|
+
}, '*');
|
|
346
|
+
}
|
|
347
|
+
}, 500); // Small delay to ensure iframe is ready
|
|
348
|
+
} else {
|
|
349
|
+
// Just show a message bubble without opening the widget
|
|
350
|
+
widget.showMessageBubble(message, config?.chatId, null, config?.senderName, config?.senderAvatar);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
310
353
|
|
|
311
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Execute send_booking command - sends a booking request message
|
|
356
|
+
* @param {string} appointmentTypeId - The appointment type ID
|
|
357
|
+
* @param {string} message - Optional custom message
|
|
358
|
+
* @param {Object} config - Additional config (chatId, openWidget flag, senderName, senderAvatar)
|
|
359
|
+
*/
|
|
360
|
+
executeSendBooking(appointmentTypeId, message, config) {
|
|
312
361
|
const widget = RbirdWebsiteWidget.getInstance();
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
362
|
+
const shouldOpenWidget = config?.openWidget !== false; // Default to true
|
|
363
|
+
|
|
364
|
+
if (shouldOpenWidget) {
|
|
365
|
+
// Open the widget and send the booking request
|
|
366
|
+
widget.openWebsiteWidget();
|
|
367
|
+
|
|
368
|
+
// Send message to iframe to display booking request
|
|
369
|
+
setTimeout(() => {
|
|
370
|
+
if (widget.iframe) {
|
|
371
|
+
widget.iframe.contentWindow?.postMessage({
|
|
372
|
+
type: 'automationBooking',
|
|
373
|
+
appointmentTypeId: appointmentTypeId,
|
|
374
|
+
message: message,
|
|
375
|
+
chatId: config?.chatId
|
|
376
|
+
}, '*');
|
|
377
|
+
}
|
|
378
|
+
}, 500); // Small delay to ensure iframe is ready
|
|
379
|
+
} else {
|
|
380
|
+
// Just show a message bubble without opening the widget
|
|
381
|
+
const displayMessage = message || 'Book an appointment';
|
|
382
|
+
widget.showMessageBubble(displayMessage, config?.chatId, appointmentTypeId, config?.senderName, config?.senderAvatar);
|
|
383
|
+
}
|
|
324
384
|
}
|
|
325
385
|
|
|
326
386
|
/**
|
|
@@ -9,6 +9,7 @@ export class RbirdBookingManager {
|
|
|
9
9
|
constructor() {
|
|
10
10
|
this.apiKey = null;
|
|
11
11
|
this.isOpen = false;
|
|
12
|
+
this.embeddedInstances = new Map(); // Track embedded instances by target selector
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
static getInstance() {
|
|
@@ -27,6 +28,7 @@ export class RbirdBookingManager {
|
|
|
27
28
|
/**
|
|
28
29
|
* Show the booking modal
|
|
29
30
|
* @param {Object} options - Optional configuration
|
|
31
|
+
* @param {string} options.appointmentTypeId - Pre-select a specific appointment type
|
|
30
32
|
* @param {Function} options.onSuccess - Callback when booking is completed
|
|
31
33
|
* @param {Function} options.onClose - Callback when modal is closed
|
|
32
34
|
*/
|
|
@@ -56,6 +58,9 @@ export class RbirdBookingManager {
|
|
|
56
58
|
if (sessionManager.anonymousIdentifier) {
|
|
57
59
|
iframeUrl += `&ai=${sessionManager.anonymousIdentifier}`;
|
|
58
60
|
}
|
|
61
|
+
if (options.appointmentTypeId) {
|
|
62
|
+
iframeUrl += `&appointmentTypeId=${encodeURIComponent(options.appointmentTypeId)}`;
|
|
63
|
+
}
|
|
59
64
|
|
|
60
65
|
this.createModal(iframeUrl, options);
|
|
61
66
|
this.isOpen = true;
|
|
@@ -150,6 +155,133 @@ export class RbirdBookingManager {
|
|
|
150
155
|
}
|
|
151
156
|
}
|
|
152
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Embed the booking form into a container element
|
|
160
|
+
* @param {Object} options - Configuration options
|
|
161
|
+
* @param {string} options.target - CSS selector for the container element (required)
|
|
162
|
+
* @param {string} options.appointmentTypeId - Pre-select a specific appointment type
|
|
163
|
+
* @param {string} options.height - Height of the embedded form (default: '600px')
|
|
164
|
+
* @param {Function} options.onSuccess - Callback when booking is completed
|
|
165
|
+
* @param {Function} options.onError - Callback when an error occurs
|
|
166
|
+
*/
|
|
167
|
+
embedBooking(options = {}) {
|
|
168
|
+
if (typeof window === 'undefined') return;
|
|
169
|
+
if (!this.apiKey) {
|
|
170
|
+
console.error('[RbirdBookingManager] SDK not initialized. Call Rbird.initialize() first.');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (!options.target) {
|
|
174
|
+
console.error('[RbirdBookingManager] Target selector is required for embedBooking().');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const container = document.querySelector(options.target);
|
|
179
|
+
if (!container) {
|
|
180
|
+
console.error(`[RbirdBookingManager] Container not found: ${options.target}`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Check if already embedded in this container
|
|
185
|
+
if (this.embeddedInstances.has(options.target)) {
|
|
186
|
+
console.warn(`[RbirdBookingManager] Booking form already embedded in ${options.target}. Call destroyEmbeddedBooking() first to re-embed.`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const sessionManager = RbirdSessionManager.getInstance();
|
|
191
|
+
const state = sessionManager.getState();
|
|
192
|
+
|
|
193
|
+
// Build iframe URL with embedded mode
|
|
194
|
+
let iframeUrl = `${CONTENT_URL}/widget?apiKey=${this.apiKey}&view=BOOKING&embedded=true`;
|
|
195
|
+
|
|
196
|
+
if (state?.identify?.people) {
|
|
197
|
+
iframeUrl += `&people=${state.identify.people}`;
|
|
198
|
+
}
|
|
199
|
+
if (state?.identify?.hash) {
|
|
200
|
+
iframeUrl += `&hash=${state.identify.hash}`;
|
|
201
|
+
}
|
|
202
|
+
if (sessionManager.anonymousIdentifier) {
|
|
203
|
+
iframeUrl += `&ai=${sessionManager.anonymousIdentifier}`;
|
|
204
|
+
}
|
|
205
|
+
if (options.appointmentTypeId) {
|
|
206
|
+
iframeUrl += `&appointmentTypeId=${encodeURIComponent(options.appointmentTypeId)}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Create wrapper div for styling
|
|
210
|
+
const wrapper = document.createElement('div');
|
|
211
|
+
wrapper.className = 'rbird-booking-embedded-wrapper';
|
|
212
|
+
wrapper.style.cssText = `
|
|
213
|
+
width: 100%;
|
|
214
|
+
height: ${options.height || '600px'};
|
|
215
|
+
border-radius: 12px;
|
|
216
|
+
overflow: hidden;
|
|
217
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
218
|
+
`;
|
|
219
|
+
|
|
220
|
+
// Create iframe
|
|
221
|
+
const iframe = document.createElement('iframe');
|
|
222
|
+
iframe.className = 'rbird-booking-embedded-iframe';
|
|
223
|
+
iframe.src = iframeUrl;
|
|
224
|
+
iframe.allow = 'clipboard-write';
|
|
225
|
+
iframe.style.cssText = `
|
|
226
|
+
width: 100%;
|
|
227
|
+
height: 100%;
|
|
228
|
+
border: none;
|
|
229
|
+
`;
|
|
230
|
+
|
|
231
|
+
wrapper.appendChild(iframe);
|
|
232
|
+
container.appendChild(wrapper);
|
|
233
|
+
|
|
234
|
+
// Handle messages from iframe
|
|
235
|
+
const messageHandler = (e) => {
|
|
236
|
+
if (e.data === 'bookingSuccess') {
|
|
237
|
+
if (options.onSuccess) {
|
|
238
|
+
options.onSuccess();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (e.data === 'bookingError') {
|
|
242
|
+
if (options.onError) {
|
|
243
|
+
options.onError(e.data.error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
window.addEventListener('message', messageHandler);
|
|
248
|
+
|
|
249
|
+
// Store instance info for cleanup
|
|
250
|
+
this.embeddedInstances.set(options.target, {
|
|
251
|
+
wrapper,
|
|
252
|
+
messageHandler
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
destroy: () => this.destroyEmbeddedBooking(options.target)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Remove an embedded booking form
|
|
262
|
+
* @param {string} target - CSS selector for the container element
|
|
263
|
+
*/
|
|
264
|
+
destroyEmbeddedBooking(target) {
|
|
265
|
+
if (!this.embeddedInstances.has(target)) {
|
|
266
|
+
console.warn(`[RbirdBookingManager] No embedded booking found for ${target}`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const instance = this.embeddedInstances.get(target);
|
|
271
|
+
|
|
272
|
+
// Remove event listener
|
|
273
|
+
if (instance.messageHandler) {
|
|
274
|
+
window.removeEventListener('message', instance.messageHandler);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Remove DOM element
|
|
278
|
+
if (instance.wrapper && instance.wrapper.parentNode) {
|
|
279
|
+
instance.wrapper.parentNode.removeChild(instance.wrapper);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
this.embeddedInstances.delete(target);
|
|
283
|
+
}
|
|
284
|
+
|
|
153
285
|
injectStyles() {
|
|
154
286
|
if (typeof window === 'undefined' || document.getElementById('rbird-booking-styles')) return;
|
|
155
287
|
|
|
@@ -107,8 +107,6 @@ export default class RbirdScreenshotManager {
|
|
|
107
107
|
|
|
108
108
|
// Wait a moment for the loader to render, then hide our UI elements
|
|
109
109
|
setTimeout(() => {
|
|
110
|
-
console.log('[Screenshot] Starting HTML export method...');
|
|
111
|
-
|
|
112
110
|
// Hide the close button and toolbar
|
|
113
111
|
const closeButton = document.getElementById('screenshotCloseButton');
|
|
114
112
|
const toolbar = document.querySelector('.menu'); // SVGEditor toolbar
|
|
@@ -131,15 +129,12 @@ export default class RbirdScreenshotManager {
|
|
|
131
129
|
// Export HTML with inline styles (now async!)
|
|
132
130
|
const htmlData = await that.exportHTML();
|
|
133
131
|
|
|
134
|
-
console.log('[Screenshot] HTML exported, sending to backend...');
|
|
135
|
-
|
|
136
132
|
// Restore loader visibility while waiting for backend
|
|
137
133
|
loader.style.visibility = 'visible';
|
|
138
134
|
|
|
139
135
|
// Send HTML to backend for rendering
|
|
140
136
|
that.renderScreenshotOnBackend(htmlData)
|
|
141
137
|
.then((screenshotDataUrl) => {
|
|
142
|
-
console.log('[Screenshot] Screenshot received from backend!');
|
|
143
138
|
screenshotCompleted = true;
|
|
144
139
|
clearTimeout(timeoutId);
|
|
145
140
|
|
|
@@ -207,7 +202,6 @@ export default class RbirdScreenshotManager {
|
|
|
207
202
|
* Returns complete HTML document with minimal modifications
|
|
208
203
|
*/
|
|
209
204
|
async exportHTML() {
|
|
210
|
-
console.log('[Screenshot] Exporting HTML with inline resources...');
|
|
211
205
|
|
|
212
206
|
// Capture scroll positions BEFORE cloning
|
|
213
207
|
this.captureScrollPositions();
|
|
@@ -246,7 +240,6 @@ export default class RbirdScreenshotManager {
|
|
|
246
240
|
const doctype = '<!DOCTYPE html>';
|
|
247
241
|
const html = doctype + clone.outerHTML;
|
|
248
242
|
|
|
249
|
-
console.log(`[Screenshot] Exported HTML: ${html.length} bytes`);
|
|
250
243
|
return html;
|
|
251
244
|
}
|
|
252
245
|
|
|
@@ -255,7 +248,6 @@ export default class RbirdScreenshotManager {
|
|
|
255
248
|
* This allows the backend to restore scroll positions before taking the screenshot
|
|
256
249
|
*/
|
|
257
250
|
captureScrollPositions() {
|
|
258
|
-
console.log('[Screenshot] Capturing scroll positions...');
|
|
259
251
|
let scrollableCount = 0;
|
|
260
252
|
|
|
261
253
|
// Find all elements that might be scrollable
|
|
@@ -289,7 +281,6 @@ export default class RbirdScreenshotManager {
|
|
|
289
281
|
}
|
|
290
282
|
|
|
291
283
|
scrollableCount++;
|
|
292
|
-
console.log(`[Screenshot] Captured scroll position for element: scrollLeft=${scrollLeft}, scrollTop=${scrollTop}`);
|
|
293
284
|
}
|
|
294
285
|
});
|
|
295
286
|
|
|
@@ -304,7 +295,6 @@ export default class RbirdScreenshotManager {
|
|
|
304
295
|
document.body.setAttribute('data-rbird-scroll-top', docScrollTop.toString());
|
|
305
296
|
}
|
|
306
297
|
|
|
307
|
-
console.log(`[Screenshot] Captured scroll positions for ${scrollableCount} elements`);
|
|
308
298
|
}
|
|
309
299
|
|
|
310
300
|
/**
|
|
@@ -346,7 +336,6 @@ export default class RbirdScreenshotManager {
|
|
|
346
336
|
|
|
347
337
|
if (totalOriginalSize > 0) {
|
|
348
338
|
const reduction = Math.round((1 - totalResizedSize / totalOriginalSize) * 100);
|
|
349
|
-
console.log(`[Screenshot] Image optimization: ${totalOriginalSize} -> ${totalResizedSize} bytes (${reduction}% reduction)`);
|
|
350
339
|
}
|
|
351
340
|
}
|
|
352
341
|
|
|
@@ -454,7 +443,6 @@ export default class RbirdScreenshotManager {
|
|
|
454
443
|
* Download all CSS and inline with data URLs
|
|
455
444
|
*/
|
|
456
445
|
async downloadAllCSS(clone) {
|
|
457
|
-
console.log(`[Screenshot] Processing ${document.styleSheets.length} stylesheets...`);
|
|
458
446
|
const processedStylesheets = [];
|
|
459
447
|
const googleFontsLinks = [];
|
|
460
448
|
|
|
@@ -491,7 +479,6 @@ export default class RbirdScreenshotManager {
|
|
|
491
479
|
styleSheet.href.includes('icon.css') ||
|
|
492
480
|
styleSheet.href.includes('icons.css')
|
|
493
481
|
)) {
|
|
494
|
-
console.log(`[Screenshot] Skipping font/icon service stylesheet: ${styleSheet.href}`);
|
|
495
482
|
googleFontsLinks.push(styleSheet.href);
|
|
496
483
|
continue;
|
|
497
484
|
}
|
|
@@ -504,16 +491,13 @@ export default class RbirdScreenshotManager {
|
|
|
504
491
|
for (let j = 0; j < styleSheet.cssRules.length; j++) {
|
|
505
492
|
cssText += styleSheet.cssRules[j].cssText + '\n';
|
|
506
493
|
}
|
|
507
|
-
console.log(`[Screenshot] Extracted ${styleSheet.cssRules.length} rules from stylesheet ${i}`);
|
|
508
494
|
}
|
|
509
495
|
} catch (corsError) {
|
|
510
496
|
// CORS blocked - try to fetch the stylesheet if it has an href
|
|
511
497
|
if (styleSheet.href) {
|
|
512
|
-
console.log(`[Screenshot] CORS blocked stylesheet ${i}, fetching: ${styleSheet.href}`);
|
|
513
498
|
try {
|
|
514
499
|
const response = await fetch(styleSheet.href);
|
|
515
500
|
cssText = await response.text();
|
|
516
|
-
console.log(`[Screenshot] Fetched ${cssText.length} bytes from ${styleSheet.href}`);
|
|
517
501
|
} catch (fetchError) {
|
|
518
502
|
console.warn('[Screenshot] Failed to fetch CORS-blocked stylesheet:', styleSheet.href, fetchError);
|
|
519
503
|
}
|
|
@@ -544,11 +528,8 @@ export default class RbirdScreenshotManager {
|
|
|
544
528
|
}
|
|
545
529
|
}
|
|
546
530
|
|
|
547
|
-
console.log(`[Screenshot] Successfully processed ${processedStylesheets.length} stylesheets`);
|
|
548
|
-
|
|
549
531
|
// Remove original link tags except Google Fonts and icon fonts
|
|
550
532
|
const links = clone.querySelectorAll('link[rel="stylesheet"]');
|
|
551
|
-
console.log(`[Screenshot] Removing ${links.length} original link tags (keeping font/icon services)`);
|
|
552
533
|
links.forEach(link => {
|
|
553
534
|
const href = link.getAttribute('href');
|
|
554
535
|
// Keep Google Fonts, icon fonts, and other font service links
|
|
@@ -627,15 +608,12 @@ export default class RbirdScreenshotManager {
|
|
|
627
608
|
fullURL = new URL(url, basePath + '/').href;
|
|
628
609
|
}
|
|
629
610
|
|
|
630
|
-
console.log(`[Screenshot] Processing CSS resource: ${fullURL} (font: ${isFont}, image: ${isImage})`);
|
|
631
|
-
|
|
632
611
|
const dataURL = await this.fetchResourceAsDataURL(fullURL);
|
|
633
612
|
|
|
634
613
|
if (dataURL) {
|
|
635
614
|
if (isFont) {
|
|
636
615
|
// Don't resize fonts - use as-is
|
|
637
616
|
cssText = cssText.split(match[0]).join(`url(${dataURL})`);
|
|
638
|
-
console.log(`[Screenshot] Embedded font: ${url}`);
|
|
639
617
|
} else if (isImage) {
|
|
640
618
|
// Resize images to reduce payload
|
|
641
619
|
try {
|
|
@@ -735,7 +713,6 @@ export default class RbirdScreenshotManager {
|
|
|
735
713
|
|
|
736
714
|
// Convert to base64 in chunks to avoid call stack issues
|
|
737
715
|
const base64 = this.arrayBufferToBase64(compressed);
|
|
738
|
-
console.log(`[Screenshot] Compression: ${data.length} -> ${compressed.length} bytes (${Math.round(compressed.length / data.length * 100)}%)`);
|
|
739
716
|
return base64;
|
|
740
717
|
} else {
|
|
741
718
|
// Fallback: no compression available
|
|
@@ -773,17 +750,11 @@ export default class RbirdScreenshotManager {
|
|
|
773
750
|
*/
|
|
774
751
|
async renderScreenshotOnBackend(html) {
|
|
775
752
|
const htmlSizeBytes = html.length;
|
|
776
|
-
const htmlSizeMB = (htmlSizeBytes / (1024 * 1024)).toFixed(2);
|
|
777
|
-
|
|
778
|
-
console.log('[Screenshot] Sending HTML to backend...');
|
|
779
|
-
console.log(`[Screenshot] Original HTML size: ${htmlSizeBytes} bytes (${htmlSizeMB} MB)`);
|
|
780
753
|
|
|
781
754
|
// If HTML > 800KB, use S3 upload to bypass ALB 1MB limit
|
|
782
755
|
if (htmlSizeBytes > 800 * 1024) {
|
|
783
|
-
console.log('[Screenshot] HTML > 800KB, using S3 upload...');
|
|
784
756
|
return this.renderViaS3(html);
|
|
785
757
|
} else {
|
|
786
|
-
console.log('[Screenshot] HTML < 800KB, using direct upload...');
|
|
787
758
|
return this.renderDirect(html);
|
|
788
759
|
}
|
|
789
760
|
}
|
|
@@ -794,8 +765,6 @@ export default class RbirdScreenshotManager {
|
|
|
794
765
|
async renderViaS3(html) {
|
|
795
766
|
return new Promise(async (resolve, reject) => {
|
|
796
767
|
try {
|
|
797
|
-
console.log('[Screenshot] Step 1: Requesting presigned URL...');
|
|
798
|
-
|
|
799
768
|
const widgetInstance = this.getRbirdWebsiteWidget().getInstance();
|
|
800
769
|
|
|
801
770
|
// Create abort controller for timeout
|
|
@@ -824,7 +793,6 @@ export default class RbirdScreenshotManager {
|
|
|
824
793
|
}
|
|
825
794
|
|
|
826
795
|
const { uploadUrl, key } = await urlResponse.json();
|
|
827
|
-
console.log(`[Screenshot] Step 2: Uploading HTML to S3 (key: ${key})...`);
|
|
828
796
|
|
|
829
797
|
// 2. Upload HTML directly to S3
|
|
830
798
|
const controller2 = new AbortController();
|
|
@@ -850,8 +818,6 @@ export default class RbirdScreenshotManager {
|
|
|
850
818
|
throw new Error(`Failed to upload to S3: ${s3Response.status}`);
|
|
851
819
|
}
|
|
852
820
|
|
|
853
|
-
console.log('[Screenshot] Step 3: Requesting screenshot render from backend...');
|
|
854
|
-
|
|
855
821
|
// 3. Request screenshot render via backend
|
|
856
822
|
const controller3 = new AbortController();
|
|
857
823
|
const timeout3 = setTimeout(() => controller3.abort(), 20000); // 20s timeout
|
|
@@ -884,7 +850,6 @@ export default class RbirdScreenshotManager {
|
|
|
884
850
|
}
|
|
885
851
|
|
|
886
852
|
const result = await renderResponse.json();
|
|
887
|
-
console.log('[Screenshot] Screenshot received successfully via S3!');
|
|
888
853
|
resolve(result.screenshot);
|
|
889
854
|
|
|
890
855
|
} catch (error) {
|
|
@@ -956,7 +921,6 @@ export default class RbirdScreenshotManager {
|
|
|
956
921
|
}
|
|
957
922
|
|
|
958
923
|
const payload = JSON.stringify(requestData);
|
|
959
|
-
console.log(`[Screenshot] Final payload size: ${payload.length} bytes`);
|
|
960
924
|
|
|
961
925
|
http.send(payload);
|
|
962
926
|
});
|
|
@@ -767,6 +767,78 @@ export default class RbirdWebsiteWidget {
|
|
|
767
767
|
}
|
|
768
768
|
}
|
|
769
769
|
|
|
770
|
+
/**
|
|
771
|
+
* Show a single message bubble for automation messages (without opening the widget)
|
|
772
|
+
* @param {string} message - The message text to display
|
|
773
|
+
* @param {string} chatId - The chat ID to open when clicked
|
|
774
|
+
* @param {string} appointmentTypeId - Optional appointment type ID for booking messages
|
|
775
|
+
* @param {string} senderName - Optional sender name (from selected user)
|
|
776
|
+
* @param {string} senderAvatar - Optional sender avatar URL (from selected user)
|
|
777
|
+
*/
|
|
778
|
+
showMessageBubble(message, chatId, appointmentTypeId = null, senderName = null, senderAvatar = null) {
|
|
779
|
+
|
|
780
|
+
if (!message) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Ensure widget button exists for positioning
|
|
785
|
+
if (!this.websiteWidget) {
|
|
786
|
+
// Retry after widget is initialized
|
|
787
|
+
setTimeout(() => this.showMessageBubble(message, chatId, appointmentTypeId, senderName, senderAvatar), 500);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Create message object in the format expected by createMessageBubble
|
|
792
|
+
// Use provided sender info, or fall back to 'Support' with no avatar
|
|
793
|
+
const msg = {
|
|
794
|
+
chatId: chatId || 'automation-' + Date.now(),
|
|
795
|
+
text: message,
|
|
796
|
+
senderName: senderName || 'Support',
|
|
797
|
+
senderAvatar: senderAvatar || null,
|
|
798
|
+
timestamp: new Date().toISOString(),
|
|
799
|
+
appointmentTypeId: appointmentTypeId
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
// Ensure bubble container exists
|
|
803
|
+
if (!this.messageBubblesContainer) {
|
|
804
|
+
this.messageBubblesContainer = document.createElement('div');
|
|
805
|
+
this.messageBubblesContainer.className = 'rbird-message-bubbles-container';
|
|
806
|
+
// Set critical styles directly to ensure visibility
|
|
807
|
+
this.messageBubblesContainer.style.position = 'fixed';
|
|
808
|
+
this.messageBubblesContainer.style.zIndex = '9999999';
|
|
809
|
+
this.messageBubblesContainer.style.display = 'flex';
|
|
810
|
+
this.messageBubblesContainer.style.flexDirection = 'column';
|
|
811
|
+
this.messageBubblesContainer.style.gap = '10px';
|
|
812
|
+
this.messageBubblesContainer.style.maxWidth = '320px';
|
|
813
|
+
document.body.appendChild(this.messageBubblesContainer);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Create and add the bubble
|
|
817
|
+
const bubble = this.createMessageBubble(msg);
|
|
818
|
+
|
|
819
|
+
// Override click handler for automation bubbles - open widget and show the chat
|
|
820
|
+
bubble.onclick = () => {
|
|
821
|
+
this.hideMessageBubbles();
|
|
822
|
+
this.openWebsiteWidget();
|
|
823
|
+
// Send message to iframe to open the specific chat
|
|
824
|
+
if (this.iframe && chatId) {
|
|
825
|
+
this.iframe.contentWindow?.postMessage({
|
|
826
|
+
type: 'openChat',
|
|
827
|
+
chatId: chatId
|
|
828
|
+
}, '*');
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
this.messageBubblesContainer.appendChild(bubble);
|
|
833
|
+
this.messageBubblesContainer.style.display = 'flex';
|
|
834
|
+
|
|
835
|
+
// Position bubbles
|
|
836
|
+
this.positionMessageBubbles();
|
|
837
|
+
|
|
838
|
+
// Play notification sound
|
|
839
|
+
this.playNotificationSound();
|
|
840
|
+
}
|
|
841
|
+
|
|
770
842
|
/**
|
|
771
843
|
* Create the drag handle element (6-dot grip pattern)
|
|
772
844
|
*/
|