releasebird-javascript-sdk 1.0.58 → 1.0.61

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.
@@ -181,207 +181,123 @@ export default class RbirdScreenshotManager {
181
181
  }
182
182
 
183
183
  /**
184
- * Export the current page HTML with all inline styles
185
- * Returns HTML string with computed styles inlined
184
+ * Export the current page HTML for server-side rendering
185
+ * Returns complete HTML document with minimal modifications
186
186
  */
187
187
  async exportHTML() {
188
- console.log('[Screenshot] Exporting HTML with inline styles...');
189
-
190
- // Clone the document
191
- const clonedDoc = document.cloneNode(true);
192
- const clonedBody = clonedDoc.body;
193
-
194
- // IMPORTANT: Keep the markerBoundary and its SVG content!
195
- // We need to preserve the SVG drawings
196
- const markerBoundary = document.getElementById('markerBoundary');
197
- const clonedMarkerBoundary = clonedBody.querySelector('#markerBoundary');
198
-
199
- // Copy SVG content from original to cloned
200
- if (markerBoundary && clonedMarkerBoundary) {
201
- const svg = markerBoundary.querySelector('svg');
202
- const clonedSvg = clonedMarkerBoundary.querySelector('svg');
203
- if (svg && clonedSvg) {
204
- clonedSvg.innerHTML = svg.innerHTML;
205
- // Copy all SVG attributes
206
- Array.from(svg.attributes).forEach(attr => {
207
- clonedSvg.setAttribute(attr.name, attr.value);
208
- });
209
- }
210
- }
211
-
212
- // Remove elements we don't want in the screenshot (but keep markerBoundary!)
213
- const elementsToRemove = [
214
- clonedBody.querySelector('#rbirdScreenshotLoader'),
215
- clonedBody.querySelector('#screenshotCloseButton'),
216
- clonedBody.querySelector('.menu'), // SVGEditor toolbar
217
- ...Array.from(clonedBody.querySelectorAll('iframe[src*="cookiehub"]')),
218
- ...Array.from(clonedBody.querySelectorAll('iframe[src*="google"]')),
219
- ...Array.from(clonedBody.querySelectorAll('iframe[src*="facebook"]'))
188
+ console.log('[Screenshot] Exporting HTML for server-side rendering...');
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;
200
+
201
+ // Remove UI elements that shouldn't be in the screenshot
202
+ const removeSelectors = [
203
+ '#rbirdScreenshotLoader',
204
+ '#screenshotCloseButton',
205
+ '.menu',
206
+ 'iframe[src*="cookiehub"]',
207
+ 'iframe[src*="google"]',
208
+ 'iframe[src*="facebook"]'
220
209
  ];
221
210
 
222
- elementsToRemove.forEach(el => {
223
- if (el && el.parentNode) {
224
- el.parentNode.removeChild(el);
225
- }
211
+ removeSelectors.forEach(selector => {
212
+ const elements = tempContainer.querySelectorAll(selector);
213
+ elements.forEach(el => {
214
+ if (el && el.parentNode) {
215
+ el.parentNode.removeChild(el);
216
+ }
217
+ });
226
218
  });
227
219
 
228
- // Inline all computed styles
229
- this.inlineStyles(clonedBody, document.body);
230
-
231
- // Convert all images to base64
232
- await this.inlineImages(clonedBody);
233
-
234
- // Get all stylesheets content (inline all CSS)
235
- const styles = await this.extractAllStyles();
220
+ // Get the cleaned body HTML
221
+ const cleanedBodyHTML = tempContainer.innerHTML;
236
222
 
237
223
  // Build complete HTML
238
- const html = `
239
- <!DOCTYPE html>
224
+ const html = `<!DOCTYPE html>
240
225
  <html>
241
- <head>
242
- <meta charset="UTF-8">
243
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
244
- <style>${styles}</style>
245
- </head>
226
+ ${headHTML}
246
227
  <body>
247
- ${clonedBody.innerHTML}
228
+ ${cleanedBodyHTML}
248
229
  </body>
249
230
  </html>`;
250
231
 
232
+ console.log(`[Screenshot] Exported HTML: ${html.length} bytes`);
251
233
  return html;
252
234
  }
253
235
 
254
- /**
255
- * Recursively inline computed styles for all elements
256
- */
257
- inlineStyles(clonedElement, originalElement) {
258
- if (!clonedElement || !originalElement) return;
259
-
260
- // Get computed style from original element
261
- const computedStyle = window.getComputedStyle(originalElement);
262
-
263
- // Create inline style string with important properties
264
- const importantProps = [
265
- 'display', 'position', 'top', 'left', 'right', 'bottom',
266
- 'width', 'height', 'margin', 'padding', 'border',
267
- 'background', 'background-color', 'background-image',
268
- 'color', 'font-family', 'font-size', 'font-weight',
269
- 'text-align', 'line-height', 'opacity', 'z-index',
270
- 'transform', 'transition', 'box-shadow', 'border-radius'
271
- ];
272
-
273
- let inlineStyle = '';
274
- importantProps.forEach(prop => {
275
- const value = computedStyle.getPropertyValue(prop);
276
- if (value) {
277
- inlineStyle += `${prop}: ${value}; `;
278
- }
279
- });
280
-
281
- if (inlineStyle && clonedElement.setAttribute) {
282
- clonedElement.setAttribute('style', inlineStyle);
283
- }
284
-
285
- // Recursively process children
286
- const clonedChildren = Array.from(clonedElement.children || []);
287
- const originalChildren = Array.from(originalElement.children || []);
288
-
289
- clonedChildren.forEach((clonedChild, index) => {
290
- if (originalChildren[index]) {
291
- this.inlineStyles(clonedChild, originalChildren[index]);
292
- }
293
- });
294
- }
295
236
 
296
237
  /**
297
- * Convert all images to base64 data URLs
238
+ * Compress string using gzip
239
+ * Returns base64-encoded compressed data
298
240
  */
299
- async inlineImages(element) {
300
- const images = element.querySelectorAll('img');
301
- const promises = [];
302
-
303
- images.forEach(img => {
304
- const promise = new Promise((resolve) => {
305
- try {
306
- const canvas = document.createElement('canvas');
307
- const ctx = canvas.getContext('2d');
308
-
309
- // Find original image element
310
- const originalImg = document.querySelector(`img[src="${img.getAttribute('src')}"]`);
311
-
312
- if (originalImg && originalImg.complete) {
313
- canvas.width = originalImg.naturalWidth;
314
- canvas.height = originalImg.naturalHeight;
315
- ctx.drawImage(originalImg, 0, 0);
316
-
317
- try {
318
- const dataUrl = canvas.toDataURL('image/png');
319
- img.setAttribute('src', dataUrl);
320
- } catch (e) {
321
- console.warn('[Screenshot] Could not convert image to base64 (CORS?):', img.src);
322
- }
241
+ async compressString(str) {
242
+ try {
243
+ // Convert string to Uint8Array
244
+ const encoder = new TextEncoder();
245
+ const data = encoder.encode(str);
246
+
247
+ // Use CompressionStream API (modern browsers)
248
+ if (typeof CompressionStream !== 'undefined') {
249
+ const stream = new ReadableStream({
250
+ start(controller) {
251
+ controller.enqueue(data);
252
+ controller.close();
323
253
  }
324
- } catch (e) {
325
- console.warn('[Screenshot] Error processing image:', e);
326
- }
327
- resolve();
328
- });
329
- promises.push(promise);
330
- });
254
+ });
331
255
 
332
- await Promise.all(promises);
333
- }
256
+ const compressedStream = stream.pipeThrough(new CompressionStream('gzip'));
257
+ const reader = compressedStream.getReader();
258
+ const chunks = [];
334
259
 
335
- /**
336
- * Extract ALL stylesheet content
337
- */
338
- async extractAllStyles() {
339
- let styles = '';
260
+ while (true) {
261
+ const { done, value } = await reader.read();
262
+ if (done) break;
263
+ chunks.push(value);
264
+ }
340
265
 
341
- // Get inline style elements
342
- const styleElements = document.querySelectorAll('style');
343
- styleElements.forEach(style => {
344
- styles += style.textContent + '\n';
345
- });
266
+ // Combine chunks
267
+ const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
268
+ const compressed = new Uint8Array(totalLength);
269
+ let offset = 0;
270
+ for (const chunk of chunks) {
271
+ compressed.set(chunk, offset);
272
+ offset += chunk.length;
273
+ }
346
274
 
347
- // Try to fetch stylesheet content
348
- const links = document.querySelectorAll('link[rel="stylesheet"]');
349
- const fetchPromises = [];
350
-
351
- links.forEach(link => {
352
- const href = link.getAttribute('href');
353
- if (href) {
354
- // Convert relative URLs to absolute
355
- const absoluteUrl = new URL(href, window.location.href).href;
356
-
357
- // Try to fetch stylesheet content
358
- const promise = fetch(absoluteUrl)
359
- .then(response => response.text())
360
- .then(cssText => {
361
- styles += `\n/* ${absoluteUrl} */\n${cssText}\n`;
362
- })
363
- .catch(error => {
364
- console.warn(`[Screenshot] Could not fetch stylesheet ${absoluteUrl}:`, error);
365
- // If CORS blocks us, try to import it
366
- styles += `@import url('${absoluteUrl}');\n`;
367
- });
368
-
369
- fetchPromises.push(promise);
275
+ // Convert to base64
276
+ const base64 = btoa(String.fromCharCode(...compressed));
277
+ console.log(`[Screenshot] Compression: ${data.length} -> ${compressed.length} bytes (${Math.round(compressed.length / data.length * 100)}%)`);
278
+ return base64;
279
+ } else {
280
+ // Fallback: no compression available
281
+ console.warn('[Screenshot] CompressionStream not available, sending uncompressed');
282
+ return null;
370
283
  }
371
- });
372
-
373
- await Promise.all(fetchPromises);
374
-
375
- return styles;
284
+ } catch (e) {
285
+ console.error('[Screenshot] Compression failed:', e);
286
+ return null;
287
+ }
376
288
  }
377
289
 
378
290
  /**
379
291
  * Send HTML to backend for rendering
380
292
  * Returns Promise with screenshot data URL
381
293
  */
382
- renderScreenshotOnBackend(html) {
383
- return new Promise((resolve, reject) => {
294
+ async renderScreenshotOnBackend(html) {
295
+ return new Promise(async (resolve, reject) => {
384
296
  console.log('[Screenshot] Sending HTML to backend...');
297
+ console.log(`[Screenshot] Original HTML size: ${html.length} bytes`);
298
+
299
+ // Try to compress the HTML
300
+ const compressedHtml = await this.compressString(html);
385
301
 
386
302
  const http = new XMLHttpRequest();
387
303
  http.open('POST', `${API}/images/screenshot/render`);
@@ -409,19 +325,30 @@ export default class RbirdScreenshotManager {
409
325
  }
410
326
  } else {
411
327
  console.error('[Screenshot] Backend request failed:', http.status, http.responseText);
412
- reject(new Error(`Backend request failed with status ${http.status}`));
328
+ reject(new Error(`Failed to render screenshot: ${http.status} : ${JSON.stringify(http.responseText)}`));
413
329
  }
414
330
  }
415
331
  };
416
332
 
417
333
  const requestData = {
418
- html: html,
419
334
  width: window.innerWidth,
420
335
  height: window.innerHeight,
421
336
  url: window.location.href
422
337
  };
423
338
 
424
- http.send(JSON.stringify(requestData));
339
+ // Add either compressed or uncompressed HTML
340
+ if (compressedHtml) {
341
+ requestData.htmlCompressed = compressedHtml;
342
+ requestData.compressed = true;
343
+ } else {
344
+ requestData.html = html;
345
+ requestData.compressed = false;
346
+ }
347
+
348
+ const payload = JSON.stringify(requestData);
349
+ console.log(`[Screenshot] Final payload size: ${payload.length} bytes`);
350
+
351
+ http.send(payload);
425
352
  });
426
353
  }
427
354