@xopcai/xopc 0.0.77 → 0.0.78

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.
Files changed (126) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{agents-DN3vr8pb.js → agents-Bh_9-1KB.js} +2 -2
  4. package/dist/gateway/static/root/assets/{agents-DN3vr8pb.js.map → agents-Bh_9-1KB.js.map} +1 -1
  5. package/dist/gateway/static/root/assets/{apps-page-BUn41aPi.js → apps-page-CB5anZpc.js} +2 -2
  6. package/dist/gateway/static/root/assets/{apps-page-BUn41aPi.js.map → apps-page-CB5anZpc.js.map} +1 -1
  7. package/dist/gateway/static/root/assets/{channels-settings-CYMmWDtP.js → channels-settings-Bt1sprhC.js} +2 -2
  8. package/dist/gateway/static/root/assets/{channels-settings-CYMmWDtP.js.map → channels-settings-Bt1sprhC.js.map} +1 -1
  9. package/dist/gateway/static/root/assets/{channels-status-swr-sJj4ueTp.js → channels-status-swr-Crgak3fg.js} +2 -2
  10. package/dist/gateway/static/root/assets/{channels-status-swr-sJj4ueTp.js.map → channels-status-swr-Crgak3fg.js.map} +1 -1
  11. package/dist/gateway/static/root/assets/{cron-api-CLxnaHdq.js → cron-api-CzJGvQQ7.js} +2 -2
  12. package/dist/gateway/static/root/assets/{cron-api-CLxnaHdq.js.map → cron-api-CzJGvQQ7.js.map} +1 -1
  13. package/dist/gateway/static/root/assets/{cron-page-BAQ8xSnJ.js → cron-page-BoNRJNVV.js} +2 -2
  14. package/dist/gateway/static/root/assets/{cron-page-BAQ8xSnJ.js.map → cron-page-BoNRJNVV.js.map} +1 -1
  15. package/dist/gateway/static/root/assets/{dist-BfJYxiK5.js → dist-a-eaOUvs.js} +2 -2
  16. package/dist/gateway/static/root/assets/{dist-BfJYxiK5.js.map → dist-a-eaOUvs.js.map} +1 -1
  17. package/dist/gateway/static/root/assets/{extension-debug-page-bgvVs-Sy.js → extension-debug-page-D-O1XjAa.js} +2 -2
  18. package/dist/gateway/static/root/assets/{extension-debug-page-bgvVs-Sy.js.map → extension-debug-page-D-O1XjAa.js.map} +1 -1
  19. package/dist/gateway/static/root/assets/{extension-page-SG4TVv-u.js → extension-page-B2VpqBTH.js} +2 -2
  20. package/dist/gateway/static/root/assets/{extension-page-SG4TVv-u.js.map → extension-page-B2VpqBTH.js.map} +1 -1
  21. package/dist/gateway/static/root/assets/{extension-settings-page-CJZRTsjF.js → extension-settings-page-CmBcQfeO.js} +2 -2
  22. package/dist/gateway/static/root/assets/{extension-settings-page-CJZRTsjF.js.map → extension-settings-page-CmBcQfeO.js.map} +1 -1
  23. package/dist/gateway/static/root/assets/{fetch-K_0JRCXU.js → fetch-EGO9T3MN.js} +3 -3
  24. package/dist/gateway/static/root/assets/{fetch-K_0JRCXU.js.map → fetch-EGO9T3MN.js.map} +1 -1
  25. package/dist/gateway/static/root/assets/{field-primitives-Z76hyBYS.js → field-primitives-Bh7G1y4D.js} +2 -2
  26. package/dist/gateway/static/root/assets/{field-primitives-Z76hyBYS.js.map → field-primitives-Bh7G1y4D.js.map} +1 -1
  27. package/dist/gateway/static/root/assets/{heartbeat-config-api-BqfDabSI.js → heartbeat-config-api-DxpIEZNs.js} +2 -2
  28. package/dist/gateway/static/root/assets/{heartbeat-config-api-BqfDabSI.js.map → heartbeat-config-api-DxpIEZNs.js.map} +1 -1
  29. package/dist/gateway/static/root/assets/{index-ChiUhJAs.js → index-Dxy9ZCtC.js} +5 -5
  30. package/dist/gateway/static/root/assets/{index-ChiUhJAs.js.map → index-Dxy9ZCtC.js.map} +1 -1
  31. package/dist/gateway/static/root/assets/{logs-page-DrIMhDE2.js → logs-page-Dw58E2GE.js} +2 -2
  32. package/dist/gateway/static/root/assets/{logs-page-DrIMhDE2.js.map → logs-page-Dw58E2GE.js.map} +1 -1
  33. package/dist/gateway/static/root/assets/{sessions-page-B-RGO3N0.js → sessions-page-CPkhCy57.js} +2 -2
  34. package/dist/gateway/static/root/assets/{sessions-page-B-RGO3N0.js.map → sessions-page-CPkhCy57.js.map} +1 -1
  35. package/dist/gateway/static/root/assets/{settings-form-section-Csvl1iL6.js → settings-form-section-DLZDVMEf.js} +2 -2
  36. package/dist/gateway/static/root/assets/{settings-form-section-Csvl1iL6.js.map → settings-form-section-DLZDVMEf.js.map} +1 -1
  37. package/dist/gateway/static/root/assets/settings-page-CVPCa0PE.js +4 -0
  38. package/dist/gateway/static/root/assets/settings-page-CVPCa0PE.js.map +1 -0
  39. package/dist/gateway/static/root/assets/{skills-page-dHwx2vh0.js → skills-page-DueZ9Qfg.js} +2 -2
  40. package/dist/gateway/static/root/assets/{skills-page-dHwx2vh0.js.map → skills-page-DueZ9Qfg.js.map} +1 -1
  41. package/dist/gateway/static/root/assets/{theme-store-Bl5A2Fd_.js → theme-store-CWPq9gW1.js} +2 -2
  42. package/dist/gateway/static/root/assets/{theme-store-Bl5A2Fd_.js.map → theme-store-CWPq9gW1.js.map} +1 -1
  43. package/dist/gateway/static/root/assets/{utils-COYrNFF7.js → utils-Cnix55r9.js} +2 -2
  44. package/dist/gateway/static/root/assets/{utils-COYrNFF7.js.map → utils-Cnix55r9.js.map} +1 -1
  45. package/dist/gateway/static/root/assets/{voice-api-key-field-5WZZaxH3.js → voice-api-key-field-BR3Ut06g.js} +2 -2
  46. package/dist/gateway/static/root/assets/{voice-api-key-field-5WZZaxH3.js.map → voice-api-key-field-BR3Ut06g.js.map} +1 -1
  47. package/dist/gateway/static/root/index.html +3 -3
  48. package/dist/package.js +1 -1
  49. package/dist/src/browser/providers/browser-ext-install.js +23 -4
  50. package/dist/src/browser/providers/browser-ext-install.js.map +1 -1
  51. package/dist/src/cli/commands/tunnel.js +4 -5
  52. package/dist/src/cli/commands/tunnel.js.map +1 -1
  53. package/dist/src/config/index.js +2 -2
  54. package/dist/src/config/rules.js +0 -5
  55. package/dist/src/config/rules.js.map +1 -1
  56. package/dist/src/config/schema.d.ts +0 -27
  57. package/dist/src/config/schema.js +4 -18
  58. package/dist/src/config/schema.js.map +1 -1
  59. package/dist/src/gateway/auth-rate-limit.d.ts +2 -0
  60. package/dist/src/gateway/auth-rate-limit.js +9 -3
  61. package/dist/src/gateway/auth-rate-limit.js.map +1 -1
  62. package/dist/src/gateway/hono/app.js +19 -13
  63. package/dist/src/gateway/hono/app.js.map +1 -1
  64. package/dist/src/gateway/hono/lib/config-payload.d.ts +3 -1
  65. package/dist/src/gateway/hono/lib/config-payload.js +1 -2
  66. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  67. package/dist/src/gateway/hono/routes/tunnel.js +32 -30
  68. package/dist/src/gateway/hono/routes/tunnel.js.map +1 -1
  69. package/dist/src/gateway/host.d.ts +24 -0
  70. package/dist/src/gateway/host.js +33 -1
  71. package/dist/src/gateway/host.js.map +1 -1
  72. package/dist/src/gateway/index.d.ts +1 -1
  73. package/dist/src/gateway/index.js +2 -2
  74. package/dist/src/gateway/runtime-config.js +1 -8
  75. package/dist/src/gateway/runtime-config.js.map +1 -1
  76. package/dist/src/gateway/security/audit.js +4 -4
  77. package/dist/src/gateway/security/audit.js.map +1 -1
  78. package/dist/src/gateway/server.js +2 -3
  79. package/dist/src/gateway/server.js.map +1 -1
  80. package/dist/src/gateway/service/types.d.ts +2 -0
  81. package/dist/src/gateway/service.d.ts +2 -0
  82. package/dist/src/gateway/service.js +7 -2
  83. package/dist/src/gateway/service.js.map +1 -1
  84. package/dist/src/tunnel/frp-subdomain-host.d.ts +2 -0
  85. package/dist/src/tunnel/frp-subdomain-host.js +15 -0
  86. package/dist/src/tunnel/frp-subdomain-host.js.map +1 -0
  87. package/dist/src/tunnel/gateway-lifecycle.d.ts +0 -4
  88. package/dist/src/tunnel/gateway-lifecycle.js +9 -11
  89. package/dist/src/tunnel/gateway-lifecycle.js.map +1 -1
  90. package/dist/src/tunnel/index.d.ts +2 -4
  91. package/dist/src/tunnel/index.js +2 -4
  92. package/dist/src/tunnel/pair-url.js +7 -1
  93. package/dist/src/tunnel/pair-url.js.map +1 -1
  94. package/dist/src/tunnel/pairing.d.ts +13 -0
  95. package/dist/src/tunnel/pairing.js +48 -1
  96. package/dist/src/tunnel/pairing.js.map +1 -1
  97. package/dist/src/tunnel/tunnel-config.js +2 -16
  98. package/dist/src/tunnel/tunnel-config.js.map +1 -1
  99. package/dist/src/tunnel/tunnel-service.d.ts +1 -10
  100. package/dist/src/tunnel/tunnel-service.js +7 -60
  101. package/dist/src/tunnel/tunnel-service.js.map +1 -1
  102. package/dist/src/tunnel/tunnel-types.d.ts +3 -18
  103. package/dist/src/tunnel/well-known.d.ts +5 -0
  104. package/dist/src/tunnel/well-known.js +2 -1
  105. package/dist/src/tunnel/well-known.js.map +1 -1
  106. package/package.json +2 -2
  107. package/dist/gateway/static/root/assets/settings-page-nxAc0ta1.js +0 -4
  108. package/dist/gateway/static/root/assets/settings-page-nxAc0ta1.js.map +0 -1
  109. package/dist/src/tunnel/acme-cert-store.d.ts +0 -34
  110. package/dist/src/tunnel/acme-cert-store.js +0 -184
  111. package/dist/src/tunnel/acme-cert-store.js.map +0 -1
  112. package/dist/src/tunnel/acme-client.d.ts +0 -50
  113. package/dist/src/tunnel/acme-client.js +0 -473
  114. package/dist/src/tunnel/acme-client.js.map +0 -1
  115. package/dist/src/tunnel/acme-crypto.d.ts +0 -25
  116. package/dist/src/tunnel/acme-crypto.js +0 -58
  117. package/dist/src/tunnel/acme-crypto.js.map +0 -1
  118. package/dist/src/tunnel/acme-csr.d.ts +0 -5
  119. package/dist/src/tunnel/acme-csr.js +0 -48
  120. package/dist/src/tunnel/acme-csr.js.map +0 -1
  121. package/dist/src/tunnel/tls-server.d.ts +0 -14
  122. package/dist/src/tunnel/tls-server.js +0 -126
  123. package/dist/src/tunnel/tls-server.js.map +0 -1
  124. package/dist/src/tunnel/tunnel-e2e-config.d.ts +0 -11
  125. package/dist/src/tunnel/tunnel-e2e-config.js +0 -29
  126. package/dist/src/tunnel/tunnel-e2e-config.js.map +0 -1
