releasebird-javascript-sdk 1.0.58 → 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.
- package/.claude/settings.local.json +11 -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 +11 -0
- package/backend-image-service/image-service/fonts.conf +39 -0
- package/backend-image-service/image-service/server.js +66 -13
- package/backend-image-service/test-compression.js +120 -0
- package/backend-image-service/test-deployed-service.js +126 -0
- package/backend-image-service/test-emoji-detailed.js +119 -0
- package/backend-image-service/test-emoji-simple.js +68 -0
- package/backend-image-service/test-emoji.html +51 -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/emoji-test.png +0 -0
- 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/1.0.62/index.js +1 -0
- package/published/latest/index.js +1 -1
- package/src/RbirdScreenshotManager.js +202 -154
|
@@ -25,7 +25,17 @@
|
|
|
25
25
|
"Bash(./add-ecr-policy.sh:*)",
|
|
26
26
|
"Bash(aws elasticbeanstalk describe-environment-health:*)",
|
|
27
27
|
"Bash(aws elasticbeanstalk describe-instances-health:*)",
|
|
28
|
-
"Bash(node -e:*)"
|
|
28
|
+
"Bash(node -e:*)",
|
|
29
|
+
"Bash(node test-compression.js:*)",
|
|
30
|
+
"Bash(node test-deployed-service.js:*)",
|
|
31
|
+
"Bash(aws elasticbeanstalk restart-app-server:*)",
|
|
32
|
+
"Bash(aws ec2 describe-instances:*)",
|
|
33
|
+
"Bash(node test-java-backend.js:*)",
|
|
34
|
+
"Read(//Users/christianzillmann/IdeaProjects/releasebook-backend/src/main/java/io/releasebook/backend/web/rest/papi/**)",
|
|
35
|
+
"Read(//Users/christianzillmann/IdeaProjects/releasebook-backend/**)",
|
|
36
|
+
"Bash(node test-full-flow.js:*)",
|
|
37
|
+
"Bash(node test-emoji-simple.js:*)",
|
|
38
|
+
"Bash(node:*)"
|
|
29
39
|
],
|
|
30
40
|
"deny": [],
|
|
31
41
|
"ask": []
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Nginx Configuration for Large Screenshot Payloads
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
The backend-image-service is returning `413 Request Entity Too Large` errors because nginx is blocking large HTML payloads before they reach the backend service.
|
|
5
|
+
|
|
6
|
+
## Solution
|
|
7
|
+
Configure nginx to accept larger request bodies by increasing `client_max_body_size`.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## For backend-image-service
|
|
12
|
+
|
|
13
|
+
### Option 1: Direct nginx.conf Configuration
|
|
14
|
+
|
|
15
|
+
If you're managing nginx directly, add or update the following in your nginx configuration:
|
|
16
|
+
|
|
17
|
+
```nginx
|
|
18
|
+
http {
|
|
19
|
+
# Set globally for all server blocks
|
|
20
|
+
client_max_body_size 50M; # Adjust size as needed (50MB recommended)
|
|
21
|
+
|
|
22
|
+
# ... other http config ...
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or configure it per server block:
|
|
27
|
+
|
|
28
|
+
```nginx
|
|
29
|
+
server {
|
|
30
|
+
listen 80;
|
|
31
|
+
server_name your-domain.com;
|
|
32
|
+
|
|
33
|
+
# Set for this specific server
|
|
34
|
+
client_max_body_size 50M;
|
|
35
|
+
|
|
36
|
+
location /images/screenshot/render {
|
|
37
|
+
# Or set for specific location only
|
|
38
|
+
client_max_body_size 50M;
|
|
39
|
+
proxy_pass http://backend-image-service:3000;
|
|
40
|
+
|
|
41
|
+
# Also consider these settings for large payloads:
|
|
42
|
+
proxy_read_timeout 300s;
|
|
43
|
+
proxy_connect_timeout 300s;
|
|
44
|
+
proxy_send_timeout 300s;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Option 2: Docker/Kubernetes Configuration
|
|
50
|
+
|
|
51
|
+
#### Docker Compose
|
|
52
|
+
If using docker-compose with an nginx container:
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
services:
|
|
56
|
+
nginx:
|
|
57
|
+
image: nginx:1.28.0
|
|
58
|
+
volumes:
|
|
59
|
+
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
|
60
|
+
environment:
|
|
61
|
+
- NGINX_CLIENT_MAX_BODY_SIZE=50M
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Kubernetes ConfigMap
|
|
65
|
+
If using Kubernetes:
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
apiVersion: v1
|
|
69
|
+
kind: ConfigMap
|
|
70
|
+
metadata:
|
|
71
|
+
name: nginx-config
|
|
72
|
+
data:
|
|
73
|
+
client-max-body-size: "50m"
|
|
74
|
+
---
|
|
75
|
+
apiVersion: v1
|
|
76
|
+
kind: ConfigMap
|
|
77
|
+
metadata:
|
|
78
|
+
name: nginx-configuration
|
|
79
|
+
namespace: ingress-nginx
|
|
80
|
+
data:
|
|
81
|
+
proxy-body-size: "50m"
|
|
82
|
+
client-max-body-size: "50m"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Option 3: AWS Elastic Beanstalk
|
|
86
|
+
|
|
87
|
+
If your backend-image-service runs on Elastic Beanstalk, add `.ebextensions/nginx.config`:
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
files:
|
|
91
|
+
"/etc/nginx/conf.d/proxy.conf":
|
|
92
|
+
mode: "000644"
|
|
93
|
+
owner: root
|
|
94
|
+
group: root
|
|
95
|
+
content: |
|
|
96
|
+
client_max_body_size 50M;
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Or use `.platform/nginx/conf.d/proxy.conf`:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
client_max_body_size 50M;
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Recommended Size
|
|
108
|
+
|
|
109
|
+
- **10M**: For typical web pages with moderate styling
|
|
110
|
+
- **25M**: For complex pages with many images and styles
|
|
111
|
+
- **50M**: For very large/complex pages (recommended)
|
|
112
|
+
- **100M**: If you experience issues even with 50M
|
|
113
|
+
|
|
114
|
+
## After Configuration
|
|
115
|
+
|
|
116
|
+
1. **Reload nginx** to apply changes:
|
|
117
|
+
```bash
|
|
118
|
+
sudo nginx -s reload
|
|
119
|
+
# or
|
|
120
|
+
sudo systemctl reload nginx
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
2. **Verify the configuration**:
|
|
124
|
+
```bash
|
|
125
|
+
sudo nginx -t
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
3. **Test with a large payload** to confirm the 413 error is resolved
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Frontend Changes (Already Implemented)
|
|
133
|
+
|
|
134
|
+
The JavaScript SDK now:
|
|
135
|
+
- ✅ Compresses HTML payloads using gzip before sending
|
|
136
|
+
- ✅ Sends compressed data as `htmlCompressed` (base64-encoded)
|
|
137
|
+
- ✅ Includes `compressed: true` flag in the request
|
|
138
|
+
- ✅ Falls back to uncompressed if compression is unavailable
|
|
139
|
+
- ✅ Logs payload sizes for debugging
|
|
140
|
+
|
|
141
|
+
## Backend Changes Required
|
|
142
|
+
|
|
143
|
+
The **backend-image-service** must be updated to handle compressed payloads:
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
// Example for Node.js backend
|
|
147
|
+
const zlib = require('zlib');
|
|
148
|
+
|
|
149
|
+
app.post('/images/screenshot/render', (req, res) => {
|
|
150
|
+
let html;
|
|
151
|
+
|
|
152
|
+
if (req.body.compressed && req.body.htmlCompressed) {
|
|
153
|
+
// Decompress the HTML
|
|
154
|
+
const compressedBuffer = Buffer.from(req.body.htmlCompressed, 'base64');
|
|
155
|
+
html = zlib.gunzipSync(compressedBuffer).toString('utf-8');
|
|
156
|
+
console.log(`Decompressed HTML: ${compressedBuffer.length} -> ${html.length} bytes`);
|
|
157
|
+
} else {
|
|
158
|
+
html = req.body.html;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Continue with screenshot rendering...
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Or for Java/Spring backend:
|
|
166
|
+
|
|
167
|
+
```java
|
|
168
|
+
import java.util.Base64;
|
|
169
|
+
import java.util.zip.GZIPInputStream;
|
|
170
|
+
|
|
171
|
+
@PostMapping("/images/screenshot/render")
|
|
172
|
+
public ResponseEntity<?> renderScreenshot(@RequestBody ScreenshotRequest request) {
|
|
173
|
+
String html;
|
|
174
|
+
|
|
175
|
+
if (request.isCompressed() && request.getHtmlCompressed() != null) {
|
|
176
|
+
// Decompress the HTML
|
|
177
|
+
byte[] compressed = Base64.getDecoder().decode(request.getHtmlCompressed());
|
|
178
|
+
try (GZIPInputStream gzis = new GZIPInputStream(new ByteArrayInputStream(compressed))) {
|
|
179
|
+
html = new String(gzis.readAllBytes(), StandardCharsets.UTF_8);
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
html = request.getHtml();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Continue with screenshot rendering...
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Verification
|
|
192
|
+
|
|
193
|
+
After implementing both changes:
|
|
194
|
+
|
|
195
|
+
1. Check browser console for compression logs:
|
|
196
|
+
```
|
|
197
|
+
[Screenshot] Original HTML size: 2500000 bytes
|
|
198
|
+
[Screenshot] Compression: 2500000 -> 250000 bytes (10%)
|
|
199
|
+
[Screenshot] Final payload size: 250500 bytes
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
2. Verify nginx accepts the request (no 413 error)
|
|
203
|
+
|
|
204
|
+
3. Verify backend successfully decompresses and processes the HTML
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
client_max_body_size 50M;
|
|
@@ -9,6 +9,11 @@ RUN apt-get update && apt-get install -y \
|
|
|
9
9
|
gnupg \
|
|
10
10
|
ca-certificates \
|
|
11
11
|
fonts-liberation \
|
|
12
|
+
fonts-noto-color-emoji \
|
|
13
|
+
fonts-noto-cjk \
|
|
14
|
+
fonts-noto-cjk-extra \
|
|
15
|
+
fonts-dejavu-core \
|
|
16
|
+
fontconfig \
|
|
12
17
|
libasound2 \
|
|
13
18
|
libatk-bridge2.0-0 \
|
|
14
19
|
libatk1.0-0 \
|
|
@@ -41,6 +46,12 @@ RUN npm ci --only=production
|
|
|
41
46
|
# Copy application
|
|
42
47
|
COPY server.js ./
|
|
43
48
|
|
|
49
|
+
# Copy font configuration
|
|
50
|
+
COPY fonts.conf /etc/fonts/local.conf
|
|
51
|
+
|
|
52
|
+
# Rebuild font cache
|
|
53
|
+
RUN fc-cache -fv
|
|
54
|
+
|
|
44
55
|
# Expose port
|
|
45
56
|
EXPOSE 3000
|
|
46
57
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<?xml version="1.0"?>
|
|
2
|
+
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
|
3
|
+
<fontconfig>
|
|
4
|
+
<!-- Enable emoji rendering -->
|
|
5
|
+
<match target="pattern">
|
|
6
|
+
<test qual="any" name="family">
|
|
7
|
+
<string>sans-serif</string>
|
|
8
|
+
</test>
|
|
9
|
+
<edit name="family" mode="append" binding="weak">
|
|
10
|
+
<string>Noto Color Emoji</string>
|
|
11
|
+
</edit>
|
|
12
|
+
</match>
|
|
13
|
+
|
|
14
|
+
<match target="pattern">
|
|
15
|
+
<test qual="any" name="family">
|
|
16
|
+
<string>serif</string>
|
|
17
|
+
</test>
|
|
18
|
+
<edit name="family" mode="append" binding="weak">
|
|
19
|
+
<string>Noto Color Emoji</string>
|
|
20
|
+
</edit>
|
|
21
|
+
</match>
|
|
22
|
+
|
|
23
|
+
<match target="pattern">
|
|
24
|
+
<test qual="any" name="family">
|
|
25
|
+
<string>monospace</string>
|
|
26
|
+
</test>
|
|
27
|
+
<edit name="family" mode="append" binding="weak">
|
|
28
|
+
<string>Noto Color Emoji</string>
|
|
29
|
+
</edit>
|
|
30
|
+
</match>
|
|
31
|
+
|
|
32
|
+
<!-- Always prefer color emoji -->
|
|
33
|
+
<alias>
|
|
34
|
+
<family>emoji</family>
|
|
35
|
+
<prefer>
|
|
36
|
+
<family>Noto Color Emoji</family>
|
|
37
|
+
</prefer>
|
|
38
|
+
</alias>
|
|
39
|
+
</fontconfig>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const puppeteer = require('puppeteer');
|
|
3
|
+
const zlib = require('zlib');
|
|
3
4
|
const app = express();
|
|
4
5
|
const port = process.env.PORT || 3000;
|
|
5
6
|
|
|
6
7
|
// Middleware
|
|
7
|
-
app.use(express.json({ limit: '50mb' })); // Large HTML payloads
|
|
8
|
+
app.use(express.json({ limit: '50mb' })); // Large HTML payloads (includes compressed)
|
|
8
9
|
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
|
|
9
10
|
|
|
10
11
|
// CORS
|
|
@@ -33,7 +34,10 @@ async function initBrowser() {
|
|
|
33
34
|
'--disable-accelerated-2d-canvas',
|
|
34
35
|
'--no-first-run',
|
|
35
36
|
'--no-zygote',
|
|
36
|
-
'--disable-gpu'
|
|
37
|
+
'--disable-gpu',
|
|
38
|
+
'--font-render-hinting=none',
|
|
39
|
+
'--enable-font-antialiasing',
|
|
40
|
+
'--disable-lcd-text'
|
|
37
41
|
]
|
|
38
42
|
});
|
|
39
43
|
console.log('[Puppeteer] Browser launched successfully');
|
|
@@ -41,18 +45,51 @@ async function initBrowser() {
|
|
|
41
45
|
return browser;
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Decompress base64-encoded gzip data
|
|
50
|
+
*/
|
|
51
|
+
function decompressHtml(base64CompressedHtml) {
|
|
52
|
+
try {
|
|
53
|
+
const compressedBuffer = Buffer.from(base64CompressedHtml, 'base64');
|
|
54
|
+
const decompressedBuffer = zlib.gunzipSync(compressedBuffer);
|
|
55
|
+
const html = decompressedBuffer.toString('utf-8');
|
|
56
|
+
console.log(`[Screenshot] Decompressed: ${compressedBuffer.length} -> ${html.length} bytes (${Math.round(html.length / compressedBuffer.length * 100)}%)`);
|
|
57
|
+
return html;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('[Screenshot] Decompression failed:', error);
|
|
60
|
+
throw new Error('Failed to decompress HTML: ' + error.message);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
44
64
|
/**
|
|
45
65
|
* Render HTML to screenshot
|
|
46
66
|
*/
|
|
47
67
|
app.post('/render', async (req, res) => {
|
|
48
68
|
console.log('[Screenshot] Received render request');
|
|
49
69
|
|
|
50
|
-
const { html, width, height, url } = req.body;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
70
|
+
const { html, htmlCompressed, compressed, width, height, url } = req.body;
|
|
71
|
+
|
|
72
|
+
// Handle compressed or uncompressed HTML
|
|
73
|
+
let htmlContent;
|
|
74
|
+
if (compressed && htmlCompressed) {
|
|
75
|
+
console.log('[Screenshot] Processing compressed HTML...');
|
|
76
|
+
try {
|
|
77
|
+
htmlContent = decompressHtml(htmlCompressed);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return res.status(400).json({
|
|
80
|
+
error: 'Failed to decompress HTML',
|
|
81
|
+
message: error.message
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
} else if (html) {
|
|
85
|
+
console.log('[Screenshot] Processing uncompressed HTML...');
|
|
86
|
+
htmlContent = html;
|
|
87
|
+
} else {
|
|
88
|
+
return res.status(400).json({ error: 'HTML content is required (html or htmlCompressed)' });
|
|
54
89
|
}
|
|
55
90
|
|
|
91
|
+
console.log(`[Screenshot] HTML size: ${htmlContent.length} bytes`);
|
|
92
|
+
|
|
56
93
|
const viewportWidth = width || 1920;
|
|
57
94
|
const viewportHeight = height || 1080;
|
|
58
95
|
|
|
@@ -74,21 +111,37 @@ app.post('/render', async (req, res) => {
|
|
|
74
111
|
|
|
75
112
|
console.log(`[Screenshot] Viewport set to ${viewportWidth}x${viewportHeight}`);
|
|
76
113
|
|
|
77
|
-
//
|
|
78
|
-
|
|
114
|
+
// Inject emoji font CSS into HTML
|
|
115
|
+
const emojiCSS = `
|
|
116
|
+
<style>
|
|
117
|
+
* {
|
|
118
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif !important;
|
|
119
|
+
}
|
|
120
|
+
</style>
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
// Insert emoji CSS into HTML
|
|
124
|
+
let enhancedHTML = htmlContent;
|
|
125
|
+
if (htmlContent.includes('<head>')) {
|
|
126
|
+
enhancedHTML = htmlContent.replace('<head>', '<head>' + emojiCSS);
|
|
127
|
+
} else if (htmlContent.includes('<html>')) {
|
|
128
|
+
enhancedHTML = htmlContent.replace('<html>', '<html><head>' + emojiCSS + '</head>');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Set content (using enhanced htmlContent with emoji fonts)
|
|
132
|
+
await page.setContent(enhancedHTML, {
|
|
79
133
|
waitUntil: 'networkidle0', // Wait for network to be idle
|
|
80
134
|
timeout: 30000
|
|
81
135
|
});
|
|
82
136
|
|
|
83
|
-
console.log('[Screenshot] HTML content loaded');
|
|
137
|
+
console.log('[Screenshot] HTML content loaded with emoji font support');
|
|
84
138
|
|
|
85
139
|
// Wait a bit for any animations/transitions
|
|
86
140
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
87
141
|
|
|
88
|
-
// Take screenshot
|
|
142
|
+
// Take screenshot (PNG for better emoji support)
|
|
89
143
|
const screenshot = await page.screenshot({
|
|
90
|
-
type: '
|
|
91
|
-
quality: 90,
|
|
144
|
+
type: 'png',
|
|
92
145
|
fullPage: false, // Only visible viewport
|
|
93
146
|
encoding: 'base64'
|
|
94
147
|
});
|
|
@@ -96,7 +149,7 @@ app.post('/render', async (req, res) => {
|
|
|
96
149
|
console.log('[Screenshot] Screenshot captured successfully');
|
|
97
150
|
|
|
98
151
|
// Return as data URL
|
|
99
|
-
const dataUrl = `data:image/
|
|
152
|
+
const dataUrl = `data:image/png;base64,${screenshot}`;
|
|
100
153
|
|
|
101
154
|
res.json({
|
|
102
155
|
screenshot: dataUrl,
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test script for compression/decompression
|
|
5
|
+
* Run with: node test-compression.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const zlib = require('zlib');
|
|
9
|
+
|
|
10
|
+
// Sample HTML (similar to what the frontend sends)
|
|
11
|
+
const testHtml = `
|
|
12
|
+
<!DOCTYPE html>
|
|
13
|
+
<html>
|
|
14
|
+
<head>
|
|
15
|
+
<meta charset="UTF-8">
|
|
16
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
17
|
+
<style>
|
|
18
|
+
body {
|
|
19
|
+
font-family: Arial, sans-serif;
|
|
20
|
+
padding: 20px;
|
|
21
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
22
|
+
}
|
|
23
|
+
h1 {
|
|
24
|
+
color: white;
|
|
25
|
+
text-align: center;
|
|
26
|
+
}
|
|
27
|
+
</style>
|
|
28
|
+
</head>
|
|
29
|
+
<body>
|
|
30
|
+
<h1>Test Screenshot with Compression</h1>
|
|
31
|
+
<p>This is a test HTML document to verify compression works correctly.</p>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
console.log('=== Compression Test ===\n');
|
|
37
|
+
console.log(`Original HTML size: ${testHtml.length} bytes`);
|
|
38
|
+
|
|
39
|
+
// Compress like the frontend does
|
|
40
|
+
const compressed = zlib.gzipSync(Buffer.from(testHtml, 'utf-8'));
|
|
41
|
+
const base64Compressed = compressed.toString('base64');
|
|
42
|
+
|
|
43
|
+
console.log(`Compressed size: ${compressed.length} bytes`);
|
|
44
|
+
console.log(`Base64 compressed size: ${base64Compressed.length} bytes`);
|
|
45
|
+
console.log(`Compression ratio: ${Math.round(compressed.length / testHtml.length * 100)}%`);
|
|
46
|
+
|
|
47
|
+
// Decompress like the backend does
|
|
48
|
+
const decompressedBuffer = zlib.gunzipSync(Buffer.from(base64Compressed, 'base64'));
|
|
49
|
+
const decompressed = decompressedBuffer.toString('utf-8');
|
|
50
|
+
|
|
51
|
+
console.log(`\nDecompressed size: ${decompressed.length} bytes`);
|
|
52
|
+
console.log(`Match: ${decompressed === testHtml ? '✅ SUCCESS' : '❌ FAILED'}`);
|
|
53
|
+
|
|
54
|
+
// Test with image-service endpoint
|
|
55
|
+
console.log('\n=== Testing Image Service ===\n');
|
|
56
|
+
|
|
57
|
+
const http = require('http');
|
|
58
|
+
|
|
59
|
+
const testPayload = {
|
|
60
|
+
htmlCompressed: base64Compressed,
|
|
61
|
+
compressed: true,
|
|
62
|
+
width: 1280,
|
|
63
|
+
height: 720,
|
|
64
|
+
url: 'http://test.local'
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const payloadJson = JSON.stringify(testPayload);
|
|
68
|
+
console.log(`Payload size: ${payloadJson.length} bytes`);
|
|
69
|
+
|
|
70
|
+
const options = {
|
|
71
|
+
hostname: 'localhost',
|
|
72
|
+
port: 3000,
|
|
73
|
+
path: '/render',
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: {
|
|
76
|
+
'Content-Type': 'application/json',
|
|
77
|
+
'Content-Length': Buffer.byteLength(payloadJson)
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
console.log('Sending request to http://localhost:3000/render...\n');
|
|
82
|
+
|
|
83
|
+
const req = http.request(options, (res) => {
|
|
84
|
+
console.log(`Status: ${res.statusCode}`);
|
|
85
|
+
|
|
86
|
+
let data = '';
|
|
87
|
+
|
|
88
|
+
res.on('data', (chunk) => {
|
|
89
|
+
data += chunk;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
res.on('end', () => {
|
|
93
|
+
try {
|
|
94
|
+
const response = JSON.parse(data);
|
|
95
|
+
if (response.screenshot) {
|
|
96
|
+
console.log('✅ Screenshot generated successfully!');
|
|
97
|
+
console.log(`Screenshot size: ${response.screenshot.length} characters`);
|
|
98
|
+
console.log(`Image dimensions: ${response.width}x${response.height}`);
|
|
99
|
+
} else if (response.error) {
|
|
100
|
+
console.error('❌ Error:', response.error);
|
|
101
|
+
if (response.message) {
|
|
102
|
+
console.error('Message:', response.message);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.error('❌ Failed to parse response:', e.message);
|
|
107
|
+
console.error('Raw response:', data);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
req.on('error', (error) => {
|
|
113
|
+
console.error('❌ Request failed:', error.message);
|
|
114
|
+
console.error('\n⚠️ Make sure the image service is running:');
|
|
115
|
+
console.error(' cd backend-image-service/image-service');
|
|
116
|
+
console.error(' npm start');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
req.write(payloadJson);
|
|
120
|
+
req.end();
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test deployed image service with compression
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const zlib = require('zlib');
|
|
8
|
+
const https = require('https');
|
|
9
|
+
|
|
10
|
+
const SERVICE_URL = 'https://image.releasebird.com';
|
|
11
|
+
|
|
12
|
+
// Test HTML
|
|
13
|
+
const testHtml = `
|
|
14
|
+
<!DOCTYPE html>
|
|
15
|
+
<html>
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="UTF-8">
|
|
18
|
+
<style>
|
|
19
|
+
body {
|
|
20
|
+
font-family: Arial, sans-serif;
|
|
21
|
+
padding: 40px;
|
|
22
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
23
|
+
color: white;
|
|
24
|
+
}
|
|
25
|
+
h1 {
|
|
26
|
+
text-align: center;
|
|
27
|
+
font-size: 48px;
|
|
28
|
+
}
|
|
29
|
+
.box {
|
|
30
|
+
background: rgba(255,255,255,0.1);
|
|
31
|
+
padding: 20px;
|
|
32
|
+
border-radius: 10px;
|
|
33
|
+
margin: 20px 0;
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<h1>✅ Deployment Successful!</h1>
|
|
39
|
+
<div class="box">
|
|
40
|
+
<h2>Backend Image Service</h2>
|
|
41
|
+
<p>Version: v20251028-193852</p>
|
|
42
|
+
<p>Compression: Enabled</p>
|
|
43
|
+
</div>
|
|
44
|
+
</body>
|
|
45
|
+
</html>
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
console.log('=== Testing Deployed Image Service ===\n');
|
|
49
|
+
console.log(`Service URL: ${SERVICE_URL}`);
|
|
50
|
+
console.log(`Original HTML: ${testHtml.length} bytes\n`);
|
|
51
|
+
|
|
52
|
+
// Compress HTML
|
|
53
|
+
const compressed = zlib.gzipSync(Buffer.from(testHtml, 'utf-8'));
|
|
54
|
+
const base64Compressed = compressed.toString('base64');
|
|
55
|
+
|
|
56
|
+
console.log(`Compressed: ${compressed.length} bytes (${Math.round(compressed.length / testHtml.length * 100)}%)`);
|
|
57
|
+
console.log(`Base64: ${base64Compressed.length} bytes\n`);
|
|
58
|
+
|
|
59
|
+
// Prepare request
|
|
60
|
+
const requestData = {
|
|
61
|
+
htmlCompressed: base64Compressed,
|
|
62
|
+
compressed: true,
|
|
63
|
+
width: 1280,
|
|
64
|
+
height: 720,
|
|
65
|
+
url: 'http://test.deployment.local'
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const requestBody = JSON.stringify(requestData);
|
|
69
|
+
console.log(`Request payload: ${requestBody.length} bytes\n`);
|
|
70
|
+
|
|
71
|
+
const url = new URL(`${SERVICE_URL}/render`);
|
|
72
|
+
const options = {
|
|
73
|
+
hostname: url.hostname,
|
|
74
|
+
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
75
|
+
path: url.pathname,
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: {
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
'Content-Length': Buffer.byteLength(requestBody)
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
console.log('Sending request...\n');
|
|
84
|
+
|
|
85
|
+
const req = (url.protocol === 'https:' ? https : require('http')).request(options, (res) => {
|
|
86
|
+
console.log(`Status: ${res.statusCode} ${res.statusMessage}`);
|
|
87
|
+
console.log(`Headers:`, res.headers);
|
|
88
|
+
console.log('');
|
|
89
|
+
|
|
90
|
+
let data = '';
|
|
91
|
+
|
|
92
|
+
res.on('data', (chunk) => {
|
|
93
|
+
data += chunk;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
res.on('end', () => {
|
|
97
|
+
try {
|
|
98
|
+
const response = JSON.parse(data);
|
|
99
|
+
|
|
100
|
+
if (response.screenshot) {
|
|
101
|
+
console.log('✅ Screenshot generated successfully!');
|
|
102
|
+
console.log(`Screenshot data URL length: ${response.screenshot.length} characters`);
|
|
103
|
+
console.log(`Dimensions: ${response.width}x${response.height}`);
|
|
104
|
+
console.log('\n=== Test PASSED ===');
|
|
105
|
+
} else if (response.error) {
|
|
106
|
+
console.error('❌ Error from service:', response.error);
|
|
107
|
+
if (response.message) {
|
|
108
|
+
console.error('Message:', response.message);
|
|
109
|
+
}
|
|
110
|
+
console.log('\n=== Test FAILED ===');
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
console.error('❌ Failed to parse response:', e.message);
|
|
114
|
+
console.error('Raw response (first 500 chars):', data.substring(0, 500));
|
|
115
|
+
console.log('\n=== Test FAILED ===');
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
req.on('error', (error) => {
|
|
121
|
+
console.error('❌ Request failed:', error.message);
|
|
122
|
+
console.log('\n=== Test FAILED ===');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
req.write(requestBody);
|
|
126
|
+
req.end();
|