releasebird-javascript-sdk 1.0.61 → 1.0.62

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.
@@ -185,31 +185,25 @@ export default class RbirdScreenshotManager {
185
185
  * Returns complete HTML document with minimal modifications
186
186
  */
187
187
  async exportHTML() {
188
- console.log('[Screenshot] Exporting HTML for server-side rendering...');
188
+ console.log('[Screenshot] Exporting HTML with inline resources...');
189
189
 
190
- // Get the complete HTML using native serialization
191
- const headHTML = document.head.outerHTML;
192
- let bodyHTML = document.body.innerHTML;
193
-
194
- // IMPORTANT: Keep the markerBoundary SVG content!
195
- // The SVG drawings are already in the body innerHTML
196
-
197
- // Create a temporary container to manipulate the body HTML
198
- const tempContainer = document.createElement('div');
199
- tempContainer.innerHTML = bodyHTML;
190
+ // Clone the document
191
+ const clone = document.documentElement.cloneNode(true);
200
192
 
201
193
  // Remove UI elements that shouldn't be in the screenshot
202
194
  const removeSelectors = [
203
195
  '#rbirdScreenshotLoader',
204
196
  '#screenshotCloseButton',
205
197
  '.menu',
198
+ 'script',
199
+ 'noscript',
206
200
  'iframe[src*="cookiehub"]',
207
201
  'iframe[src*="google"]',
208
202
  'iframe[src*="facebook"]'
209
203
  ];
210
204
 
211
205
  removeSelectors.forEach(selector => {
212
- const elements = tempContainer.querySelectorAll(selector);
206
+ const elements = clone.querySelectorAll(selector);
213
207
  elements.forEach(el => {
214
208
  if (el && el.parentNode) {
215
209
  el.parentNode.removeChild(el);
@@ -217,22 +211,149 @@ export default class RbirdScreenshotManager {
217
211
  });
218
212
  });
219
213
 
220
- // Get the cleaned body HTML
221
- const cleanedBodyHTML = tempContainer.innerHTML;
214
+ // Download all images and convert to base64
215
+ await this.downloadAllImages(clone);
216
+
217
+ // Download all CSS and convert url() references to data URLs
218
+ await this.downloadAllCSS(clone);
222
219
 
223
220
  // Build complete HTML
224
- const html = `<!DOCTYPE html>
225
- <html>
226
- ${headHTML}
227
- <body>
228
- ${cleanedBodyHTML}
229
- </body>
230
- </html>`;
221
+ const doctype = '<!DOCTYPE html>';
222
+ const html = doctype + clone.outerHTML;
231
223
 
232
224
  console.log(`[Screenshot] Exported HTML: ${html.length} bytes`);
233
225
  return html;
234
226
  }
235
227
 
228
+ /**
229
+ * Download all images and convert to base64 data URLs
230
+ */
231
+ async downloadAllImages(clone) {
232
+ const images = clone.querySelectorAll('img');
233
+ const promises = [];
234
+
235
+ for (let i = 0; i < images.length; i++) {
236
+ const img = images[i];
237
+ if (img.src && !img.src.startsWith('data:')) {
238
+ promises.push(
239
+ this.fetchImageAsDataURL(img.src)
240
+ .then(dataURL => {
241
+ if (dataURL) {
242
+ img.src = dataURL;
243
+ }
244
+ })
245
+ .catch(err => {
246
+ console.warn('[Screenshot] Failed to load image:', img.src, err);
247
+ })
248
+ );
249
+ }
250
+ }
251
+
252
+ await Promise.all(promises);
253
+ }
254
+
255
+ /**
256
+ * Fetch image and convert to data URL
257
+ */
258
+ fetchImageAsDataURL(url) {
259
+ return new Promise((resolve, reject) => {
260
+ const xhr = new XMLHttpRequest();
261
+ xhr.onload = function() {
262
+ const reader = new FileReader();
263
+ reader.onloadend = function() {
264
+ resolve(reader.result);
265
+ };
266
+ reader.onerror = function() {
267
+ reject(new Error('Failed to read image'));
268
+ };
269
+ reader.readAsDataURL(xhr.response);
270
+ };
271
+ xhr.onerror = function() {
272
+ reject(new Error('Failed to fetch image'));
273
+ };
274
+ xhr.open('GET', url);
275
+ xhr.responseType = 'blob';
276
+ xhr.send();
277
+ });
278
+ }
279
+
280
+ /**
281
+ * Download all CSS and inline with data URLs
282
+ */
283
+ async downloadAllCSS(clone) {
284
+ const promises = [];
285
+
286
+ // Process all stylesheets
287
+ for (let i = 0; i < document.styleSheets.length; i++) {
288
+ const styleSheet = document.styleSheets[i];
289
+
290
+ try {
291
+ let cssText = '';
292
+
293
+ // Get CSS rules
294
+ if (styleSheet.cssRules) {
295
+ for (let j = 0; j < styleSheet.cssRules.length; j++) {
296
+ cssText += styleSheet.cssRules[j].cssText;
297
+ }
298
+ }
299
+
300
+ if (cssText) {
301
+ // Convert url() references to data URLs
302
+ const basePath = styleSheet.href ? styleSheet.href.substring(0, styleSheet.href.lastIndexOf('/')) : window.location.href.substring(0, window.location.href.lastIndexOf('/'));
303
+
304
+ const processedCSS = await this.loadCSSUrlResources(cssText, basePath);
305
+
306
+ // Create inline style tag
307
+ const head = clone.querySelector('head');
308
+ const styleNode = document.createElement('style');
309
+ styleNode.type = 'text/css';
310
+ styleNode.appendChild(document.createTextNode(processedCSS));
311
+ head.appendChild(styleNode);
312
+ }
313
+ } catch (err) {
314
+ console.warn('[Screenshot] Failed to process stylesheet:', err);
315
+ }
316
+ }
317
+
318
+ // Remove original link tags
319
+ const links = clone.querySelectorAll('link[rel="stylesheet"]');
320
+ links.forEach(link => link.remove());
321
+ }
322
+
323
+ /**
324
+ * Convert CSS url() references to data URLs
325
+ */
326
+ async loadCSSUrlResources(cssText, basePath) {
327
+ const urlRegex = /url\(['"]?([^'"()]+)['"]?\)/g;
328
+ const matches = [];
329
+ let match;
330
+
331
+ while ((match = urlRegex.exec(cssText)) !== null) {
332
+ matches.push(match);
333
+ }
334
+
335
+ for (const match of matches) {
336
+ const url = match[1];
337
+
338
+ // Skip if already a data URL or absolute URL
339
+ if (url.startsWith('data:') || url.startsWith('http')) {
340
+ continue;
341
+ }
342
+
343
+ try {
344
+ const fullURL = new URL(url, basePath + '/').href;
345
+ const dataURL = await this.fetchImageAsDataURL(fullURL);
346
+ if (dataURL) {
347
+ cssText = cssText.replace(match[0], `url(${dataURL})`);
348
+ }
349
+ } catch (err) {
350
+ console.warn('[Screenshot] Failed to convert CSS url:', url, err);
351
+ }
352
+ }
353
+
354
+ return cssText;
355
+ }
356
+
236
357
 
237
358
  /**
238
359
  * Compress string using gzip