@@ -1,34 +0,0 @@
1
- import { type AcmeConfig, type AcmeCertResult } from './acme-client.js';
2
- export type StoredCert = {
3
- certPem: string;
4
- keyPem: string;
5
- domain: string;
6
- issuedAt: string;
7
- expiresAt: string;
8
- };
9
- type CertStatusListener = (summary: ReturnType<typeof getCertStatusSummary>) => void;
10
- export declare function subscribeCertStatus(listener: CertStatusListener): () => void;
11
- export declare function recordRenewalFailure(err: unknown): void;
12
- export declare function clearRenewalFailure(): void;
13
- export declare function loadStoredCert(): StoredCert | null;
14
- export declare function needsRenewal(cert: StoredCert): boolean;
15
- export declare function saveCert(result: AcmeCertResult): void;
16
- export declare function ensureValidCert(acmeConfig: AcmeConfig): Promise<StoredCert>;
17
- export declare function startRenewalScheduler(acmeConfig: AcmeConfig, onRenewed: () => void): void;
18
- export declare function stopRenewalScheduler(): void;
19
- export declare function getCertStatusSummary(): {
20
- status: 'no_cert' | 'healthy' | 'expiring_soon' | 'critical' | 'renewal_failed';
21
- domain: string | null;
22
- issuedAt: string | null;
23
- expiresAt: string | null;
24
- daysUntilExpiry: number | null;
25
- autoRenewal: boolean;
26
- renewalFailed: boolean;
27
- lastRenewalError: string | null;
28
- lastRenewalErrorAt: string | null;
29
- };
30
- /** @internal */
31
- export declare function stopRenewalSchedulerForTests(): void;
32
- /** @internal */
33
- export declare function resetCertStoreStateForTests(): void;
34
- export {};
@@ -1,184 +0,0 @@
1
- import { resolveStateDir } from "../config/paths-state.js";
2
- import { createLogger } from "../utils/logger/index.js";
3
- import { init_logger } from "../utils/logger.js";
4
- import { init_paths } from "../config/paths.js";
5
- import { requestCertificate } from "./acme-client.js";
6
- import { join } from "node:path";
7
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
- //#region src/tunnel/acme-cert-store.ts
9
- init_paths();
10
- init_logger();
11
- const log = createLogger("TunnelCert");
12
- const RENEWAL_THRESHOLD_DAYS = 30;
13
- const RENEWAL_CHECK_INTERVAL_MS = 720 * 60 * 1e3;
14
- let renewalTimer = null;
15
- let lastRenewalError = null;
16
- let lastRenewalErrorAt = null;
17
- const certStatusListeners = /* @__PURE__ */ new Set();
18
- function subscribeCertStatus(listener) {
19
- certStatusListeners.add(listener);
20
- return () => certStatusListeners.delete(listener);
21
- }
22
- function emitCertStatusChange() {
23
- const summary = getCertStatusSummary();
24
- for (const listener of certStatusListeners) try {
25
- listener(summary);
26
- } catch (err) {
27
- log.warn({
28
- err,
29
- phase: "cert_status_listener"
30
- }, "Cert status listener failed");
31
- }
32
- }
33
- function recordRenewalFailure(err) {
34
- const errorMessage = err instanceof Error ? err.message : String(err);
35
- lastRenewalError = errorMessage;
36
- lastRenewalErrorAt = (/* @__PURE__ */ new Date()).toISOString();
37
- log.error({
38
- err,
39
- errorMessage,
40
- phase: "cert_renewal"
41
- }, `Tunnel certificate renewal failed: ${errorMessage}`);
42
- emitCertStatusChange();
43
- }
44
- function clearRenewalFailure() {
45
- if (!lastRenewalError && !lastRenewalErrorAt) return;
46
- lastRenewalError = null;
47
- lastRenewalErrorAt = null;
48
- emitCertStatusChange();
49
- }
50
- function getCertDir() {
51
- const dir = join(resolveStateDir(), "tunnel", "cert");
52
- mkdirSync(dir, { recursive: true });
53
- return dir;
54
- }
55
- function loadStoredCert() {
56
- const metaPath = join(getCertDir(), "cert-meta.json");
57
- if (!existsSync(metaPath)) return null;
58
- try {
59
- const meta = JSON.parse(readFileSync(metaPath, "utf8"));
60
- const certPath = join(getCertDir(), "fullchain.pem");
61
- const keyPath = join(getCertDir(), "privkey.pem");
62
- if (!existsSync(certPath) || !existsSync(keyPath)) return null;
63
- meta.certPem = readFileSync(certPath, "utf8");
64
- meta.keyPem = readFileSync(keyPath, "utf8");
65
- return meta;
66
- } catch {
67
- return null;
68
- }
69
- }
70
- function needsRenewal(cert) {
71
- return (new Date(cert.expiresAt).getTime() - Date.now()) / 864e5 <= RENEWAL_THRESHOLD_DAYS;
72
- }
73
- function storedCertIsStaging(certPem) {
74
- return certPem.includes("(STAGING)");
75
- }
76
- function storedCertMatchesAcmeConfig(existing, acmeConfig) {
77
- const staging = acmeConfig.staging ?? false;
78
- return storedCertIsStaging(existing.certPem) === staging;
79
- }
80
- function saveCert(result) {
81
- const dir = getCertDir();
82
- writeFileSync(join(dir, "fullchain.pem"), result.certPem, { mode: 420 });
83
- writeFileSync(join(dir, "privkey.pem"), result.keyPem, { mode: 384 });
84
- writeFileSync(join(dir, "cert-meta.json"), JSON.stringify({
85
- domain: result.domain,
86
- issuedAt: result.issuedAt.toISOString(),
87
- expiresAt: result.expiresAt.toISOString()
88
- }), "utf8");
89
- log.info({
90
- domain: result.domain,
91
- expiresAt: result.expiresAt.toISOString()
92
- }, "Certificate saved");
93
- clearRenewalFailure();
94
- }
95
- async function ensureValidCert(acmeConfig) {
96
- const existing = loadStoredCert();
97
- const expectedDomain = `${acmeConfig.subdomain}.${acmeConfig.frpSubdomainHost}`;
98
- if (existing && existing.domain === expectedDomain && !needsRenewal(existing) && storedCertMatchesAcmeConfig(existing, acmeConfig)) return existing;
99
- if (existing && !storedCertMatchesAcmeConfig(existing, acmeConfig)) log.info({
100
- domain: existing.domain,
101
- staging: acmeConfig.staging ?? false
102
- }, "Certificate CA mode mismatch, re-issuing");
103
- else if (existing) log.info({ domain: existing.domain }, "Certificate expiring soon, renewing");
104
- else log.info({ subdomain: acmeConfig.subdomain }, "No certificate, requesting new one");
105
- try {
106
- const result = await requestCertificate(acmeConfig);
107
- saveCert(result);
108
- return {
109
- certPem: result.certPem,
110
- keyPem: result.keyPem,
111
- domain: result.domain,
112
- issuedAt: result.issuedAt.toISOString(),
113
- expiresAt: result.expiresAt.toISOString()
114
- };
115
- } catch (err) {
116
- recordRenewalFailure(err);
117
- throw err;
118
- }
119
- }
120
- function startRenewalScheduler(acmeConfig, onRenewed) {
121
- stopRenewalScheduler();
122
- renewalTimer = setInterval(() => {
123
- (async () => {
124
- const cert = loadStoredCert();
125
- if (!cert || !needsRenewal(cert)) return;
126
- log.info({ domain: cert.domain }, "Auto-renewing certificate");
127
- try {
128
- await ensureValidCert(acmeConfig);
129
- onRenewed();
130
- } catch {}
131
- })();
132
- }, RENEWAL_CHECK_INTERVAL_MS);
133
- }
134
- function stopRenewalScheduler() {
135
- if (renewalTimer) {
136
- clearInterval(renewalTimer);
137
- renewalTimer = null;
138
- }
139
- }
140
- function getCertStatusSummary() {
141
- const cert = loadStoredCert();
142
- if (!cert) return {
143
- status: lastRenewalError ? "renewal_failed" : "no_cert",
144
- domain: null,
145
- issuedAt: null,
146
- expiresAt: null,
147
- daysUntilExpiry: null,
148
- autoRenewal: true,
149
- renewalFailed: Boolean(lastRenewalError),
150
- lastRenewalError,
151
- lastRenewalErrorAt
152
- };
153
- const daysUntilExpiry = Math.floor((new Date(cert.expiresAt).getTime() - Date.now()) / 864e5);
154
- let status = "healthy";
155
- if (lastRenewalError) status = "renewal_failed";
156
- else if (daysUntilExpiry <= 7) status = "critical";
157
- else if (daysUntilExpiry <= 30) status = "expiring_soon";
158
- return {
159
- status,
160
- domain: cert.domain,
161
- issuedAt: cert.issuedAt,
162
- expiresAt: cert.expiresAt,
163
- daysUntilExpiry,
164
- autoRenewal: true,
165
- renewalFailed: Boolean(lastRenewalError),
166
- lastRenewalError,
167
- lastRenewalErrorAt
168
- };
169
- }
170
- /** @internal */
171
- function stopRenewalSchedulerForTests() {
172
- stopRenewalScheduler();
173
- }
174
- /** @internal */
175
- function resetCertStoreStateForTests() {
176
- stopRenewalScheduler();
177
- lastRenewalError = null;
178
- lastRenewalErrorAt = null;
179
- certStatusListeners.clear();
180
- }
181
- //#endregion
182
- export { clearRenewalFailure, ensureValidCert, getCertStatusSummary, loadStoredCert, needsRenewal, recordRenewalFailure, resetCertStoreStateForTests, saveCert, startRenewalScheduler, stopRenewalScheduler, stopRenewalSchedulerForTests, subscribeCertStatus };
183
-
184
- //# sourceMappingURL=acme-cert-store.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"acme-cert-store.js","names":[],"sources":["../../../src/tunnel/acme-cert-store.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { resolveStateDir } from '../config/paths.js';\nimport { createLogger } from '../utils/logger.js';\nimport { requestCertificate, type AcmeConfig, type AcmeCertResult } from './acme-client.js';\n\nconst log = createLogger('TunnelCert');\n\nconst RENEWAL_THRESHOLD_DAYS = 30;\nconst RENEWAL_CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;\n\nexport type StoredCert = {\n certPem: string;\n keyPem: string;\n domain: string;\n issuedAt: string;\n expiresAt: string;\n};\n\nlet renewalTimer: ReturnType<typeof setInterval> | null = null;\nlet lastRenewalError: string | null = null;\nlet lastRenewalErrorAt: string | null = null;\n\ntype CertStatusListener = (summary: ReturnType<typeof getCertStatusSummary>) => void;\nconst certStatusListeners = new Set<CertStatusListener>();\n\nexport function subscribeCertStatus(listener: CertStatusListener): () => void {\n certStatusListeners.add(listener);\n return () => certStatusListeners.delete(listener);\n}\n\nfunction emitCertStatusChange(): void {\n const summary = getCertStatusSummary();\n for (const listener of certStatusListeners) {\n try {\n listener(summary);\n } catch (err) {\n log.warn({ err, phase: 'cert_status_listener' }, 'Cert status listener failed');\n }\n }\n}\n\nexport function recordRenewalFailure(err: unknown): void {\n const errorMessage = err instanceof Error ? err.message : String(err);\n lastRenewalError = errorMessage;\n lastRenewalErrorAt = new Date().toISOString();\n log.error({ err, errorMessage, phase: 'cert_renewal' }, `Tunnel certificate renewal failed: ${errorMessage}`);\n emitCertStatusChange();\n}\n\nexport function clearRenewalFailure(): void {\n if (!lastRenewalError && !lastRenewalErrorAt) return;\n lastRenewalError = null;\n lastRenewalErrorAt = null;\n emitCertStatusChange();\n}\n\nfunction getCertDir(): string {\n const dir = join(resolveStateDir(), 'tunnel', 'cert');\n mkdirSync(dir, { recursive: true });\n return dir;\n}\n\nexport function loadStoredCert(): StoredCert | null {\n const metaPath = join(getCertDir(), 'cert-meta.json');\n if (!existsSync(metaPath)) return null;\n try {\n const meta = JSON.parse(readFileSync(metaPath, 'utf8')) as StoredCert;\n const certPath = join(getCertDir(), 'fullchain.pem');\n const keyPath = join(getCertDir(), 'privkey.pem');\n if (!existsSync(certPath) || !existsSync(keyPath)) return null;\n meta.certPem = readFileSync(certPath, 'utf8');\n meta.keyPem = readFileSync(keyPath, 'utf8');\n return meta;\n } catch {\n return null;\n }\n}\n\nexport function needsRenewal(cert: StoredCert): boolean {\n const days = (new Date(cert.expiresAt).getTime() - Date.now()) / 86_400_000;\n return days <= RENEWAL_THRESHOLD_DAYS;\n}\n\nfunction storedCertIsStaging(certPem: string): boolean {\n return certPem.includes('(STAGING)');\n}\n\nfunction storedCertMatchesAcmeConfig(existing: StoredCert, acmeConfig: AcmeConfig): boolean {\n const staging = acmeConfig.staging ?? false;\n return storedCertIsStaging(existing.certPem) === staging;\n}\n\nexport function saveCert(result: AcmeCertResult): void {\n const dir = getCertDir();\n writeFileSync(join(dir, 'fullchain.pem'), result.certPem, { mode: 0o644 });\n writeFileSync(join(dir, 'privkey.pem'), result.keyPem, { mode: 0o600 });\n writeFileSync(\n join(dir, 'cert-meta.json'),\n JSON.stringify({\n domain: result.domain,\n issuedAt: result.issuedAt.toISOString(),\n expiresAt: result.expiresAt.toISOString(),\n }),\n 'utf8',\n );\n log.info({ domain: result.domain, expiresAt: result.expiresAt.toISOString() }, 'Certificate saved');\n clearRenewalFailure();\n}\n\nexport async function ensureValidCert(acmeConfig: AcmeConfig): Promise<StoredCert> {\n const existing = loadStoredCert();\n const expectedDomain = `${acmeConfig.subdomain}.${acmeConfig.frpSubdomainHost}`;\n if (\n existing &&\n existing.domain === expectedDomain &&\n !needsRenewal(existing) &&\n storedCertMatchesAcmeConfig(existing, acmeConfig)\n ) {\n return existing;\n }\n\n if (existing && !storedCertMatchesAcmeConfig(existing, acmeConfig)) {\n log.info(\n { domain: existing.domain, staging: acmeConfig.staging ?? false },\n 'Certificate CA mode mismatch, re-issuing',\n );\n } else if (existing) {\n log.info({ domain: existing.domain }, 'Certificate expiring soon, renewing');\n } else {\n log.info({ subdomain: acmeConfig.subdomain }, 'No certificate, requesting new one');\n }\n\n try {\n const result = await requestCertificate(acmeConfig);\n saveCert(result);\n return {\n certPem: result.certPem,\n keyPem: result.keyPem,\n domain: result.domain,\n issuedAt: result.issuedAt.toISOString(),\n expiresAt: result.expiresAt.toISOString(),\n };\n } catch (err) {\n recordRenewalFailure(err);\n throw err;\n }\n}\n\nexport function startRenewalScheduler(acmeConfig: AcmeConfig, onRenewed: () => void): void {\n stopRenewalScheduler();\n renewalTimer = setInterval(() => {\n void (async () => {\n const cert = loadStoredCert();\n if (!cert || !needsRenewal(cert)) return;\n log.info({ domain: cert.domain }, 'Auto-renewing certificate');\n try {\n await ensureValidCert(acmeConfig);\n onRenewed();\n } catch {\n /* failure recorded in ensureValidCert */\n }\n })();\n }, RENEWAL_CHECK_INTERVAL_MS);\n}\n\nexport function stopRenewalScheduler(): void {\n if (renewalTimer) {\n clearInterval(renewalTimer);\n renewalTimer = null;\n }\n}\n\nexport function getCertStatusSummary(): {\n status: 'no_cert' | 'healthy' | 'expiring_soon' | 'critical' | 'renewal_failed';\n domain: string | null;\n issuedAt: string | null;\n expiresAt: string | null;\n daysUntilExpiry: number | null;\n autoRenewal: boolean;\n renewalFailed: boolean;\n lastRenewalError: string | null;\n lastRenewalErrorAt: string | null;\n} {\n const cert = loadStoredCert();\n if (!cert) {\n return {\n status: lastRenewalError ? 'renewal_failed' : 'no_cert',\n domain: null,\n issuedAt: null,\n expiresAt: null,\n daysUntilExpiry: null,\n autoRenewal: true,\n renewalFailed: Boolean(lastRenewalError),\n lastRenewalError,\n lastRenewalErrorAt,\n };\n }\n const daysUntilExpiry = Math.floor(\n (new Date(cert.expiresAt).getTime() - Date.now()) / 86_400_000,\n );\n let status: 'healthy' | 'expiring_soon' | 'critical' | 'renewal_failed' = 'healthy';\n if (lastRenewalError) status = 'renewal_failed';\n else if (daysUntilExpiry <= 7) status = 'critical';\n else if (daysUntilExpiry <= 30) status = 'expiring_soon';\n\n return {\n status,\n domain: cert.domain,\n issuedAt: cert.issuedAt,\n expiresAt: cert.expiresAt,\n daysUntilExpiry,\n autoRenewal: true,\n renewalFailed: Boolean(lastRenewalError),\n lastRenewalError,\n lastRenewalErrorAt,\n };\n}\n\n/** @internal */\nexport function stopRenewalSchedulerForTests(): void {\n stopRenewalScheduler();\n}\n\n/** @internal */\nexport function resetCertStoreStateForTests(): void {\n stopRenewalScheduler();\n lastRenewalError = null;\n lastRenewalErrorAt = null;\n certStatusListeners.clear();\n}\n"],"mappings":"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"}
@@ -1,50 +0,0 @@
1
- import type { TunnelBrokerClient } from './broker-client.js';
2
- import type { TunnelAcmeProgressStep } from './tunnel-types.js';
3
- /** Public resolvers — LE validators use global DNS, not the host's stale cache. */
4
- export declare const ACME_DNS_RESOLVERS: readonly ["8.8.8.8", "1.1.1.1", "9.9.9.9"];
5
- export type AcmeDnsTxtProbe = {
6
- resolver: string;
7
- values: string[];
8
- error?: string;
9
- };
10
- /** Query each public resolver independently (Node tries setServers in order — not sufficient for LE). */
11
- export declare function probeAcmeDnsTxtAllResolvers(fqdn: string): Promise<AcmeDnsTxtProbe[]>;
12
- export declare function allAcmeDnsResolversHaveTxt(probes: AcmeDnsTxtProbe[], expectedValue: string): boolean;
13
- export type AcmeConfig = {
14
- broker: TunnelBrokerClient;
15
- tunnelId: string;
16
- tunnelToken: string;
17
- subdomain: string;
18
- frpSubdomainHost: string;
19
- staging?: boolean;
20
- onProgress?: (step: TunnelAcmeProgressStep) => void;
21
- };
22
- export type AcmeCertResult = {
23
- certPem: string;
24
- keyPem: string;
25
- domain: string;
26
- expiresAt: Date;
27
- issuedAt: Date;
28
- };
29
- /** LE always validates `_acme-challenge.{domain}` (RFC 8555 §8.4). */
30
- export declare function resolveAcmeChallengeFqdn(domain: string): string;
31
- export declare function formatAcmeDnsChallengeInvalidError(fqdn: string, data: {
32
- error?: {
33
- detail?: string;
34
- type?: string;
35
- };
36
- validationRecord?: unknown[];
37
- }): string;
38
- export declare function isRetryableAcmeDnsError(err: unknown): boolean;
39
- export type WaitForDnsTxtOptions = {
40
- minPropagationMs?: number;
41
- stableMs?: number;
42
- timeoutMs?: number;
43
- consensusRounds?: number;
44
- pollIntervalMs?: number;
45
- probe?: (fqdn: string) => Promise<AcmeDnsTxtProbe[]>;
46
- sleep?: (ms: number) => Promise<void>;
47
- };
48
- /** Poll public resolvers until TXT is stable — no blind initial sleep. */
49
- export declare function waitForDnsTxtConsensus(fqdn: string, expectedValue: string, opts?: WaitForDnsTxtOptions): Promise<void>;
50
- export declare function requestCertificate(config: AcmeConfig): Promise<AcmeCertResult>;