yo-bug 0.1.2 → 0.1.4
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/dist/src/server/html-injector.d.ts +1 -0
- package/dist/src/server/html-injector.d.ts.map +1 -1
- package/dist/src/server/html-injector.js +8 -4
- package/dist/src/server/html-injector.js.map +1 -1
- package/dist/src/server/proxy-server.d.ts.map +1 -1
- package/dist/src/server/proxy-server.js +54 -22
- package/dist/src/server/proxy-server.js.map +1 -1
- package/package.json +1 -1
- package/src/server/proxy-server.ts +57 -27
- package/src/server/html-injector.ts +0 -41
|
@@ -2,6 +2,7 @@ import type { IncomingMessage, ServerResponse } from 'http';
|
|
|
2
2
|
/**
|
|
3
3
|
* Middleware that intercepts HTML responses from the proxy
|
|
4
4
|
* and injects the vibe-feedback SDK script before </body>.
|
|
5
|
+
* Only intercepts 2xx HTML responses — redirects and errors pass through.
|
|
5
6
|
*/
|
|
6
7
|
export declare function createHtmlInjector(proxyRes: IncomingMessage, res: ServerResponse): {
|
|
7
8
|
onData(chunk: Buffer): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html-injector.d.ts","sourceRoot":"","sources":["../../../src/server/html-injector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAI5D
|
|
1
|
+
{"version":3,"file":"html-injector.d.ts","sourceRoot":"","sources":["../../../src/server/html-injector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAI5D;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc;kBAmB/D,MAAM;;SAiBvB"}
|
|
@@ -2,14 +2,19 @@ const INJECT_SCRIPT = `<script src="/vibe-feedback.js"></script>`;
|
|
|
2
2
|
/**
|
|
3
3
|
* Middleware that intercepts HTML responses from the proxy
|
|
4
4
|
* and injects the vibe-feedback SDK script before </body>.
|
|
5
|
+
* Only intercepts 2xx HTML responses — redirects and errors pass through.
|
|
5
6
|
*/
|
|
6
7
|
export function createHtmlInjector(proxyRes, res) {
|
|
8
|
+
const statusCode = proxyRes.statusCode || 200;
|
|
7
9
|
const contentType = proxyRes.headers['content-type'] || '';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
// Only inject into 2xx HTML responses
|
|
11
|
+
// Don't touch redirects (3xx), errors (4xx/5xx), or non-HTML
|
|
12
|
+
if (statusCode < 200 || statusCode >= 300 || !contentType.includes('text/html')) {
|
|
13
|
+
return null;
|
|
10
14
|
}
|
|
11
15
|
// Copy all response headers except content-length (we'll modify the body)
|
|
12
|
-
|
|
16
|
+
const headers = Object.fromEntries(Object.entries(proxyRes.headers).filter(([key]) => key !== 'content-length'));
|
|
17
|
+
res.writeHead(statusCode, headers);
|
|
13
18
|
const chunks = [];
|
|
14
19
|
return {
|
|
15
20
|
onData(chunk) {
|
|
@@ -17,7 +22,6 @@ export function createHtmlInjector(proxyRes, res) {
|
|
|
17
22
|
},
|
|
18
23
|
onEnd() {
|
|
19
24
|
let html = Buffer.concat(chunks).toString('utf-8');
|
|
20
|
-
// Inject before </body> or at end if no </body>
|
|
21
25
|
if (html.includes('</body>')) {
|
|
22
26
|
html = html.replace('</body>', `${INJECT_SCRIPT}\n</body>`);
|
|
23
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html-injector.js","sourceRoot":"","sources":["../../../src/server/html-injector.ts"],"names":[],"mappings":"AAEA,MAAM,aAAa,GAAG,2CAA2C,CAAC;AAElE
|
|
1
|
+
{"version":3,"file":"html-injector.js","sourceRoot":"","sources":["../../../src/server/html-injector.ts"],"names":[],"mappings":"AAEA,MAAM,aAAa,GAAG,2CAA2C,CAAC;AAElE;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAyB,EAAE,GAAmB;IAC/E,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,IAAI,GAAG,CAAC;IAC9C,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IAE3D,sCAAsC;IACtC,6DAA6D;IAC7D,IAAI,UAAU,GAAG,GAAG,IAAI,UAAU,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAChF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0EAA0E;IAC1E,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAChC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,gBAAgB,CAAC,CAC7E,CAAC;IACF,GAAG,CAAC,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAEnC,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,OAAO;QACL,MAAM,CAAC,KAAa;YAClB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;QACD,KAAK;YACH,IAAI,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAEnD,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC7B,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,aAAa,WAAW,CAAC,CAAC;YAC9D,CAAC;iBAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBACpC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,aAAa,WAAW,CAAC,CAAC;YAC9D,CAAC;iBAAM,CAAC;gBACN,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;YAC/B,CAAC;YAED,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy-server.d.ts","sourceRoot":"","sources":["../../../src/server/proxy-server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUpD,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"proxy-server.d.ts","sourceRoot":"","sources":["../../../src/server/proxy-server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUpD,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAwGlD;AAED,wBAAgB,eAAe,IAAI,IAAI,CAStC;AAED,wBAAgB,cAAc,IAAI,OAAO,CAExC"}
|
|
@@ -3,8 +3,8 @@ import cors from 'cors';
|
|
|
3
3
|
import httpProxy from 'http-proxy';
|
|
4
4
|
import { createFeedbackRouter } from './feedback-api.js';
|
|
5
5
|
import { createSdkRouter } from './sdk-serve.js';
|
|
6
|
-
import { createHtmlInjector } from './html-injector.js';
|
|
7
6
|
const PROXY_PORT = 3695;
|
|
7
|
+
const INJECT_SCRIPT = `<script src="/vibe-feedback.js"></script>`;
|
|
8
8
|
let server = null;
|
|
9
9
|
let proxy = null;
|
|
10
10
|
export async function startProxyServer(targetPort, store) {
|
|
@@ -14,45 +14,77 @@ export async function startProxyServer(targetPort, store) {
|
|
|
14
14
|
const targetUrl = `http://localhost:${targetPort}`;
|
|
15
15
|
const proxyUrl = `http://localhost:${PROXY_PORT}`;
|
|
16
16
|
const app = express();
|
|
17
|
-
// CORS for SDK to submit feedback
|
|
18
17
|
app.use(cors());
|
|
19
|
-
// Parse JSON for feedback API
|
|
20
18
|
app.use(express.json({ limit: '10mb' }));
|
|
21
|
-
//
|
|
19
|
+
// Our own routes — served directly, not proxied
|
|
22
20
|
app.use(createSdkRouter());
|
|
23
|
-
// Feedback API routes
|
|
24
21
|
app.use(createFeedbackRouter(store));
|
|
25
|
-
// Create proxy
|
|
22
|
+
// Create proxy — let it handle responses by default (no selfHandleResponse)
|
|
26
23
|
proxy = httpProxy.createProxyServer({
|
|
27
24
|
target: targetUrl,
|
|
28
|
-
ws: true,
|
|
25
|
+
ws: true,
|
|
29
26
|
changeOrigin: true,
|
|
30
27
|
});
|
|
31
28
|
proxy.on('error', (_err, _req, res) => {
|
|
32
29
|
if (res && 'writeHead' in res) {
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
try {
|
|
31
|
+
res.writeHead?.(502, { 'Content-Type': 'text/plain' });
|
|
32
|
+
res.end?.('Dev server not responding');
|
|
33
|
+
}
|
|
34
|
+
catch { }
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
|
-
//
|
|
37
|
+
// Inject SDK into HTML responses using the modifyResponse approach
|
|
38
|
+
// We check Accept header on REQUEST to decide if we need to intercept
|
|
39
|
+
app.all('*', (req, res) => {
|
|
40
|
+
const accept = req.headers['accept'] || '';
|
|
41
|
+
const isHtmlRequest = accept.includes('text/html');
|
|
42
|
+
if (isHtmlRequest) {
|
|
43
|
+
// Potentially an HTML page — intercept to inject SDK
|
|
44
|
+
proxy.web(req, res, { selfHandleResponse: true });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Static asset (JS/CSS/images/fonts/etc) — let proxy handle directly
|
|
48
|
+
proxy.web(req, res);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
// Handle HTML injection in proxyRes (only fires for selfHandleResponse requests)
|
|
38
52
|
proxy.on('proxyRes', (proxyRes, _req, res) => {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
53
|
+
const statusCode = proxyRes.statusCode || 200;
|
|
54
|
+
const contentType = proxyRes.headers['content-type'] || '';
|
|
55
|
+
// Only inject into 2xx HTML responses
|
|
56
|
+
if (statusCode >= 200 && statusCode < 300 && contentType.includes('text/html')) {
|
|
57
|
+
// Buffer response, inject SDK, send
|
|
58
|
+
const chunks = [];
|
|
59
|
+
proxyRes.on('data', (chunk) => chunks.push(chunk));
|
|
60
|
+
proxyRes.on('end', () => {
|
|
61
|
+
let html = Buffer.concat(chunks).toString('utf-8');
|
|
62
|
+
if (html.includes('</body>')) {
|
|
63
|
+
html = html.replace('</body>', `${INJECT_SCRIPT}\n</body>`);
|
|
64
|
+
}
|
|
65
|
+
else if (html.includes('</html>')) {
|
|
66
|
+
html = html.replace('</html>', `${INJECT_SCRIPT}\n</html>`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
html += `\n${INJECT_SCRIPT}`;
|
|
70
|
+
}
|
|
71
|
+
// Copy headers except content-length (body size changed)
|
|
72
|
+
const headers = { ...proxyRes.headers };
|
|
73
|
+
delete headers['content-length'];
|
|
74
|
+
// Remove content-encoding since we decoded the buffer as utf-8
|
|
75
|
+
delete headers['content-encoding'];
|
|
76
|
+
delete headers['transfer-encoding'];
|
|
77
|
+
headers['content-length'] = String(Buffer.byteLength(html));
|
|
78
|
+
res.writeHead(statusCode, headers);
|
|
79
|
+
res.end(html);
|
|
80
|
+
});
|
|
44
81
|
}
|
|
45
82
|
else {
|
|
46
|
-
//
|
|
83
|
+
// Redirect or non-HTML — pass through as-is
|
|
84
|
+
res.writeHead(statusCode, proxyRes.headers);
|
|
47
85
|
proxyRes.pipe(res);
|
|
48
86
|
}
|
|
49
87
|
});
|
|
50
|
-
// All other requests → proxy to dev server
|
|
51
|
-
app.all('*', (req, res) => {
|
|
52
|
-
proxy.web(req, res, {
|
|
53
|
-
selfHandleResponse: true, // We handle response in proxyRes event
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
88
|
// Start server
|
|
57
89
|
await new Promise((resolve, reject) => {
|
|
58
90
|
server = app.listen(PROXY_PORT, () => resolve());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy-server.js","sourceRoot":"","sources":["../../../src/server/proxy-server.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,SAAS,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"proxy-server.js","sourceRoot":"","sources":["../../../src/server/proxy-server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import cors from 'cors';
|
|
3
3
|
import httpProxy from 'http-proxy';
|
|
4
|
-
import type { Server } from 'http';
|
|
4
|
+
import type { Server, IncomingMessage, ServerResponse } from 'http';
|
|
5
5
|
import { FeedbackStore } from '../storage/store.js';
|
|
6
6
|
import { createFeedbackRouter } from './feedback-api.js';
|
|
7
7
|
import { createSdkRouter } from './sdk-serve.js';
|
|
8
|
-
import { createHtmlInjector } from './html-injector.js';
|
|
9
8
|
|
|
10
9
|
const PROXY_PORT = 3695;
|
|
10
|
+
const INJECT_SCRIPT = `<script src="/vibe-feedback.js"></script>`;
|
|
11
11
|
|
|
12
12
|
let server: Server | null = null;
|
|
13
13
|
let proxy: httpProxy | null = null;
|
|
@@ -24,51 +24,81 @@ export async function startProxyServer(
|
|
|
24
24
|
const proxyUrl = `http://localhost:${PROXY_PORT}`;
|
|
25
25
|
|
|
26
26
|
const app = express();
|
|
27
|
-
|
|
28
|
-
// CORS for SDK to submit feedback
|
|
29
27
|
app.use(cors());
|
|
30
|
-
|
|
31
|
-
// Parse JSON for feedback API
|
|
32
28
|
app.use(express.json({ limit: '10mb' }));
|
|
33
29
|
|
|
34
|
-
//
|
|
30
|
+
// Our own routes — served directly, not proxied
|
|
35
31
|
app.use(createSdkRouter());
|
|
36
|
-
|
|
37
|
-
// Feedback API routes
|
|
38
32
|
app.use(createFeedbackRouter(store));
|
|
39
33
|
|
|
40
|
-
// Create proxy
|
|
34
|
+
// Create proxy — let it handle responses by default (no selfHandleResponse)
|
|
41
35
|
proxy = httpProxy.createProxyServer({
|
|
42
36
|
target: targetUrl,
|
|
43
|
-
ws: true,
|
|
37
|
+
ws: true,
|
|
44
38
|
changeOrigin: true,
|
|
45
39
|
});
|
|
46
40
|
|
|
47
41
|
proxy.on('error', (_err, _req, res) => {
|
|
48
42
|
if (res && 'writeHead' in res) {
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
try {
|
|
44
|
+
(res as any).writeHead?.(502, { 'Content-Type': 'text/plain' });
|
|
45
|
+
(res as any).end?.('Dev server not responding');
|
|
46
|
+
} catch {}
|
|
51
47
|
}
|
|
52
48
|
});
|
|
53
49
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
// Inject SDK into HTML responses using the modifyResponse approach
|
|
51
|
+
// We check Accept header on REQUEST to decide if we need to intercept
|
|
52
|
+
app.all('*', (req, res) => {
|
|
53
|
+
const accept = req.headers['accept'] || '';
|
|
54
|
+
const isHtmlRequest = accept.includes('text/html');
|
|
55
|
+
|
|
56
|
+
if (isHtmlRequest) {
|
|
57
|
+
// Potentially an HTML page — intercept to inject SDK
|
|
58
|
+
proxy!.web(req, res, { selfHandleResponse: true });
|
|
61
59
|
} else {
|
|
62
|
-
//
|
|
63
|
-
|
|
60
|
+
// Static asset (JS/CSS/images/fonts/etc) — let proxy handle directly
|
|
61
|
+
proxy!.web(req, res);
|
|
64
62
|
}
|
|
65
63
|
});
|
|
66
64
|
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
// Handle HTML injection in proxyRes (only fires for selfHandleResponse requests)
|
|
66
|
+
proxy.on('proxyRes', (proxyRes, _req, res) => {
|
|
67
|
+
const statusCode = proxyRes.statusCode || 200;
|
|
68
|
+
const contentType = proxyRes.headers['content-type'] || '';
|
|
69
|
+
|
|
70
|
+
// Only inject into 2xx HTML responses
|
|
71
|
+
if (statusCode >= 200 && statusCode < 300 && contentType.includes('text/html')) {
|
|
72
|
+
// Buffer response, inject SDK, send
|
|
73
|
+
const chunks: Buffer[] = [];
|
|
74
|
+
proxyRes.on('data', (chunk) => chunks.push(chunk));
|
|
75
|
+
proxyRes.on('end', () => {
|
|
76
|
+
let html = Buffer.concat(chunks).toString('utf-8');
|
|
77
|
+
|
|
78
|
+
if (html.includes('</body>')) {
|
|
79
|
+
html = html.replace('</body>', `${INJECT_SCRIPT}\n</body>`);
|
|
80
|
+
} else if (html.includes('</html>')) {
|
|
81
|
+
html = html.replace('</html>', `${INJECT_SCRIPT}\n</html>`);
|
|
82
|
+
} else {
|
|
83
|
+
html += `\n${INJECT_SCRIPT}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Copy headers except content-length (body size changed)
|
|
87
|
+
const headers = { ...proxyRes.headers };
|
|
88
|
+
delete headers['content-length'];
|
|
89
|
+
// Remove content-encoding since we decoded the buffer as utf-8
|
|
90
|
+
delete headers['content-encoding'];
|
|
91
|
+
delete headers['transfer-encoding'];
|
|
92
|
+
headers['content-length'] = String(Buffer.byteLength(html));
|
|
93
|
+
|
|
94
|
+
res.writeHead(statusCode, headers);
|
|
95
|
+
res.end(html);
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
// Redirect or non-HTML — pass through as-is
|
|
99
|
+
res.writeHead(statusCode, proxyRes.headers);
|
|
100
|
+
proxyRes.pipe(res);
|
|
101
|
+
}
|
|
72
102
|
});
|
|
73
103
|
|
|
74
104
|
// Start server
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { IncomingMessage, ServerResponse } from 'http';
|
|
2
|
-
|
|
3
|
-
const INJECT_SCRIPT = `<script src="/vibe-feedback.js"></script>`;
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Middleware that intercepts HTML responses from the proxy
|
|
7
|
-
* and injects the vibe-feedback SDK script before </body>.
|
|
8
|
-
*/
|
|
9
|
-
export function createHtmlInjector(proxyRes: IncomingMessage, res: ServerResponse) {
|
|
10
|
-
const contentType = proxyRes.headers['content-type'] || '';
|
|
11
|
-
if (!contentType.includes('text/html')) {
|
|
12
|
-
return null; // Not HTML, don't intercept
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Copy all response headers except content-length (we'll modify the body)
|
|
16
|
-
res.writeHead(proxyRes.statusCode || 200, Object.fromEntries(
|
|
17
|
-
Object.entries(proxyRes.headers).filter(([key]) => key !== 'content-length')
|
|
18
|
-
));
|
|
19
|
-
|
|
20
|
-
const chunks: Buffer[] = [];
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
onData(chunk: Buffer) {
|
|
24
|
-
chunks.push(chunk);
|
|
25
|
-
},
|
|
26
|
-
onEnd() {
|
|
27
|
-
let html = Buffer.concat(chunks).toString('utf-8');
|
|
28
|
-
|
|
29
|
-
// Inject before </body> or at end if no </body>
|
|
30
|
-
if (html.includes('</body>')) {
|
|
31
|
-
html = html.replace('</body>', `${INJECT_SCRIPT}\n</body>`);
|
|
32
|
-
} else if (html.includes('</html>')) {
|
|
33
|
-
html = html.replace('</html>', `${INJECT_SCRIPT}\n</html>`);
|
|
34
|
-
} else {
|
|
35
|
-
html += `\n${INJECT_SCRIPT}`;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
res.end(html);
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
}
|