tina4-nodejs 3.13.141 → 3.13.143
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.md +28 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +817 -538
- package/packages/cli/src/bin.ts +4 -14
- package/packages/cli/src/commands/build.ts +2 -18
- package/packages/cli/src/commands/generate.ts +9 -24
- package/packages/cli/src/commands/init.ts +18 -8
- package/packages/cli/src/commands/lint.ts +3 -13
- package/packages/cli/src/commands/migrate.ts +2 -6
- package/packages/cli/src/commands/migrateRollback.ts +2 -6
- package/packages/cli/src/commands/migrateStatus.ts +2 -6
- package/packages/cli/src/commands/queue.ts +2 -30
- package/packages/cli/src/util.ts +90 -0
- package/packages/core/dist/index.js +592 -262
- package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +27 -11
- package/packages/core/gallery/queue/src/lib/queueDb.ts +11 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/consume/post.ts +2 -5
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/fail/post.ts +2 -5
- package/packages/core/gallery/queue/src/routes/gallery/queue/get.ts +20 -7
- package/packages/core/gallery/templates/src/templates/gallery_page.twig +34 -15
- package/packages/core/src/csp.ts +138 -0
- package/packages/core/src/devAdmin.ts +3 -12
- package/packages/core/src/dispatchPipeline.ts +2 -18
- package/packages/core/src/errorOverlay.ts +80 -37
- package/packages/core/src/etag.ts +24 -0
- package/packages/core/src/feedback.ts +2 -11
- package/packages/core/src/index.ts +10 -0
- package/packages/core/src/middleware.ts +20 -6
- package/packages/core/src/response.ts +14 -3
- package/packages/core/src/server.ts +52 -15
- package/packages/core/src/session.ts +174 -71
- package/packages/core/src/sessionHandlers/respSessionHandler.ts +94 -0
- package/packages/core/src/sessionHandlers/valkeyHandler.ts +22 -73
- package/packages/core/src/sso.ts +1 -1
- package/packages/core/src/static.ts +1 -13
- package/packages/core/src/types.ts +8 -0
- package/packages/core/src/upstreamResponse.ts +31 -0
- package/packages/core/src/websocket.ts +48 -7
- package/packages/core/templates/errors/302.twig +1 -1
- package/packages/core/templates/errors/401.twig +2 -2
- package/packages/core/templates/errors/403.twig +1 -1
- package/packages/core/templates/errors/404.twig +1 -1
- package/packages/core/templates/errors/500.twig +1 -1
- package/packages/core/templates/errors/502.twig +2 -2
- package/packages/core/templates/errors/503.twig +2 -2
- package/packages/core/templates/errors/base.twig +4 -1
- package/packages/frond/dist/index.js +2 -0
- package/packages/frond/src/engine.ts +23 -0
- package/packages/orm/dist/index.js +593 -271
- package/packages/orm/src/graph/adapters/bolt.ts +8 -29
- package/packages/orm/src/graph/adapters/rowMapping.ts +57 -0
- package/packages/orm/src/graph/adapters/ultipa.ts +8 -30
- package/packages/swagger/dist/index.js +4 -4
- package/packages/swagger/src/ui.ts +7 -4
- package/types/cli/src/util.d.ts +28 -0
- package/types/core/src/csp.d.ts +42 -0
- package/types/core/src/etag.d.ts +9 -0
- package/types/core/src/index.d.ts +1 -0
- package/types/core/src/middleware.d.ts +6 -0
- package/types/core/src/session.d.ts +64 -21
- package/types/core/src/sessionHandlers/respSessionHandler.d.ts +52 -0
- package/types/core/src/sessionHandlers/valkeyHandler.d.ts +2 -26
- package/types/core/src/types.d.ts +8 -0
- package/types/core/src/upstreamResponse.d.ts +10 -0
- package/types/core/src/websocket.d.ts +21 -0
- package/types/frond/src/engine.d.ts +7 -0
- package/types/orm/src/graph/adapters/rowMapping.d.ts +26 -0
|
@@ -5406,6 +5406,73 @@ var init_trustedProxy = __esm({
|
|
|
5406
5406
|
}
|
|
5407
5407
|
});
|
|
5408
5408
|
|
|
5409
|
+
// ../core/src/csp.ts
|
|
5410
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
5411
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
5412
|
+
function generateNonce() {
|
|
5413
|
+
return randomBytes2(16).toString("base64");
|
|
5414
|
+
}
|
|
5415
|
+
function runWithCspNonce(nonce, fn) {
|
|
5416
|
+
return nonceStore.run({ nonce }, fn);
|
|
5417
|
+
}
|
|
5418
|
+
function setCurrentNonce(nonce) {
|
|
5419
|
+
const store = nonceStore.getStore();
|
|
5420
|
+
if (store) store.nonce = nonce;
|
|
5421
|
+
else nonceFallback = nonce;
|
|
5422
|
+
}
|
|
5423
|
+
function clearCurrentNonce() {
|
|
5424
|
+
setCurrentNonce(void 0);
|
|
5425
|
+
}
|
|
5426
|
+
function currentCspNonce() {
|
|
5427
|
+
const store = nonceStore.getStore();
|
|
5428
|
+
let value = store ? store.nonce : nonceFallback;
|
|
5429
|
+
if (!value) {
|
|
5430
|
+
value = generateNonce();
|
|
5431
|
+
if (store) store.nonce = value;
|
|
5432
|
+
else nonceFallback = value;
|
|
5433
|
+
}
|
|
5434
|
+
return value;
|
|
5435
|
+
}
|
|
5436
|
+
function cspNonce() {
|
|
5437
|
+
return currentCspNonce();
|
|
5438
|
+
}
|
|
5439
|
+
function injectNonceIntoCsp(csp, nonce) {
|
|
5440
|
+
const token = `'nonce-${nonce}'`;
|
|
5441
|
+
const directives = [];
|
|
5442
|
+
for (const rawPart of csp.split(";")) {
|
|
5443
|
+
const part = rawPart.trim();
|
|
5444
|
+
if (!part) continue;
|
|
5445
|
+
const spaceAt = part.search(/\s/);
|
|
5446
|
+
const name = (spaceAt === -1 ? part : part.slice(0, spaceAt)).toLowerCase();
|
|
5447
|
+
const value = spaceAt === -1 ? "" : part.slice(spaceAt + 1).trim();
|
|
5448
|
+
directives.push([name, value]);
|
|
5449
|
+
}
|
|
5450
|
+
const defaultEntry = directives.find(([name]) => name === "default-src");
|
|
5451
|
+
const defaultValue = defaultEntry && defaultEntry[1] ? defaultEntry[1] : "'self'";
|
|
5452
|
+
for (const directive of ["style-src", "script-src"]) {
|
|
5453
|
+
const existing = directives.find(([name]) => name === directive);
|
|
5454
|
+
if (existing) {
|
|
5455
|
+
if (!existing[1].split(/\s+/).includes(token)) {
|
|
5456
|
+
existing[1] = `${existing[1]} ${token}`.trim();
|
|
5457
|
+
}
|
|
5458
|
+
} else {
|
|
5459
|
+
directives.push([directive, `${defaultValue} ${token}`.trim()]);
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
5462
|
+
return directives.map(([name, value]) => value ? `${name} ${value}` : name).join("; ");
|
|
5463
|
+
}
|
|
5464
|
+
function resolveCspHeader(nonce) {
|
|
5465
|
+
const csp = process.env.TINA4_CSP || "default-src 'self'";
|
|
5466
|
+
return injectNonceIntoCsp(csp, nonce);
|
|
5467
|
+
}
|
|
5468
|
+
var nonceStore, nonceFallback;
|
|
5469
|
+
var init_csp = __esm({
|
|
5470
|
+
"../core/src/csp.ts"() {
|
|
5471
|
+
"use strict";
|
|
5472
|
+
nonceStore = new AsyncLocalStorage2();
|
|
5473
|
+
}
|
|
5474
|
+
});
|
|
5475
|
+
|
|
5409
5476
|
// ../frond/src/engine.ts
|
|
5410
5477
|
var engine_exports = {};
|
|
5411
5478
|
__export(engine_exports, {
|
|
@@ -5415,9 +5482,10 @@ __export(engine_exports, {
|
|
|
5415
5482
|
expressionFormCache: () => expressionFormCache,
|
|
5416
5483
|
filterChainCache: () => filterChainCache,
|
|
5417
5484
|
pathParseCache: () => pathParseCache,
|
|
5485
|
+
setCspNonceProvider: () => setCspNonceProvider,
|
|
5418
5486
|
setFormTokenSessionId: () => setFormTokenSessionId
|
|
5419
5487
|
});
|
|
5420
|
-
import { createHash as createHash3, createHmac as createHmac2, randomBytes as
|
|
5488
|
+
import { createHash as createHash3, createHmac as createHmac2, randomBytes as randomBytes3 } from "node:crypto";
|
|
5421
5489
|
import { readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync4, realpathSync } from "node:fs";
|
|
5422
5490
|
import { join as join5, resolve as resolve2, isAbsolute as isAbsolute2, sep } from "node:path";
|
|
5423
5491
|
function jsonText(value) {
|
|
@@ -6484,6 +6552,9 @@ function _b64url(data) {
|
|
|
6484
6552
|
function setFormTokenSessionId(sessionId) {
|
|
6485
6553
|
_formTokenSessionId = sessionId || "";
|
|
6486
6554
|
}
|
|
6555
|
+
function setCspNonceProvider(fn) {
|
|
6556
|
+
_cspNonceProvider = fn;
|
|
6557
|
+
}
|
|
6487
6558
|
function _buildFormTokenJwt(descriptor = "") {
|
|
6488
6559
|
const secret = process.env.TINA4_SECRET ?? "";
|
|
6489
6560
|
const secretBytes = Buffer.byteLength(secret, "utf8");
|
|
@@ -6495,7 +6566,7 @@ function _buildFormTokenJwt(descriptor = "") {
|
|
|
6495
6566
|
const ttlMinutes = parseInt(process.env.TINA4_TOKEN_LIMIT || "60", 10);
|
|
6496
6567
|
const header = { alg: "HS256", typ: "JWT" };
|
|
6497
6568
|
const now = Math.floor(Date.now() / 1e3);
|
|
6498
|
-
const payload = { type: "form", nonce:
|
|
6569
|
+
const payload = { type: "form", nonce: randomBytes3(8).toString("hex"), iat: now, exp: now + ttlMinutes * 60 };
|
|
6499
6570
|
if (descriptor) {
|
|
6500
6571
|
if (descriptor.includes("|")) {
|
|
6501
6572
|
const [ctx, ref] = descriptor.split("|", 2);
|
|
@@ -6522,7 +6593,7 @@ function _generateFormToken(descriptor = "") {
|
|
|
6522
6593
|
function _generateFormTokenValue(descriptor = "") {
|
|
6523
6594
|
return new SafeString(_buildFormTokenJwt(descriptor));
|
|
6524
6595
|
}
|
|
6525
|
-
var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FAST_FILTERS, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, EXTENDS_RE, EXTENDS_RE_GLOBAL, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, MEMO_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, EXPR_NOT_MATCHED, ARITHMETIC_OPERATIONS, EXPR_EVALUATORS, expressionFormCache, VarRef, JS_SAFE_RE, MEDIA_TYPE_RE, SANDBOX_EXPR_KEYWORDS, SANDBOX_IDENT_RE, SANDBOX_STRING_RE, UNSAFE_KEYS, BUILTIN_FILTERS, _formTokenSessionId, Frond;
|
|
6596
|
+
var SafeString, KNOWN_TAGS, TERMINATOR_TAGS, GATEABLE_TAGS, BLOCK_TAG_ENDS, JSON_UNSAFE_RE, JSON_UNSAFE_MAP, NUMERIC_RE, METHOD_CALL_RE, FN_CALL_RE, IS_NOT_RE, IS_RE, NOT_IN_RE, IN_RE, DIVISIBLE_BY_RE, FILTER_WITH_ARGS_RE, FILTER_COMPARISON_RE, TITLE_WORD_RE, STRIP_TAGS_RE, FAST_FILTERS, FORMAT_RE, LEADING_WS_RE, TRAILING_WS_RE, THOUSANDS_RE, LIVE_RE, LIVE_WS_RE, LIVE_SRC_RE, EXTENDS_RE, EXTENDS_RE_GLOBAL, filterChainCache, pathParseCache, TEMPLATE_CACHE_MAX, MEMO_CACHE_MAX, TOKEN_RE, RAW_BLOCK_RE, EXPR_NOT_MATCHED, ARITHMETIC_OPERATIONS, EXPR_EVALUATORS, expressionFormCache, VarRef, JS_SAFE_RE, MEDIA_TYPE_RE, SANDBOX_EXPR_KEYWORDS, SANDBOX_IDENT_RE, SANDBOX_STRING_RE, UNSAFE_KEYS, BUILTIN_FILTERS, _formTokenSessionId, _cspNonceProvider, Frond;
|
|
6526
6597
|
var init_engine = __esm({
|
|
6527
6598
|
"../frond/src/engine.ts"() {
|
|
6528
6599
|
"use strict";
|
|
@@ -6876,6 +6947,7 @@ var init_engine = __esm({
|
|
|
6876
6947
|
js_escape: (v) => new SafeString(jsEscape(v))
|
|
6877
6948
|
};
|
|
6878
6949
|
_formTokenSessionId = "";
|
|
6950
|
+
_cspNonceProvider = () => "";
|
|
6879
6951
|
Frond = class _Frond {
|
|
6880
6952
|
// ── Class-level registries ──────────────────────────────────
|
|
6881
6953
|
// Persist globals, filters, and tests across hot-reloads and at
|
|
@@ -7029,6 +7101,7 @@ var init_engine = __esm({
|
|
|
7029
7101
|
this.globals.formTokenValue = (descriptor) => _generateFormTokenValue(descriptor || "");
|
|
7030
7102
|
this.globals.form_token_value = (descriptor) => _generateFormTokenValue(descriptor || "");
|
|
7031
7103
|
this.globals.dump = (value) => renderDump(value);
|
|
7104
|
+
this.globals.csp_nonce = () => _cspNonceProvider();
|
|
7032
7105
|
for (const [k, v] of _Frond.classFilters) this.filters[k] = v;
|
|
7033
7106
|
for (const [k, v] of _Frond.classGlobals) this.globals[k] = v;
|
|
7034
7107
|
for (const [k, v] of _Frond.classTests) this.tests[k] = v;
|
|
@@ -8292,7 +8365,8 @@ async function getFrond() {
|
|
|
8292
8365
|
const dir = _defaultTemplatesDir ?? nodePath.resolve(process.cwd(), "src/templates");
|
|
8293
8366
|
let engine = _frondCache.get(dir);
|
|
8294
8367
|
if (!engine) {
|
|
8295
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
8368
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
8369
|
+
setCspNonceProvider2(currentCspNonce);
|
|
8296
8370
|
engine = new Frond2(dir);
|
|
8297
8371
|
_frondCache.set(dir, engine);
|
|
8298
8372
|
}
|
|
@@ -8303,7 +8377,8 @@ async function getFrameworkFrond() {
|
|
|
8303
8377
|
const frameworkDir = nodePath.resolve(nodePath.dirname(import.meta.url.replace("file://", "")), "..", "templates");
|
|
8304
8378
|
if (!_frameworkFrond && fs2.existsSync(frameworkDir)) {
|
|
8305
8379
|
try {
|
|
8306
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
8380
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
8381
|
+
setCspNonceProvider2(currentCspNonce);
|
|
8307
8382
|
_frameworkFrond = new Frond2(frameworkDir);
|
|
8308
8383
|
} catch {
|
|
8309
8384
|
return null;
|
|
@@ -8403,6 +8478,7 @@ function createResponse(res) {
|
|
|
8403
8478
|
return response;
|
|
8404
8479
|
};
|
|
8405
8480
|
response.raw = res;
|
|
8481
|
+
response.cspNonce = currentCspNonce();
|
|
8406
8482
|
response.json = function(data, status2) {
|
|
8407
8483
|
if (res.headersSent) return response;
|
|
8408
8484
|
if (status2 !== void 0) res.statusCode = status2;
|
|
@@ -8548,7 +8624,8 @@ function createResponse(res) {
|
|
|
8548
8624
|
};
|
|
8549
8625
|
response.render = async function(templateName, data, status2, templateDir) {
|
|
8550
8626
|
try {
|
|
8551
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
8627
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
8628
|
+
setCspNonceProvider2(currentCspNonce);
|
|
8552
8629
|
const dir = templateDir ?? _defaultTemplatesDir ?? nodePath.resolve(process.cwd(), "src/templates");
|
|
8553
8630
|
let engine = _frondCache.get(dir);
|
|
8554
8631
|
if (!engine) {
|
|
@@ -8666,6 +8743,7 @@ var HEADER_TOKEN, _frondCache, _defaultTemplatesDir, _globalFrond, _frameworkFro
|
|
|
8666
8743
|
var init_response = __esm({
|
|
8667
8744
|
"../core/src/response.ts"() {
|
|
8668
8745
|
"use strict";
|
|
8746
|
+
init_csp();
|
|
8669
8747
|
HEADER_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
8670
8748
|
_frondCache = /* @__PURE__ */ new Map();
|
|
8671
8749
|
_defaultTemplatesDir = null;
|
|
@@ -8877,6 +8955,7 @@ var init_middleware = __esm({
|
|
|
8877
8955
|
init_dotenv();
|
|
8878
8956
|
init_router();
|
|
8879
8957
|
init_trustedProxy();
|
|
8958
|
+
init_csp();
|
|
8880
8959
|
init_response();
|
|
8881
8960
|
MiddlewareChain = class {
|
|
8882
8961
|
middlewares = [];
|
|
@@ -9309,12 +9388,18 @@ var init_middleware = __esm({
|
|
|
9309
9388
|
* overrides, WITHOUT Strict-Transport-Security, which depends on the
|
|
9310
9389
|
* request's scheme. Also what a transport rejection carries (ADR-0068
|
|
9311
9390
|
* section 4), written before the scheme is known.
|
|
9391
|
+
*
|
|
9392
|
+
* Content-Security-Policy always carries a per-response nonce in style-src
|
|
9393
|
+
* AND script-src (ADR-0088), read from the request async-context (minted on
|
|
9394
|
+
* first access), so the framework's own inline content runs under the strict
|
|
9395
|
+
* default without 'unsafe-inline'. The nonce is random per response, so a
|
|
9396
|
+
* caller comparing this header must assert its STRUCTURE, never a byte string.
|
|
9312
9397
|
*/
|
|
9313
9398
|
static canonicalHeaders() {
|
|
9314
9399
|
return {
|
|
9315
9400
|
"X-Frame-Options": process.env.TINA4_FRAME_OPTIONS ?? "SAMEORIGIN",
|
|
9316
9401
|
"X-Content-Type-Options": "nosniff",
|
|
9317
|
-
"Content-Security-Policy":
|
|
9402
|
+
"Content-Security-Policy": resolveCspHeader(currentCspNonce()),
|
|
9318
9403
|
"Referrer-Policy": process.env.TINA4_REFERRER_POLICY ?? "strict-origin-when-cross-origin",
|
|
9319
9404
|
"X-XSS-Protection": "0",
|
|
9320
9405
|
"Permissions-Policy": process.env.TINA4_PERMISSIONS_POLICY ?? "camera=(), microphone=(), geolocation=()"
|
|
@@ -9356,7 +9441,7 @@ var init_middleware = __esm({
|
|
|
9356
9441
|
static warnCspDefaultOnce() {
|
|
9357
9442
|
if (_SecurityHeadersMiddleware.cspDefaultWarned) return;
|
|
9358
9443
|
_SecurityHeadersMiddleware.cspDefaultWarned = true;
|
|
9359
|
-
const message = `TINA4_CSP is not set, so Tina4 is serving the default Content-Security-Policy "default-src 'self'" on every response.
|
|
9444
|
+
const message = `TINA4_CSP is not set, so Tina4 is serving the default Content-Security-Policy "default-src 'self'" on every response. The framework injects a per-response nonce into style-src and script-src, so its own inline <style>/<script> (and your templates using the csp_nonce() Frond global) work under this policy. The default still blocks cross-origin fonts/scripts/CDNs, data: URIs, and cross-origin WebSocket/XHR (e.g. a separate API or LiveKit host). If your app uses any of these, set TINA4_CSP to a policy that allows them (see https://tina4.com); to silence this notice without changing behaviour, set TINA4_CSP="default-src 'self'".`;
|
|
9360
9445
|
try {
|
|
9361
9446
|
Log.warning(message);
|
|
9362
9447
|
} catch {
|
|
@@ -10361,6 +10446,21 @@ var init_portTakeover = __esm({
|
|
|
10361
10446
|
}
|
|
10362
10447
|
});
|
|
10363
10448
|
|
|
10449
|
+
// ../core/src/etag.ts
|
|
10450
|
+
function etagMatches(ifNoneMatch, etag) {
|
|
10451
|
+
const strip = (tag) => tag.trim().replace(/^W\//, "");
|
|
10452
|
+
const target = strip(etag);
|
|
10453
|
+
return ifNoneMatch.split(",").some((candidate) => {
|
|
10454
|
+
const trimmed = candidate.trim();
|
|
10455
|
+
return trimmed === "*" || strip(trimmed) === target;
|
|
10456
|
+
});
|
|
10457
|
+
}
|
|
10458
|
+
var init_etag = __esm({
|
|
10459
|
+
"../core/src/etag.ts"() {
|
|
10460
|
+
"use strict";
|
|
10461
|
+
}
|
|
10462
|
+
});
|
|
10463
|
+
|
|
10364
10464
|
// ../core/src/sessionHandlers/syncBridge.ts
|
|
10365
10465
|
import { Worker } from "node:worker_threads";
|
|
10366
10466
|
function getBridge(key, workerSource, workerData) {
|
|
@@ -10642,42 +10742,35 @@ var init_respClient = __esm({
|
|
|
10642
10742
|
}
|
|
10643
10743
|
});
|
|
10644
10744
|
|
|
10645
|
-
// ../core/src/sessionHandlers/
|
|
10646
|
-
var
|
|
10647
|
-
var
|
|
10648
|
-
"../core/src/sessionHandlers/
|
|
10745
|
+
// ../core/src/sessionHandlers/respSessionHandler.ts
|
|
10746
|
+
var RespSessionHandler;
|
|
10747
|
+
var init_respSessionHandler = __esm({
|
|
10748
|
+
"../core/src/sessionHandlers/respSessionHandler.ts"() {
|
|
10649
10749
|
"use strict";
|
|
10650
10750
|
init_respClient();
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
prefix;
|
|
10656
|
-
db;
|
|
10657
|
-
constructor(config) {
|
|
10658
|
-
this.host = config?.host ?? process.env.TINA4_SESSION_VALKEY_HOST ?? "127.0.0.1";
|
|
10659
|
-
this.port = config?.port ?? (process.env.TINA4_SESSION_VALKEY_PORT ? parseInt(process.env.TINA4_SESSION_VALKEY_PORT, 10) : 6379);
|
|
10660
|
-
this.password = config?.password ?? process.env.TINA4_SESSION_VALKEY_PASSWORD ?? "";
|
|
10661
|
-
this.prefix = config?.prefix ?? process.env.TINA4_SESSION_VALKEY_PREFIX ?? "tina4:session:";
|
|
10662
|
-
this.db = config?.db ?? (process.env.TINA4_SESSION_VALKEY_DB ? parseInt(process.env.TINA4_SESSION_VALKEY_DB, 10) : 0);
|
|
10751
|
+
RespSessionHandler = class {
|
|
10752
|
+
constructor(conn, label) {
|
|
10753
|
+
this.conn = conn;
|
|
10754
|
+
this.label = label;
|
|
10663
10755
|
}
|
|
10756
|
+
conn;
|
|
10757
|
+
label;
|
|
10664
10758
|
/**
|
|
10665
|
-
* Execute a RESP command synchronously against the live
|
|
10759
|
+
* Execute a RESP command synchronously against the live server.
|
|
10666
10760
|
*
|
|
10667
|
-
* Delegates to the shared {@link respCommandSync} transport: a genuine key
|
|
10668
|
-
* yields `""`, and a transport/connection FAILURE
|
|
10669
|
-
*
|
|
10670
|
-
* (silent) from "backend failed" (log-loud + degrade). Backend-failure parity.
|
|
10761
|
+
* Delegates to the shared {@link respCommandSync} transport: a genuine key
|
|
10762
|
+
* miss yields `""`, and a transport/connection FAILURE THROWS so the caller
|
|
10763
|
+
* can distinguish "not found" from "backend failed". Backend-failure parity.
|
|
10671
10764
|
*/
|
|
10672
10765
|
execSync(args) {
|
|
10673
10766
|
return respCommandSync(
|
|
10674
|
-
{ host: this.host, port: this.port, password: this.password, db: this.db },
|
|
10767
|
+
{ host: this.conn.host, port: this.conn.port, password: this.conn.password, db: this.conn.db },
|
|
10675
10768
|
args,
|
|
10676
|
-
|
|
10769
|
+
this.label
|
|
10677
10770
|
);
|
|
10678
10771
|
}
|
|
10679
10772
|
key(sessionId) {
|
|
10680
|
-
return `${this.prefix}${sessionId}`;
|
|
10773
|
+
return `${this.conn.prefix}${sessionId}`;
|
|
10681
10774
|
}
|
|
10682
10775
|
read(sessionId) {
|
|
10683
10776
|
const raw = this.execSync(["GET", this.key(sessionId)]);
|
|
@@ -10688,7 +10781,7 @@ var init_valkeyHandler = __esm({
|
|
|
10688
10781
|
return null;
|
|
10689
10782
|
}
|
|
10690
10783
|
}
|
|
10691
|
-
write(sessionId, data, ttl) {
|
|
10784
|
+
write(sessionId, data, ttl = 0) {
|
|
10692
10785
|
const json = JSON.stringify(data);
|
|
10693
10786
|
if (ttl > 0) {
|
|
10694
10787
|
this.execSync(["SETEX", this.key(sessionId), String(ttl), json]);
|
|
@@ -10703,6 +10796,29 @@ var init_valkeyHandler = __esm({
|
|
|
10703
10796
|
}
|
|
10704
10797
|
});
|
|
10705
10798
|
|
|
10799
|
+
// ../core/src/sessionHandlers/valkeyHandler.ts
|
|
10800
|
+
var ValkeySessionHandler;
|
|
10801
|
+
var init_valkeyHandler = __esm({
|
|
10802
|
+
"../core/src/sessionHandlers/valkeyHandler.ts"() {
|
|
10803
|
+
"use strict";
|
|
10804
|
+
init_respSessionHandler();
|
|
10805
|
+
ValkeySessionHandler = class extends RespSessionHandler {
|
|
10806
|
+
constructor(config) {
|
|
10807
|
+
super(
|
|
10808
|
+
{
|
|
10809
|
+
host: config?.host ?? process.env.TINA4_SESSION_VALKEY_HOST ?? "127.0.0.1",
|
|
10810
|
+
port: config?.port ?? (process.env.TINA4_SESSION_VALKEY_PORT ? parseInt(process.env.TINA4_SESSION_VALKEY_PORT, 10) : 6379),
|
|
10811
|
+
password: config?.password ?? process.env.TINA4_SESSION_VALKEY_PASSWORD ?? "",
|
|
10812
|
+
prefix: config?.prefix ?? process.env.TINA4_SESSION_VALKEY_PREFIX ?? "tina4:session:",
|
|
10813
|
+
db: config?.db ?? (process.env.TINA4_SESSION_VALKEY_DB ? parseInt(process.env.TINA4_SESSION_VALKEY_DB, 10) : 0)
|
|
10814
|
+
},
|
|
10815
|
+
"Valkey"
|
|
10816
|
+
);
|
|
10817
|
+
}
|
|
10818
|
+
};
|
|
10819
|
+
}
|
|
10820
|
+
});
|
|
10821
|
+
|
|
10706
10822
|
// ../core/src/sessionHandlers/memcachedHandler.ts
|
|
10707
10823
|
import { createHash as createHash4 } from "node:crypto";
|
|
10708
10824
|
var MAX_KEY_BYTES, MAX_RELATIVE_EXPTIME, MemcachedSessionHandler;
|
|
@@ -11718,7 +11834,7 @@ __export(session_exports, {
|
|
|
11718
11834
|
sessionCookieName: () => sessionCookieName,
|
|
11719
11835
|
sessionStrictMode: () => sessionStrictMode
|
|
11720
11836
|
});
|
|
11721
|
-
import { createHash as createHash5, randomBytes as
|
|
11837
|
+
import { createHash as createHash5, randomBytes as randomBytes4 } from "node:crypto";
|
|
11722
11838
|
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "node:fs";
|
|
11723
11839
|
import { join as join9 } from "node:path";
|
|
11724
11840
|
function isValidSessionId(sessionId) {
|
|
@@ -11736,6 +11852,20 @@ function resolveBackend(name) {
|
|
|
11736
11852
|
}
|
|
11737
11853
|
return normalised;
|
|
11738
11854
|
}
|
|
11855
|
+
function fingerprint(value) {
|
|
11856
|
+
try {
|
|
11857
|
+
return JSON.stringify(value);
|
|
11858
|
+
} catch {
|
|
11859
|
+
return void 0;
|
|
11860
|
+
}
|
|
11861
|
+
}
|
|
11862
|
+
function fingerprints(data) {
|
|
11863
|
+
const prints = /* @__PURE__ */ new Map();
|
|
11864
|
+
for (const [key, value] of Object.entries(data)) {
|
|
11865
|
+
prints.set(key, fingerprint(value));
|
|
11866
|
+
}
|
|
11867
|
+
return prints;
|
|
11868
|
+
}
|
|
11739
11869
|
function isSecureScheme(forwardedProto, socketEncrypted) {
|
|
11740
11870
|
const forwarded = (forwardedProto ?? "").trim();
|
|
11741
11871
|
if (forwarded !== "") {
|
|
@@ -11768,7 +11898,7 @@ var init_session = __esm({
|
|
|
11768
11898
|
"use strict";
|
|
11769
11899
|
init_logger();
|
|
11770
11900
|
init_dotenv();
|
|
11771
|
-
|
|
11901
|
+
init_respSessionHandler();
|
|
11772
11902
|
init_valkeyHandler();
|
|
11773
11903
|
init_memcachedHandler();
|
|
11774
11904
|
init_mongoHandler();
|
|
@@ -11894,57 +12024,19 @@ var init_session = __esm({
|
|
|
11894
12024
|
}
|
|
11895
12025
|
}
|
|
11896
12026
|
};
|
|
11897
|
-
RedisSessionHandler = class {
|
|
11898
|
-
host;
|
|
11899
|
-
port;
|
|
11900
|
-
password;
|
|
11901
|
-
prefix;
|
|
11902
|
-
db;
|
|
12027
|
+
RedisSessionHandler = class extends RespSessionHandler {
|
|
11903
12028
|
constructor(config) {
|
|
11904
|
-
|
|
11905
|
-
|
|
11906
|
-
|
|
11907
|
-
|
|
11908
|
-
|
|
11909
|
-
|
|
11910
|
-
|
|
11911
|
-
|
|
11912
|
-
*
|
|
11913
|
-
* Delegates to the shared {@link respCommandSync} transport: a genuine key miss
|
|
11914
|
-
* yields `""`, and a transport/connection FAILURE (server unreachable, rejected
|
|
11915
|
-
* AUTH, timeout) THROWS so the Session boundary can distinguish "not found"
|
|
11916
|
-
* (silent) from "backend failed" (log-loud + degrade). Backend-failure parity.
|
|
11917
|
-
*/
|
|
11918
|
-
execSync(args) {
|
|
11919
|
-
return respCommandSync(
|
|
11920
|
-
{ host: this.host, port: this.port, password: this.password, db: this.db },
|
|
11921
|
-
args,
|
|
12029
|
+
super(
|
|
12030
|
+
{
|
|
12031
|
+
host: config?.redisHost ?? process.env.TINA4_SESSION_REDIS_HOST ?? "127.0.0.1",
|
|
12032
|
+
port: config?.redisPort ?? (process.env.TINA4_SESSION_REDIS_PORT ? parseInt(process.env.TINA4_SESSION_REDIS_PORT, 10) : 6379),
|
|
12033
|
+
password: config?.redisPassword ?? process.env.TINA4_SESSION_REDIS_PASSWORD ?? "",
|
|
12034
|
+
prefix: config?.redisPrefix ?? process.env.TINA4_SESSION_REDIS_PREFIX ?? "tina4:session:",
|
|
12035
|
+
db: config?.redisDb ?? (process.env.TINA4_SESSION_REDIS_DB ? parseInt(process.env.TINA4_SESSION_REDIS_DB, 10) : 0)
|
|
12036
|
+
},
|
|
11922
12037
|
"Redis"
|
|
11923
12038
|
);
|
|
11924
12039
|
}
|
|
11925
|
-
key(sessionId) {
|
|
11926
|
-
return `${this.prefix}${sessionId}`;
|
|
11927
|
-
}
|
|
11928
|
-
read(sessionId) {
|
|
11929
|
-
const raw = this.execSync(["GET", this.key(sessionId)]);
|
|
11930
|
-
if (!raw) return null;
|
|
11931
|
-
try {
|
|
11932
|
-
return JSON.parse(raw);
|
|
11933
|
-
} catch {
|
|
11934
|
-
return null;
|
|
11935
|
-
}
|
|
11936
|
-
}
|
|
11937
|
-
write(sessionId, data, ttl = 0) {
|
|
11938
|
-
const json = JSON.stringify(data);
|
|
11939
|
-
if (ttl > 0) {
|
|
11940
|
-
this.execSync(["SETEX", this.key(sessionId), String(ttl), json]);
|
|
11941
|
-
} else {
|
|
11942
|
-
this.execSync(["SET", this.key(sessionId), json]);
|
|
11943
|
-
}
|
|
11944
|
-
}
|
|
11945
|
-
destroy(sessionId) {
|
|
11946
|
-
this.execSync(["DEL", this.key(sessionId)]);
|
|
11947
|
-
}
|
|
11948
12040
|
};
|
|
11949
12041
|
FLASH_PREFIX = "_flash_";
|
|
11950
12042
|
Session = class {
|
|
@@ -11971,6 +12063,29 @@ var init_session = __esm({
|
|
|
11971
12063
|
* Lets `start()` tell "no such session" from "the store is unreachable".
|
|
11972
12064
|
*/
|
|
11973
12065
|
lastReadFailed = false;
|
|
12066
|
+
/**
|
|
12067
|
+
* What this request last saw of the stored record, one fingerprint per key:
|
|
12068
|
+
* taken by start() and again by every save. save() compares the live data
|
|
12069
|
+
* against it to find the keys THIS request changed.
|
|
12070
|
+
*/
|
|
12071
|
+
loaded = /* @__PURE__ */ new Map();
|
|
12072
|
+
/**
|
|
12073
|
+
* True when this request is working on a record the store holds: start()
|
|
12074
|
+
* adopted one, or a save has written one since. A save never re-creates such
|
|
12075
|
+
* a record once another request has removed it.
|
|
12076
|
+
*/
|
|
12077
|
+
stored = false;
|
|
12078
|
+
/**
|
|
12079
|
+
* True when clear() ran since the last save: the save then replaces the
|
|
12080
|
+
* stored record instead of merging into it.
|
|
12081
|
+
*/
|
|
12082
|
+
cleared = false;
|
|
12083
|
+
/**
|
|
12084
|
+
* True once this request found its session ended by another request (the
|
|
12085
|
+
* record it loaded is gone). It stays ended for the rest of the request:
|
|
12086
|
+
* regenerate() mints nothing either.
|
|
12087
|
+
*/
|
|
12088
|
+
ended = false;
|
|
11974
12089
|
constructor(backend, config) {
|
|
11975
12090
|
const backendType = resolveBackend(
|
|
11976
12091
|
backend ?? config?.backend ?? process.env.TINA4_SESSION_BACKEND ?? "file"
|
|
@@ -12115,6 +12230,10 @@ var init_session = __esm({
|
|
|
12115
12230
|
const now2 = Math.floor(Date.now() / 1e3);
|
|
12116
12231
|
this.data = { _created: now2, _accessed: now2 };
|
|
12117
12232
|
this.dirty = false;
|
|
12233
|
+
this.stored = false;
|
|
12234
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
12235
|
+
this.cleared = false;
|
|
12236
|
+
this.ended = false;
|
|
12118
12237
|
return sessionId;
|
|
12119
12238
|
}
|
|
12120
12239
|
if (loaded) {
|
|
@@ -12124,14 +12243,22 @@ var init_session = __esm({
|
|
|
12124
12243
|
this.data._accessed = now2;
|
|
12125
12244
|
this.dirty = false;
|
|
12126
12245
|
this.safeWrite(this.sessionId, this.data, this.ttl);
|
|
12246
|
+
this.stored = true;
|
|
12247
|
+
this.loaded = fingerprints(this.data);
|
|
12248
|
+
this.cleared = false;
|
|
12249
|
+
this.ended = false;
|
|
12127
12250
|
return sessionId;
|
|
12128
12251
|
}
|
|
12129
12252
|
}
|
|
12130
|
-
this.sessionId =
|
|
12253
|
+
this.sessionId = randomBytes4(16).toString("hex");
|
|
12131
12254
|
const now = Math.floor(Date.now() / 1e3);
|
|
12132
12255
|
this.data = { _created: now, _accessed: now };
|
|
12133
12256
|
this.dirty = false;
|
|
12134
12257
|
this.safeWrite(this.sessionId, this.data, this.ttl);
|
|
12258
|
+
this.stored = false;
|
|
12259
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
12260
|
+
this.cleared = false;
|
|
12261
|
+
this.ended = false;
|
|
12135
12262
|
return this.sessionId;
|
|
12136
12263
|
}
|
|
12137
12264
|
/**
|
|
@@ -12172,9 +12299,7 @@ var init_session = __esm({
|
|
|
12172
12299
|
if (this.sessionId) {
|
|
12173
12300
|
this.safeDestroy(this.sessionId);
|
|
12174
12301
|
}
|
|
12175
|
-
this.
|
|
12176
|
-
this.data = null;
|
|
12177
|
-
this.dirty = false;
|
|
12302
|
+
this.forget();
|
|
12178
12303
|
}
|
|
12179
12304
|
/**
|
|
12180
12305
|
* Get all session data (excluding internal keys).
|
|
@@ -12197,6 +12322,7 @@ var init_session = __esm({
|
|
|
12197
12322
|
const now = Math.floor(Date.now() / 1e3);
|
|
12198
12323
|
this.data = { _created: this.data._created, _accessed: now };
|
|
12199
12324
|
this.dirty = true;
|
|
12325
|
+
this.cleared = true;
|
|
12200
12326
|
this.save();
|
|
12201
12327
|
}
|
|
12202
12328
|
/**
|
|
@@ -12213,17 +12339,36 @@ var init_session = __esm({
|
|
|
12213
12339
|
* session fixation — the pre-auth ID is destroyed and the data is carried
|
|
12214
12340
|
* onto a fresh, unguessable ID. A backend destroy/write failure is logged
|
|
12215
12341
|
* (never silent) but does not throw under the default policy.
|
|
12342
|
+
*
|
|
12343
|
+
* What is carried is the session as it is stored now with this request's
|
|
12344
|
+
* own changes applied, the same merge save() does. If another request ended
|
|
12345
|
+
* the session after this one loaded it (a logout, or a regenerate of its
|
|
12346
|
+
* own), it stays ended: nothing is carried, no id is minted and null is
|
|
12347
|
+
* returned, so no cookie goes out to replace the one that request sent.
|
|
12216
12348
|
*/
|
|
12217
12349
|
regenerate() {
|
|
12350
|
+
if (this.ended) return null;
|
|
12351
|
+
if (this.sessionId && this.stored && this.data) {
|
|
12352
|
+
const current = this.safeRead(this.sessionId);
|
|
12353
|
+
if (!this.lastReadFailed) {
|
|
12354
|
+
if (!current) {
|
|
12355
|
+
this.forget();
|
|
12356
|
+
this.ended = true;
|
|
12357
|
+
return null;
|
|
12358
|
+
}
|
|
12359
|
+
if (!this.cleared) this.data = this.merged(current);
|
|
12360
|
+
}
|
|
12361
|
+
}
|
|
12218
12362
|
const oldId = this.sessionId;
|
|
12219
12363
|
const oldData = this.data;
|
|
12220
12364
|
if (oldId) {
|
|
12221
12365
|
this.safeDestroy(oldId);
|
|
12222
12366
|
}
|
|
12223
|
-
this.sessionId =
|
|
12367
|
+
this.sessionId = randomBytes4(16).toString("hex");
|
|
12224
12368
|
this.data = oldData ?? { _created: Math.floor(Date.now() / 1e3), _accessed: Math.floor(Date.now() / 1e3) };
|
|
12225
12369
|
this.data._accessed = Math.floor(Date.now() / 1e3);
|
|
12226
12370
|
this.dirty = true;
|
|
12371
|
+
this.stored = false;
|
|
12227
12372
|
this.save();
|
|
12228
12373
|
return this.sessionId;
|
|
12229
12374
|
}
|
|
@@ -12299,17 +12444,84 @@ var init_session = __esm({
|
|
|
12299
12444
|
*
|
|
12300
12445
|
* Returns true on a successful persist, false if the backend was unreachable
|
|
12301
12446
|
* (logged). The dirty flag is cleared only on success so a later save()
|
|
12302
|
-
* retries once the backend recovers. A
|
|
12447
|
+
* retries once the backend recovers. A call with no session returns true.
|
|
12448
|
+
*
|
|
12449
|
+
* Other requests on the same session run while this one does, so the save
|
|
12450
|
+
* writes only THIS request's changes, onto the record as it is stored now.
|
|
12451
|
+
* Writing back the whole snapshot start() loaded undid whatever another
|
|
12452
|
+
* request did in between: a logout (destroy, clear, regenerate) or a
|
|
12453
|
+
* privilege change made with set(). A record removed since this request
|
|
12454
|
+
* loaded it is never re-created: the session has ended, for this request
|
|
12455
|
+
* too. A request that has not loaded a stored record (a new session, the new
|
|
12456
|
+
* id regenerate() mints) writes its data whole, as before.
|
|
12457
|
+
*
|
|
12458
|
+
* A session the store already holds is re-written even when this request
|
|
12459
|
+
* changed nothing: that write re-stamps the backend deadline to
|
|
12460
|
+
* now + TINA4_SESSION_TTL, so a session expires after that span of INACTIVITY
|
|
12461
|
+
* rather than that long after its last CHANGE (ADR-0087 — a session's expiry
|
|
12462
|
+
* slides on activity). The re-write is the re-read, merged record, so sliding
|
|
12463
|
+
* the deadline is concurrent-safe: it never clobbers another request's change,
|
|
12464
|
+
* never re-creates an ended record, and TINA4_SESSION_TTL=0 still re-stamps a
|
|
12465
|
+
* never-expires deadline (ADR-0027). A request with no stored session and no
|
|
12466
|
+
* change writes nothing — there is no deadline to move.
|
|
12303
12467
|
*/
|
|
12304
12468
|
save() {
|
|
12305
12469
|
if (!this.sessionId || !this.data) return true;
|
|
12306
|
-
if (!this.dirty) return true;
|
|
12307
|
-
|
|
12470
|
+
if (!this.dirty && !this.stored) return true;
|
|
12471
|
+
let record = this.data;
|
|
12472
|
+
if (this.stored) {
|
|
12473
|
+
const current = this.safeRead(this.sessionId);
|
|
12474
|
+
if (this.lastReadFailed) return false;
|
|
12475
|
+
if (!current) {
|
|
12476
|
+
this.forget();
|
|
12477
|
+
this.ended = true;
|
|
12478
|
+
return true;
|
|
12479
|
+
}
|
|
12480
|
+
if (!this.cleared) record = this.merged(current);
|
|
12481
|
+
}
|
|
12482
|
+
if (this.safeWrite(this.sessionId, record, this.ttl)) {
|
|
12308
12483
|
this.dirty = false;
|
|
12484
|
+
this.stored = true;
|
|
12485
|
+
this.cleared = false;
|
|
12486
|
+
this.loaded = fingerprints(this.data);
|
|
12309
12487
|
return true;
|
|
12310
12488
|
}
|
|
12311
12489
|
return false;
|
|
12312
12490
|
}
|
|
12491
|
+
/**
|
|
12492
|
+
* The stored record with this request's changes applied: every key whose
|
|
12493
|
+
* value differs from what this request loaded or last saved, and the
|
|
12494
|
+
* removal of every key it deleted. Keys it did not touch keep whatever is
|
|
12495
|
+
* stored now.
|
|
12496
|
+
*/
|
|
12497
|
+
merged(current) {
|
|
12498
|
+
const data = this.data;
|
|
12499
|
+
const record = { ...current };
|
|
12500
|
+
for (const [key, value] of Object.entries(data)) {
|
|
12501
|
+
const print = fingerprint(value);
|
|
12502
|
+
if (print === void 0 || !this.loaded.has(key) || this.loaded.get(key) !== print) {
|
|
12503
|
+
record[key] = value;
|
|
12504
|
+
}
|
|
12505
|
+
}
|
|
12506
|
+
for (const key of this.loaded.keys()) {
|
|
12507
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
12508
|
+
delete record[key];
|
|
12509
|
+
}
|
|
12510
|
+
}
|
|
12511
|
+
return record;
|
|
12512
|
+
}
|
|
12513
|
+
/**
|
|
12514
|
+
* End the session for this request without touching the store: no id and
|
|
12515
|
+
* no data, so nothing more is written and no cookie goes out for it.
|
|
12516
|
+
*/
|
|
12517
|
+
forget() {
|
|
12518
|
+
this.sessionId = null;
|
|
12519
|
+
this.data = null;
|
|
12520
|
+
this.dirty = false;
|
|
12521
|
+
this.stored = false;
|
|
12522
|
+
this.cleared = false;
|
|
12523
|
+
this.loaded = /* @__PURE__ */ new Map();
|
|
12524
|
+
}
|
|
12313
12525
|
};
|
|
12314
12526
|
}
|
|
12315
12527
|
});
|
|
@@ -12354,14 +12566,6 @@ function headStripIntercept(rawReq, rawRes) {
|
|
|
12354
12566
|
function isCompressibleContentType(contentType) {
|
|
12355
12567
|
return COMPRESSIBLE_PREFIXES.some((prefix) => contentType.includes(prefix));
|
|
12356
12568
|
}
|
|
12357
|
-
function etagMatchesInm(ifNoneMatch, etag) {
|
|
12358
|
-
const strip = (tag) => tag.trim().replace(/^W\//, "");
|
|
12359
|
-
const target = strip(etag);
|
|
12360
|
-
return ifNoneMatch.split(",").some((candidate) => {
|
|
12361
|
-
const trimmed = candidate.trim();
|
|
12362
|
-
return trimmed === "*" || strip(trimmed) === target;
|
|
12363
|
-
});
|
|
12364
|
-
}
|
|
12365
12569
|
function maybeCompressResponse(body, rawReq, rawRes) {
|
|
12366
12570
|
const acceptEncoding = String(rawReq.headers["accept-encoding"] ?? "");
|
|
12367
12571
|
const contentTypeHeader = rawRes.getHeader("content-type");
|
|
@@ -12377,7 +12581,7 @@ function maybeCompressResponse(body, rawReq, rawRes) {
|
|
|
12377
12581
|
}
|
|
12378
12582
|
function responseIsNotModified(rawReq, rawRes, etag) {
|
|
12379
12583
|
const ifNoneMatch = String(rawReq.headers["if-none-match"] ?? "");
|
|
12380
|
-
if (ifNoneMatch) return
|
|
12584
|
+
if (ifNoneMatch) return etagMatches(ifNoneMatch, etag);
|
|
12381
12585
|
const lastModifiedHeader = rawRes.getHeader("last-modified");
|
|
12382
12586
|
if (typeof lastModifiedHeader !== "string") return false;
|
|
12383
12587
|
const ifModifiedSince = String(rawReq.headers["if-modified-since"] ?? "");
|
|
@@ -12505,6 +12709,7 @@ var init_dispatchPipeline = __esm({
|
|
|
12505
12709
|
"../core/src/dispatchPipeline.ts"() {
|
|
12506
12710
|
"use strict";
|
|
12507
12711
|
init_logger();
|
|
12712
|
+
init_etag();
|
|
12508
12713
|
COMPRESSIBLE_PREFIXES = [
|
|
12509
12714
|
"text/",
|
|
12510
12715
|
"application/json",
|
|
@@ -12600,18 +12805,11 @@ function isNotModified(req2, etag, stat) {
|
|
|
12600
12805
|
}
|
|
12601
12806
|
return false;
|
|
12602
12807
|
}
|
|
12603
|
-
function etagMatches(ifNoneMatch, etag) {
|
|
12604
|
-
const strip = (tag) => tag.trim().replace(/^W\//, "");
|
|
12605
|
-
const target = strip(etag);
|
|
12606
|
-
return ifNoneMatch.split(",").some((candidate) => {
|
|
12607
|
-
const trimmed = candidate.trim();
|
|
12608
|
-
return trimmed === "*" || strip(trimmed) === target;
|
|
12609
|
-
});
|
|
12610
|
-
}
|
|
12611
12808
|
var COMPRESSIBLE_PREFIXES2, MIME_TYPES;
|
|
12612
12809
|
var init_static = __esm({
|
|
12613
12810
|
"../core/src/static.ts"() {
|
|
12614
12811
|
"use strict";
|
|
12812
|
+
init_etag();
|
|
12615
12813
|
COMPRESSIBLE_PREFIXES2 = [
|
|
12616
12814
|
"text/",
|
|
12617
12815
|
"application/json",
|
|
@@ -12692,11 +12890,11 @@ function formatSourceBlock(filename, lineno) {
|
|
|
12692
12890
|
const lines = readSourceLines(filename, lineno);
|
|
12693
12891
|
if (lines.length === 0) return "";
|
|
12694
12892
|
const rows = lines.map(([num, text, isError]) => {
|
|
12695
|
-
const
|
|
12893
|
+
const rowClass = isError ? "eo-line eo-line-err" : "eo-line";
|
|
12696
12894
|
const marker = isError ? "▶" : " ";
|
|
12697
|
-
return `<div
|
|
12895
|
+
return `<div class="${rowClass}"><span class="eo-ln">${num}</span><span class="eo-marker">${marker}</span><span class="eo-code">${esc(text)}</span></div>`;
|
|
12698
12896
|
}).join("\n");
|
|
12699
|
-
return `<div
|
|
12897
|
+
return `<div class="eo-source">` + rows + `</div>`;
|
|
12700
12898
|
}
|
|
12701
12899
|
function formatFrame(frame, capturedAt = 0) {
|
|
12702
12900
|
const source = frame.file && frame.line > 0 ? formatSourceBlock(frame.file, frame.line) : "";
|
|
@@ -12708,23 +12906,23 @@ function formatFrame(frame, capturedAt = 0) {
|
|
|
12708
12906
|
if (mtime > capturedAt + 0.5) {
|
|
12709
12907
|
const d = new Date(mtime * 1e3);
|
|
12710
12908
|
const mtimeIso = `${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}:${String(d.getUTCSeconds()).padStart(2, "0")}`;
|
|
12711
|
-
staleBadge = ` <span
|
|
12909
|
+
staleBadge = ` <span class="eo-stale">FILE MODIFIED @ ${mtimeIso} UTC — source may not match what failed</span>`;
|
|
12712
12910
|
}
|
|
12713
12911
|
} catch {
|
|
12714
12912
|
}
|
|
12715
12913
|
}
|
|
12716
|
-
return `<div
|
|
12914
|
+
return `<div class="eo-frame"><div class="eo-frame-head"><span class="eo-file">${esc(frame.file)}</span><span class="eo-sep"> : </span><span class="eo-lineno">${frame.line}</span><span class="eo-sep"> in </span><span class="eo-fn">${esc(frame.func)}</span>` + staleBadge + `</div>` + source + `</div>`;
|
|
12717
12915
|
}
|
|
12718
12916
|
function collapsible(title, content, openByDefault = false) {
|
|
12719
12917
|
const open = openByDefault ? " open" : "";
|
|
12720
|
-
return `<details
|
|
12918
|
+
return `<details class="eo-details"${open}><summary class="eo-summary">${esc(title)}</summary><div class="eo-details-body">${content}</div></details>`;
|
|
12721
12919
|
}
|
|
12722
12920
|
function table(pairs) {
|
|
12723
|
-
if (pairs.length === 0) return `<span
|
|
12921
|
+
if (pairs.length === 0) return `<span class="eo-none">None</span>`;
|
|
12724
12922
|
const rows = pairs.map(
|
|
12725
|
-
([key, val]) => `<tr><td
|
|
12923
|
+
([key, val]) => `<tr><td class="eo-key">${esc(key)}</td><td class="eo-val">${esc(val)}</td></tr>`
|
|
12726
12924
|
).join("");
|
|
12727
|
-
return `<table
|
|
12925
|
+
return `<table class="eo-table">${rows}</table>`;
|
|
12728
12926
|
}
|
|
12729
12927
|
function renderErrorOverlay(error2, request) {
|
|
12730
12928
|
const capturedAt = Date.now() / 1e3;
|
|
@@ -12737,7 +12935,7 @@ function renderErrorOverlay(error2, request) {
|
|
|
12737
12935
|
}
|
|
12738
12936
|
const hidden = frames.length - Math.min(frames.length, MAX_FRAMES);
|
|
12739
12937
|
if (hidden > 0) {
|
|
12740
|
-
framesHtml += `<div
|
|
12938
|
+
framesHtml += `<div class="eo-hidden-frames">… ${hidden} more stack frames hidden (truncated at ${MAX_FRAMES})</div>`;
|
|
12741
12939
|
}
|
|
12742
12940
|
const requestPairs = [];
|
|
12743
12941
|
if (request != null) {
|
|
@@ -12774,37 +12972,72 @@ function renderErrorOverlay(error2, request) {
|
|
|
12774
12972
|
];
|
|
12775
12973
|
const envSection = collapsible("Environment", table(envPairs));
|
|
12776
12974
|
const stackSection = collapsible("Stack Trace", framesHtml, true);
|
|
12975
|
+
const nonce = currentCspNonce();
|
|
12777
12976
|
return `<!DOCTYPE html>
|
|
12778
12977
|
<html lang="en">
|
|
12779
12978
|
<head>
|
|
12780
12979
|
<meta charset="utf-8">
|
|
12781
12980
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
12782
12981
|
<title>Tina4 Error \u2014 ${esc(excType)}</title>
|
|
12783
|
-
<style>
|
|
12784
|
-
|
|
12785
|
-
body{background:${BG};color:${TEXT};font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px;line-height:1.5;}
|
|
12982
|
+
<style nonce="${nonce}">
|
|
12983
|
+
${overlayStylesheet()}
|
|
12786
12984
|
</style>
|
|
12787
12985
|
</head>
|
|
12788
12986
|
<body>
|
|
12789
|
-
<div
|
|
12790
|
-
<div
|
|
12791
|
-
<div
|
|
12792
|
-
<span
|
|
12793
|
-
<span
|
|
12987
|
+
<div class="eo-wrap">
|
|
12988
|
+
<div class="eo-header">
|
|
12989
|
+
<div class="eo-badge-row">
|
|
12990
|
+
<span class="eo-badge">Error</span>
|
|
12991
|
+
<span class="eo-sub">Tina4 Debug Overlay</span>
|
|
12794
12992
|
</div>
|
|
12795
|
-
<h1
|
|
12796
|
-
<p
|
|
12993
|
+
<h1 class="eo-type">${esc(excType)}</h1>
|
|
12994
|
+
<p class="eo-msg">${esc(excMsg)}</p>
|
|
12797
12995
|
</div>
|
|
12798
12996
|
${stackSection}
|
|
12799
12997
|
${requestSection}
|
|
12800
12998
|
${envSection}
|
|
12801
|
-
<div
|
|
12999
|
+
<div class="eo-footer">
|
|
12802
13000
|
Tina4 Debug Overlay — This page is only shown in debug mode. Set TINA4_DEBUG=false in production.
|
|
12803
13001
|
</div>
|
|
12804
13002
|
</div>
|
|
12805
13003
|
</body>
|
|
12806
13004
|
</html>`;
|
|
12807
13005
|
}
|
|
13006
|
+
function overlayStylesheet() {
|
|
13007
|
+
return `
|
|
13008
|
+
*{margin:0;padding:0;box-sizing:border-box;}
|
|
13009
|
+
body{background:${BG};color:${TEXT};font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;padding:24px;line-height:1.5;}
|
|
13010
|
+
.eo-wrap{max-width:960px;margin:0 auto;}
|
|
13011
|
+
.eo-header{margin-bottom:24px;}
|
|
13012
|
+
.eo-badge-row{display:flex;align-items:center;gap:12px;margin-bottom:12px;}
|
|
13013
|
+
.eo-badge{background:${RED};color:${BG};padding:4px 12px;border-radius:4px;font-weight:700;font-size:13px;text-transform:uppercase;}
|
|
13014
|
+
.eo-sub{color:${SUBTEXT};font-size:14px;}
|
|
13015
|
+
.eo-type{color:${RED};font-size:28px;font-weight:700;margin-bottom:8px;}
|
|
13016
|
+
.eo-msg{color:${TEXT};font-size:18px;font-family:'SF Mono','Fira Code','Consolas',monospace;background:${SURFACE};padding:12px 16px;border-radius:6px;border-left:4px solid ${RED};}
|
|
13017
|
+
.eo-footer{margin-top:32px;padding-top:16px;border-top:1px solid ${OVERLAY};color:${SUBTEXT};font-size:12px;}
|
|
13018
|
+
.eo-source{background:${SURFACE};border-radius:6px;padding:12px;overflow-x:auto;font-family:'SF Mono','Fira Code','Consolas',monospace;font-size:13px;line-height:1.6;}
|
|
13019
|
+
.eo-line{display:flex;padding:1px 0;}
|
|
13020
|
+
.eo-line-err{background:${ERROR_LINE_BG};}
|
|
13021
|
+
.eo-ln{color:${YELLOW};min-width:3.5em;text-align:right;padding-right:1em;user-select:none;}
|
|
13022
|
+
.eo-marker{color:${RED};width:1.2em;user-select:none;}
|
|
13023
|
+
.eo-code{color:${TEXT};white-space:pre-wrap;tab-size:4;}
|
|
13024
|
+
.eo-frame{margin-bottom:16px;}
|
|
13025
|
+
.eo-frame-head{margin-bottom:4px;}
|
|
13026
|
+
.eo-file{color:${BLUE};}
|
|
13027
|
+
.eo-sep{color:${SUBTEXT};}
|
|
13028
|
+
.eo-lineno{color:${YELLOW};}
|
|
13029
|
+
.eo-fn{color:${GREEN};}
|
|
13030
|
+
.eo-stale{background:${PEACH};color:${BG};padding:1px 8px;border-radius:3px;font-size:11px;font-weight:700;margin-left:6px;}
|
|
13031
|
+
.eo-details{margin-top:16px;}
|
|
13032
|
+
.eo-summary{cursor:pointer;color:${LAVENDER};font-weight:600;font-size:15px;padding:8px 0;user-select:none;}
|
|
13033
|
+
.eo-details-body{padding:8px 0;}
|
|
13034
|
+
.eo-table{border-collapse:collapse;width:100%;}
|
|
13035
|
+
.eo-key{color:${PEACH};padding:4px 16px 4px 0;vertical-align:top;white-space:nowrap;}
|
|
13036
|
+
.eo-val{color:${TEXT};padding:4px 0;word-break:break-all;}
|
|
13037
|
+
.eo-none{color:${SUBTEXT};}
|
|
13038
|
+
.eo-hidden-frames{color:${SUBTEXT};padding:8px 0;font-size:13px;}
|
|
13039
|
+
`;
|
|
13040
|
+
}
|
|
12808
13041
|
function isDebugMode() {
|
|
12809
13042
|
return isTruthy(process.env.TINA4_DEBUG);
|
|
12810
13043
|
}
|
|
@@ -12813,6 +13046,7 @@ var init_errorOverlay = __esm({
|
|
|
12813
13046
|
"../core/src/errorOverlay.ts"() {
|
|
12814
13047
|
"use strict";
|
|
12815
13048
|
init_dotenv();
|
|
13049
|
+
init_csp();
|
|
12816
13050
|
MAX_FRAMES = 50;
|
|
12817
13051
|
SENSITIVE_KEY_RE = /password|passwd|secret|token|authorization|cookie|key/i;
|
|
12818
13052
|
REDACTED2 = "[redacted]";
|
|
@@ -13188,6 +13422,25 @@ Generated by DevMailbox.seed() for development purposes.`,
|
|
|
13188
13422
|
}
|
|
13189
13423
|
});
|
|
13190
13424
|
|
|
13425
|
+
// ../core/src/upstreamResponse.ts
|
|
13426
|
+
async function relayUpstreamResponse(res, upstream) {
|
|
13427
|
+
const raw = await upstream.text();
|
|
13428
|
+
const status2 = upstream.status || 200;
|
|
13429
|
+
try {
|
|
13430
|
+
res.json(JSON.parse(raw), status2);
|
|
13431
|
+
} catch {
|
|
13432
|
+
res.raw.writeHead(status2, {
|
|
13433
|
+
"Content-Type": upstream.headers.get("content-type") ?? "text/plain; charset=utf-8"
|
|
13434
|
+
});
|
|
13435
|
+
res.raw.end(raw);
|
|
13436
|
+
}
|
|
13437
|
+
}
|
|
13438
|
+
var init_upstreamResponse = __esm({
|
|
13439
|
+
"../core/src/upstreamResponse.ts"() {
|
|
13440
|
+
"use strict";
|
|
13441
|
+
}
|
|
13442
|
+
});
|
|
13443
|
+
|
|
13191
13444
|
// ../core/src/metrics.ts
|
|
13192
13445
|
import * as fs3 from "node:fs";
|
|
13193
13446
|
import * as path2 from "node:path";
|
|
@@ -13403,6 +13656,7 @@ var init_feedback = __esm({
|
|
|
13403
13656
|
"use strict";
|
|
13404
13657
|
init_auth();
|
|
13405
13658
|
init_devAdmin();
|
|
13659
|
+
init_upstreamResponse();
|
|
13406
13660
|
RATE_LIMIT_WINDOW_SEC = 3600;
|
|
13407
13661
|
RATE_LIMIT_MAX = 5;
|
|
13408
13662
|
_rateLimitHits = /* @__PURE__ */ new Map();
|
|
@@ -13449,16 +13703,7 @@ var init_feedback = __esm({
|
|
|
13449
13703
|
return;
|
|
13450
13704
|
}
|
|
13451
13705
|
clearTimeout(timer);
|
|
13452
|
-
|
|
13453
|
-
const status2 = upstream.status || 200;
|
|
13454
|
-
try {
|
|
13455
|
-
res.json(JSON.parse(raw), status2);
|
|
13456
|
-
} catch {
|
|
13457
|
-
res.raw.writeHead(status2, {
|
|
13458
|
-
"Content-Type": upstream.headers.get("content-type") ?? "text/plain; charset=utf-8"
|
|
13459
|
-
});
|
|
13460
|
-
res.raw.end(raw);
|
|
13461
|
-
}
|
|
13706
|
+
await relayUpstreamResponse(res, upstream);
|
|
13462
13707
|
};
|
|
13463
13708
|
__feedbackDirname = dirname5(fileURLToPath2(import.meta.url));
|
|
13464
13709
|
WIDGET_BUNDLE_PATH = resolve5(__feedbackDirname, "__feedback", "widget.js");
|
|
@@ -13508,6 +13753,38 @@ var init_version = __esm({
|
|
|
13508
13753
|
}
|
|
13509
13754
|
});
|
|
13510
13755
|
|
|
13756
|
+
// ../cli/src/util.ts
|
|
13757
|
+
function parseFlags(args, booleanFlags) {
|
|
13758
|
+
const flags = {};
|
|
13759
|
+
const positional = [];
|
|
13760
|
+
let i = 0;
|
|
13761
|
+
while (i < args.length) {
|
|
13762
|
+
const arg = args[i];
|
|
13763
|
+
if (arg.startsWith("--")) {
|
|
13764
|
+
const key = arg.slice(2);
|
|
13765
|
+
if (booleanFlags?.has(key)) {
|
|
13766
|
+
flags[key] = true;
|
|
13767
|
+
i += 1;
|
|
13768
|
+
} else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
13769
|
+
flags[key] = args[i + 1];
|
|
13770
|
+
i += 2;
|
|
13771
|
+
} else {
|
|
13772
|
+
flags[key] = true;
|
|
13773
|
+
i += 1;
|
|
13774
|
+
}
|
|
13775
|
+
} else {
|
|
13776
|
+
positional.push(arg);
|
|
13777
|
+
i += 1;
|
|
13778
|
+
}
|
|
13779
|
+
}
|
|
13780
|
+
return { flags, positional };
|
|
13781
|
+
}
|
|
13782
|
+
var init_util = __esm({
|
|
13783
|
+
"../cli/src/util.ts"() {
|
|
13784
|
+
"use strict";
|
|
13785
|
+
}
|
|
13786
|
+
});
|
|
13787
|
+
|
|
13511
13788
|
// ../cli/src/commands/generate.ts
|
|
13512
13789
|
var generate_exports = {};
|
|
13513
13790
|
__export(generate_exports, {
|
|
@@ -13772,28 +14049,7 @@ function parseCliArgs(args) {
|
|
|
13772
14049
|
"json",
|
|
13773
14050
|
"dry-run"
|
|
13774
14051
|
]);
|
|
13775
|
-
|
|
13776
|
-
const positional = [];
|
|
13777
|
-
let i = 0;
|
|
13778
|
-
while (i < args.length) {
|
|
13779
|
-
if (args[i].startsWith("--")) {
|
|
13780
|
-
const key = args[i].slice(2);
|
|
13781
|
-
if (booleanFlags.has(key)) {
|
|
13782
|
-
flags[key] = true;
|
|
13783
|
-
i += 1;
|
|
13784
|
-
} else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
13785
|
-
flags[key] = args[i + 1];
|
|
13786
|
-
i += 2;
|
|
13787
|
-
} else {
|
|
13788
|
-
flags[key] = true;
|
|
13789
|
-
i += 1;
|
|
13790
|
-
}
|
|
13791
|
-
} else {
|
|
13792
|
-
positional.push(args[i]);
|
|
13793
|
-
i += 1;
|
|
13794
|
-
}
|
|
13795
|
-
}
|
|
13796
|
-
return { flags, positional };
|
|
14052
|
+
return parseFlags(args, booleanFlags);
|
|
13797
14053
|
}
|
|
13798
14054
|
function parseEvery(every) {
|
|
13799
14055
|
if (!every || every === true) return 60;
|
|
@@ -14641,7 +14897,11 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
14641
14897
|
`{% extends "base.twig" %}
|
|
14642
14898
|
{% block title %}Login{% endblock %}
|
|
14643
14899
|
{% block content %}
|
|
14644
|
-
|
|
14900
|
+
{# No inline style attribute: a nonce lets this <style> block run under the
|
|
14901
|
+
strict default Content-Security-Policy. csp_nonce() is a built-in Frond global
|
|
14902
|
+
that returns this response's nonce (ADR-0088). #}
|
|
14903
|
+
<style nonce="{{ csp_nonce() }}">.auth-form{max-width:400px;margin:0 auto}</style>
|
|
14904
|
+
<div class="container mt-4 auth-form">
|
|
14645
14905
|
<h1>Login</h1>
|
|
14646
14906
|
<form method="post" action="/api/auth/login">
|
|
14647
14907
|
{{ form_token() }}
|
|
@@ -14665,7 +14925,8 @@ export default async function (req: Tina4Request, res: Tina4Response) {
|
|
|
14665
14925
|
`{% extends "base.twig" %}
|
|
14666
14926
|
{% block title %}Register{% endblock %}
|
|
14667
14927
|
{% block content %}
|
|
14668
|
-
<
|
|
14928
|
+
<style nonce="{{ csp_nonce() }}">.auth-form{max-width:400px;margin:0 auto}</style>
|
|
14929
|
+
<div class="container mt-4 auth-form">
|
|
14669
14930
|
<h1>Register</h1>
|
|
14670
14931
|
<form method="post" action="/api/auth/register">
|
|
14671
14932
|
{{ form_token() }}
|
|
@@ -15354,6 +15615,7 @@ var FIELD_TYPE_MAP, SQL_RESERVED_TABLE_NAMES, RESOLUTION_ENVELOPE_VERSION, __res
|
|
|
15354
15615
|
var init_generate = __esm({
|
|
15355
15616
|
"../cli/src/commands/generate.ts"() {
|
|
15356
15617
|
"use strict";
|
|
15618
|
+
init_util();
|
|
15357
15619
|
FIELD_TYPE_MAP = {
|
|
15358
15620
|
string: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
|
|
15359
15621
|
str: { orm: '"string"', sql: "VARCHAR(255)", defaultVal: "''" },
|
|
@@ -15601,7 +15863,7 @@ import * as path3 from "node:path";
|
|
|
15601
15863
|
import * as os from "node:os";
|
|
15602
15864
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
15603
15865
|
import { createRequire as createRequire2 } from "node:module";
|
|
15604
|
-
import { randomBytes as
|
|
15866
|
+
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
15605
15867
|
function reqSibling(pkg) {
|
|
15606
15868
|
return req(`../../${pkg}/src/index.ts`);
|
|
15607
15869
|
}
|
|
@@ -16923,7 +17185,7 @@ var init_mcp = __esm({
|
|
|
16923
17185
|
// ── Session lifecycle + protocol negotiation ──────────────────
|
|
16924
17186
|
/** Mint a new session id and remember it. Called on `initialize`. */
|
|
16925
17187
|
openSession() {
|
|
16926
|
-
const sid =
|
|
17188
|
+
const sid = randomBytes5(16).toString("hex");
|
|
16927
17189
|
this._sessions.set(sid, Date.now());
|
|
16928
17190
|
return sid;
|
|
16929
17191
|
}
|
|
@@ -18972,6 +19234,7 @@ __export(websocket_exports, {
|
|
|
18972
19234
|
CLOSE_NORMAL: () => CLOSE_NORMAL,
|
|
18973
19235
|
CLOSE_POLICY_VIOLATION: () => CLOSE_POLICY_VIOLATION,
|
|
18974
19236
|
CLOSE_PROTOCOL_ERROR: () => CLOSE_PROTOCOL_ERROR,
|
|
19237
|
+
MessageAssembler: () => MessageAssembler,
|
|
18975
19238
|
OP_BINARY: () => OP_BINARY,
|
|
18976
19239
|
OP_CLOSE: () => OP_CLOSE,
|
|
18977
19240
|
OP_CONTINUATION: () => OP_CONTINUATION,
|
|
@@ -19229,21 +19492,24 @@ function serveWebSocketRoute(req2, socket, head) {
|
|
|
19229
19492
|
state.closed = true;
|
|
19230
19493
|
void Promise.resolve().then(() => conn._onClose ? conn._onClose() : handler(conn, "close", "")).catch((e) => Log.error(`WebSocket close handler error: ${e.message}`)).finally(() => wsRouteManager.remove(conn.id));
|
|
19231
19494
|
};
|
|
19495
|
+
const assembler = new MessageAssembler();
|
|
19232
19496
|
socket.on("data", (chunk) => {
|
|
19233
19497
|
buffer = Buffer.concat([buffer, chunk]);
|
|
19234
19498
|
while (buffer.length > 0) {
|
|
19235
19499
|
const frame = parseFrame(buffer);
|
|
19236
19500
|
if (!frame) break;
|
|
19237
19501
|
buffer = buffer.subarray(frame.bytesConsumed);
|
|
19238
|
-
|
|
19502
|
+
const whole = assembler.push(frame);
|
|
19503
|
+
if (!whole) continue;
|
|
19504
|
+
switch (whole.opcode) {
|
|
19239
19505
|
case OP_TEXT: {
|
|
19240
|
-
const text =
|
|
19506
|
+
const text = whole.payload.toString("utf-8");
|
|
19241
19507
|
void Promise.resolve().then(() => conn._onMessage ? conn._onMessage(text) : handler(conn, "message", text)).catch((e) => Log.error(`WebSocket message handler error: ${e.message}`));
|
|
19242
19508
|
break;
|
|
19243
19509
|
}
|
|
19244
19510
|
case OP_PING:
|
|
19245
19511
|
try {
|
|
19246
|
-
socket.write(buildFrame(OP_PONG,
|
|
19512
|
+
socket.write(buildFrame(OP_PONG, whole.payload));
|
|
19247
19513
|
} catch {
|
|
19248
19514
|
}
|
|
19249
19515
|
break;
|
|
@@ -19262,7 +19528,7 @@ function serveWebSocketRoute(req2, socket, head) {
|
|
|
19262
19528
|
socket.on("error", fireClose);
|
|
19263
19529
|
return true;
|
|
19264
19530
|
}
|
|
19265
|
-
var MAGIC_STRING, OP_CONTINUATION, OP_TEXT, OP_BINARY, OP_CLOSE, OP_PING, OP_PONG, CLOSE_NORMAL, CLOSE_GOING_AWAY, CLOSE_PROTOCOL_ERROR, CLOSE_POLICY_VIOLATION, WebSocketServer, WsRouteManager, wsRouteManager, DevReloadWsManager, devReloadWs;
|
|
19531
|
+
var MAGIC_STRING, OP_CONTINUATION, OP_TEXT, OP_BINARY, OP_CLOSE, OP_PING, OP_PONG, CLOSE_NORMAL, CLOSE_GOING_AWAY, CLOSE_PROTOCOL_ERROR, CLOSE_POLICY_VIOLATION, MessageAssembler, WebSocketServer, WsRouteManager, wsRouteManager, DevReloadWsManager, devReloadWs;
|
|
19266
19532
|
var init_websocket = __esm({
|
|
19267
19533
|
"../core/src/websocket.ts"() {
|
|
19268
19534
|
"use strict";
|
|
@@ -19281,6 +19547,30 @@ var init_websocket = __esm({
|
|
|
19281
19547
|
CLOSE_GOING_AWAY = 1001;
|
|
19282
19548
|
CLOSE_PROTOCOL_ERROR = 1002;
|
|
19283
19549
|
CLOSE_POLICY_VIOLATION = 1008;
|
|
19550
|
+
MessageAssembler = class {
|
|
19551
|
+
parts = [];
|
|
19552
|
+
opcode = 0;
|
|
19553
|
+
push(frame) {
|
|
19554
|
+
if (frame.opcode >= 8) return { opcode: frame.opcode, payload: frame.payload };
|
|
19555
|
+
if (frame.opcode === OP_CONTINUATION) {
|
|
19556
|
+
if (!this.opcode) return null;
|
|
19557
|
+
this.parts.push(frame.payload);
|
|
19558
|
+
if (!frame.fin) return null;
|
|
19559
|
+
const whole = { opcode: this.opcode, payload: Buffer.concat(this.parts) };
|
|
19560
|
+
this.parts = [];
|
|
19561
|
+
this.opcode = 0;
|
|
19562
|
+
return whole;
|
|
19563
|
+
}
|
|
19564
|
+
if (frame.fin) {
|
|
19565
|
+
this.parts = [];
|
|
19566
|
+
this.opcode = 0;
|
|
19567
|
+
return { opcode: frame.opcode, payload: frame.payload };
|
|
19568
|
+
}
|
|
19569
|
+
this.opcode = frame.opcode;
|
|
19570
|
+
this.parts = [frame.payload];
|
|
19571
|
+
return null;
|
|
19572
|
+
}
|
|
19573
|
+
};
|
|
19284
19574
|
WebSocketServer = class {
|
|
19285
19575
|
port;
|
|
19286
19576
|
server = null;
|
|
@@ -19723,6 +20013,15 @@ var init_websocket = __esm({
|
|
|
19723
20013
|
this.emit("error", err, client);
|
|
19724
20014
|
});
|
|
19725
20015
|
}
|
|
20016
|
+
assemblers = /* @__PURE__ */ new WeakMap();
|
|
20017
|
+
assemblerFor(client) {
|
|
20018
|
+
let a = this.assemblers.get(client);
|
|
20019
|
+
if (!a) {
|
|
20020
|
+
a = new MessageAssembler();
|
|
20021
|
+
this.assemblers.set(client, a);
|
|
20022
|
+
}
|
|
20023
|
+
return a;
|
|
20024
|
+
}
|
|
19726
20025
|
processBuffer(client, buffer, setBuffer) {
|
|
19727
20026
|
let remaining = buffer;
|
|
19728
20027
|
while (remaining.length > 0) {
|
|
@@ -19730,15 +20029,17 @@ var init_websocket = __esm({
|
|
|
19730
20029
|
if (!frame) break;
|
|
19731
20030
|
remaining = remaining.subarray(frame.bytesConsumed);
|
|
19732
20031
|
client.lastActivity = Date.now();
|
|
19733
|
-
|
|
20032
|
+
const whole = this.assemblerFor(client).push(frame);
|
|
20033
|
+
if (!whole) continue;
|
|
20034
|
+
switch (whole.opcode) {
|
|
19734
20035
|
case OP_TEXT:
|
|
19735
|
-
this.emit("message", client,
|
|
20036
|
+
this.emit("message", client, whole.payload.toString("utf-8"));
|
|
19736
20037
|
break;
|
|
19737
20038
|
case OP_BINARY:
|
|
19738
|
-
this.emit("message", client,
|
|
20039
|
+
this.emit("message", client, whole.payload);
|
|
19739
20040
|
break;
|
|
19740
20041
|
case OP_PING: {
|
|
19741
|
-
const pongFrame = buildFrame(OP_PONG,
|
|
20042
|
+
const pongFrame = buildFrame(OP_PONG, whole.payload);
|
|
19742
20043
|
try {
|
|
19743
20044
|
client.socket.write(pongFrame);
|
|
19744
20045
|
} catch {
|
|
@@ -24116,16 +24417,7 @@ async function proxyToSupervisor(req2, res, downstreamPath) {
|
|
|
24116
24417
|
return;
|
|
24117
24418
|
}
|
|
24118
24419
|
clearTimeout(timer);
|
|
24119
|
-
|
|
24120
|
-
const status2 = upstream.status || 200;
|
|
24121
|
-
try {
|
|
24122
|
-
res.json(JSON.parse(raw), status2);
|
|
24123
|
-
} catch {
|
|
24124
|
-
res.raw.writeHead(status2, {
|
|
24125
|
-
"Content-Type": upstream.headers.get("content-type") ?? "text/plain; charset=utf-8"
|
|
24126
|
-
});
|
|
24127
|
-
res.raw.end(raw);
|
|
24128
|
-
}
|
|
24420
|
+
await relayUpstreamResponse(res, upstream);
|
|
24129
24421
|
}
|
|
24130
24422
|
function resolveDevEnvVar(key) {
|
|
24131
24423
|
const live = process.env[key];
|
|
@@ -24567,6 +24859,7 @@ var init_devAdmin = __esm({
|
|
|
24567
24859
|
"../core/src/devAdmin.ts"() {
|
|
24568
24860
|
"use strict";
|
|
24569
24861
|
init_devMailbox();
|
|
24862
|
+
init_upstreamResponse();
|
|
24570
24863
|
init_dotenv();
|
|
24571
24864
|
init_metrics();
|
|
24572
24865
|
init_feedback();
|
|
@@ -24668,8 +24961,8 @@ var init_devAdmin = __esm({
|
|
|
24668
24961
|
* Duplicate errors (same message) increment count and update last_seen.
|
|
24669
24962
|
*/
|
|
24670
24963
|
static capture(errorType, message, traceback = "", file = "", line = 0) {
|
|
24671
|
-
const
|
|
24672
|
-
const existing = this.errors.find((e) => e.fingerprint ===
|
|
24964
|
+
const fingerprint2 = `${errorType}|${message}|${file}|${line}`;
|
|
24965
|
+
const existing = this.errors.find((e) => e.fingerprint === fingerprint2);
|
|
24673
24966
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24674
24967
|
if (existing) {
|
|
24675
24968
|
existing.count = (existing.count || 1) + 1;
|
|
@@ -24682,7 +24975,7 @@ var init_devAdmin = __esm({
|
|
|
24682
24975
|
message,
|
|
24683
24976
|
stack: traceback || void 0,
|
|
24684
24977
|
resolved: false,
|
|
24685
|
-
...{ fingerprint, error_type: errorType, file, line, count: 1, first_seen: now, last_seen: now }
|
|
24978
|
+
...{ fingerprint: fingerprint2, error_type: errorType, file, line, count: 1, first_seen: now, last_seen: now }
|
|
24686
24979
|
});
|
|
24687
24980
|
if (this.errors.length > this.maxErrors) {
|
|
24688
24981
|
this.errors = this.errors.slice(-this.maxErrors);
|
|
@@ -27361,7 +27654,7 @@ function swaggerEnabled() {
|
|
|
27361
27654
|
}
|
|
27362
27655
|
function createSwaggerRoutes(getSpec) {
|
|
27363
27656
|
const serveUi = async (_req, res) => {
|
|
27364
|
-
res.html(SWAGGER_UI_HTML("/swagger/openapi.json"));
|
|
27657
|
+
res.html(SWAGGER_UI_HTML("/swagger/openapi.json", res.cspNonce));
|
|
27365
27658
|
};
|
|
27366
27659
|
return [
|
|
27367
27660
|
{
|
|
@@ -27405,13 +27698,13 @@ var SWAGGER_UI_HTML;
|
|
|
27405
27698
|
var init_ui = __esm({
|
|
27406
27699
|
"../swagger/src/ui.ts"() {
|
|
27407
27700
|
"use strict";
|
|
27408
|
-
SWAGGER_UI_HTML = (specUrl) => `<!DOCTYPE html>
|
|
27701
|
+
SWAGGER_UI_HTML = (specUrl, nonce) => `<!DOCTYPE html>
|
|
27409
27702
|
<html lang="en">
|
|
27410
27703
|
<head>
|
|
27411
27704
|
<meta charset="UTF-8">
|
|
27412
27705
|
<title>Tina4 API Documentation</title>
|
|
27413
27706
|
<link rel="stylesheet" href="${swaggerUiCdn()}/swagger-ui.css">
|
|
27414
|
-
<style>
|
|
27707
|
+
<style nonce="${nonce}">
|
|
27415
27708
|
body { margin: 0; background: #fafafa; }
|
|
27416
27709
|
.topbar { display: none !important; }
|
|
27417
27710
|
</style>
|
|
@@ -27419,7 +27712,7 @@ var init_ui = __esm({
|
|
|
27419
27712
|
<body>
|
|
27420
27713
|
<div id="swagger-ui"></div>
|
|
27421
27714
|
<script src="${swaggerUiCdn()}/swagger-ui-bundle.js"></script>
|
|
27422
|
-
<script>
|
|
27715
|
+
<script nonce="${nonce}">
|
|
27423
27716
|
SwaggerUIBundle({
|
|
27424
27717
|
url: "${specUrl}",
|
|
27425
27718
|
dom_id: '#swagger-ui',
|
|
@@ -27459,7 +27752,7 @@ __export(sso_exports, {
|
|
|
27459
27752
|
SsoError: () => SsoError,
|
|
27460
27753
|
liveSessionIdentity: () => liveSessionIdentity
|
|
27461
27754
|
});
|
|
27462
|
-
import { createHash as createHash9, randomBytes as
|
|
27755
|
+
import { createHash as createHash9, randomBytes as randomBytes6, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
|
|
27463
27756
|
function expiresAt(tokens) {
|
|
27464
27757
|
const lifetime = Number(tokens.expires_in ?? 0);
|
|
27465
27758
|
return lifetime > 0 ? Math.floor(Date.now() / 1e3) + lifetime : 0;
|
|
@@ -27587,9 +27880,9 @@ var init_sso = __esm({
|
|
|
27587
27880
|
async login(requestOrSession, returnTo = "/") {
|
|
27588
27881
|
const session = this.session(requestOrSession);
|
|
27589
27882
|
if (!session) throw new SsoError("SSO login requires a Tina4 Session");
|
|
27590
|
-
const state =
|
|
27591
|
-
const nonce =
|
|
27592
|
-
const verifier =
|
|
27883
|
+
const state = randomBytes6(32).toString("base64url");
|
|
27884
|
+
const nonce = randomBytes6(32).toString("base64url");
|
|
27885
|
+
const verifier = randomBytes6(64).toString("base64url");
|
|
27593
27886
|
const challenge = createHash9("sha256").update(verifier).digest("base64url");
|
|
27594
27887
|
session.set(_Sso.PENDING_KEY, { state, nonce, verifier, return_to: _Sso.safeReturn(returnTo), created_at: Math.floor(Date.now() / 1e3) });
|
|
27595
27888
|
const metadata = await this.discover();
|
|
@@ -27817,7 +28110,7 @@ var init_docsAutoDiscovery = __esm({
|
|
|
27817
28110
|
});
|
|
27818
28111
|
|
|
27819
28112
|
// ../core/src/server.ts
|
|
27820
|
-
import { randomBytes as
|
|
28113
|
+
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
27821
28114
|
import { resolve as resolve15, dirname as dirname11, join as join25, relative as relative10 } from "node:path";
|
|
27822
28115
|
import { existsSync as existsSync23, readdirSync as readdirSync14, readFileSync as readFileSync21, statSync as statSync16 } from "node:fs";
|
|
27823
28116
|
import { isatty } from "node:tty";
|
|
@@ -27997,7 +28290,8 @@ function isDevMode() {
|
|
|
27997
28290
|
}
|
|
27998
28291
|
async function renderErrorPage(code, data, templatesDir) {
|
|
27999
28292
|
try {
|
|
28000
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
28293
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
28294
|
+
setCspNonceProvider2(currentCspNonce);
|
|
28001
28295
|
const templateFile = `errors/${code}.twig`;
|
|
28002
28296
|
const getCachedFrond = (dir) => {
|
|
28003
28297
|
let instance = frondCache.get(dir);
|
|
@@ -28113,6 +28407,7 @@ function resolveTemplate(pathname, templatesDir) {
|
|
|
28113
28407
|
}
|
|
28114
28408
|
function renderLandingPage(port = 7148) {
|
|
28115
28409
|
const version = TINA4_VERSION;
|
|
28410
|
+
const nonce = currentCspNonce();
|
|
28116
28411
|
const galleryItems = [
|
|
28117
28412
|
{ id: "rest-api", icon: "🚀", name: "REST API", desc: "A simple JSON API with GET and POST endpoints", accent: "accent-blue", tryUrl: "/api/gallery/hello" },
|
|
28118
28413
|
{ id: "orm", icon: "🗃", name: "ORM", desc: "Product model with CRUD endpoints", accent: "accent-green", tryUrl: "/api/gallery/products" },
|
|
@@ -28127,7 +28422,7 @@ function renderLandingPage(port = 7148) {
|
|
|
28127
28422
|
const isDeployed = deployed[item.id] === true;
|
|
28128
28423
|
const tryBtn = isDeployed ? `<a href="${item.tryUrl}" class="gbtn gbtn-try" target="_blank">Try It</a>` : "";
|
|
28129
28424
|
const viewBtn = isDeployed ? `<a href="${item.tryUrl}" class="gbtn gbtn-view" target="_blank">View</a>` : "";
|
|
28130
|
-
const deployBtn = isDeployed ? `<span class="gbtn gbtn-deployed">Deployed</span>` : `<button class="gbtn gbtn-deploy"
|
|
28425
|
+
const deployBtn = isDeployed ? `<span class="gbtn gbtn-deployed">Deployed</span>` : `<button class="gbtn gbtn-deploy" data-deploy="${item.id}">Deploy</button>`;
|
|
28131
28426
|
return `<div class="gallery-card">
|
|
28132
28427
|
<div class="accent ${item.accent}"></div>
|
|
28133
28428
|
<div class="icon">${item.icon}</div>
|
|
@@ -28142,7 +28437,7 @@ function renderLandingPage(port = 7148) {
|
|
|
28142
28437
|
<meta charset="utf-8">
|
|
28143
28438
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
28144
28439
|
<title>Tina4NodeJs</title>
|
|
28145
|
-
<style>
|
|
28440
|
+
<style nonce="${nonce}">
|
|
28146
28441
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
28147
28442
|
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0f172a;color:#e2e8f0;min-height:100vh;display:flex;flex-direction:column;align-items:center;position:relative}
|
|
28148
28443
|
.bg-watermark{position:fixed;bottom:-5%;right:-5%;width:45%;opacity:0.04;pointer-events:none;z-index:0}
|
|
@@ -28181,6 +28476,11 @@ h1{font-size:3rem;font-weight:700;margin-bottom:0.25rem;letter-spacing:-1px}
|
|
|
28181
28476
|
.gbtn-deployed{background:transparent;border:1px solid #22c55e;color:#22c55e;cursor:default;font-size:0.7rem}
|
|
28182
28477
|
@keyframes wiggle{0%{transform:rotate(0deg)}15%{transform:rotate(14deg)}30%{transform:rotate(-10deg)}45%{transform:rotate(8deg)}60%{transform:rotate(-4deg)}75%{transform:rotate(2deg)}100%{transform:rotate(0deg)}}
|
|
28183
28478
|
.star-wiggle{display:inline-block;transform-origin:center}
|
|
28479
|
+
.gallery-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1rem}
|
|
28480
|
+
.tok-comment{color:#64748b}
|
|
28481
|
+
.tok-kw{color:#c084fc}
|
|
28482
|
+
.tok-str{color:#4ade80}
|
|
28483
|
+
.tok-fn{color:#38bdf8}
|
|
28184
28484
|
</style>
|
|
28185
28485
|
</head>
|
|
28186
28486
|
<body>
|
|
@@ -28206,23 +28506,23 @@ h1{font-size:3rem;font-weight:700;margin-bottom:0.25rem;letter-spacing:-1px}
|
|
|
28206
28506
|
<div class="section">
|
|
28207
28507
|
<div class="card">
|
|
28208
28508
|
<h2>Getting Started</h2>
|
|
28209
|
-
<pre class="code-block"><code><span
|
|
28210
|
-
<span
|
|
28509
|
+
<pre class="code-block"><code><span class="tok-comment">// app.ts</span>
|
|
28510
|
+
<span class="tok-kw">import</span> { startServer, Router } <span class="tok-kw">from</span> <span class="tok-str">"tina4-nodejs"</span>;
|
|
28211
28511
|
|
|
28212
|
-
Router.get(<span
|
|
28213
|
-
<span
|
|
28512
|
+
Router.get(<span class="tok-str">"/hello"</span>, <span class="tok-kw">async</span> (<span class="tok-fn">req</span>, <span class="tok-fn">res</span>) => {
|
|
28513
|
+
<span class="tok-kw">return</span> res.json({ message: <span class="tok-str">"Hello World!"</span> });
|
|
28214
28514
|
});
|
|
28215
28515
|
|
|
28216
|
-
startServer({ port: 7148 }); <span
|
|
28516
|
+
startServer({ port: 7148 }); <span class="tok-comment">// starts on port 7148</span></code></pre>
|
|
28217
28517
|
</div>
|
|
28218
28518
|
</div>
|
|
28219
28519
|
<div class="gallery">
|
|
28220
28520
|
<h2 id="gallery">What You Can Build</h2>
|
|
28221
|
-
<div
|
|
28521
|
+
<div class="gallery-cards">
|
|
28222
28522
|
${galleryCards}
|
|
28223
28523
|
</div>
|
|
28224
28524
|
</div>
|
|
28225
|
-
<script>
|
|
28525
|
+
<script nonce="${nonce}">
|
|
28226
28526
|
function deployGallery(name) {
|
|
28227
28527
|
if (!confirm('Deploy the "' + name + '" gallery example into your project? This will copy files into src/.')) return;
|
|
28228
28528
|
fetch('/__dev/api/gallery/deploy', {
|
|
@@ -28257,6 +28557,12 @@ function deployGallery(name) {
|
|
|
28257
28557
|
}
|
|
28258
28558
|
setTimeout(doWiggle,3000);
|
|
28259
28559
|
})();
|
|
28560
|
+
// CSP-clean wiring: no inline onclick handlers \u2014 bind every Deploy button from
|
|
28561
|
+
// its data-deploy attribute (a nonce covers script elements, not event-handler
|
|
28562
|
+
// attributes).
|
|
28563
|
+
document.querySelectorAll('.gbtn-deploy[data-deploy]').forEach(function(btn){
|
|
28564
|
+
btn.addEventListener('click', function(){ deployGallery(btn.dataset.deploy); });
|
|
28565
|
+
});
|
|
28260
28566
|
</script>
|
|
28261
28567
|
</body>
|
|
28262
28568
|
</html>`;
|
|
@@ -28473,7 +28779,7 @@ async function renderDispatchError(err, req2, res, templatesDir) {
|
|
|
28473
28779
|
}
|
|
28474
28780
|
}
|
|
28475
28781
|
}
|
|
28476
|
-
const requestId = Log.getRequestId() ??
|
|
28782
|
+
const requestId = Log.getRequestId() ?? randomBytes7(4).toString("hex");
|
|
28477
28783
|
if (wantsJson(req2)) {
|
|
28478
28784
|
const body = negotiatedErrorBody(500, "Internal Server Error", requestId);
|
|
28479
28785
|
res.raw.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -28544,7 +28850,7 @@ function serveStaticAsset(ctx) {
|
|
|
28544
28850
|
return false;
|
|
28545
28851
|
}
|
|
28546
28852
|
async function serveNotFound(ctx) {
|
|
28547
|
-
const requestId = Log.getRequestId() ??
|
|
28853
|
+
const requestId = Log.getRequestId() ?? randomBytes7(4).toString("hex");
|
|
28548
28854
|
if (wantsJson(ctx.req)) {
|
|
28549
28855
|
const body = negotiatedErrorBody(404, "Not Found", requestId);
|
|
28550
28856
|
ctx.res.raw.writeHead(404, httpReason(404), { "Content-Type": "application/json" });
|
|
@@ -28567,7 +28873,8 @@ async function buildDispatchContext(router, base) {
|
|
|
28567
28873
|
const templatesDir = resolve15(root, "src/templates");
|
|
28568
28874
|
let frondEngine = null;
|
|
28569
28875
|
try {
|
|
28570
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
28876
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
28877
|
+
setCspNonceProvider2(currentCspNonce);
|
|
28571
28878
|
frondEngine = new Frond2(templatesDir);
|
|
28572
28879
|
} catch {
|
|
28573
28880
|
}
|
|
@@ -28677,9 +28984,13 @@ async function dispatchInner(ctx, rawReq, rawRes, requestId) {
|
|
|
28677
28984
|
}
|
|
28678
28985
|
}
|
|
28679
28986
|
async function runDispatch(ctx, rawReq, rawRes) {
|
|
28680
|
-
const requestId = Log.sanitizeRequestId(rawReq.headers["x-request-id"]) ??
|
|
28987
|
+
const requestId = Log.sanitizeRequestId(rawReq.headers["x-request-id"]) ?? randomBytes7(4).toString("hex");
|
|
28681
28988
|
if (!rawRes.headersSent) rawRes.setHeader("x-request-id", requestId);
|
|
28682
|
-
|
|
28989
|
+
const requestNonce = generateNonce();
|
|
28990
|
+
return Log.runWithRequestId(
|
|
28991
|
+
requestId,
|
|
28992
|
+
() => runWithCspNonce(requestNonce, () => dispatchInner(ctx, rawReq, rawRes, requestId))
|
|
28993
|
+
);
|
|
28683
28994
|
}
|
|
28684
28995
|
function loopbackBindHosts(host) {
|
|
28685
28996
|
const normalized = host.trim().replace(/^\[|\]$/g, "").toLowerCase();
|
|
@@ -28806,7 +29117,8 @@ async function startServer(config) {
|
|
|
28806
29117
|
let frondEngine = null;
|
|
28807
29118
|
setDefaultTemplatesDir(templatesDir);
|
|
28808
29119
|
try {
|
|
28809
|
-
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
29120
|
+
const { Frond: Frond2, setCspNonceProvider: setCspNonceProvider2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
29121
|
+
setCspNonceProvider2(currentCspNonce);
|
|
28810
29122
|
frondEngine = new Frond2(templatesDir);
|
|
28811
29123
|
router.addRoute({
|
|
28812
29124
|
method: "GET",
|
|
@@ -28947,10 +29259,12 @@ async function startServer(config) {
|
|
|
28947
29259
|
}
|
|
28948
29260
|
});
|
|
28949
29261
|
aiServer.on("error", (err) => {
|
|
28950
|
-
if (err
|
|
29262
|
+
if (err?.code === "EADDRINUSE") {
|
|
28951
29263
|
Log.warning(`Test port ${testPort} in use \u2014 skipping`);
|
|
28952
|
-
|
|
29264
|
+
} else {
|
|
29265
|
+
Log.error(`Test port ${testPort} bind failed (${err?.code ?? err}) \u2014 skipping`);
|
|
28953
29266
|
}
|
|
29267
|
+
aiServer = null;
|
|
28954
29268
|
});
|
|
28955
29269
|
aiServer.listen(testPort, host);
|
|
28956
29270
|
}
|
|
@@ -29165,6 +29479,7 @@ var init_server = __esm({
|
|
|
29165
29479
|
init_static();
|
|
29166
29480
|
init_dotenv();
|
|
29167
29481
|
init_errorOverlay();
|
|
29482
|
+
init_csp();
|
|
29168
29483
|
init_health();
|
|
29169
29484
|
init_rateLimiter();
|
|
29170
29485
|
init_logger();
|
|
@@ -29945,7 +30260,7 @@ var init_mqttMessage = __esm({
|
|
|
29945
30260
|
// ../core/src/mqtt.ts
|
|
29946
30261
|
import net2 from "node:net";
|
|
29947
30262
|
import tls from "node:tls";
|
|
29948
|
-
import { randomBytes as
|
|
30263
|
+
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
29949
30264
|
import { existsSync as existsSync25, readFileSync as readFileSync22 } from "node:fs";
|
|
29950
30265
|
var MqttError, MqttTimeoutError, CONNECT, CONNACK, PUBLISH, PUBACK, SUBSCRIBE, SUBACK, PINGREQ, PINGRESP, DISCONNECT, PROTOCOL_LEVEL, DEFAULT_PORT, DEFAULT_TLS_PORT, DEFAULT_URL, DEFAULT_KEEPALIVE, SUBSCRIPTION_REFUSED, MAX_REMAINING_LENGTH, QOS2_REFUSED_MESSAGE, CONNACK_RETURN_CODES, Mqtt;
|
|
29951
30266
|
var init_mqtt = __esm({
|
|
@@ -30033,7 +30348,7 @@ var init_mqtt = __esm({
|
|
|
30033
30348
|
this.caFile = options.caFile ?? (Env.str("TINA4_MQTT_CA_FILE") || null);
|
|
30034
30349
|
this.tlsVerify = options.tlsVerify ?? Env.bool("TINA4_MQTT_TLS_VERIFY", true);
|
|
30035
30350
|
let cid = options.clientId ?? (Env.str("TINA4_MQTT_CLIENT_ID") || null);
|
|
30036
|
-
if (cid === null || cid === "") cid = "tina4-" +
|
|
30351
|
+
if (cid === null || cid === "") cid = "tina4-" + randomBytes8(8).toString("hex");
|
|
30037
30352
|
this.clientId = cid;
|
|
30038
30353
|
this.keepalive = options.keepalive ?? Env.int("TINA4_MQTT_KEEPALIVE", DEFAULT_KEEPALIVE);
|
|
30039
30354
|
this.cleanSession = options.cleanSession ?? true;
|
|
@@ -31147,7 +31462,7 @@ var init_ssrf = __esm({
|
|
|
31147
31462
|
import http from "node:http";
|
|
31148
31463
|
import https from "node:https";
|
|
31149
31464
|
import { URL as URL2 } from "node:url";
|
|
31150
|
-
import { randomBytes as
|
|
31465
|
+
import { randomBytes as randomBytes9 } from "node:crypto";
|
|
31151
31466
|
import { promises as fsp, createWriteStream } from "node:fs";
|
|
31152
31467
|
import { basename as basename6 } from "node:path";
|
|
31153
31468
|
import { pipeline } from "node:stream/promises";
|
|
@@ -31539,7 +31854,7 @@ var init_api = __esm({
|
|
|
31539
31854
|
return { http_code: null, body: null, headers: {}, error: "upload requires filePath or fileBytes" };
|
|
31540
31855
|
}
|
|
31541
31856
|
const partContentType = guessContentType(uploadName);
|
|
31542
|
-
const boundary = "----Tina4Boundary" +
|
|
31857
|
+
const boundary = "----Tina4Boundary" + randomBytes9(16).toString("hex");
|
|
31543
31858
|
const bodyBuffer = buildMultipartBody(boundary, fieldName, uploadName, content, partContentType, extraFields);
|
|
31544
31859
|
const contentType = `multipart/form-data; boundary=${boundary}`;
|
|
31545
31860
|
return this.execute("POST", this.buildUrl(path10), bodyBuffer, contentType, headers);
|
|
@@ -34503,7 +34818,7 @@ import {
|
|
|
34503
34818
|
createHmac as createHmac3,
|
|
34504
34819
|
createPrivateKey,
|
|
34505
34820
|
createSign as createSign2,
|
|
34506
|
-
randomBytes as
|
|
34821
|
+
randomBytes as randomBytes10
|
|
34507
34822
|
} from "node:crypto";
|
|
34508
34823
|
function encodeBase64Url(value) {
|
|
34509
34824
|
return Buffer.from(value).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
@@ -34583,7 +34898,7 @@ function encryptPayload(payload, subscription) {
|
|
|
34583
34898
|
]);
|
|
34584
34899
|
const prkKey = hmac(authSecret, sharedSecret);
|
|
34585
34900
|
const ikm = hkdfExpand(prkKey, keyInfo, 32);
|
|
34586
|
-
const salt =
|
|
34901
|
+
const salt = randomBytes10(16);
|
|
34587
34902
|
const contentPrk = hmac(salt, ikm);
|
|
34588
34903
|
const cek = hkdfExpand(contentPrk, Buffer.from("Content-Encoding: aes128gcm\0", "ascii"), 16);
|
|
34589
34904
|
const nonce = hkdfExpand(contentPrk, Buffer.from("Content-Encoding: nonce\0", "ascii"), 12);
|
|
@@ -37340,6 +37655,7 @@ __export(src_exports2, {
|
|
|
37340
37655
|
cacheStats: () => cacheStats,
|
|
37341
37656
|
checkPassword: () => checkPassword,
|
|
37342
37657
|
clearCache: () => clearCache,
|
|
37658
|
+
clearCurrentNonce: () => clearCurrentNonce,
|
|
37343
37659
|
computeAcceptKey: () => computeAcceptKey,
|
|
37344
37660
|
container: () => container,
|
|
37345
37661
|
cors: () => cors,
|
|
@@ -37351,6 +37667,8 @@ __export(src_exports2, {
|
|
|
37351
37667
|
createMessenger: () => createMessenger,
|
|
37352
37668
|
createRequest: () => createRequest,
|
|
37353
37669
|
createResponse: () => createResponse,
|
|
37670
|
+
cspNonce: () => cspNonce,
|
|
37671
|
+
currentCspNonce: () => currentCspNonce,
|
|
37354
37672
|
decodeRequest: () => decodeRequest,
|
|
37355
37673
|
defaultContext: () => defaultContext,
|
|
37356
37674
|
defaultRouter: () => defaultRouter,
|
|
@@ -37376,6 +37694,7 @@ __export(src_exports2, {
|
|
|
37376
37694
|
feedbackWhitelist: () => feedbackWhitelist,
|
|
37377
37695
|
fts5Supported: () => fts5Supported,
|
|
37378
37696
|
generateContext: () => generateContext,
|
|
37697
|
+
generateNonce: () => generateNonce,
|
|
37379
37698
|
generateVapidKeys: () => generateVapidKeys,
|
|
37380
37699
|
get: () => get,
|
|
37381
37700
|
getDefaultDevServer: () => getDefaultDevServer,
|
|
@@ -37398,6 +37717,7 @@ __export(src_exports2, {
|
|
|
37398
37717
|
httpReason: () => httpReason,
|
|
37399
37718
|
inContainer: () => inContainer,
|
|
37400
37719
|
injectFeedbackWidget: () => injectFeedbackWidget,
|
|
37720
|
+
injectNonceIntoCsp: () => injectNonceIntoCsp,
|
|
37401
37721
|
installAll: () => installAll,
|
|
37402
37722
|
installSelected: () => installSelected,
|
|
37403
37723
|
isBannerSuppressed: () => isBannerSuppressed,
|
|
@@ -37452,6 +37772,7 @@ __export(src_exports2, {
|
|
|
37452
37772
|
resetTrustedProxyCache: () => resetTrustedProxyCache,
|
|
37453
37773
|
resolveAlgorithm: () => resolveAlgorithm,
|
|
37454
37774
|
resolveClientIp: () => resolveClientIp,
|
|
37775
|
+
resolveCspHeader: () => resolveCspHeader,
|
|
37455
37776
|
resolveFrameworkVersion: () => resolveFrameworkVersion,
|
|
37456
37777
|
resolvePortAndHost: () => resolvePortAndHost,
|
|
37457
37778
|
resolveStringMiddleware: () => resolveStringMiddleware,
|
|
@@ -37459,12 +37780,14 @@ __export(src_exports2, {
|
|
|
37459
37780
|
responseCache: () => responseCache,
|
|
37460
37781
|
runAll: () => runAll,
|
|
37461
37782
|
runRouteMiddlewares: () => runRouteMiddlewares,
|
|
37783
|
+
runWithCspNonce: () => runWithCspNonce,
|
|
37462
37784
|
runtimeDir: () => runtimeDir,
|
|
37463
37785
|
saveUpload: () => saveUpload,
|
|
37464
37786
|
schemaFromParams: () => schemaFromParams,
|
|
37465
37787
|
selectablePids: () => selectablePids,
|
|
37466
37788
|
serveWebSocketRoute: () => serveWebSocketRoute,
|
|
37467
37789
|
sessionCookieName: () => sessionCookieName,
|
|
37790
|
+
setCurrentNonce: () => setCurrentNonce,
|
|
37468
37791
|
setDefaultTemplatesDir: () => setDefaultTemplatesDir,
|
|
37469
37792
|
setFrond: () => setFrond,
|
|
37470
37793
|
showMenu: () => showMenu,
|
|
@@ -37531,6 +37854,7 @@ var init_src2 = __esm({
|
|
|
37531
37854
|
init_wsdl();
|
|
37532
37855
|
init_htmlElement();
|
|
37533
37856
|
init_errorOverlay();
|
|
37857
|
+
init_csp();
|
|
37534
37858
|
init_ai();
|
|
37535
37859
|
init_sso();
|
|
37536
37860
|
init_push();
|
|
@@ -41697,7 +42021,7 @@ __export(database_exports, {
|
|
|
41697
42021
|
stripTrailingSemicolons: () => stripTrailingSemicolons,
|
|
41698
42022
|
wrapWithCache: () => wrapWithCache
|
|
41699
42023
|
});
|
|
41700
|
-
import { AsyncLocalStorage as
|
|
42024
|
+
import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
|
|
41701
42025
|
function stripTrailingSemicolons(sql) {
|
|
41702
42026
|
if (!sql) return sql;
|
|
41703
42027
|
let stripped = sql.replace(/\s+$/, "");
|
|
@@ -42111,9 +42435,9 @@ var init_database = __esm({
|
|
|
42111
42435
|
leases = /* @__PURE__ */ new Map();
|
|
42112
42436
|
discardedSlots = /* @__PURE__ */ new Set();
|
|
42113
42437
|
repairs = /* @__PURE__ */ new Map();
|
|
42114
|
-
leaseOwner = new
|
|
42115
|
-
operationStore = new
|
|
42116
|
-
txStore = new
|
|
42438
|
+
leaseOwner = new AsyncLocalStorage3();
|
|
42439
|
+
operationStore = new AsyncLocalStorage3();
|
|
42440
|
+
txStore = new AsyncLocalStorage3();
|
|
42117
42441
|
/**
|
|
42118
42442
|
* Create a Database wrapping an existing adapter.
|
|
42119
42443
|
* For creating a Database from a URL, use the async static factories:
|
|
@@ -46864,7 +47188,7 @@ var init_seeder = __esm({
|
|
|
46864
47188
|
|
|
46865
47189
|
// src/docstore.ts
|
|
46866
47190
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
46867
|
-
import { randomBytes as
|
|
47191
|
+
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
46868
47192
|
import { mkdirSync as mkdirSync20 } from "node:fs";
|
|
46869
47193
|
import { dirname as dirname14, isAbsolute as isAbsolute6, join as join32 } from "node:path";
|
|
46870
47194
|
function iso(d) {
|
|
@@ -47221,8 +47545,8 @@ var init_docstore = __esm({
|
|
|
47221
47545
|
OID_RE = /^[0-9a-fA-F]{24}$/;
|
|
47222
47546
|
ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?$/;
|
|
47223
47547
|
ObjectId = class _ObjectId {
|
|
47224
|
-
static _counter =
|
|
47225
|
-
static _process =
|
|
47548
|
+
static _counter = randomBytes11(3).readUIntBE(0, 3);
|
|
47549
|
+
static _process = randomBytes11(5);
|
|
47226
47550
|
_bytes;
|
|
47227
47551
|
constructor(oid) {
|
|
47228
47552
|
if (oid === void 0 || oid === null) {
|
|
@@ -47718,6 +48042,36 @@ var init_shapes = __esm({
|
|
|
47718
48042
|
}
|
|
47719
48043
|
});
|
|
47720
48044
|
|
|
48045
|
+
// src/graph/adapters/rowMapping.ts
|
|
48046
|
+
function errorMessage(exc) {
|
|
48047
|
+
if (exc instanceof Error) return exc.message;
|
|
48048
|
+
return String(exc);
|
|
48049
|
+
}
|
|
48050
|
+
function nodeFromRow(row) {
|
|
48051
|
+
if (row === null || row === void 0) return null;
|
|
48052
|
+
return new GraphNode(
|
|
48053
|
+
String(row.id),
|
|
48054
|
+
row.labels ?? [],
|
|
48055
|
+
row.props ?? {}
|
|
48056
|
+
);
|
|
48057
|
+
}
|
|
48058
|
+
function edgeFromRow(row) {
|
|
48059
|
+
if (row === null || row === void 0) return null;
|
|
48060
|
+
return new GraphEdge(
|
|
48061
|
+
String(row.id),
|
|
48062
|
+
String(row.type),
|
|
48063
|
+
String(row.f),
|
|
48064
|
+
String(row.t),
|
|
48065
|
+
row.props ?? {}
|
|
48066
|
+
);
|
|
48067
|
+
}
|
|
48068
|
+
var init_rowMapping = __esm({
|
|
48069
|
+
"src/graph/adapters/rowMapping.ts"() {
|
|
48070
|
+
"use strict";
|
|
48071
|
+
init_shapes();
|
|
48072
|
+
}
|
|
48073
|
+
});
|
|
48074
|
+
|
|
47721
48075
|
// src/graph/connectTimeout.ts
|
|
47722
48076
|
function resolveGraphConnectTimeout() {
|
|
47723
48077
|
const raw = (process.env[GRAPH_CONNECT_TIMEOUT_VARIABLE] ?? "").trim();
|
|
@@ -47757,15 +48111,12 @@ function propClause(properties) {
|
|
|
47757
48111
|
for (const key of keys) params[`p_${key}`] = props[key];
|
|
47758
48112
|
return { clause: `{${pairs}}`, params };
|
|
47759
48113
|
}
|
|
47760
|
-
function errorMessage(exc) {
|
|
47761
|
-
if (exc instanceof Error) return exc.message;
|
|
47762
|
-
return String(exc);
|
|
47763
|
-
}
|
|
47764
48114
|
var DRIVER_PACKAGE2, driver, UltipaClient, UltipaConnectError, UNBOUNDED_CONNECT_SECONDS, UltipaGraphAdapter;
|
|
47765
48115
|
var init_ultipa = __esm({
|
|
47766
48116
|
async "src/graph/adapters/ultipa.ts"() {
|
|
47767
48117
|
"use strict";
|
|
47768
48118
|
init_shapes();
|
|
48119
|
+
init_rowMapping();
|
|
47769
48120
|
init_errors();
|
|
47770
48121
|
init_connectTimeout2();
|
|
47771
48122
|
DRIVER_PACKAGE2 = "tina4-ultipa";
|
|
@@ -47823,12 +48174,7 @@ var init_ultipa = __esm({
|
|
|
47823
48174
|
}
|
|
47824
48175
|
// -- portable node/edge/traverse core (GQL) ----------------------------
|
|
47825
48176
|
nodeFromRow(row) {
|
|
47826
|
-
|
|
47827
|
-
return new GraphNode(
|
|
47828
|
-
String(row.id),
|
|
47829
|
-
row.labels ?? [],
|
|
47830
|
-
row.props ?? {}
|
|
47831
|
-
);
|
|
48177
|
+
return nodeFromRow(row);
|
|
47832
48178
|
}
|
|
47833
48179
|
async addNode(label, properties = null) {
|
|
47834
48180
|
const { clause, params } = propClause(properties);
|
|
@@ -47842,15 +48188,7 @@ var init_ultipa = __esm({
|
|
|
47842
48188
|
params.to_id = toId;
|
|
47843
48189
|
const gql = `MATCH (a), (b) WHERE id(a) = $from_id AND id(b) = $to_id INSERT (a)-[e:\`${type2}\` ${clause}]->(b) RETURN id(e) AS id, type(e) AS type, id(a) AS f, id(b) AS t, properties(e) AS props`;
|
|
47844
48190
|
const rows = (await this.run(gql, params, false)).dicts();
|
|
47845
|
-
|
|
47846
|
-
const row = rows[0];
|
|
47847
|
-
return new GraphEdge(
|
|
47848
|
-
String(row.id),
|
|
47849
|
-
String(row.type),
|
|
47850
|
-
String(row.f),
|
|
47851
|
-
String(row.t),
|
|
47852
|
-
row.props ?? {}
|
|
47853
|
-
);
|
|
48191
|
+
return edgeFromRow(rows[0]);
|
|
47854
48192
|
}
|
|
47855
48193
|
async getNode(nodeId) {
|
|
47856
48194
|
const gql = `MATCH (n) WHERE id(n) = $id RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
|
|
@@ -47915,10 +48253,6 @@ var bolt_exports = {};
|
|
|
47915
48253
|
__export(bolt_exports, {
|
|
47916
48254
|
BoltGraphAdapter: () => BoltGraphAdapter
|
|
47917
48255
|
});
|
|
47918
|
-
function errorMessage2(exc) {
|
|
47919
|
-
if (exc instanceof Error) return exc.message;
|
|
47920
|
-
return String(exc);
|
|
47921
|
-
}
|
|
47922
48256
|
function boltId(id) {
|
|
47923
48257
|
const value = Number(id);
|
|
47924
48258
|
return Number.isNaN(value) ? -1 : value;
|
|
@@ -47928,6 +48262,7 @@ var init_bolt = __esm({
|
|
|
47928
48262
|
async "src/graph/adapters/bolt.ts"() {
|
|
47929
48263
|
"use strict";
|
|
47930
48264
|
init_shapes();
|
|
48265
|
+
init_rowMapping();
|
|
47931
48266
|
init_errors();
|
|
47932
48267
|
init_connectTimeout2();
|
|
47933
48268
|
DRIVER_PACKAGE3 = "neo4j-driver";
|
|
@@ -47963,7 +48298,7 @@ var init_bolt = __esm({
|
|
|
47963
48298
|
const result = await session.run(cypher, params ?? {});
|
|
47964
48299
|
return result.records.map((record) => record.toObject());
|
|
47965
48300
|
} catch (exc) {
|
|
47966
|
-
this.lastError =
|
|
48301
|
+
this.lastError = errorMessage(exc);
|
|
47967
48302
|
const code = exc?.code ?? "";
|
|
47968
48303
|
const message = this.lastError.toLowerCase();
|
|
47969
48304
|
if (code === "ServiceUnavailable" || code === neo4j.error?.SERVICE_UNAVAILABLE || message.includes("timed out") || message.includes("timeout")) {
|
|
@@ -47987,12 +48322,7 @@ var init_bolt = __esm({
|
|
|
47987
48322
|
}
|
|
47988
48323
|
// -- portable node/edge/traverse core (Cypher) -------------------------
|
|
47989
48324
|
nodeFromRow(row) {
|
|
47990
|
-
|
|
47991
|
-
return new GraphNode(
|
|
47992
|
-
String(row.id),
|
|
47993
|
-
row.labels ?? [],
|
|
47994
|
-
row.props ?? {}
|
|
47995
|
-
);
|
|
48325
|
+
return nodeFromRow(row);
|
|
47996
48326
|
}
|
|
47997
48327
|
async addNode(label, properties = null) {
|
|
47998
48328
|
const cypher = `CREATE (n:\`${label}\` $props) RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
|
|
@@ -48006,15 +48336,7 @@ var init_bolt = __esm({
|
|
|
48006
48336
|
to_id: boltId(toId),
|
|
48007
48337
|
props: properties ?? {}
|
|
48008
48338
|
});
|
|
48009
|
-
|
|
48010
|
-
const row = rows[0];
|
|
48011
|
-
return new GraphEdge(
|
|
48012
|
-
String(row.id),
|
|
48013
|
-
String(row.type),
|
|
48014
|
-
String(row.f),
|
|
48015
|
-
String(row.t),
|
|
48016
|
-
row.props ?? {}
|
|
48017
|
-
);
|
|
48339
|
+
return edgeFromRow(rows[0]);
|
|
48018
48340
|
}
|
|
48019
48341
|
async getNode(nodeId) {
|
|
48020
48342
|
const cypher = `MATCH (n) WHERE id(n) = $id RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props`;
|
|
@@ -48080,7 +48402,7 @@ function cleanProps(doc) {
|
|
|
48080
48402
|
}
|
|
48081
48403
|
return props;
|
|
48082
48404
|
}
|
|
48083
|
-
function
|
|
48405
|
+
function errorMessage2(exc) {
|
|
48084
48406
|
if (exc instanceof Error) return exc.message;
|
|
48085
48407
|
return String(exc);
|
|
48086
48408
|
}
|
|
@@ -48119,14 +48441,14 @@ var init_arango = __esm({
|
|
|
48119
48441
|
this.db = new ArangoDatabase(config);
|
|
48120
48442
|
}
|
|
48121
48443
|
connectOrError(exc) {
|
|
48122
|
-
const text =
|
|
48444
|
+
const text = errorMessage2(exc).toLowerCase();
|
|
48123
48445
|
if (text.includes("timed out") || text.includes("timeout") || text.includes("connection") || text.includes("econnrefused") || text.includes("etimedout") || text.includes("max retries")) {
|
|
48124
48446
|
return new GraphConnectTimeout(
|
|
48125
48447
|
`Graph connect to ${this.url.host}:${this.url.port} timed out (${GRAPH_CONNECT_TIMEOUT_VARIABLE}). Raise ${GRAPH_CONNECT_TIMEOUT_VARIABLE} if the server is simply slow, or set it to 0 to wait indefinitely.`,
|
|
48126
48448
|
exc
|
|
48127
48449
|
);
|
|
48128
48450
|
}
|
|
48129
|
-
return new GraphError(
|
|
48451
|
+
return new GraphError(errorMessage2(exc), exc);
|
|
48130
48452
|
}
|
|
48131
48453
|
async ensureCollections() {
|
|
48132
48454
|
if (this.ensured) return;
|
|
@@ -48137,7 +48459,7 @@ var init_arango = __esm({
|
|
|
48137
48459
|
if (!await edges.exists()) await this.db.createEdgeCollection(EDGE_COLLECTION);
|
|
48138
48460
|
this.ensured = true;
|
|
48139
48461
|
} catch (exc) {
|
|
48140
|
-
this.lastError =
|
|
48462
|
+
this.lastError = errorMessage2(exc);
|
|
48141
48463
|
throw this.connectOrError(exc);
|
|
48142
48464
|
}
|
|
48143
48465
|
}
|
|
@@ -48147,7 +48469,7 @@ var init_arango = __esm({
|
|
|
48147
48469
|
const cursor = await this.db.query({ query, bindVars: bind2 ?? {} });
|
|
48148
48470
|
return await cursor.all();
|
|
48149
48471
|
} catch (exc) {
|
|
48150
|
-
this.lastError =
|
|
48472
|
+
this.lastError = errorMessage2(exc);
|
|
48151
48473
|
throw this.connectOrError(exc);
|
|
48152
48474
|
}
|
|
48153
48475
|
}
|
|
@@ -48421,7 +48743,7 @@ var init_attachment = __esm({
|
|
|
48421
48743
|
});
|
|
48422
48744
|
|
|
48423
48745
|
// src/realtime/storage.ts
|
|
48424
|
-
import { randomBytes as
|
|
48746
|
+
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
48425
48747
|
import { mkdirSync as mkdirSync21, readFileSync as readFileSync26, writeFileSync as writeFileSync18, unlinkSync as unlinkSync8, statSync as statSync19 } from "node:fs";
|
|
48426
48748
|
import { resolve as resolve20, sep as sep8 } from "node:path";
|
|
48427
48749
|
import { createRequire as createRequire8 } from "node:module";
|
|
@@ -48441,7 +48763,7 @@ function storageKey(filename = "") {
|
|
|
48441
48763
|
const clean = raw.replace(UNSAFE, "").slice(0, 12);
|
|
48442
48764
|
if (clean) ext = `.${clean}`;
|
|
48443
48765
|
}
|
|
48444
|
-
return `${
|
|
48766
|
+
return `${randomBytes12(16).toString("hex")}${ext}`;
|
|
48445
48767
|
}
|
|
48446
48768
|
function selectStorage(storage) {
|
|
48447
48769
|
if (storage) return storage;
|