tina4-nodejs 3.13.137 → 3.13.138
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 +33 -3
- package/COMMERCIAL-LICENSE.md +8 -0
- package/LICENSE +373 -0
- package/NOTICE +11 -0
- package/README.md +24 -2
- package/package.json +44 -6
- package/packages/cli/dist/bin.js +19466 -18048
- package/packages/cli/src/bin.ts +8 -0
- package/packages/cli/src/commands/build.ts +8 -0
- package/packages/cli/src/commands/generate.ts +8 -0
- package/packages/cli/src/commands/init.ts +8 -0
- package/packages/cli/src/commands/lint.ts +8 -0
- package/packages/cli/src/commands/migrate.ts +8 -0
- package/packages/cli/src/commands/migrateCreate.ts +8 -0
- package/packages/cli/src/commands/migrateRollback.ts +8 -0
- package/packages/cli/src/commands/migrateStatus.ts +8 -0
- package/packages/cli/src/commands/queue.ts +8 -0
- package/packages/cli/src/commands/routes.ts +8 -0
- package/packages/cli/src/commands/seed.ts +8 -0
- package/packages/cli/src/commands/serve.ts +11 -0
- package/packages/cli/src/commands/test.ts +8 -0
- package/packages/core/dist/index.js +19458 -18043
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/login/post.ts +8 -0
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/verify/get.ts +8 -0
- package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +8 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/get.ts +8 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/post.ts +8 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/tables/get.ts +8 -0
- package/packages/core/gallery/error-overlay/src/routes/api/gallery/crash/get.ts +8 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/get.ts +8 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/post.ts +8 -0
- package/packages/core/gallery/queue/src/lib/queueDb.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/consume/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/fail/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/produce/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/retry/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/status/get.ts +8 -0
- package/packages/core/gallery/queue/src/routes/gallery/queue/get.ts +8 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/get.ts +8 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/post.ts +8 -0
- package/packages/core/gallery/templates/src/routes/gallery/page/get.ts +8 -0
- package/packages/core/gallery/templates/src/templates/gallery_page.twig +8 -0
- package/packages/core/public/THIRD-PARTY-NOTICES.md +55 -0
- package/packages/core/public/bundled-component-provenance.json +48 -0
- package/packages/core/public/js/tina4js.min.js +13 -3
- package/packages/core/public/licenses/swagger-ui-3.10.0-original-notice.txt +11 -0
- package/packages/core/public/licenses/swagger-ui-5.17.14-NOTICE.txt +2 -0
- package/packages/core/public/licenses/swagger-ui-Apache-2.0.txt +202 -0
- package/packages/core/public/licenses/tina4-css-MIT.txt +21 -0
- package/packages/core/public/licenses/tina4-dev-admin-components.json +240 -0
- package/packages/core/public/licenses/tina4-dev-admin-third-party.txt +919 -0
- package/packages/core/public/licenses/tina4-js-MIT.txt +20 -0
- package/packages/core/src/__feedback/widget.js +8 -0
- package/packages/core/src/_missing.ts +8 -0
- package/packages/core/src/ai.ts +8 -0
- package/packages/core/src/aiClient.ts +8 -0
- package/packages/core/src/api.ts +15 -3
- package/packages/core/src/auth.ts +21 -8
- package/packages/core/src/authGate.ts +8 -0
- package/packages/core/src/background.ts +8 -0
- package/packages/core/src/cache.ts +43 -4
- package/packages/core/src/constants.ts +8 -0
- package/packages/core/src/container.ts +8 -0
- package/packages/core/src/context/chunker.ts +8 -0
- package/packages/core/src/context/index.ts +8 -0
- package/packages/core/src/devAdmin.ts +185 -59
- package/packages/core/src/devMailbox.ts +8 -0
- package/packages/core/src/dispatchPipeline.ts +8 -0
- package/packages/core/src/docs.ts +8 -0
- package/packages/core/src/docsAutoDiscovery.ts +8 -0
- package/packages/core/src/docsParser.ts +8 -0
- package/packages/core/src/docsScanner.ts +44 -0
- package/packages/core/src/docsSignatures.ts +8 -0
- package/packages/core/src/dotenv.ts +8 -0
- package/packages/core/src/env.ts +8 -0
- package/packages/core/src/errorOverlay.ts +8 -0
- package/packages/core/src/events.ts +8 -0
- package/packages/core/src/fakeData.ts +8 -0
- package/packages/core/src/feedback.ts +8 -0
- package/packages/core/src/graphql.ts +16 -5
- package/packages/core/src/health.ts +8 -0
- package/packages/core/src/htmlElement.ts +8 -0
- package/packages/core/src/i18n.ts +8 -0
- package/packages/core/src/index.ts +8 -0
- package/packages/core/src/job.ts +8 -0
- package/packages/core/src/logger.ts +8 -0
- package/packages/core/src/mcp.test.ts +8 -0
- package/packages/core/src/mcp.ts +8 -0
- package/packages/core/src/messenger.ts +143 -86
- package/packages/core/src/metrics.ts +13 -0
- package/packages/core/src/middleware.ts +1178 -1096
- package/packages/core/src/mqtt.ts +8 -0
- package/packages/core/src/mqttMessage.ts +8 -0
- package/packages/core/src/optionalPackage.ts +48 -0
- package/packages/core/src/plan.ts +8 -0
- package/packages/core/src/portTakeover.ts +8 -0
- package/packages/core/src/projectIndex.ts +8 -0
- package/packages/core/src/projectIndexExtractors.ts +8 -0
- package/packages/core/src/projectIndexStorage.ts +8 -0
- package/packages/core/src/push.ts +8 -0
- package/packages/core/src/queue.ts +8 -0
- package/packages/core/src/queueBackends/kafkaBackend.ts +8 -0
- package/packages/core/src/queueBackends/liteBackend.ts +8 -0
- package/packages/core/src/queueBackends/mongoBackend.ts +21 -8
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +8 -0
- package/packages/core/src/rateLimiter.ts +8 -0
- package/packages/core/src/request.ts +59 -10
- package/packages/core/src/response.ts +79 -5
- package/packages/core/src/routeDiscovery.ts +8 -0
- package/packages/core/src/router.ts +8 -0
- package/packages/core/src/server.ts +73 -24
- package/packages/core/src/service.ts +8 -0
- package/packages/core/src/session.ts +8 -0
- package/packages/core/src/sessionHandlers/childError.ts +8 -0
- package/packages/core/src/sessionHandlers/databaseHandler.ts +8 -0
- package/packages/core/src/sessionHandlers/memcachedHandler.ts +8 -0
- package/packages/core/src/sessionHandlers/mongoClient.ts +8 -0
- package/packages/core/src/sessionHandlers/mongoHandler.ts +8 -0
- package/packages/core/src/sessionHandlers/respClient.ts +8 -0
- package/packages/core/src/sessionHandlers/sqlClient.ts +8 -0
- package/packages/core/src/sessionHandlers/syncBridge.ts +8 -0
- package/packages/core/src/sessionHandlers/syncSocket.ts +8 -0
- package/packages/core/src/sessionHandlers/valkeyHandler.ts +8 -0
- package/packages/core/src/sso.ts +8 -0
- package/packages/core/src/static.ts +8 -0
- package/packages/core/src/test.ts +8 -0
- package/packages/core/src/testClient.ts +8 -0
- package/packages/core/src/testing.ts +8 -0
- package/packages/core/src/transport.ts +369 -0
- package/packages/core/src/trustedProxy.ts +8 -0
- package/packages/core/src/types.ts +13 -0
- package/packages/core/src/validator.ts +8 -0
- package/packages/core/src/version.ts +8 -0
- package/packages/core/src/websocket.ts +8 -0
- package/packages/core/src/websocketBackplane.ts +49 -32
- package/packages/core/src/websocketConnection.ts +8 -0
- package/packages/core/src/wsdl.ts +197 -115
- package/packages/core/templates/errors/302.twig +8 -0
- package/packages/core/templates/errors/401.twig +8 -0
- package/packages/core/templates/errors/403.twig +8 -0
- package/packages/core/templates/errors/404.twig +8 -0
- package/packages/core/templates/errors/500.twig +8 -0
- package/packages/core/templates/errors/502.twig +8 -0
- package/packages/core/templates/errors/503.twig +8 -0
- package/packages/core/templates/errors/base.twig +8 -0
- package/packages/frond/dist/index.js +151 -17
- package/packages/frond/src/engine.ts +177 -17
- package/packages/frond/src/index.ts +8 -0
- package/packages/orm/dist/index.js +8125 -6708
- package/packages/orm/src/adapters/firebird.ts +50 -3
- package/packages/orm/src/adapters/mongodb.ts +8 -0
- package/packages/orm/src/adapters/mssql.ts +29 -7
- package/packages/orm/src/adapters/mysql.ts +17 -1
- package/packages/orm/src/adapters/odbc.ts +11 -0
- package/packages/orm/src/adapters/pg-types.d.ts +8 -0
- package/packages/orm/src/adapters/postgres.ts +34 -8
- package/packages/orm/src/adapters/sqlDialect.ts +57 -0
- package/packages/orm/src/adapters/sqlite.ts +26 -0
- package/packages/orm/src/autoCrud.ts +78 -36
- package/packages/orm/src/baseModel.ts +61 -29
- package/packages/orm/src/cachedDatabase.ts +67 -1
- package/packages/orm/src/connectTimeout.ts +8 -0
- package/packages/orm/src/database.ts +649 -401
- package/packages/orm/src/databaseResult.ts +8 -0
- package/packages/orm/src/databaseUrl.ts +8 -0
- package/packages/orm/src/docstore.ts +23 -1
- package/packages/orm/src/fakeData.ts +8 -0
- package/packages/orm/src/graph/adapters/arango.ts +8 -0
- package/packages/orm/src/graph/adapters/bolt.ts +8 -0
- package/packages/orm/src/graph/adapters/ultipa.ts +8 -0
- package/packages/orm/src/graph/connectTimeout.ts +8 -0
- package/packages/orm/src/graph/errors.ts +8 -0
- package/packages/orm/src/graph/graphAdapter.ts +8 -0
- package/packages/orm/src/graph/graphDatabase.ts +8 -0
- package/packages/orm/src/graph/graphUrl.ts +10 -1
- package/packages/orm/src/graph/shapes.ts +8 -0
- package/packages/orm/src/index.ts +147 -139
- package/packages/orm/src/migration.ts +8 -0
- package/packages/orm/src/model.ts +8 -0
- package/packages/orm/src/modelCollection.ts +8 -0
- package/packages/orm/src/point.ts +8 -0
- package/packages/orm/src/query.ts +149 -33
- package/packages/orm/src/queryBuilder.ts +8 -0
- package/packages/orm/src/realtime/index.ts +8 -0
- package/packages/orm/src/realtime/models/attachment.ts +8 -0
- package/packages/orm/src/realtime/models/channel.ts +8 -0
- package/packages/orm/src/realtime/models/channelMember.ts +8 -0
- package/packages/orm/src/realtime/models/message.ts +8 -0
- package/packages/orm/src/realtime/models/workspace.ts +8 -0
- package/packages/orm/src/realtime/realtime.ts +8 -0
- package/packages/orm/src/realtime/storage.ts +43 -12
- package/packages/orm/src/seeder.ts +8 -0
- package/packages/orm/src/seederTable.ts +8 -0
- package/packages/orm/src/seederTypes.ts +8 -0
- package/packages/orm/src/sqlTranslator.ts +185 -8
- package/packages/orm/src/types.ts +15 -0
- package/packages/orm/src/validation.ts +8 -0
- package/packages/swagger/src/generator.ts +8 -0
- package/packages/swagger/src/index.ts +8 -0
- package/packages/swagger/src/ui.ts +8 -0
- package/types/core/src/background.d.ts +0 -16
- package/types/core/src/cache.d.ts +6 -0
- package/types/core/src/devAdmin.d.ts +18 -12
- package/types/core/src/docs.d.ts +0 -17
- package/types/core/src/errorOverlay.d.ts +0 -24
- package/types/core/src/events.d.ts +0 -12
- package/types/core/src/feedback.d.ts +0 -23
- package/types/core/src/graphql.d.ts +0 -21
- package/types/core/src/htmlElement.d.ts +0 -15
- package/types/core/src/messenger.d.ts +12 -1
- package/types/core/src/metrics.d.ts +2 -0
- package/types/core/src/middleware.d.ts +45 -1
- package/types/core/src/mqtt.d.ts +0 -33
- package/types/core/src/optionalPackage.d.ts +25 -0
- package/types/core/src/plan.d.ts +0 -10
- package/types/core/src/projectIndex.d.ts +0 -10
- package/types/core/src/queueBackends/mongoBackend.d.ts +2 -0
- package/types/core/src/request.d.ts +12 -0
- package/types/core/src/response.d.ts +13 -0
- package/types/core/src/server.d.ts +19 -0
- package/types/core/src/sessionHandlers/childError.d.ts +0 -15
- package/types/core/src/transport.d.ts +28 -0
- package/types/core/src/trustedProxy.d.ts +0 -16
- package/types/core/src/types.d.ts +5 -0
- package/types/core/src/websocketBackplane.d.ts +1 -0
- package/types/core/src/wsdl.d.ts +8 -22
- package/types/frond/src/engine.d.ts +7 -1
- package/types/orm/src/adapters/firebird.d.ts +12 -0
- package/types/orm/src/adapters/mssql.d.ts +11 -0
- package/types/orm/src/adapters/mysql.d.ts +2 -0
- package/types/orm/src/adapters/postgres.d.ts +11 -0
- package/types/orm/src/adapters/sqlDialect.d.ts +25 -0
- package/types/orm/src/adapters/sqlite.d.ts +7 -0
- package/types/orm/src/baseModel.d.ts +5 -0
- package/types/orm/src/cachedDatabase.d.ts +27 -0
- package/types/orm/src/database.d.ts +46 -7
- package/types/orm/src/graph/graphUrl.d.ts +0 -8
- package/types/orm/src/index.d.ts +1 -1
- package/types/orm/src/query.d.ts +48 -2
- package/types/orm/src/realtime/storage.d.ts +2 -1
- package/types/orm/src/sqlTranslator.d.ts +70 -18
- package/types/orm/src/types.d.ts +6 -0
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Programmatic HTML builder — avoids string concatenation.
|
|
3
11
|
*
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
// Tina4 i18n — Internationalization and localization, zero dependencies.
|
|
2
10
|
// Load JSON translation files, support nested keys, parameter substitution, locale fallback.
|
|
3
11
|
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
export type {
|
|
2
10
|
Tina4Request,
|
|
3
11
|
Tina4Response,
|
package/packages/core/src/job.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Tina4 Queue Job — a single queue job with lifecycle methods.
|
|
3
11
|
*/
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
import {
|
|
2
10
|
appendFileSync,
|
|
3
11
|
closeSync,
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Unit tests for the MCP (Model Context Protocol) implementation.
|
|
3
11
|
* Run with: npx tsx packages/core/src/mcp.test.ts
|
package/packages/core/src/mcp.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
// Tina4 MCP Server — Model Context Protocol for AI tool integration.
|
|
2
10
|
//
|
|
3
11
|
// Built-in MCP server for dev tools + developer API for custom MCP servers.
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Tina4 Messenger — SMTP email client using Node.js built-in modules only.
|
|
3
11
|
*
|
|
@@ -32,15 +40,80 @@ import { isTruthy } from "./dotenv.js";
|
|
|
32
40
|
import { DevMailbox } from "./devMailbox.js";
|
|
33
41
|
import { Log } from "./logger.js";
|
|
34
42
|
|
|
43
|
+
// ── Transport security (ADR-0071) ───────────────────────────
|
|
44
|
+
//
|
|
45
|
+
// `encryption` means what it says. Port 465 is always implicit TLS; `ssl` is
|
|
46
|
+
// implicit TLS on ANY port; `tls` / `starttls` upgrade with STARTTLS and FAIL
|
|
47
|
+
// when the server does not offer it; `none` never upgrades. An unknown value
|
|
48
|
+
// is refused when the Messenger is built, so a typo never means cleartext.
|
|
49
|
+
//
|
|
50
|
+
// Every TLS connection verifies the certificate and the host name. There is
|
|
51
|
+
// no Tina4 switch to turn that off (TINA4_MAIL_TLS_INSECURE is withdrawn);
|
|
52
|
+
// `rejectUnauthorized: true` is passed explicitly so NODE_TLS_REJECT_UNAUTHORIZED=0
|
|
53
|
+
// cannot turn it off either. Trust a private CA the standard Node way:
|
|
54
|
+
// NODE_EXTRA_CA_CERTS=/path/ca.pem.
|
|
55
|
+
|
|
56
|
+
const MAIL_ENCRYPTION_VALUES = ["ssl", "tls", "starttls", "none"];
|
|
57
|
+
|
|
58
|
+
/** Trim + lowercase an encryption setting, refusing anything that is not a known value. */
|
|
59
|
+
function normaliseEncryption(value: string, kind: "mail" | "IMAP"): string {
|
|
60
|
+
const normalised = value.trim().toLowerCase();
|
|
61
|
+
if (!MAIL_ENCRYPTION_VALUES.includes(normalised)) {
|
|
62
|
+
throw new Error(`Unknown ${kind} encryption '${value}'. Valid values: ${MAIL_ENCRYPTION_VALUES.join(", ")}.`);
|
|
63
|
+
}
|
|
64
|
+
return normalised;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type SmtpTransport = "implicit_tls" | "starttls" | "plain";
|
|
68
|
+
|
|
69
|
+
/** Matches Python's smtplib timeouts: 30 s for a send, 10 s for a connection check. */
|
|
70
|
+
const SMTP_SEND_TIMEOUT_MS = 30_000;
|
|
71
|
+
const SMTP_CHECK_TIMEOUT_MS = 10_000;
|
|
72
|
+
|
|
35
73
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* `rejectUnauthorized: false`, silently disabling certificate validation for
|
|
40
|
-
* every TLS connection (a man-in-the-middle risk).
|
|
74
|
+
* Give a socket an idle timeout that fails it with a clear error. Without one a
|
|
75
|
+
* client that waits for a greeting the server never sends (a plaintext client
|
|
76
|
+
* on a TLS-only port) hangs forever.
|
|
41
77
|
*/
|
|
42
|
-
function
|
|
43
|
-
|
|
78
|
+
function failOnIdle<T extends net.Socket>(socket: T, timeoutMs: number, what: string): T {
|
|
79
|
+
socket.setTimeout(timeoutMs, () => {
|
|
80
|
+
socket.destroy(new Error(`${what} timed out after ${timeoutMs / 1000}s`));
|
|
81
|
+
});
|
|
82
|
+
return socket;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Open a verified TLS connection (implicit TLS), or upgrade `socket` (STARTTLS). */
|
|
86
|
+
function connectVerifiedTls(host: string, port: number, socket?: net.Socket): Promise<tls.TLSSocket> {
|
|
87
|
+
const secureSocket = tls.connect({
|
|
88
|
+
host,
|
|
89
|
+
port,
|
|
90
|
+
socket,
|
|
91
|
+
servername: net.isIP(host) ? undefined : host,
|
|
92
|
+
rejectUnauthorized: true,
|
|
93
|
+
});
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
secureSocket.once("secureConnect", () => {
|
|
96
|
+
secureSocket.removeListener("error", reject);
|
|
97
|
+
resolve(secureSocket);
|
|
98
|
+
});
|
|
99
|
+
secureSocket.once("error", reject);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function connectPlain(host: string, port: number): Promise<net.Socket> {
|
|
104
|
+
const socket = net.createConnection({ host, port });
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
socket.once("connect", () => {
|
|
107
|
+
socket.removeListener("error", reject);
|
|
108
|
+
resolve(socket);
|
|
109
|
+
});
|
|
110
|
+
socket.once("error", reject);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Does this EHLO reply advertise the STARTTLS extension? */
|
|
115
|
+
function offersStarttls(ehloReply: string): boolean {
|
|
116
|
+
return /^\d{3}[ -]STARTTLS\b/im.test(ehloReply);
|
|
44
117
|
}
|
|
45
118
|
|
|
46
119
|
/**
|
|
@@ -384,7 +457,6 @@ export class Messenger {
|
|
|
384
457
|
private fromAddress: string;
|
|
385
458
|
private fromName: string;
|
|
386
459
|
private encryption: string;
|
|
387
|
-
private useTls: boolean;
|
|
388
460
|
/** Whether an SMTP host was actually configured (see the constructor). */
|
|
389
461
|
private smtpConfigured: boolean = false;
|
|
390
462
|
/** The local mailbox, present only when this messenger captures. */
|
|
@@ -421,17 +493,18 @@ export class Messenger {
|
|
|
421
493
|
?? process.env.TINA4_MAIL_FROM_NAME
|
|
422
494
|
?? "";
|
|
423
495
|
|
|
424
|
-
// Encryption: constructor > .env > backward-compat useTls > default "tls"
|
|
425
|
-
|
|
496
|
+
// Encryption: constructor > .env > backward-compat useTls > default "tls".
|
|
497
|
+
// A configured value is validated (ADR-0071): an unknown one, an empty one
|
|
498
|
+
// included, raises here rather than sending in cleartext later.
|
|
499
|
+
const configuredEncryption = options?.encryption
|
|
426
500
|
?? process.env.TINA4_MAIL_ENCRYPTION;
|
|
427
|
-
if (
|
|
428
|
-
this.encryption =
|
|
501
|
+
if (configuredEncryption !== undefined) {
|
|
502
|
+
this.encryption = normaliseEncryption(configuredEncryption, "mail");
|
|
429
503
|
} else if (options?.useTls !== undefined) {
|
|
430
504
|
this.encryption = options.useTls ? "tls" : "none";
|
|
431
505
|
} else {
|
|
432
506
|
this.encryption = "tls";
|
|
433
507
|
}
|
|
434
|
-
this.useTls = ["tls", "starttls"].includes(this.encryption);
|
|
435
508
|
|
|
436
509
|
this.imapHost = options?.imapHost
|
|
437
510
|
?? process.env.TINA4_MAIL_IMAP_HOST
|
|
@@ -448,10 +521,23 @@ export class Messenger {
|
|
|
448
521
|
// IMAP encryption — separate from SMTP encryption because IMAP almost
|
|
449
522
|
// always uses port 993 + implicit TLS while SMTP toggles between 587
|
|
450
523
|
// (STARTTLS) and 465 (implicit TLS). Default is "tls" to match
|
|
451
|
-
// industry-standard IMAPS port 993 behaviour.
|
|
452
|
-
|
|
524
|
+
// industry-standard IMAPS port 993 behaviour. "tls" and "ssl" are implicit
|
|
525
|
+
// TLS, "starttls" is a REQUIRED STARTTLS upgrade, "none" is plaintext; any
|
|
526
|
+
// other value raises here.
|
|
527
|
+
this.imapEncryption = normaliseEncryption(options?.imapEncryption
|
|
453
528
|
?? process.env.TINA4_MAIL_IMAP_ENCRYPTION
|
|
454
|
-
?? "tls")
|
|
529
|
+
?? "tls", "IMAP");
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* How the SMTP connection is secured (ADR-0071 section 1). Port 465 is the
|
|
534
|
+
* implicit-TLS submission port whatever the setting; `ssl` is implicit TLS on
|
|
535
|
+
* any port; `tls` / `starttls` upgrade with a required STARTTLS; `none` is
|
|
536
|
+
* plaintext on purpose.
|
|
537
|
+
*/
|
|
538
|
+
private smtpTransport(): SmtpTransport {
|
|
539
|
+
if (this.port === 465 || this.encryption === "ssl") return "implicit_tls";
|
|
540
|
+
return this.encryption === "none" ? "plain" : "starttls";
|
|
455
541
|
}
|
|
456
542
|
|
|
457
543
|
/**
|
|
@@ -507,21 +593,12 @@ export class Messenger {
|
|
|
507
593
|
return { toList, ccList, bccList, allRecipients };
|
|
508
594
|
}
|
|
509
595
|
|
|
510
|
-
private async connectSmtpSocket(): Promise<SmtpSocket> {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
await
|
|
514
|
-
socket.once("secureConnect", resolve);
|
|
515
|
-
socket.once("error", reject);
|
|
516
|
-
});
|
|
517
|
-
return socket;
|
|
596
|
+
private async connectSmtpSocket(timeoutMs: number): Promise<SmtpSocket> {
|
|
597
|
+
const what = `SMTP connection to ${this.host}:${this.port}`;
|
|
598
|
+
if (this.smtpTransport() === "implicit_tls") {
|
|
599
|
+
return failOnIdle(await connectVerifiedTls(this.host, this.port), timeoutMs, what);
|
|
518
600
|
}
|
|
519
|
-
|
|
520
|
-
await new Promise<void>((resolve, reject) => {
|
|
521
|
-
socket.once("connect", resolve);
|
|
522
|
-
socket.once("error", reject);
|
|
523
|
-
});
|
|
524
|
-
return socket;
|
|
601
|
+
return failOnIdle(await connectPlain(this.host, this.port), timeoutMs, what);
|
|
525
602
|
}
|
|
526
603
|
|
|
527
604
|
private async requireSmtpResponse(socket: SmtpSocket, command: string, expected: number, failure: string): Promise<{ code: number; text: string }> {
|
|
@@ -530,20 +607,23 @@ export class Messenger {
|
|
|
530
607
|
return response;
|
|
531
608
|
}
|
|
532
609
|
|
|
533
|
-
|
|
534
|
-
|
|
610
|
+
/**
|
|
611
|
+
* Connect, read the greeting, EHLO, and - for the STARTTLS transport - upgrade.
|
|
612
|
+
* STARTTLS is REQUIRED: when the server does not offer it the session fails
|
|
613
|
+
* here, before AUTH or MAIL FROM is ever sent.
|
|
614
|
+
*/
|
|
615
|
+
private async openSmtpSession(timeoutMs: number = SMTP_SEND_TIMEOUT_MS): Promise<SmtpSocket> {
|
|
616
|
+
let socket = await this.connectSmtpSocket(timeoutMs);
|
|
535
617
|
try {
|
|
536
618
|
await this.requireSmtpResponse(socket, "", 220, "SMTP greeting failed");
|
|
537
619
|
const ehlo = await this.requireSmtpResponse(socket, `EHLO ${this.host}`, 250, "EHLO failed");
|
|
538
|
-
if (this.
|
|
620
|
+
if (this.smtpTransport() === "starttls") {
|
|
621
|
+
if (!offersStarttls(ehlo.text)) {
|
|
622
|
+
throw new SmtpCommandError(`STARTTLS was requested but ${this.host}:${this.port} does not offer it`);
|
|
623
|
+
}
|
|
539
624
|
await this.requireSmtpResponse(socket, "STARTTLS", 220, "STARTTLS failed");
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
await new Promise<void>((resolve, reject) => {
|
|
543
|
-
secureSocket.once("secureConnect", resolve);
|
|
544
|
-
secureSocket.once("error", reject);
|
|
545
|
-
});
|
|
546
|
-
socket = secureSocket;
|
|
625
|
+
socket = failOnIdle(await connectVerifiedTls(this.host, this.port, socket as net.Socket), timeoutMs,
|
|
626
|
+
`SMTP connection to ${this.host}:${this.port}`);
|
|
547
627
|
await this.requireSmtpResponse(socket, `EHLO ${this.host}`, 250, "EHLO after STARTTLS failed");
|
|
548
628
|
}
|
|
549
629
|
return socket;
|
|
@@ -679,42 +759,19 @@ export class Messenger {
|
|
|
679
759
|
* Test the SMTP connection without sending an email.
|
|
680
760
|
*/
|
|
681
761
|
async testConnection(): Promise<{ success: boolean; message: string }> {
|
|
762
|
+
// Same transport rules as send(): implicit TLS / required STARTTLS / plain,
|
|
763
|
+
// certificates verified.
|
|
764
|
+
let socket: SmtpSocket | null = null;
|
|
682
765
|
try {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
if (this.port === 465) {
|
|
686
|
-
socket = tls.connect({ host: this.host, port: this.port, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
687
|
-
await new Promise<void>((resolve, reject) => {
|
|
688
|
-
socket.once("secureConnect", resolve);
|
|
689
|
-
socket.once("error", reject);
|
|
690
|
-
});
|
|
691
|
-
} else {
|
|
692
|
-
socket = net.createConnection({ host: this.host, port: this.port });
|
|
693
|
-
await new Promise<void>((resolve, reject) => {
|
|
694
|
-
socket.once("connect", resolve);
|
|
695
|
-
socket.once("error", reject);
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
const greeting = await readResponse(socket);
|
|
700
|
-
if (greeting.code !== 220) {
|
|
701
|
-
socket.destroy();
|
|
702
|
-
return { success: false, message: `SMTP greeting failed: ${greeting.text}` };
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
const ehlo = await sendCommand(socket, `EHLO ${this.host}`);
|
|
706
|
-
if (ehlo.code !== 250) {
|
|
707
|
-
socket.destroy();
|
|
708
|
-
return { success: false, message: `EHLO failed: ${ehlo.text}` };
|
|
709
|
-
}
|
|
710
|
-
|
|
766
|
+
socket = await this.openSmtpSession(SMTP_CHECK_TIMEOUT_MS);
|
|
711
767
|
await sendCommand(socket, "QUIT");
|
|
712
|
-
socket.destroy();
|
|
713
|
-
|
|
714
768
|
return { success: true, message: `Connected to ${this.host}:${this.port}` };
|
|
715
769
|
} catch (err) {
|
|
770
|
+
if (err instanceof SmtpCommandError) return { success: false, message: err.message };
|
|
716
771
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
717
772
|
return { success: false, message: `Connection failed: ${errMsg}` };
|
|
773
|
+
} finally {
|
|
774
|
+
socket?.destroy();
|
|
718
775
|
}
|
|
719
776
|
}
|
|
720
777
|
|
|
@@ -731,28 +788,28 @@ export class Messenger {
|
|
|
731
788
|
|
|
732
789
|
let socket: net.Socket | tls.TLSSocket;
|
|
733
790
|
|
|
734
|
-
//
|
|
735
|
-
//
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
if (useTls) {
|
|
740
|
-
socket = tls.connect({ host: this.imapHost, port: this.imapPort, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
741
|
-
await new Promise<void>((resolve, reject) => {
|
|
742
|
-
socket.once("secureConnect", resolve);
|
|
743
|
-
socket.once("error", reject);
|
|
744
|
-
});
|
|
791
|
+
// "tls" / "ssl" → implicit TLS; "starttls" → plain connect, then a REQUIRED
|
|
792
|
+
// STARTTLS upgrade before LOGIN; "none" → plaintext. (Validated in the constructor.)
|
|
793
|
+
if (this.imapEncryption === "tls" || this.imapEncryption === "ssl") {
|
|
794
|
+
socket = await connectVerifiedTls(this.imapHost, this.imapPort);
|
|
745
795
|
} else {
|
|
746
|
-
socket =
|
|
747
|
-
await new Promise<void>((resolve, reject) => {
|
|
748
|
-
socket.once("connect", resolve);
|
|
749
|
-
socket.once("error", reject);
|
|
750
|
-
});
|
|
796
|
+
socket = await connectPlain(this.imapHost, this.imapPort);
|
|
751
797
|
}
|
|
752
798
|
|
|
753
799
|
// Read server greeting
|
|
754
800
|
await imapReadLine(socket);
|
|
755
801
|
|
|
802
|
+
if (this.imapEncryption === "starttls") {
|
|
803
|
+
try {
|
|
804
|
+
await imapCommand(socket, "STARTTLS");
|
|
805
|
+
} catch {
|
|
806
|
+
socket.destroy();
|
|
807
|
+
throw new MessengerConnectionError(
|
|
808
|
+
`STARTTLS was requested but ${this.imapHost}:${this.imapPort} does not offer it`);
|
|
809
|
+
}
|
|
810
|
+
socket = await connectVerifiedTls(this.imapHost, this.imapPort, socket);
|
|
811
|
+
}
|
|
812
|
+
|
|
756
813
|
// Login
|
|
757
814
|
if (this.imapUser && this.imapPass) {
|
|
758
815
|
const loginResp = await imapCommand(socket, `LOGIN ${imapQuote(this.imapUser)} ${imapQuote(this.imapPass)}`);
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
// Thin dev-admin adapter for the native `tina4 metrics` engine (ADR-0054).
|
|
2
10
|
|
|
3
11
|
import * as fs from "node:fs";
|
|
@@ -119,6 +127,11 @@ export function fullAnalysis(root: string = "src"): Record<string, any> {
|
|
|
119
127
|
};
|
|
120
128
|
}
|
|
121
129
|
|
|
130
|
+
/** The root the last full scan covered ("" before any scan). */
|
|
131
|
+
export function metricsScanRoot(): string {
|
|
132
|
+
return lastScanRoot;
|
|
133
|
+
}
|
|
134
|
+
|
|
122
135
|
export function fileDetail(filePath: string): Record<string, any> {
|
|
123
136
|
if (!filePath) throw new MetricsEngineError("fileDetail needs a path");
|
|
124
137
|
let target = filePath;
|