@chidchanun/bcp 0.1.0
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/CHANGELOG.md +49 -0
- package/LICENSE +21 -0
- package/README.md +241 -0
- package/docs/caching.md +76 -0
- package/docs/configuration.md +97 -0
- package/docs/deployment.md +74 -0
- package/docs/getting-started.md +82 -0
- package/docs/middleware.md +58 -0
- package/docs/releasing.md +299 -0
- package/docs/routing.md +103 -0
- package/docs/security.md +57 -0
- package/package.json +68 -0
- package/packages/bundler/src/client-islands.ts +1457 -0
- package/packages/bundler/src/incremental-context.ts +206 -0
- package/packages/bundler/src/index.ts +1991 -0
- package/packages/bundler/src/module-graph.ts +317 -0
- package/packages/bundler/src/partial-hydration.ts +414 -0
- package/packages/bundler/src/production.ts +974 -0
- package/packages/bundler/src/server-production-middleware.ts +447 -0
- package/packages/bundler/src/server-production.ts +1193 -0
- package/packages/bundler/src/special-files.ts +131 -0
- package/packages/cache/src/index.ts +761 -0
- package/packages/cli/bin/bcp.mjs +93 -0
- package/packages/cli/src/args.ts +305 -0
- package/packages/cli/src/bootstrap.ts +514 -0
- package/packages/cli/src/index.ts +504 -0
- package/packages/cli/src/version.ts +45 -0
- package/packages/client/src/cache.ts +11 -0
- package/packages/client/src/config.ts +18 -0
- package/packages/client/src/error-boundary.tsx +149 -0
- package/packages/client/src/hydration.ts +3 -0
- package/packages/client/src/index.tsx +57 -0
- package/packages/client/src/islands.tsx +315 -0
- package/packages/client/src/metadata.ts +281 -0
- package/packages/client/src/navigation-loading.ts +52 -0
- package/packages/client/src/navigation-state.ts +80 -0
- package/packages/client/src/not-found.ts +34 -0
- package/packages/client/src/persistent-layout-runtime.ts +273 -0
- package/packages/client/src/router-v2.tsx +969 -0
- package/packages/client/src/router.tsx +1 -0
- package/packages/config/src/index.ts +1038 -0
- package/packages/env/src/index.ts +593 -0
- package/packages/router/src/advanced-router.ts +1032 -0
- package/packages/router/src/index.ts +1 -0
- package/packages/server/src/compression.ts +249 -0
- package/packages/server/src/dev-document-metadata.ts +154 -0
- package/packages/server/src/dev-hmr.ts +225 -0
- package/packages/server/src/index.ts +2265 -0
- package/packages/server/src/metadata.ts +478 -0
- package/packages/server/src/middleware-dev-server.ts +260 -0
- package/packages/server/src/middleware-loader.ts +140 -0
- package/packages/server/src/middleware-proxy.ts +516 -0
- package/packages/server/src/middleware.ts +704 -0
- package/packages/server/src/navigation-payload.ts +471 -0
- package/packages/server/src/production-server.ts +1746 -0
- package/packages/server/src/response-cache-proxy.ts +828 -0
- package/packages/server/src/security-proxy.ts +406 -0
- package/packages/server/src/security.ts +451 -0
- package/packages/server/src/standalone-production-runtime-v2.ts +2047 -0
- package/packages/server/src/standalone-production-runtime-v3.ts +250 -0
- package/packages/server/src/standalone-production-runtime-v4.ts +289 -0
- package/packages/server/src/standalone-production-runtime-v5.ts +289 -0
- package/packages/server/src/standalone-production-runtime.ts +1951 -0
- package/packages/server/src/standalone-production-server.ts +6 -0
- package/packages/server/src/static-assets.ts +262 -0
- package/packages/server/src/static-dev-server.ts +854 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./advanced-router.js";
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type * as http from "node:http";
|
|
2
|
+
import {
|
|
3
|
+
brotliCompressSync,
|
|
4
|
+
constants,
|
|
5
|
+
gzipSync,
|
|
6
|
+
} from "node:zlib";
|
|
7
|
+
|
|
8
|
+
export type ContentEncoding =
|
|
9
|
+
| "br"
|
|
10
|
+
| "gzip"
|
|
11
|
+
| null;
|
|
12
|
+
|
|
13
|
+
const MIN_COMPRESSION_BYTES =
|
|
14
|
+
1024;
|
|
15
|
+
|
|
16
|
+
export function selectContentEncoding(
|
|
17
|
+
req: http.IncomingMessage,
|
|
18
|
+
contentType: string,
|
|
19
|
+
contentLength: number,
|
|
20
|
+
available: {
|
|
21
|
+
br?: boolean;
|
|
22
|
+
gzip?: boolean;
|
|
23
|
+
} = {
|
|
24
|
+
br: true,
|
|
25
|
+
gzip: true,
|
|
26
|
+
}
|
|
27
|
+
): ContentEncoding {
|
|
28
|
+
if (
|
|
29
|
+
process.env
|
|
30
|
+
.BCP_COMPRESSION ===
|
|
31
|
+
"0"
|
|
32
|
+
) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
contentLength <
|
|
38
|
+
MIN_COMPRESSION_BYTES ||
|
|
39
|
+
!isCompressibleContentType(
|
|
40
|
+
contentType
|
|
41
|
+
)
|
|
42
|
+
) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const header =
|
|
47
|
+
req.headers[
|
|
48
|
+
"accept-encoding"
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const value =
|
|
52
|
+
Array.isArray(header)
|
|
53
|
+
? header.join(",")
|
|
54
|
+
: header ?? "";
|
|
55
|
+
|
|
56
|
+
const brQuality =
|
|
57
|
+
available.br === false
|
|
58
|
+
? 0
|
|
59
|
+
: getEncodingQuality(
|
|
60
|
+
value,
|
|
61
|
+
"br"
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const gzipQuality =
|
|
65
|
+
available.gzip === false
|
|
66
|
+
? 0
|
|
67
|
+
: getEncodingQuality(
|
|
68
|
+
value,
|
|
69
|
+
"gzip"
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (
|
|
73
|
+
brQuality > 0 &&
|
|
74
|
+
brQuality >= gzipQuality
|
|
75
|
+
) {
|
|
76
|
+
return "br";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
gzipQuality > 0
|
|
81
|
+
) {
|
|
82
|
+
return "gzip";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function compressDynamicContent(
|
|
89
|
+
content: Buffer,
|
|
90
|
+
encoding: ContentEncoding
|
|
91
|
+
): Buffer {
|
|
92
|
+
if (
|
|
93
|
+
encoding === "br"
|
|
94
|
+
) {
|
|
95
|
+
return brotliCompressSync(
|
|
96
|
+
content,
|
|
97
|
+
{
|
|
98
|
+
params: {
|
|
99
|
+
[constants.BROTLI_PARAM_QUALITY]:
|
|
100
|
+
4,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (
|
|
107
|
+
encoding === "gzip"
|
|
108
|
+
) {
|
|
109
|
+
return gzipSync(
|
|
110
|
+
content,
|
|
111
|
+
{
|
|
112
|
+
level: 6,
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return content;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function isCompressibleContentType(
|
|
121
|
+
contentType: string
|
|
122
|
+
): boolean {
|
|
123
|
+
const normalized =
|
|
124
|
+
contentType
|
|
125
|
+
.split(";", 1)[0]
|
|
126
|
+
.trim()
|
|
127
|
+
.toLowerCase();
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
normalized.startsWith(
|
|
131
|
+
"text/"
|
|
132
|
+
) ||
|
|
133
|
+
normalized ===
|
|
134
|
+
"application/javascript" ||
|
|
135
|
+
normalized ===
|
|
136
|
+
"application/json" ||
|
|
137
|
+
normalized ===
|
|
138
|
+
"application/xml" ||
|
|
139
|
+
normalized ===
|
|
140
|
+
"application/manifest+json" ||
|
|
141
|
+
normalized ===
|
|
142
|
+
"image/svg+xml"
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function appendVary(
|
|
147
|
+
existing: string | undefined,
|
|
148
|
+
value: string
|
|
149
|
+
): string {
|
|
150
|
+
if (!existing) {
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const values =
|
|
155
|
+
existing
|
|
156
|
+
.split(",")
|
|
157
|
+
.map(
|
|
158
|
+
(item) =>
|
|
159
|
+
item.trim()
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
values.some(
|
|
164
|
+
(item) =>
|
|
165
|
+
item.toLowerCase() ===
|
|
166
|
+
value.toLowerCase()
|
|
167
|
+
)
|
|
168
|
+
) {
|
|
169
|
+
return existing;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return `${existing}, ${value}`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getEncodingQuality(
|
|
176
|
+
header: string,
|
|
177
|
+
encoding: string
|
|
178
|
+
): number {
|
|
179
|
+
let wildcardQuality:
|
|
180
|
+
number | null =
|
|
181
|
+
null;
|
|
182
|
+
|
|
183
|
+
for (
|
|
184
|
+
const item
|
|
185
|
+
of header.split(",")
|
|
186
|
+
) {
|
|
187
|
+
const [
|
|
188
|
+
tokenPart,
|
|
189
|
+
...parameters
|
|
190
|
+
] =
|
|
191
|
+
item.split(";");
|
|
192
|
+
|
|
193
|
+
const token =
|
|
194
|
+
tokenPart
|
|
195
|
+
.trim()
|
|
196
|
+
.toLowerCase();
|
|
197
|
+
|
|
198
|
+
if (!token) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let quality = 1;
|
|
203
|
+
|
|
204
|
+
for (
|
|
205
|
+
const parameter
|
|
206
|
+
of parameters
|
|
207
|
+
) {
|
|
208
|
+
const match =
|
|
209
|
+
/^\s*q\s*=\s*(\d(?:\.\d+)?)\s*$/i.exec(
|
|
210
|
+
parameter
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
if (match) {
|
|
214
|
+
const parsed =
|
|
215
|
+
Number(
|
|
216
|
+
match[1]
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
quality =
|
|
220
|
+
Number.isFinite(
|
|
221
|
+
parsed
|
|
222
|
+
)
|
|
223
|
+
? Math.max(
|
|
224
|
+
0,
|
|
225
|
+
Math.min(
|
|
226
|
+
1,
|
|
227
|
+
parsed
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
: 0;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
token === encoding
|
|
236
|
+
) {
|
|
237
|
+
return quality;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (
|
|
241
|
+
token === "*"
|
|
242
|
+
) {
|
|
243
|
+
wildcardQuality =
|
|
244
|
+
quality;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return wildcardQuality ?? 0;
|
|
249
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMetadataHeadEntries,
|
|
3
|
+
type ResolvedMetadata,
|
|
4
|
+
} from "../../client/src/metadata.js";
|
|
5
|
+
|
|
6
|
+
export function applyDevDocumentMetadata(
|
|
7
|
+
html: string,
|
|
8
|
+
metadata: ResolvedMetadata
|
|
9
|
+
): string {
|
|
10
|
+
const title =
|
|
11
|
+
`<title>${escapeHtmlText(
|
|
12
|
+
metadata.title
|
|
13
|
+
)}</title>`;
|
|
14
|
+
|
|
15
|
+
let result =
|
|
16
|
+
/<title\b[^>]*>[\s\S]*?<\/title>/i.test(
|
|
17
|
+
html
|
|
18
|
+
)
|
|
19
|
+
? html.replace(
|
|
20
|
+
/<title\b[^>]*>[\s\S]*?<\/title>/i,
|
|
21
|
+
title
|
|
22
|
+
)
|
|
23
|
+
: html.replace(
|
|
24
|
+
/<\/head>/i,
|
|
25
|
+
` ${title}\n </head>`
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* Keep this transformation idempotent. This lets the outer dev server
|
|
30
|
+
* remain correct if the inner renderer starts emitting metadata itself
|
|
31
|
+
* in a future implementation.
|
|
32
|
+
*/
|
|
33
|
+
result =
|
|
34
|
+
result.replace(
|
|
35
|
+
/\s*<(?:meta|link)\b[^>]*\bdata-bcp-metadata=(?:"[^"]*"|'[^']*')[^>]*\/?\s*>/gi,
|
|
36
|
+
""
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const entries =
|
|
40
|
+
createMetadataHeadEntries(
|
|
41
|
+
metadata
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
entries.length > 0
|
|
46
|
+
) {
|
|
47
|
+
const head =
|
|
48
|
+
entries
|
|
49
|
+
.map(
|
|
50
|
+
(entry) => {
|
|
51
|
+
const attributes = [
|
|
52
|
+
[
|
|
53
|
+
"data-bcp-metadata",
|
|
54
|
+
entry.key,
|
|
55
|
+
] as const,
|
|
56
|
+
...Object.entries(
|
|
57
|
+
entry.attributes
|
|
58
|
+
),
|
|
59
|
+
]
|
|
60
|
+
.map(
|
|
61
|
+
([name, value]) =>
|
|
62
|
+
`${name}="${escapeHtmlAttribute(
|
|
63
|
+
value
|
|
64
|
+
)}"`
|
|
65
|
+
)
|
|
66
|
+
.join(" ");
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
entry.tag === "meta"
|
|
70
|
+
? `<meta ${attributes} />`
|
|
71
|
+
: `<link ${attributes} />`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
.join("\n ");
|
|
76
|
+
|
|
77
|
+
result =
|
|
78
|
+
result.replace(
|
|
79
|
+
/<\/head>/i,
|
|
80
|
+
` ${head}\n </head>`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return attachFrameworkMetadata(
|
|
85
|
+
result,
|
|
86
|
+
metadata
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function attachFrameworkMetadata(
|
|
91
|
+
html: string,
|
|
92
|
+
metadata: ResolvedMetadata
|
|
93
|
+
): string {
|
|
94
|
+
return html.replace(
|
|
95
|
+
/(<script\s+id="__BCP_DATA__"\s+type="application\/json"\s*>)([\s\S]*?)(<\/script>)/i,
|
|
96
|
+
(
|
|
97
|
+
match,
|
|
98
|
+
openTag: string,
|
|
99
|
+
serialized: string,
|
|
100
|
+
closeTag: string
|
|
101
|
+
) => {
|
|
102
|
+
try {
|
|
103
|
+
const data =
|
|
104
|
+
JSON.parse(
|
|
105
|
+
serialized.trim()
|
|
106
|
+
) as Record<
|
|
107
|
+
string,
|
|
108
|
+
unknown
|
|
109
|
+
>;
|
|
110
|
+
|
|
111
|
+
data.metadata =
|
|
112
|
+
metadata;
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
`${openTag}${serializeJsonForHtml(
|
|
116
|
+
data
|
|
117
|
+
)}${closeTag}`
|
|
118
|
+
);
|
|
119
|
+
} catch {
|
|
120
|
+
return match;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function serializeJsonForHtml(
|
|
127
|
+
value: unknown
|
|
128
|
+
): string {
|
|
129
|
+
return JSON.stringify(
|
|
130
|
+
value
|
|
131
|
+
)
|
|
132
|
+
.replace(/</g, "\\u003c")
|
|
133
|
+
.replace(/\u2028/g, "\\u2028")
|
|
134
|
+
.replace(/\u2029/g, "\\u2029");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function escapeHtmlText(
|
|
138
|
+
value: string
|
|
139
|
+
): string {
|
|
140
|
+
return value
|
|
141
|
+
.replace(/&/g, "&")
|
|
142
|
+
.replace(/</g, "<")
|
|
143
|
+
.replace(/>/g, ">");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function escapeHtmlAttribute(
|
|
147
|
+
value: string
|
|
148
|
+
): string {
|
|
149
|
+
return escapeHtmlText(
|
|
150
|
+
value
|
|
151
|
+
)
|
|
152
|
+
.replace(/"/g, """)
|
|
153
|
+
.replace(/'/g, "'");
|
|
154
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import type * as http from "node:http";
|
|
2
|
+
|
|
3
|
+
export const DEV_EVENTS_PATH =
|
|
4
|
+
"/_bcp/dev-events";
|
|
5
|
+
|
|
6
|
+
const ENV_RESTART_COOKIE =
|
|
7
|
+
"__bcp_env_restart";
|
|
8
|
+
|
|
9
|
+
export class DevHmrServer {
|
|
10
|
+
private readonly clients =
|
|
11
|
+
new Set<
|
|
12
|
+
http.ServerResponse
|
|
13
|
+
>();
|
|
14
|
+
|
|
15
|
+
connect(
|
|
16
|
+
req: http.IncomingMessage,
|
|
17
|
+
res: http.ServerResponse
|
|
18
|
+
) {
|
|
19
|
+
const restartToken =
|
|
20
|
+
process.env
|
|
21
|
+
.BCP_DEV_ENV_RESTART ??
|
|
22
|
+
"0";
|
|
23
|
+
|
|
24
|
+
const shouldReload =
|
|
25
|
+
restartToken !== "0" &&
|
|
26
|
+
readCookie(
|
|
27
|
+
req,
|
|
28
|
+
ENV_RESTART_COOKIE
|
|
29
|
+
) !== restartToken;
|
|
30
|
+
|
|
31
|
+
const headers:
|
|
32
|
+
Record<string, string> = {
|
|
33
|
+
"Content-Type":
|
|
34
|
+
"text/event-stream; charset=utf-8",
|
|
35
|
+
"Cache-Control":
|
|
36
|
+
"no-cache, no-store",
|
|
37
|
+
Connection:
|
|
38
|
+
"keep-alive",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (shouldReload) {
|
|
42
|
+
headers[
|
|
43
|
+
"Set-Cookie"
|
|
44
|
+
] =
|
|
45
|
+
`${ENV_RESTART_COOKIE}=${encodeURIComponent(restartToken)}; Path=/; SameSite=Lax`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
res.writeHead(
|
|
49
|
+
200,
|
|
50
|
+
headers
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
res.write(
|
|
54
|
+
": bcp-hmr-connected\n\n"
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
this.clients.add(
|
|
58
|
+
res
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (shouldReload) {
|
|
62
|
+
this.emitTo(
|
|
63
|
+
res,
|
|
64
|
+
"reload",
|
|
65
|
+
{
|
|
66
|
+
version:
|
|
67
|
+
Date.now(),
|
|
68
|
+
reason:
|
|
69
|
+
"environment",
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const cleanup =
|
|
75
|
+
() => {
|
|
76
|
+
this.clients.delete(
|
|
77
|
+
res
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
req.once(
|
|
82
|
+
"close",
|
|
83
|
+
cleanup
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
res.once(
|
|
87
|
+
"close",
|
|
88
|
+
cleanup
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
refresh(
|
|
93
|
+
version: number,
|
|
94
|
+
routes: string[]
|
|
95
|
+
) {
|
|
96
|
+
this.emit(
|
|
97
|
+
"refresh",
|
|
98
|
+
{
|
|
99
|
+
version,
|
|
100
|
+
routes,
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
reload(
|
|
106
|
+
version: number
|
|
107
|
+
) {
|
|
108
|
+
this.emit(
|
|
109
|
+
"reload",
|
|
110
|
+
{
|
|
111
|
+
version,
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
close() {
|
|
117
|
+
for (
|
|
118
|
+
const client
|
|
119
|
+
of this.clients
|
|
120
|
+
) {
|
|
121
|
+
client.end();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.clients.clear();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private emit(
|
|
128
|
+
event: string,
|
|
129
|
+
data: unknown
|
|
130
|
+
) {
|
|
131
|
+
for (
|
|
132
|
+
const client
|
|
133
|
+
of this.clients
|
|
134
|
+
) {
|
|
135
|
+
if (
|
|
136
|
+
client.destroyed ||
|
|
137
|
+
client.writableEnded
|
|
138
|
+
) {
|
|
139
|
+
this.clients.delete(
|
|
140
|
+
client
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.emitTo(
|
|
147
|
+
client,
|
|
148
|
+
event,
|
|
149
|
+
data
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private emitTo(
|
|
155
|
+
client: http.ServerResponse,
|
|
156
|
+
event: string,
|
|
157
|
+
data: unknown
|
|
158
|
+
): void {
|
|
159
|
+
const payload =
|
|
160
|
+
JSON.stringify(
|
|
161
|
+
data
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
client.write(
|
|
165
|
+
`event: ${event}\n`
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
client.write(
|
|
169
|
+
`data: ${payload}\n\n`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function readCookie(
|
|
175
|
+
req: http.IncomingMessage,
|
|
176
|
+
name: string
|
|
177
|
+
): string | null {
|
|
178
|
+
const header =
|
|
179
|
+
req.headers.cookie;
|
|
180
|
+
|
|
181
|
+
if (!header) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (
|
|
186
|
+
const item
|
|
187
|
+
of header.split(";")
|
|
188
|
+
) {
|
|
189
|
+
const separator =
|
|
190
|
+
item.indexOf("=");
|
|
191
|
+
|
|
192
|
+
if (separator < 0) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const key =
|
|
197
|
+
item
|
|
198
|
+
.slice(
|
|
199
|
+
0,
|
|
200
|
+
separator
|
|
201
|
+
)
|
|
202
|
+
.trim();
|
|
203
|
+
|
|
204
|
+
if (key !== name) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const value =
|
|
209
|
+
item
|
|
210
|
+
.slice(
|
|
211
|
+
separator + 1
|
|
212
|
+
)
|
|
213
|
+
.trim();
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
return decodeURIComponent(
|
|
217
|
+
value
|
|
218
|
+
);
|
|
219
|
+
} catch {
|
|
220
|
+
return value;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return null;
|
|
225
|
+
}
|