@vesk/compiler 0.0.1
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/README.md +23 -0
- package/dist/acorn-ts-plugin/index.d.ts +482 -0
- package/dist/acorn-ts-plugin/index.d.ts.map +1 -0
- package/dist/acorn-ts-plugin/index.js +5028 -0
- package/dist/actions.d.ts +22 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +167 -0
- package/dist/api-routes.d.ts +30 -0
- package/dist/api-routes.d.ts.map +1 -0
- package/dist/api-routes.js +350 -0
- package/dist/client-codegen.d.ts +19 -0
- package/dist/client-codegen.d.ts.map +1 -0
- package/dist/client-codegen.js +1523 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +106 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +210 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/ir-generator.d.ts +10 -0
- package/dist/ir-generator.d.ts.map +1 -0
- package/dist/ir-generator.js +1062 -0
- package/dist/ir.d.ts +160 -0
- package/dist/ir.d.ts.map +1 -0
- package/dist/ir.js +219 -0
- package/dist/middleware.d.ts +15 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +183 -0
- package/dist/package.json +15 -0
- package/dist/parser.d.ts +38 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +242 -0
- package/dist/router.d.ts +31 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +451 -0
- package/dist/scan.d.ts +106 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +438 -0
- package/dist/server-codegen.d.ts +5 -0
- package/dist/server-codegen.d.ts.map +1 -0
- package/dist/server-codegen.js +4 -0
- package/dist/server-cookies.d.ts +2 -0
- package/dist/server-cookies.d.ts.map +1 -0
- package/dist/server-cookies.js +15 -0
- package/dist/server-head.d.ts +10 -0
- package/dist/server-head.d.ts.map +1 -0
- package/dist/server-head.js +289 -0
- package/dist/server-jsgen.d.ts +6 -0
- package/dist/server-jsgen.d.ts.map +1 -0
- package/dist/server-jsgen.js +446 -0
- package/dist/server-render.d.ts +16 -0
- package/dist/server-render.d.ts.map +1 -0
- package/dist/server-render.js +397 -0
- package/dist/server-utils.d.ts +93 -0
- package/dist/server-utils.d.ts.map +1 -0
- package/dist/server-utils.js +747 -0
- package/dist/ssr-store.d.ts +4 -0
- package/dist/ssr-store.d.ts.map +1 -0
- package/dist/ssr-store.js +29 -0
- package/dist/strip-ts.d.ts +25 -0
- package/dist/strip-ts.d.ts.map +1 -0
- package/dist/strip-ts.js +171 -0
- package/dist/tokens.d.ts +50 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +334 -0
- package/dist/typecheck.d.ts +19 -0
- package/dist/typecheck.d.ts.map +1 -0
- package/dist/typecheck.js +365 -0
- package/dist/types.d.ts +147 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/vesk-plugin.d.ts +53 -0
- package/dist/vesk-plugin.d.ts.map +1 -0
- package/dist/vesk-plugin.js +413 -0
- package/dist/vsk-imports.d.ts +30 -0
- package/dist/vsk-imports.d.ts.map +1 -0
- package/dist/vsk-imports.js +141 -0
- package/dist/vsk-tsx.d.ts +3 -0
- package/dist/vsk-tsx.d.ts.map +1 -0
- package/dist/vsk-tsx.js +604 -0
- package/package.json +52 -0
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
import { StaticNode, TextNode } from '@vesk/compiler/src/ir';
|
|
2
|
+
import * as __defaultRuntimeModule from '@vesk/runtime/src/index-server';
|
|
3
|
+
import { parse } from '@vesk/compiler/src/parser';
|
|
4
|
+
import { generateIR } from '@vesk/compiler/src/ir-generator';
|
|
5
|
+
import { htmlTagName, htmlTagEnd } from '@vesk/compiler/src/scan';
|
|
6
|
+
import { importModuleTarget, extractImportNames } from '@vesk/compiler/src/tokens';
|
|
7
|
+
const VOID_ELEMENTS = new Set([
|
|
8
|
+
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr',
|
|
9
|
+
]);
|
|
10
|
+
const RAW_TEXT_ELEMENTS = new Set(['style', 'script', 'title']);
|
|
11
|
+
export let __vskHydrate = false;
|
|
12
|
+
export let __vskId = 0;
|
|
13
|
+
export let __vskForceClaim = false;
|
|
14
|
+
export let __vskImportedNames = null;
|
|
15
|
+
export function resetVskState(hydrate = false) {
|
|
16
|
+
__vskHydrate = hydrate;
|
|
17
|
+
__vskId = 0;
|
|
18
|
+
__vskForceClaim = false;
|
|
19
|
+
}
|
|
20
|
+
export function setVskHydrate(v) {
|
|
21
|
+
__vskHydrate = v;
|
|
22
|
+
}
|
|
23
|
+
export function setVskImportedNames(names) {
|
|
24
|
+
__vskImportedNames = names;
|
|
25
|
+
}
|
|
26
|
+
export function setVskForceClaim(v) {
|
|
27
|
+
__vskForceClaim = v;
|
|
28
|
+
}
|
|
29
|
+
export function takeVskForceClaim() {
|
|
30
|
+
const v = __vskForceClaim;
|
|
31
|
+
__vskForceClaim = false;
|
|
32
|
+
return v;
|
|
33
|
+
}
|
|
34
|
+
export function nextVskId() {
|
|
35
|
+
return __vskId++;
|
|
36
|
+
}
|
|
37
|
+
export function prettifyHtml(html) {
|
|
38
|
+
let out = '';
|
|
39
|
+
let depth = 0;
|
|
40
|
+
let inRaw = false;
|
|
41
|
+
let rawTag = '';
|
|
42
|
+
for (const token of htmlTokenize(html)) {
|
|
43
|
+
if (!token)
|
|
44
|
+
continue;
|
|
45
|
+
if (!token.startsWith('<')) {
|
|
46
|
+
const text = token.trim();
|
|
47
|
+
if (inRaw) {
|
|
48
|
+
out += token;
|
|
49
|
+
}
|
|
50
|
+
else if (text) {
|
|
51
|
+
out += '\t'.repeat(depth) + text + '\n';
|
|
52
|
+
}
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const isClose = token[1] === '/';
|
|
56
|
+
const isComment = token.startsWith('<!--');
|
|
57
|
+
if (isComment) {
|
|
58
|
+
out += '\t'.repeat(depth) + token + '\n';
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const tagName = htmlTagName(token);
|
|
62
|
+
if (!tagName) {
|
|
63
|
+
out += token;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const tag = tagName.toLowerCase();
|
|
67
|
+
const selfClose = token.endsWith('/>');
|
|
68
|
+
if (isClose) {
|
|
69
|
+
if (inRaw && tag === rawTag)
|
|
70
|
+
inRaw = false;
|
|
71
|
+
depth = Math.max(0, depth - 1);
|
|
72
|
+
out += '\t'.repeat(depth) + token + '\n';
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
out += '\t'.repeat(depth) + token + '\n';
|
|
76
|
+
if (!selfClose && !VOID_ELEMENTS.has(tag)) {
|
|
77
|
+
depth++;
|
|
78
|
+
if (RAW_TEXT_ELEMENTS.has(tag)) {
|
|
79
|
+
inRaw = true;
|
|
80
|
+
rawTag = tag;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return out.trimEnd();
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Splits HTML into text and tag tokens. `>` inside quoted attribute values
|
|
89
|
+
* does not end a tag, so `alt="a > b"` and `data-x="1>0"` are kept intact
|
|
90
|
+
* (the old `split(/(<[^>]+>)/)` approach could not do this).
|
|
91
|
+
*/
|
|
92
|
+
function htmlTokenize(html) {
|
|
93
|
+
const tokens = [];
|
|
94
|
+
let i = 0;
|
|
95
|
+
while (i < html.length) {
|
|
96
|
+
if (html[i] === '<') {
|
|
97
|
+
if (html.startsWith('<!--', i)) {
|
|
98
|
+
const close = html.indexOf('-->', i + 4);
|
|
99
|
+
const end = close === -1 ? html.length : close + 3;
|
|
100
|
+
tokens.push(html.slice(i, end));
|
|
101
|
+
i = end;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const end = htmlTagEnd(html, i);
|
|
105
|
+
if (end === -1) {
|
|
106
|
+
tokens.push(html.slice(i));
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
tokens.push(html.slice(i, end));
|
|
110
|
+
i = end;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const next = html.indexOf('<', i);
|
|
114
|
+
if (next === -1) {
|
|
115
|
+
tokens.push(html.slice(i));
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
tokens.push(html.slice(i, next));
|
|
119
|
+
i = next;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return tokens;
|
|
123
|
+
}
|
|
124
|
+
export function isStatic(body) {
|
|
125
|
+
for (const node of body) {
|
|
126
|
+
if (node instanceof StaticNode) {
|
|
127
|
+
if (!isStatic(node.children))
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
else if (!(node instanceof TextNode))
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
export function escapeHtml(str) {
|
|
136
|
+
return str
|
|
137
|
+
.replace(/&/g, '&')
|
|
138
|
+
.replace(/</g, '<')
|
|
139
|
+
.replace(/>/g, '>')
|
|
140
|
+
.replace(/"/g, '"')
|
|
141
|
+
.replace(/'/g, ''');
|
|
142
|
+
}
|
|
143
|
+
const SECRET_PATTERNS = [
|
|
144
|
+
{ pattern: /(sk_live_[a-zA-Z0-9]{20,})/g, replace: 'sk_live_***' },
|
|
145
|
+
{ pattern: /(sk_test_[a-zA-Z0-9]{20,})/g, replace: 'sk_test_***' },
|
|
146
|
+
{ pattern: /(ghp_[a-zA-Z0-9]{36,})/g, replace: 'ghp_***' },
|
|
147
|
+
{ pattern: /(gho_[a-zA-Z0-9]{36,})/g, replace: 'gho_***' },
|
|
148
|
+
{ pattern: /(xox[bpsra]-[a-zA-Z0-9-]{20,})/g, replace: 'xox-***' },
|
|
149
|
+
{ pattern: /(Bearer\s+)[a-zA-Z0-9._-]{20,}/g, replace: '$1***' },
|
|
150
|
+
{ pattern: /(Authorization:\s*Basic\s+)[a-zA-Z0-9+/=]{20,}/gi, replace: '$1***' },
|
|
151
|
+
{ pattern: /(-----BEGIN\s+(?:RSA\s+)?PRIVATE KEY-----)[\s\S]*?(-----END\s+(?:RSA\s+)?PRIVATE KEY-----)/g, replace: '$1***$2' },
|
|
152
|
+
{ pattern: /(['"]?(?:api[_-]?key|secret|password|token|auth|private[_-]?key|access[_-]?key|session[_-]?secret)[, }\]'"*]*[:=]\s*['"]?)(?!.*\*\*\*)([^'"\s,}\]]{8,})(['"]?)/gi, replace: '$1***$3' },
|
|
153
|
+
];
|
|
154
|
+
export function redactLog(str) {
|
|
155
|
+
if (!str || typeof str !== 'string')
|
|
156
|
+
return str;
|
|
157
|
+
let result = str;
|
|
158
|
+
for (const { pattern, replace } of SECRET_PATTERNS) {
|
|
159
|
+
result = result.replace(pattern, replace);
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
const _origConsoleError = console.error;
|
|
164
|
+
const _origConsoleLog = console.log;
|
|
165
|
+
let _redactEnabled = true;
|
|
166
|
+
export function setRedactLogging(enabled) {
|
|
167
|
+
_redactEnabled = enabled;
|
|
168
|
+
}
|
|
169
|
+
if (typeof console !== 'undefined') {
|
|
170
|
+
console.error = function (...args) {
|
|
171
|
+
_origConsoleError.apply(console, args.map(a => typeof a === 'string' && _redactEnabled ? redactLog(a) : a));
|
|
172
|
+
};
|
|
173
|
+
console.log = function (...args) {
|
|
174
|
+
_origConsoleLog.apply(console, args.map(a => typeof a === 'string' && _redactEnabled ? redactLog(a) : a));
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
export function raw(value) {
|
|
178
|
+
if (value == null)
|
|
179
|
+
return '';
|
|
180
|
+
return String(value);
|
|
181
|
+
}
|
|
182
|
+
const __csrfSecrets = new Map();
|
|
183
|
+
function csrfSecret(host) {
|
|
184
|
+
if (!host)
|
|
185
|
+
host = 'localhost';
|
|
186
|
+
if (!__csrfSecrets.has(host)) {
|
|
187
|
+
__csrfSecrets.set(host, Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2));
|
|
188
|
+
}
|
|
189
|
+
return __csrfSecrets.get(host);
|
|
190
|
+
}
|
|
191
|
+
function csrfHmac(value, secret) {
|
|
192
|
+
let h = 0;
|
|
193
|
+
for (let i = 0; i < value.length; i++) {
|
|
194
|
+
h = ((h << 5) - h + value.charCodeAt(i)) | 0;
|
|
195
|
+
}
|
|
196
|
+
for (let i = 0; i < secret.length; i++) {
|
|
197
|
+
h = ((h << 5) - h + secret.charCodeAt(i)) | 0;
|
|
198
|
+
}
|
|
199
|
+
return (h >>> 0).toString(36);
|
|
200
|
+
}
|
|
201
|
+
export function csrfToken(sessionId, host) {
|
|
202
|
+
const secret = csrfSecret(host);
|
|
203
|
+
const value = sessionId || 'anonymous';
|
|
204
|
+
const sig = csrfHmac(value, secret);
|
|
205
|
+
return `${value}:${sig}`;
|
|
206
|
+
}
|
|
207
|
+
export function verifyCsrfToken(token, host) {
|
|
208
|
+
if (!token || typeof token !== 'string')
|
|
209
|
+
return false;
|
|
210
|
+
const parts = token.split(':');
|
|
211
|
+
if (parts.length !== 2)
|
|
212
|
+
return false;
|
|
213
|
+
const [value, sig] = parts;
|
|
214
|
+
const secret = csrfSecret(host);
|
|
215
|
+
const expected = csrfHmac(value, secret);
|
|
216
|
+
return sig === expected;
|
|
217
|
+
}
|
|
218
|
+
export function csrfGuard(request, host) {
|
|
219
|
+
if (!request || typeof request !== 'object')
|
|
220
|
+
return;
|
|
221
|
+
const method = (request.method || 'GET').toUpperCase();
|
|
222
|
+
if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS')
|
|
223
|
+
return;
|
|
224
|
+
const headers = request.headers;
|
|
225
|
+
const token = headers?.['x-csrf-token']
|
|
226
|
+
|| request.body?._csrf
|
|
227
|
+
|| '';
|
|
228
|
+
const requestHost = host || headers?.['host'] || '';
|
|
229
|
+
if (!verifyCsrfToken(token, requestHost)) {
|
|
230
|
+
throw new Error('CSRF validation failed');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const __cookieSecrets = new Map();
|
|
234
|
+
function cookieSecret(host) {
|
|
235
|
+
if (!host)
|
|
236
|
+
host = 'localhost';
|
|
237
|
+
if (!__cookieSecrets.has(host)) {
|
|
238
|
+
__cookieSecrets.set(host, Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2));
|
|
239
|
+
}
|
|
240
|
+
return __cookieSecrets.get(host);
|
|
241
|
+
}
|
|
242
|
+
export async function signCookie(name, value, host) {
|
|
243
|
+
const secret = cookieSecret(host);
|
|
244
|
+
const payload = `${name}=${value}`;
|
|
245
|
+
const enc = new TextEncoder();
|
|
246
|
+
const key = await crypto.subtle.importKey('raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
247
|
+
const sig = await crypto.subtle.sign('HMAC', key, enc.encode(payload));
|
|
248
|
+
const sigB64 = btoa(String.fromCharCode(...new Uint8Array(sig))).replace(/=+$/, '');
|
|
249
|
+
return `${value}.${sigB64}`;
|
|
250
|
+
}
|
|
251
|
+
export async function unsignCookie(name, signedValue, host) {
|
|
252
|
+
if (!signedValue || typeof signedValue !== 'string')
|
|
253
|
+
return null;
|
|
254
|
+
const dot = signedValue.lastIndexOf('.');
|
|
255
|
+
if (dot === -1)
|
|
256
|
+
return null;
|
|
257
|
+
const value = signedValue.slice(0, dot);
|
|
258
|
+
const sig = signedValue.slice(dot + 1);
|
|
259
|
+
let expectedSig;
|
|
260
|
+
try {
|
|
261
|
+
expectedSig = (await signCookie(name, value, host)).split('.').pop();
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
return sig === expectedSig ? value : null;
|
|
267
|
+
}
|
|
268
|
+
export async function setSignedCookie(name, value, options = {}, host) {
|
|
269
|
+
const signed = await signCookie(name, value, host);
|
|
270
|
+
const parts = [`${name}=${signed}`];
|
|
271
|
+
if (options.httpOnly !== false)
|
|
272
|
+
parts.push('HttpOnly');
|
|
273
|
+
if (options.secure !== false)
|
|
274
|
+
parts.push('Secure');
|
|
275
|
+
parts.push('SameSite=' + (options.sameSite || 'Lax'));
|
|
276
|
+
if (options.path !== undefined)
|
|
277
|
+
parts.push('Path=' + options.path);
|
|
278
|
+
else
|
|
279
|
+
parts.push('Path=/');
|
|
280
|
+
if (options.maxAge !== undefined)
|
|
281
|
+
parts.push('Max-Age=' + options.maxAge);
|
|
282
|
+
if (options.domain)
|
|
283
|
+
parts.push('Domain=' + options.domain);
|
|
284
|
+
return parts.join('; ');
|
|
285
|
+
}
|
|
286
|
+
export async function readSignedCookie(name, cookieString, host) {
|
|
287
|
+
if (!cookieString)
|
|
288
|
+
return null;
|
|
289
|
+
const cookies = {};
|
|
290
|
+
for (const pair of cookieString.split(';')) {
|
|
291
|
+
const eq = pair.indexOf('=');
|
|
292
|
+
if (eq === -1)
|
|
293
|
+
continue;
|
|
294
|
+
cookies[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
295
|
+
}
|
|
296
|
+
const signed = cookies[name];
|
|
297
|
+
if (!signed)
|
|
298
|
+
return null;
|
|
299
|
+
return await unsignCookie(name, signed, host);
|
|
300
|
+
}
|
|
301
|
+
export function securityHeaders(config = {}) {
|
|
302
|
+
const sec = config.security || {};
|
|
303
|
+
const headers = {
|
|
304
|
+
'X-Frame-Options': sec.xFrameOptions || 'DENY',
|
|
305
|
+
'X-Content-Type-Options': 'nosniff',
|
|
306
|
+
'Referrer-Policy': sec.referrerPolicy || 'strict-origin-when-cross-origin',
|
|
307
|
+
...(sec.hsts !== false ? { 'Strict-Transport-Security': sec.hsts || 'max-age=31536000; includeSubDomains' } : {}),
|
|
308
|
+
'X-XSS-Protection': '0',
|
|
309
|
+
};
|
|
310
|
+
if (sec.contentSecurityPolicy !== false) {
|
|
311
|
+
headers['Content-Security-Policy'] = sec.contentSecurityPolicy ||
|
|
312
|
+
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; frame-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'";
|
|
313
|
+
}
|
|
314
|
+
return headers;
|
|
315
|
+
}
|
|
316
|
+
export function corsHeaders(security = {}, requestOrigin = '', host = '') {
|
|
317
|
+
if (!requestOrigin)
|
|
318
|
+
return {};
|
|
319
|
+
const originHost = (requestOrigin || '').replace(/^https?:\/\//, '').split('/')[0].split(':')[0];
|
|
320
|
+
const localHost = (host || '').split(':')[0];
|
|
321
|
+
if (originHost && localHost && originHost === localHost) {
|
|
322
|
+
return {};
|
|
323
|
+
}
|
|
324
|
+
const cors = security?.cors;
|
|
325
|
+
if (!cors || !cors.origin)
|
|
326
|
+
return {};
|
|
327
|
+
const allowedOrigins = Array.isArray(cors.origin) ? cors.origin : [cors.origin];
|
|
328
|
+
const origin = allowedOrigins.includes('*')
|
|
329
|
+
? '*'
|
|
330
|
+
: allowedOrigins.includes(requestOrigin) ? requestOrigin : null;
|
|
331
|
+
if (!origin)
|
|
332
|
+
return {};
|
|
333
|
+
return {
|
|
334
|
+
'Access-Control-Allow-Origin': origin,
|
|
335
|
+
'Access-Control-Allow-Methods': cors.methods || 'GET,POST,PUT,DELETE,PATCH,OPTIONS',
|
|
336
|
+
'Access-Control-Allow-Headers': cors.headers || 'Content-Type,Authorization,X-CSRF-Token',
|
|
337
|
+
...(cors.credentials !== false ? { 'Access-Control-Allow-Credentials': 'true' } : {}),
|
|
338
|
+
'Access-Control-Max-Age': String(cors.maxAge || 86400),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
export function corsPreflight(request, security) {
|
|
342
|
+
if ((request.method || 'GET').toUpperCase() !== 'OPTIONS')
|
|
343
|
+
return false;
|
|
344
|
+
const headers = request.headers;
|
|
345
|
+
const origin = headers?.['origin'] || '';
|
|
346
|
+
const host = headers?.['host'] || '';
|
|
347
|
+
const corsRespHeaders = corsHeaders(security, origin, host);
|
|
348
|
+
if (!corsRespHeaders['Access-Control-Allow-Origin'])
|
|
349
|
+
return false;
|
|
350
|
+
throw new CorsResponse(corsRespHeaders);
|
|
351
|
+
}
|
|
352
|
+
export class CorsResponse extends Error {
|
|
353
|
+
status;
|
|
354
|
+
headers;
|
|
355
|
+
constructor(headers) {
|
|
356
|
+
super('CORS preflight');
|
|
357
|
+
this.name = 'CorsResponse';
|
|
358
|
+
this.status = 204;
|
|
359
|
+
this.headers = { ...headers, 'Content-Length': '0' };
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
export function securityComment(config = {}) {
|
|
363
|
+
const sec = config.security || {};
|
|
364
|
+
const features = [];
|
|
365
|
+
if (sec.autoEscape !== false)
|
|
366
|
+
features.push('auto-escape');
|
|
367
|
+
if (sec.csrf !== false)
|
|
368
|
+
features.push('csrf');
|
|
369
|
+
if (sec.xFrameOptions !== false)
|
|
370
|
+
features.push('x-frame-options');
|
|
371
|
+
if (sec.hsts !== false)
|
|
372
|
+
features.push('hsts');
|
|
373
|
+
if (sec.contentSecurityPolicy !== false)
|
|
374
|
+
features.push('csp');
|
|
375
|
+
if (sec.trustProxy)
|
|
376
|
+
features.push('trust-proxy');
|
|
377
|
+
return `<!-- vesk-sec: ${features.join(', ')} -->`;
|
|
378
|
+
}
|
|
379
|
+
export function createRateLimiter(options = {}) {
|
|
380
|
+
const windowMs = options.windowMs || 60000;
|
|
381
|
+
const max = options.max || 100;
|
|
382
|
+
const timestamps = new Map();
|
|
383
|
+
const cleanupInterval = setInterval(() => {
|
|
384
|
+
const now = Date.now();
|
|
385
|
+
for (const [key, times] of timestamps) {
|
|
386
|
+
const valid = times.filter(t => now - t < windowMs);
|
|
387
|
+
if (valid.length === 0)
|
|
388
|
+
timestamps.delete(key);
|
|
389
|
+
else
|
|
390
|
+
timestamps.set(key, valid);
|
|
391
|
+
}
|
|
392
|
+
}, options.cleanupIntervalMs || 60000);
|
|
393
|
+
if (cleanupInterval.unref)
|
|
394
|
+
cleanupInterval.unref();
|
|
395
|
+
function getClientIp(request) {
|
|
396
|
+
const headers = request?.headers;
|
|
397
|
+
const forwarded = headers?.['x-forwarded-for'];
|
|
398
|
+
if (forwarded) {
|
|
399
|
+
const ip = (typeof forwarded === 'string' ? forwarded.split(',')[0] : forwarded[0]).trim();
|
|
400
|
+
return ip;
|
|
401
|
+
}
|
|
402
|
+
const realIp = headers?.['x-real-ip'];
|
|
403
|
+
const cfIp = headers?.['cf-connecting-ip'];
|
|
404
|
+
return (typeof realIp === 'string' ? realIp : typeof cfIp === 'string' ? cfIp : 'unknown');
|
|
405
|
+
}
|
|
406
|
+
function _check(key) {
|
|
407
|
+
if (!key)
|
|
408
|
+
return true;
|
|
409
|
+
const now = Date.now();
|
|
410
|
+
const times = timestamps.get(key) || [];
|
|
411
|
+
const valid = times.filter(t => now - t < windowMs);
|
|
412
|
+
if (valid.length >= max) {
|
|
413
|
+
timestamps.set(key, valid);
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
valid.push(now);
|
|
417
|
+
timestamps.set(key, valid);
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
check(key) {
|
|
422
|
+
return _check(key);
|
|
423
|
+
},
|
|
424
|
+
remaining(key) {
|
|
425
|
+
if (!key)
|
|
426
|
+
return max;
|
|
427
|
+
const now = Date.now();
|
|
428
|
+
const times = timestamps.get(key) || [];
|
|
429
|
+
return Math.max(0, max - times.filter(t => now - t < windowMs).length);
|
|
430
|
+
},
|
|
431
|
+
reset(key) {
|
|
432
|
+
timestamps.delete(key);
|
|
433
|
+
},
|
|
434
|
+
getConfig() {
|
|
435
|
+
return { windowMs, max };
|
|
436
|
+
},
|
|
437
|
+
middleware(request, response) {
|
|
438
|
+
const ip = getClientIp(request);
|
|
439
|
+
if (!_check(ip)) {
|
|
440
|
+
if (response && typeof response.headers === 'object') {
|
|
441
|
+
response.headers['Retry-After'] = String(Math.ceil(windowMs / 1000));
|
|
442
|
+
}
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
return true;
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
export function getClientIp(request, trustProxy = false) {
|
|
450
|
+
const headers = (request?.headers || {});
|
|
451
|
+
if (trustProxy) {
|
|
452
|
+
const forwarded = headers['x-forwarded-for'];
|
|
453
|
+
if (forwarded) {
|
|
454
|
+
const ips = (typeof forwarded === 'string' ? forwarded : String(forwarded)).split(',').map(s => s.trim());
|
|
455
|
+
return ips[0] || 'unknown';
|
|
456
|
+
}
|
|
457
|
+
const realIp = headers['x-real-ip'];
|
|
458
|
+
if (realIp)
|
|
459
|
+
return typeof realIp === 'string' ? realIp : String(realIp);
|
|
460
|
+
}
|
|
461
|
+
if (headers['x-forwarded-for']) {
|
|
462
|
+
const fwd = headers['x-forwarded-for'];
|
|
463
|
+
return typeof fwd === 'string' ? fwd.split(',')[0].trim() : fwd[0];
|
|
464
|
+
}
|
|
465
|
+
if (headers['x-real-ip'])
|
|
466
|
+
return typeof headers['x-real-ip'] === 'string' ? headers['x-real-ip'] : String(headers['x-real-ip']);
|
|
467
|
+
return 'unknown';
|
|
468
|
+
}
|
|
469
|
+
export function getClientProtocol(request, trustProxy = false) {
|
|
470
|
+
if (trustProxy) {
|
|
471
|
+
const proto = request?.headers?.['x-forwarded-proto'];
|
|
472
|
+
if (proto)
|
|
473
|
+
return (typeof proto === 'string' ? proto.split(',')[0] : String(proto)).trim();
|
|
474
|
+
}
|
|
475
|
+
return request?.headers?.['x-forwarded-proto'] ? 'https' : 'http';
|
|
476
|
+
}
|
|
477
|
+
export function applyTrustProxy(ctx, trustProxy) {
|
|
478
|
+
if (!ctx || !trustProxy)
|
|
479
|
+
return;
|
|
480
|
+
ctx.ip = getClientIp(ctx, trustProxy);
|
|
481
|
+
ctx.protocol = getClientProtocol(ctx, trustProxy);
|
|
482
|
+
ctx.host = (ctx.headers?.['x-forwarded-host']) || ctx.host;
|
|
483
|
+
}
|
|
484
|
+
export function exprJS(raw) {
|
|
485
|
+
return `(${raw})`;
|
|
486
|
+
}
|
|
487
|
+
export function indent(code, level = 1) {
|
|
488
|
+
const tab = '\t';
|
|
489
|
+
return code.split('\n').map(line => line ? tab.repeat(level) + line : line).join('\n');
|
|
490
|
+
}
|
|
491
|
+
export function tryEvalExpr(raw, props, locals = {}) {
|
|
492
|
+
try {
|
|
493
|
+
const fn = new Function('props', 'return (' + raw + ')');
|
|
494
|
+
return fn(props);
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
const merged = { ...props, ...locals };
|
|
498
|
+
try {
|
|
499
|
+
const fn = new Function('props', 'return (' + raw + ')');
|
|
500
|
+
return fn(merged);
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
const paramNames = Object.keys({ ...props, ...locals });
|
|
504
|
+
const paramValues = paramNames.map((k) => (k in props ? props[k] : locals[k]));
|
|
505
|
+
try {
|
|
506
|
+
const fn = new Function(...paramNames, 'return (' + raw + ')');
|
|
507
|
+
return fn(...paramValues);
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
throw new Error('Cannot evaluate: ' + raw);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
export function childrenToHTML(nodes) {
|
|
516
|
+
const parts = [];
|
|
517
|
+
for (const n of nodes) {
|
|
518
|
+
if (n instanceof StaticNode) {
|
|
519
|
+
const tag = n.tag;
|
|
520
|
+
const attrs = n.attributes.map(a => ` ${a.name}="${escapeHtml(a.value)}"`).join('');
|
|
521
|
+
if (n.selfClosing) {
|
|
522
|
+
parts.push(`<${tag}${attrs}/>`);
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
const inner = childrenToHTML(n.children);
|
|
526
|
+
parts.push(`<${tag}${attrs}>${inner}</${tag}>`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
else if (n instanceof TextNode) {
|
|
530
|
+
parts.push(escapeHtml(n.value));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return parts.join('');
|
|
534
|
+
}
|
|
535
|
+
export function extractTopLevelNames(topLevelCode) {
|
|
536
|
+
const names = [];
|
|
537
|
+
for (const code of topLevelCode) {
|
|
538
|
+
const ast = tryParseTopLevel(code);
|
|
539
|
+
if (ast) {
|
|
540
|
+
for (const stmt of ast.body) {
|
|
541
|
+
const target = unwrapExport(stmt);
|
|
542
|
+
if (!target)
|
|
543
|
+
continue;
|
|
544
|
+
if (target.type === 'VariableDeclaration') {
|
|
545
|
+
for (const d of target.declarations) {
|
|
546
|
+
if (d.id && d.id.type === 'Identifier')
|
|
547
|
+
names.push(d.id.name);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
else if (target.type === 'FunctionDeclaration' && target.id) {
|
|
551
|
+
names.push(target.id.name);
|
|
552
|
+
}
|
|
553
|
+
else if (target.type === 'ClassDeclaration' && target.id) {
|
|
554
|
+
names.push(target.id.name);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return names;
|
|
560
|
+
}
|
|
561
|
+
function unwrapExport(stmt) {
|
|
562
|
+
if (stmt.type === 'ExportNamedDeclaration')
|
|
563
|
+
return stmt.declaration;
|
|
564
|
+
if (stmt.type === 'ExportDefaultDeclaration')
|
|
565
|
+
return stmt.declaration;
|
|
566
|
+
return stmt;
|
|
567
|
+
}
|
|
568
|
+
function tryParseTopLevel(code) {
|
|
569
|
+
try {
|
|
570
|
+
return parse(code, { filename: 'top-level.mjs' });
|
|
571
|
+
}
|
|
572
|
+
catch {
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
export function extractRuntimeNames(importStrs) {
|
|
577
|
+
const names = [];
|
|
578
|
+
for (const imp of importStrs) {
|
|
579
|
+
const ast = tryParseImport(imp);
|
|
580
|
+
if (ast) {
|
|
581
|
+
for (const stmt of ast.body) {
|
|
582
|
+
if (stmt.type !== 'ImportDeclaration')
|
|
583
|
+
continue;
|
|
584
|
+
const src = stmt.source?.value;
|
|
585
|
+
if (typeof src !== 'string' || (src !== '@vesk/runtime' && src !== '@vesk/reactivity'))
|
|
586
|
+
continue;
|
|
587
|
+
for (const spec of stmt.specifiers || []) {
|
|
588
|
+
if (spec.importKind === 'type')
|
|
589
|
+
continue;
|
|
590
|
+
if (spec.type === 'ImportSpecifier') {
|
|
591
|
+
const name = spec.local?.name || spec.imported?.name;
|
|
592
|
+
if (name)
|
|
593
|
+
names.push(name);
|
|
594
|
+
}
|
|
595
|
+
else if (spec.type === 'ImportDefaultSpecifier' || spec.type === 'ImportNamespaceSpecifier') {
|
|
596
|
+
if (spec.local?.name)
|
|
597
|
+
names.push(spec.local.name);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
const target = importModuleTarget(imp);
|
|
604
|
+
if (target === '@vesk/runtime' || target === '@vesk/reactivity') {
|
|
605
|
+
for (const name of extractImportNames(imp))
|
|
606
|
+
names.push(name);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return names;
|
|
610
|
+
}
|
|
611
|
+
function tryParseImport(code) {
|
|
612
|
+
try {
|
|
613
|
+
return parse(code, { filename: 'import.mjs' });
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
export function buildParamInit(paramNames) {
|
|
620
|
+
if (paramNames.length === 1 && paramNames[0] === 'props') {
|
|
621
|
+
return '';
|
|
622
|
+
}
|
|
623
|
+
if (paramNames.length === 0)
|
|
624
|
+
return '';
|
|
625
|
+
return `const { ${paramNames.join(', ')} } = props;`;
|
|
626
|
+
}
|
|
627
|
+
export function resolveComponentName(source) {
|
|
628
|
+
try {
|
|
629
|
+
const ir = generateIR(parse(source), source);
|
|
630
|
+
const defaultComp = ir.components.find((c) => c.defaultExport);
|
|
631
|
+
if (defaultComp)
|
|
632
|
+
return defaultComp.name;
|
|
633
|
+
if (ir.components.length > 0)
|
|
634
|
+
return ir.components[0].name;
|
|
635
|
+
const exportedComp = ir.components.find((c) => c.exported);
|
|
636
|
+
if (exportedComp)
|
|
637
|
+
return exportedComp.name;
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
catch {
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
let __cachedRuntimeModule = null;
|
|
645
|
+
export function setRuntimeModule(mod) {
|
|
646
|
+
__cachedRuntimeModule = mod;
|
|
647
|
+
}
|
|
648
|
+
export function loadRuntimeImports(importStrs) {
|
|
649
|
+
const names = extractRuntimeNames(importStrs);
|
|
650
|
+
const mod = __cachedRuntimeModule || __defaultRuntimeModule;
|
|
651
|
+
if (mod) {
|
|
652
|
+
const result = {};
|
|
653
|
+
if (mod.getActiveComponent)
|
|
654
|
+
result.getActiveComponent = mod.getActiveComponent;
|
|
655
|
+
if (mod.setActiveComponent)
|
|
656
|
+
result.setActiveComponent = mod.setActiveComponent;
|
|
657
|
+
for (const name of ['get', 'set', 'track']) {
|
|
658
|
+
if (name in mod)
|
|
659
|
+
result[name] = mod[name];
|
|
660
|
+
}
|
|
661
|
+
for (const name of names) {
|
|
662
|
+
if (name in mod)
|
|
663
|
+
result[name] = mod[name];
|
|
664
|
+
}
|
|
665
|
+
return result;
|
|
666
|
+
}
|
|
667
|
+
return {};
|
|
668
|
+
}
|
|
669
|
+
export function evalTopLevelCode(topLevelCode, __vesk) {
|
|
670
|
+
for (const code of topLevelCode) {
|
|
671
|
+
const ast = tryParseTopLevel(code);
|
|
672
|
+
if (ast && ast.body.length > 0) {
|
|
673
|
+
let handled = false;
|
|
674
|
+
for (const stmt of ast.body) {
|
|
675
|
+
const target = unwrapExport(stmt);
|
|
676
|
+
if (!target)
|
|
677
|
+
continue;
|
|
678
|
+
if (target.type === 'VariableDeclaration') {
|
|
679
|
+
for (const d of target.declarations) {
|
|
680
|
+
if (!d.id || d.id.type !== 'Identifier')
|
|
681
|
+
continue;
|
|
682
|
+
const initSrc = d.init ? code.slice(d.init.start, d.init.end) : 'undefined';
|
|
683
|
+
try {
|
|
684
|
+
const keys = Object.keys(__vesk);
|
|
685
|
+
const params = [...keys, '__vesk', 'result'];
|
|
686
|
+
const body = `result.value = (${initSrc});`;
|
|
687
|
+
const fn = new Function(...params, body);
|
|
688
|
+
const result = { value: undefined };
|
|
689
|
+
fn(...keys.map(k => __vesk[k]), __vesk, result);
|
|
690
|
+
__vesk[d.id.name] = result.value;
|
|
691
|
+
}
|
|
692
|
+
catch {
|
|
693
|
+
// skip evaluation errors
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
handled = true;
|
|
697
|
+
}
|
|
698
|
+
else if (target.type === 'FunctionDeclaration' && target.id) {
|
|
699
|
+
try {
|
|
700
|
+
const keys = Object.keys(__vesk);
|
|
701
|
+
const params = [...keys, '__vesk'];
|
|
702
|
+
const paramSrc = target.params.length
|
|
703
|
+
? code.slice(target.params[0].start, target.params[target.params.length - 1].end)
|
|
704
|
+
: '';
|
|
705
|
+
const bodySrc = code.slice(target.body.start, target.body.end);
|
|
706
|
+
const asyncKw = target.async ? 'async ' : '';
|
|
707
|
+
const fn = new Function(...params, `__vesk['${target.id.name}'] = ${asyncKw}function ${target.id.name}(${paramSrc}) ${bodySrc};`);
|
|
708
|
+
fn(...keys.map(k => __vesk[k]), __vesk);
|
|
709
|
+
}
|
|
710
|
+
catch {
|
|
711
|
+
// skip evaluation errors
|
|
712
|
+
}
|
|
713
|
+
handled = true;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (handled)
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
export async function callStaticProps(fnSource) {
|
|
722
|
+
const isAsync = fnSource.trimStart().startsWith('async');
|
|
723
|
+
const wrapper = isAsync
|
|
724
|
+
? `return (async () => {\n${fnSource}\nreturn await getStaticProps();\n})()`
|
|
725
|
+
: `return (() => {\n${fnSource}\nreturn getStaticProps();\n})()`;
|
|
726
|
+
const fn = new Function(wrapper);
|
|
727
|
+
const result = fn();
|
|
728
|
+
const resolved = result && typeof result.then === 'function' ? await result : result;
|
|
729
|
+
return resolved && resolved.props ? resolved.props : resolved;
|
|
730
|
+
}
|
|
731
|
+
export async function callLoadFunction(fnSource, currentProps, __vesk) {
|
|
732
|
+
const isAsync = fnSource.trimStart().startsWith('async');
|
|
733
|
+
const ctx = {
|
|
734
|
+
params: currentProps.params || {},
|
|
735
|
+
request: currentProps.request || null,
|
|
736
|
+
fetch: globalThis.fetch,
|
|
737
|
+
url: currentProps.url || '',
|
|
738
|
+
};
|
|
739
|
+
const ctxCode = `const __ctx = ${JSON.stringify(ctx)};\n`;
|
|
740
|
+
const wrapper = isAsync
|
|
741
|
+
? `return (async () => {\n${ctxCode}\n${fnSource}\nreturn await load(__ctx);\n})()`
|
|
742
|
+
: `return (() => {\n${ctxCode}\n${fnSource}\nreturn load(__ctx);\n})()`;
|
|
743
|
+
const fn = new Function(wrapper);
|
|
744
|
+
const result = fn();
|
|
745
|
+
const resolved = result && typeof result.then === 'function' ? await result : result;
|
|
746
|
+
return resolved;
|
|
747
|
+
}
|