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.
- package/.claude/settings.local.json +9 -1
- package/NGINX_CONFIG_GUIDE.md +204 -0
- package/backend-image-service/image-service/.ebextensions/04_nginx.config +12 -0
- package/backend-image-service/image-service/.platform/nginx/conf.d/client_max_body_size.conf +1 -0
- package/backend-image-service/image-service/Dockerfile +3 -0
- package/backend-image-service/image-service/server.js +41 -7
- package/backend-image-service/test-compression.js +120 -0
- package/backend-image-service/test-deployed-service.js +126 -0
- package/backend-image-service/test-full-flow.js +226 -0
- package/backend-image-service/test-java-backend.js +72 -0
- package/backend-image-service/test-svg-debug.html +28 -0
- package/build/index.js +1 -1
- package/package.json +1 -1
- package/published/1.0./index.js +1 -0
- package/published/1.0.59/index.js +1 -0
- package/published/1.0.60/index.js +1 -0
- package/published/1.0.61/index.js +1 -0
- package/published/latest/index.js +1 -1
- package/src/RbirdScreenshotManager.js +99 -172
|
@@ -181,207 +181,123 @@ export default class RbirdScreenshotManager {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
/**
|
|
184
|
-
* Export the current page HTML
|
|
185
|
-
* Returns HTML
|
|
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
|
|
189
|
-
|
|
190
|
-
//
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// IMPORTANT: Keep the markerBoundary
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
//
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
238
|
+
* Compress string using gzip
|
|
239
|
+
* Returns base64-encoded compressed data
|
|
298
240
|
*/
|
|
299
|
-
async
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
}
|
|
325
|
-
console.warn('[Screenshot] Error processing image:', e);
|
|
326
|
-
}
|
|
327
|
-
resolve();
|
|
328
|
-
});
|
|
329
|
-
promises.push(promise);
|
|
330
|
-
});
|
|
254
|
+
});
|
|
331
255
|
|
|
332
|
-
|
|
333
|
-
|
|
256
|
+
const compressedStream = stream.pipeThrough(new CompressionStream('gzip'));
|
|
257
|
+
const reader = compressedStream.getReader();
|
|
258
|
+
const chunks = [];
|
|
334
259
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
260
|
+
while (true) {
|
|
261
|
+
const { done, value } = await reader.read();
|
|
262
|
+
if (done) break;
|
|
263
|
+
chunks.push(value);
|
|
264
|
+
}
|
|
340
265
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
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(`
|
|
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
|
-
|
|
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
|
|