@tapflowio/relay 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/dist/RelayServer.d.ts +10 -0
  2. package/dist/RelayServer.d.ts.map +1 -1
  3. package/dist/RelayServer.js +53 -5
  4. package/dist/RelayServer.js.map +1 -1
  5. package/dist/api/builds.d.ts.map +1 -1
  6. package/dist/api/builds.js +14 -13
  7. package/dist/api/builds.js.map +1 -1
  8. package/dist/api/comments.d.ts.map +1 -1
  9. package/dist/api/comments.js +23 -10
  10. package/dist/api/comments.js.map +1 -1
  11. package/dist/api/team.d.ts.map +1 -1
  12. package/dist/api/team.js +4 -2
  13. package/dist/api/team.js.map +1 -1
  14. package/dist/index.d.ts +7 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +3 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/lib/cert/AcmeCertProvider.d.ts +55 -0
  19. package/dist/lib/cert/AcmeCertProvider.d.ts.map +1 -0
  20. package/dist/lib/cert/AcmeCertProvider.js +62 -0
  21. package/dist/lib/cert/AcmeCertProvider.js.map +1 -0
  22. package/dist/lib/cert/AcmeClientIssuer.d.ts +23 -0
  23. package/dist/lib/cert/AcmeClientIssuer.d.ts.map +1 -0
  24. package/dist/lib/cert/AcmeClientIssuer.js +54 -0
  25. package/dist/lib/cert/AcmeClientIssuer.js.map +1 -0
  26. package/dist/lib/cert/CertProvider.d.ts +30 -0
  27. package/dist/lib/cert/CertProvider.d.ts.map +1 -0
  28. package/dist/lib/cert/CertProvider.js +4 -0
  29. package/dist/lib/cert/CertProvider.js.map +1 -0
  30. package/dist/lib/cert/CloudflareDnsProvider.d.ts +40 -0
  31. package/dist/lib/cert/CloudflareDnsProvider.d.ts.map +1 -0
  32. package/dist/lib/cert/CloudflareDnsProvider.js +93 -0
  33. package/dist/lib/cert/CloudflareDnsProvider.js.map +1 -0
  34. package/dist/lib/cert/DiskCertStore.d.ts +10 -0
  35. package/dist/lib/cert/DiskCertStore.d.ts.map +1 -0
  36. package/dist/lib/cert/DiskCertStore.js +35 -0
  37. package/dist/lib/cert/DiskCertStore.js.map +1 -0
  38. package/dist/lib/cert/DnsProvider.d.ts +7 -0
  39. package/dist/lib/cert/DnsProvider.d.ts.map +1 -0
  40. package/dist/lib/cert/DnsProvider.js +2 -0
  41. package/dist/lib/cert/DnsProvider.js.map +1 -0
  42. package/dist/lib/cert/ImportCertProvider.d.ts +20 -0
  43. package/dist/lib/cert/ImportCertProvider.d.ts.map +1 -0
  44. package/dist/lib/cert/ImportCertProvider.js +24 -0
  45. package/dist/lib/cert/ImportCertProvider.js.map +1 -0
  46. package/dist/lib/cert/VercelDnsProvider.d.ts +38 -0
  47. package/dist/lib/cert/VercelDnsProvider.d.ts.map +1 -0
  48. package/dist/lib/cert/VercelDnsProvider.js +146 -0
  49. package/dist/lib/cert/VercelDnsProvider.js.map +1 -0
  50. package/dist/lib/cert/accountKey.d.ts +2 -0
  51. package/dist/lib/cert/accountKey.d.ts.map +1 -0
  52. package/dist/lib/cert/accountKey.js +23 -0
  53. package/dist/lib/cert/accountKey.js.map +1 -0
  54. package/dist/lib/cert/addressPublisher.d.ts +20 -0
  55. package/dist/lib/cert/addressPublisher.d.ts.map +1 -0
  56. package/dist/lib/cert/addressPublisher.js +103 -0
  57. package/dist/lib/cert/addressPublisher.js.map +1 -0
  58. package/dist/lib/cert/createCertProvider.d.ts +20 -0
  59. package/dist/lib/cert/createCertProvider.d.ts.map +1 -0
  60. package/dist/lib/cert/createCertProvider.js +25 -0
  61. package/dist/lib/cert/createCertProvider.js.map +1 -0
  62. package/dist/lib/cert/dnsRegistry.d.ts +24 -0
  63. package/dist/lib/cert/dnsRegistry.d.ts.map +1 -0
  64. package/dist/lib/cert/dnsRegistry.js +36 -0
  65. package/dist/lib/cert/dnsRegistry.js.map +1 -0
  66. package/dist/lib/cert/index.d.ts +23 -0
  67. package/dist/lib/cert/index.d.ts.map +1 -0
  68. package/dist/lib/cert/index.js +12 -0
  69. package/dist/lib/cert/index.js.map +1 -0
  70. package/dist/lib/cert/parseCert.d.ts +3 -0
  71. package/dist/lib/cert/parseCert.d.ts.map +1 -0
  72. package/dist/lib/cert/parseCert.js +6 -0
  73. package/dist/lib/cert/parseCert.js.map +1 -0
  74. package/dist/lib/cert/renewal.d.ts +13 -0
  75. package/dist/lib/cert/renewal.d.ts.map +1 -0
  76. package/dist/lib/cert/renewal.js +28 -0
  77. package/dist/lib/cert/renewal.js.map +1 -0
  78. package/dist/lib/config.d.ts +22 -0
  79. package/dist/lib/config.d.ts.map +1 -1
  80. package/dist/lib/config.js +39 -0
  81. package/dist/lib/config.js.map +1 -1
  82. package/dist/lib/cors.d.ts +2 -0
  83. package/dist/lib/cors.d.ts.map +1 -0
  84. package/dist/lib/cors.js +18 -0
  85. package/dist/lib/cors.js.map +1 -0
  86. package/dist/lib/csrf.d.ts +3 -0
  87. package/dist/lib/csrf.d.ts.map +1 -0
  88. package/dist/lib/csrf.js +42 -0
  89. package/dist/lib/csrf.js.map +1 -0
  90. package/dist/lib/loadEnvFile.d.ts +2 -0
  91. package/dist/lib/loadEnvFile.d.ts.map +1 -0
  92. package/dist/lib/loadEnvFile.js +17 -0
  93. package/dist/lib/loadEnvFile.js.map +1 -0
  94. package/dist/lib/proxyConfig.d.ts +6 -0
  95. package/dist/lib/proxyConfig.d.ts.map +1 -0
  96. package/dist/lib/proxyConfig.js +27 -0
  97. package/dist/lib/proxyConfig.js.map +1 -0
  98. package/dist/lib/publicUrl.d.ts +3 -0
  99. package/dist/lib/publicUrl.d.ts.map +1 -0
  100. package/dist/lib/publicUrl.js +17 -0
  101. package/dist/lib/publicUrl.js.map +1 -0
  102. package/dist/lib/uploads.d.ts +2 -0
  103. package/dist/lib/uploads.d.ts.map +1 -0
  104. package/dist/lib/uploads.js +14 -0
  105. package/dist/lib/uploads.js.map +1 -0
  106. package/dist/router.d.ts.map +1 -1
  107. package/dist/router.js +13 -1
  108. package/dist/router.js.map +1 -1
  109. package/dist/server.js +44 -5
  110. package/dist/server.js.map +1 -1
  111. package/package.json +6 -5
  112. package/public/assets/AppCenter-CaY73Tvf.js +16 -0
  113. package/public/assets/AppCenter-CaY73Tvf.js.br +0 -0
  114. package/public/assets/Default-DQiKj0NT.js +1 -0
  115. package/public/assets/Default-DQiKj0NT.js.br +0 -0
  116. package/public/assets/Invite-B4MMcQtw.js +1 -0
  117. package/public/assets/Invite-B4MMcQtw.js.br +0 -0
  118. package/public/assets/MacResources-BfZAJ5BP.js +5 -0
  119. package/public/assets/MacResources-BfZAJ5BP.js.br +0 -0
  120. package/public/assets/NotFound-D85RKzwv.js +1 -0
  121. package/public/assets/NotFound-D85RKzwv.js.br +0 -0
  122. package/public/assets/QASession-B9FSUtZo.js +130 -0
  123. package/public/assets/QASession-B9FSUtZo.js.br +0 -0
  124. package/public/assets/ResetPassword-Degmb6sl.js +1 -0
  125. package/public/assets/ResetPassword-Degmb6sl.js.br +0 -0
  126. package/public/assets/Setup-DKOFxDo2.js +1 -0
  127. package/public/assets/Setup-DKOFxDo2.js.br +0 -0
  128. package/public/assets/Team-Bgspan2B.js +6 -0
  129. package/public/assets/Team-Bgspan2B.js.br +0 -0
  130. package/public/assets/Tokens-C3u48FUG.js +6 -0
  131. package/public/assets/Tokens-C3u48FUG.js.br +0 -0
  132. package/public/assets/alert-dialog-Ci_cWoVo.js +7 -0
  133. package/public/assets/alert-dialog-Ci_cWoVo.js.br +0 -0
  134. package/public/assets/badge-Cktrrdvf.js +1 -0
  135. package/public/assets/badge-Cktrrdvf.js.br +0 -0
  136. package/public/assets/build-format-Blzidb1D.js +11 -0
  137. package/public/assets/build-format-Blzidb1D.js.br +0 -0
  138. package/public/assets/dialog-BuZjQWBQ.js +11 -0
  139. package/public/assets/dialog-BuZjQWBQ.js.br +0 -0
  140. package/public/assets/index-BI7E3MD-.css +1 -0
  141. package/public/assets/index-BI7E3MD-.css.br +0 -0
  142. package/public/assets/index-ClAB0eKw.js +258 -0
  143. package/public/assets/index-ClAB0eKw.js.br +0 -0
  144. package/public/assets/inter-latin-ext-standard-normal-DpK-iCPk.woff2 +0 -0
  145. package/public/assets/inter-latin-standard-normal-BwkfbSeq.woff2 +0 -0
  146. package/public/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  147. package/public/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  148. package/public/assets/pencil-BoxokH2R.js +6 -0
  149. package/public/assets/pencil-BoxokH2R.js.br +0 -0
  150. package/public/assets/plus-C0Y4Yk9P.js +6 -0
  151. package/public/assets/plus-C0Y4Yk9P.js.br +0 -0
  152. package/public/assets/table-NBRnBazr.js +1 -0
  153. package/public/assets/table-NBRnBazr.js.br +0 -0
  154. package/public/assets/tabs-CB9a8dpY.js +1 -0
  155. package/public/assets/tabs-CB9a8dpY.js.br +0 -0
  156. package/public/assets/tinyh264.worker-DiJ50Hai.js.br +0 -0
  157. package/public/favicon.svg.br +0 -0
  158. package/public/index.html +2 -2
  159. package/public/index.html.br +0 -0
  160. package/public/logo-dark.svg.br +0 -0
  161. package/public/logo.svg.br +0 -0
  162. package/public/assets/index-Bhoumejm.js +0 -520
  163. package/public/assets/index-DX5Pf9T6.css +0 -1
  164. package/public/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
  165. package/public/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
  166. package/public/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
  167. package/public/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
  168. package/public/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
  169. package/public/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
  170. package/public/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
  171. package/public/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
  172. package/public/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
  173. package/public/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
  174. package/public/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
  175. package/public/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
  176. package/public/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
  177. package/public/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
  178. package/public/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
  179. package/public/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
  180. package/public/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
  181. package/public/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
  182. package/public/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
  183. package/public/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
  184. package/public/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
  185. package/public/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
  186. package/public/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
  187. package/public/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
  188. package/public/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  189. package/public/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
  190. package/public/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  191. package/public/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  192. package/public/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
  193. package/public/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  194. package/public/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
  195. package/public/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
  196. package/public/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
  197. package/public/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
  198. package/public/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
  199. package/public/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
  200. package/public/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
  201. package/public/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
  202. package/public/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
  203. package/public/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
  204. package/public/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
  205. package/public/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
  206. package/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  207. package/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  208. package/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  209. package/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  210. package/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  211. package/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  212. package/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  213. package/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  214. package/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
@@ -0,0 +1,38 @@
1
+ import type { DnsProvider } from './DnsProvider.js';
2
+ import { type FetchLike } from './CloudflareDnsProvider.js';
3
+ export interface VercelDnsProviderOptions {
4
+ token: string;
5
+ /** 명시하면 도메인 디스커버리(GET /v5/domains)를 생략. */
6
+ zoneName?: string;
7
+ /** 팀 계정이면 teamId. */
8
+ teamId?: string;
9
+ /** 테스트 주입용. 기본 global fetch. */
10
+ fetchFn?: FetchLike;
11
+ /** 기본 https://api.vercel.com */
12
+ apiBase?: string;
13
+ }
14
+ export declare class VercelDnsProvider implements DnsProvider {
15
+ readonly name = "vercel";
16
+ private readonly token;
17
+ private readonly fetchFn;
18
+ private readonly apiBase;
19
+ private readonly fixedZoneName?;
20
+ private readonly teamId?;
21
+ private readonly zoneCache;
22
+ constructor(opts: VercelDnsProviderOptions);
23
+ setTxtRecord(fqdn: string, value: string): Promise<void>;
24
+ removeTxtRecord(fqdn: string, value: string): Promise<void>;
25
+ upsertAddressRecord(fqdn: string, ip: string): Promise<void>;
26
+ private subnameOf;
27
+ private url;
28
+ private zoneFor;
29
+ private findRecords;
30
+ private listAllDomains;
31
+ private listAllRecords;
32
+ private createRecord;
33
+ private deleteRecord;
34
+ private request;
35
+ }
36
+ /** env(TAPFLOW_VERCEL_TOKEN)에서 토큰을 읽어 provider를 만든다. 미설정 시 throw. (팀이면 TAPFLOW_VERCEL_TEAM_ID) */
37
+ export declare function vercelDnsFromEnv(zoneName?: string): VercelDnsProvider;
38
+ //# sourceMappingURL=VercelDnsProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VercelDnsProvider.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/VercelDnsProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAS/E,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAA;IACb,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qBAAqB;IACrB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gCAAgC;IAChC,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,gCAAgC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AASD,qBAAa,iBAAkB,YAAW,WAAW;IACnD,QAAQ,CAAC,IAAI,YAAW;IACxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;gBAE1C,IAAI,EAAE,wBAAwB;IASpC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASxD,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU3D,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlE,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,GAAG;YAOG,OAAO;YAWP,WAAW;YAQX,cAAc;YAkBd,cAAc;YAkBd,YAAY;YAIZ,YAAY;YAIZ,OAAO;CActB;AAED,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAIrE"}
@@ -0,0 +1,146 @@
1
+ import { DNS_API_TIMEOUT_MS } from './CloudflareDnsProvider.js';
2
+ // Vercel DNS-01 solver (issue #232). 사용자 자기 Vercel 계정 토큰으로 동작한다(BYO).
3
+ // 토큰은 config가 아니라 env(TAPFLOW_VERCEL_TOKEN)에서 읽는다(vercelDnsFromEnv).
4
+ // Vercel은 개별 레코드(id 보유) 모델: name은 도메인 기준 상대 subname(루트는 ''), TXT 값은 따옴표 없이.
5
+ const DEFAULT_API_BASE = 'https://api.vercel.com';
6
+ const TTL_SECONDS = 60;
7
+ export class VercelDnsProvider {
8
+ name = 'vercel';
9
+ token;
10
+ fetchFn;
11
+ apiBase;
12
+ fixedZoneName;
13
+ teamId;
14
+ zoneCache = new Map();
15
+ constructor(opts) {
16
+ if (!opts.token)
17
+ throw new Error('Vercel API token is required (set TAPFLOW_VERCEL_TOKEN)');
18
+ this.token = opts.token;
19
+ this.fetchFn = opts.fetchFn ?? globalThis.fetch;
20
+ this.apiBase = opts.apiBase ?? DEFAULT_API_BASE;
21
+ this.fixedZoneName = opts.zoneName;
22
+ this.teamId = opts.teamId;
23
+ }
24
+ async setTxtRecord(fqdn, value) {
25
+ const recordName = `_acme-challenge.${fqdn}`;
26
+ const zone = await this.zoneFor(recordName);
27
+ const subname = this.subnameOf(recordName, zone);
28
+ const existing = await this.findRecords(zone, 'TXT', subname);
29
+ if (existing.some((r) => r.value === value || r.value === `"${value}"`))
30
+ return;
31
+ await this.createRecord(zone, { type: 'TXT', name: subname, value, ttl: TTL_SECONDS });
32
+ }
33
+ async removeTxtRecord(fqdn, value) {
34
+ const recordName = `_acme-challenge.${fqdn}`;
35
+ const zone = await this.zoneFor(recordName);
36
+ const subname = this.subnameOf(recordName, zone);
37
+ const matches = (await this.findRecords(zone, 'TXT', subname)).filter((r) => r.value === value || r.value === `"${value}"`);
38
+ for (const r of matches)
39
+ await this.deleteRecord(zone, r.id);
40
+ }
41
+ async upsertAddressRecord(fqdn, ip) {
42
+ const zone = await this.zoneFor(fqdn);
43
+ const subname = this.subnameOf(fqdn, zone);
44
+ const type = ip.includes(':') ? 'AAAA' : 'A';
45
+ // Vercel엔 레코드 PATCH가 번거로우니 기존 동type 레코드 삭제 후 재생성(upsert).
46
+ for (const r of await this.findRecords(zone, type, subname))
47
+ await this.deleteRecord(zone, r.id);
48
+ await this.createRecord(zone, { type, name: subname, value: ip, ttl: TTL_SECONDS });
49
+ }
50
+ // --- internals ---
51
+ subnameOf(fqdn, zone) {
52
+ if (fqdn === zone)
53
+ return '';
54
+ if (fqdn.endsWith(`.${zone}`))
55
+ return fqdn.slice(0, -(zone.length + 1));
56
+ throw new Error(`${fqdn} is not under Vercel zone ${zone}`);
57
+ }
58
+ url(path, params = {}) {
59
+ const q = new URLSearchParams(params);
60
+ if (this.teamId)
61
+ q.set('teamId', this.teamId);
62
+ const qs = q.toString();
63
+ return `${this.apiBase}${path}${qs ? `?${qs}` : ''}`;
64
+ }
65
+ async zoneFor(fqdn) {
66
+ if (this.fixedZoneName)
67
+ return this.fixedZoneName;
68
+ const cached = this.zoneCache.get(fqdn);
69
+ if (cached)
70
+ return cached;
71
+ const names = await this.listAllDomains();
72
+ const zone = names.filter((n) => fqdn === n || fqdn.endsWith(`.${n}`)).sort((a, b) => b.length - a.length)[0];
73
+ if (!zone)
74
+ throw new Error(`No Vercel domain found for ${fqdn}`);
75
+ this.zoneCache.set(fqdn, zone);
76
+ return zone;
77
+ }
78
+ async findRecords(zone, type, subname) {
79
+ const fullName = subname === '' ? zone : `${subname}.${zone}`;
80
+ const records = await this.listAllRecords(zone);
81
+ return records.filter((r) => r.type === type && (r.name === subname || r.name === fullName));
82
+ }
83
+ // Vercel 목록 API는 페이지네이션됨(기본/limit 초과 시). pagination.next(타임스탬프)를 until로 넘겨 끝까지 순회.
84
+ // >100 도메인/레코드 계정에서 "No Vercel domain found"나 누락으로 인한 idempotency 깨짐 방지.
85
+ async listAllDomains() {
86
+ const names = [];
87
+ let until;
88
+ for (let page = 0; page < 100; page++) {
89
+ const params = { limit: '100' };
90
+ if (until)
91
+ params.until = until;
92
+ const body = await this.request(this.url('/v5/domains', params), 'GET');
93
+ for (const d of body.domains)
94
+ names.push(d.name);
95
+ const next = body.pagination?.next;
96
+ if (!next)
97
+ break;
98
+ until = String(next);
99
+ }
100
+ return names;
101
+ }
102
+ async listAllRecords(zone) {
103
+ const all = [];
104
+ let until;
105
+ for (let page = 0; page < 100; page++) {
106
+ const params = { limit: '100' };
107
+ if (until)
108
+ params.until = until;
109
+ const body = await this.request(this.url(`/v5/domains/${zone}/records`, params), 'GET');
110
+ all.push(...(body.records ?? []));
111
+ const next = body.pagination?.next;
112
+ if (!next)
113
+ break;
114
+ until = String(next);
115
+ }
116
+ return all;
117
+ }
118
+ async createRecord(zone, record) {
119
+ await this.request(this.url(`/v2/domains/${zone}/records`), 'POST', record);
120
+ }
121
+ async deleteRecord(zone, recordId) {
122
+ await this.request(this.url(`/v2/domains/${zone}/records/${recordId}`), 'DELETE');
123
+ }
124
+ async request(url, method, body) {
125
+ const res = await this.fetchFn(url, {
126
+ method,
127
+ headers: { Authorization: `Bearer ${this.token}`, 'content-type': 'application/json' },
128
+ body: body === undefined ? undefined : JSON.stringify(body),
129
+ signal: AbortSignal.timeout(DNS_API_TIMEOUT_MS),
130
+ });
131
+ const data = (await res.json().catch(() => null));
132
+ if (!res.ok) {
133
+ const msg = data?.error?.message || `HTTP ${res.status}`;
134
+ throw new Error(`Vercel API error: ${msg}`);
135
+ }
136
+ return data;
137
+ }
138
+ }
139
+ /** env(TAPFLOW_VERCEL_TOKEN)에서 토큰을 읽어 provider를 만든다. 미설정 시 throw. (팀이면 TAPFLOW_VERCEL_TEAM_ID) */
140
+ export function vercelDnsFromEnv(zoneName) {
141
+ const token = process.env.TAPFLOW_VERCEL_TOKEN;
142
+ if (!token)
143
+ throw new Error('TAPFLOW_VERCEL_TOKEN is not set');
144
+ return new VercelDnsProvider({ token, zoneName, teamId: process.env.TAPFLOW_VERCEL_TEAM_ID || undefined });
145
+ }
146
+ //# sourceMappingURL=VercelDnsProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VercelDnsProvider.js","sourceRoot":"","sources":["../../../src/lib/cert/VercelDnsProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function loadOrCreateAccountKey(filePath: string, create: () => Promise<string>): Promise<string>;
2
+ //# sourceMappingURL=accountKey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accountKey.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/accountKey.ts"],"names":[],"mappings":"AAKA,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAe7G"}
@@ -0,0 +1,23 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ // ACME 계정 키를 디스크에 캐시한다. 매 발급마다 새 키로 새 LE 계정을 등록하면
4
+ // Accounts-per-IP 한도(3h/10)에 닿을 수 있으므로 재사용한다. 키 파일은 0600.
5
+ export async function loadOrCreateAccountKey(filePath, create) {
6
+ try {
7
+ return fs.readFileSync(filePath, 'utf-8');
8
+ }
9
+ catch {
10
+ // not yet created
11
+ }
12
+ const key = await create();
13
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
14
+ fs.writeFileSync(filePath, key, { mode: 0o600 });
15
+ try {
16
+ fs.chmodSync(filePath, 0o600);
17
+ }
18
+ catch {
19
+ // best-effort on platforms without POSIX permissions
20
+ }
21
+ return key;
22
+ }
23
+ //# sourceMappingURL=accountKey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accountKey.js","sourceRoot":"","sources":["../../../src/lib/cert/accountKey.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB,kDAAkD;AAClD,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,QAAgB,EAAE,MAA6B;IAC1F,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,kBAAkB;IACpB,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,MAAM,EAAE,CAAA;IAC1B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACzD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAChD,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,qDAAqD;IACvD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { DnsProvider } from './DnsProvider.js';
2
+ export interface AddressPublisherTls {
3
+ domain: string;
4
+ dnsProvider: string;
5
+ /** 자동 감지 대신 쓸 고정 IP. */
6
+ address?: string;
7
+ }
8
+ export interface AddressPublisherOptions {
9
+ intervalMs?: number;
10
+ /** IP 소스 주입(테스트/오버라이드). 기본 = address ?? detectLanIPv4. */
11
+ getIp?: () => Promise<string | null>;
12
+ /** DnsProvider 주입(테스트). 기본 = dnsProvider별 env 팩토리. */
13
+ provider?: DnsProvider;
14
+ /** IP 불변이어도 이 틱 수마다 강제 재발행(self-heal). 기본 12(~1h). */
15
+ reassertEveryTicks?: number;
16
+ onError?: (err: unknown) => void;
17
+ }
18
+ export declare function detectLanIPv4(): Promise<string | null>;
19
+ export declare function startAddressPublisher(tls: AddressPublisherTls, opts?: AddressPublisherOptions): () => void;
20
+ //# sourceMappingURL=addressPublisher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"addressPublisher.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/addressPublisher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAYnD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,wBAAwB;IACxB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACpC,sDAAsD;IACtD,QAAQ,CAAC,EAAE,WAAW,CAAA;IACtB,sDAAsD;IACtD,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CACjC;AAGD,wBAAgB,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsBtD;AAGD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,mBAAmB,EAAE,IAAI,GAAE,uBAA4B,GAAG,MAAM,IAAI,CAkD9G"}
@@ -0,0 +1,103 @@
1
+ import dgram from 'dgram';
2
+ import { createLogger } from '@tapflowio/agent-core';
3
+ import { dnsProviders } from './dnsRegistry.js';
4
+ const logger = createLogger('relay:dns');
5
+ // DHCP로 IP가 바뀔 수 있어 cert 갱신(12h)보다 자주 재확인한다.
6
+ const DEFAULT_INTERVAL_MS = 5 * 60_000;
7
+ // IP 불변이어도 이 틱 수마다 강제 재발행 — 외부에서 지워지거나 바뀐 레코드를 self-heal(기본 ~1h).
8
+ const DEFAULT_REASSERT_TICKS = 12;
9
+ // connect 콜백/error 둘 다 안 오는 극단 케이스에서 영구 await 방지.
10
+ const DETECT_TIMEOUT_MS = 2_000;
11
+ // UDP connect(패킷 미전송)로 기본 라우트 인터페이스 IPv4를 얻는다 — networkInterfaces()는 VPN/멀티NIC 오감지 위험.
12
+ export function detectLanIPv4() {
13
+ return new Promise((resolve) => {
14
+ const sock = dgram.createSocket('udp4');
15
+ let settled = false;
16
+ const done = (ip) => {
17
+ if (settled)
18
+ return;
19
+ settled = true;
20
+ clearTimeout(timer);
21
+ try {
22
+ sock.close();
23
+ }
24
+ catch { /* already closed */ }
25
+ resolve(ip && ip !== '0.0.0.0' ? ip : null);
26
+ };
27
+ const timer = setTimeout(() => done(null), DETECT_TIMEOUT_MS);
28
+ timer.unref?.();
29
+ sock.once('error', () => done(null));
30
+ try {
31
+ sock.connect(53, '8.8.8.8', () => {
32
+ try {
33
+ done(sock.address().address);
34
+ }
35
+ catch {
36
+ done(null);
37
+ }
38
+ });
39
+ }
40
+ catch {
41
+ done(null);
42
+ }
43
+ });
44
+ }
45
+ // byo-api-token일 때 LAN IP를 자기 도메인 A 레코드로 발행 — 팀원은 DNS를 손대지 않고 도메인만 연다.
46
+ export function startAddressPublisher(tls, opts = {}) {
47
+ const dns = opts.provider ?? dnsProviders.get(tls.dnsProvider)?.fromEnv();
48
+ if (!dns) {
49
+ logger.warn(`unknown dnsProvider "${tls.dnsProvider}" — skipping address auto-publish`);
50
+ return () => { };
51
+ }
52
+ const upsert = dns.upsertAddressRecord?.bind(dns);
53
+ if (!upsert) {
54
+ logger.warn(`${dns.name} cannot publish A records — skipping address auto-publish`);
55
+ return () => { };
56
+ }
57
+ const getIp = opts.getIp ?? (tls.address ? () => Promise.resolve(tls.address ?? null) : detectLanIPv4);
58
+ const reassertEvery = opts.reassertEveryTicks ?? DEFAULT_REASSERT_TICKS;
59
+ let last = null;
60
+ let skipped = 0;
61
+ const publish = async () => {
62
+ try {
63
+ const ip = await getIp();
64
+ if (!ip) {
65
+ logger.warn(`could not determine LAN IP to publish ${tls.domain}`);
66
+ return;
67
+ }
68
+ // 보통은 IP 불변이면 skip하되, reassertEvery 틱마다 한 번은 강제 재발행해 외부 변경을 self-heal한다.
69
+ if (ip === last && skipped < reassertEvery) {
70
+ skipped++;
71
+ return;
72
+ }
73
+ await upsert(tls.domain, ip);
74
+ last = ip;
75
+ skipped = 0;
76
+ logger.info(`published ${tls.domain} A -> ${ip}`);
77
+ }
78
+ catch (err) {
79
+ if (opts.onError)
80
+ opts.onError(err);
81
+ else
82
+ logger.warn(`failed to publish ${tls.domain} A record: ${String(err)}`);
83
+ }
84
+ };
85
+ // setInterval이 이전 upsert 완료 전 다음 publish를 시작하면 쓰기가 역순으로 끝나 stale IP가 남을 수 있다.
86
+ let inFlight = false;
87
+ const tick = async () => {
88
+ if (inFlight)
89
+ return;
90
+ inFlight = true;
91
+ try {
92
+ await publish();
93
+ }
94
+ finally {
95
+ inFlight = false;
96
+ }
97
+ };
98
+ void tick();
99
+ const timer = setInterval(() => void tick(), opts.intervalMs ?? DEFAULT_INTERVAL_MS);
100
+ timer.unref?.();
101
+ return () => clearInterval(timer);
102
+ }
103
+ //# sourceMappingURL=addressPublisher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"addressPublisher.js","sourceRoot":"","sources":["../../../src/lib/cert/addressPublisher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { CertProvider } from './CertProvider.js';
2
+ export type TlsConfig = {
3
+ mode: 'byo-api-token';
4
+ domain: string;
5
+ dnsProvider: string;
6
+ } | {
7
+ mode: 'import-cert';
8
+ certPath: string;
9
+ keyPath: string;
10
+ };
11
+ export interface CreateCertProviderDeps {
12
+ dataDir: string;
13
+ /** ACME 계정 이메일. 기본 env TAPFLOW_ACME_EMAIL. */
14
+ email?: string;
15
+ /** LE 스테이징 사용(테스트). 기본 env TAPFLOW_ACME_STAGING === '1'. */
16
+ staging?: boolean;
17
+ }
18
+ /** config.tls를 적절한 CertProvider로 매핑한다. 비밀(토큰)은 env에서 읽는다. */
19
+ export declare function createCertProvider(tls: TlsConfig, deps: CreateCertProviderDeps): CertProvider;
20
+ //# sourceMappingURL=createCertProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createCertProvider.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/createCertProvider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AASrD,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC9D;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9D,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAA;IACf,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,6DAA6D;AAC7D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,sBAAsB,GAAG,YAAY,CAe7F"}
@@ -0,0 +1,25 @@
1
+ import path from 'path';
2
+ import { AcmeCertProvider } from './AcmeCertProvider.js';
3
+ import { AcmeClientIssuer } from './AcmeClientIssuer.js';
4
+ import { dnsProviders } from './dnsRegistry.js';
5
+ import { ImportCertProvider } from './ImportCertProvider.js';
6
+ import { DiskCertStore } from './DiskCertStore.js';
7
+ /** config.tls를 적절한 CertProvider로 매핑한다. 비밀(토큰)은 env에서 읽는다. */
8
+ export function createCertProvider(tls, deps) {
9
+ if (tls.mode === 'import-cert') {
10
+ return new ImportCertProvider({ certPath: tls.certPath, keyPath: tls.keyPath });
11
+ }
12
+ // byo-api-token: 레지스트리에서 provider를 찾아 자기 DNS 계정 토큰(env)으로 DNS-01.
13
+ const entry = dnsProviders.get(tls.dnsProvider);
14
+ if (!entry)
15
+ throw new Error(`unknown dnsProvider "${tls.dnsProvider}" — available: ${dnsProviders.names().join(', ')}`);
16
+ const dns = entry.fromEnv();
17
+ const issuer = new AcmeClientIssuer({
18
+ email: deps.email ?? process.env.TAPFLOW_ACME_EMAIL ?? '',
19
+ staging: deps.staging ?? process.env.TAPFLOW_ACME_STAGING === '1',
20
+ accountKeyPath: path.join(deps.dataDir, 'tls', 'account.pem'),
21
+ });
22
+ const store = new DiskCertStore(path.join(deps.dataDir, 'tls'));
23
+ return new AcmeCertProvider({ domain: tls.domain, dns, issuer, store });
24
+ }
25
+ //# sourceMappingURL=createCertProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createCertProvider.js","sourceRoot":"","sources":["../../../src/lib/cert/createCertProvider.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAGvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAelD,6DAA6D;AAC7D,MAAM,UAAU,kBAAkB,CAAC,GAAc,EAAE,IAA4B;IAC7E,IAAI,GAAG,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QAC/B,OAAO,IAAI,kBAAkB,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;IACjF,CAAC;IACD,kEAAkE;IAClE,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IAC/C,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,CAAC,WAAW,kBAAkB,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACvH,MAAM,GAAG,GAAgB,KAAK,CAAC,OAAO,EAAE,CAAA;IACxC,MAAM,MAAM,GAAG,IAAI,gBAAgB,CAAC;QAClC,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE;QACzD,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,GAAG;QACjE,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,CAAC;KAC9D,CAAC,CAAA;IACF,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAA;IAC/D,OAAO,IAAI,gBAAgB,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;AACzE,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { DnsProvider } from './DnsProvider.js';
2
+ export interface DnsProviderEntry {
3
+ /** config의 dnsProvider 값이자 식별자. */
4
+ name: string;
5
+ /** wizard 표시 라벨. */
6
+ label: string;
7
+ /** wizard 힌트. */
8
+ hint: string;
9
+ /** 자격 증명 env 변수(검증·요약용). 비밀은 config가 아니라 env에서만 읽는다. */
10
+ envVars: string[];
11
+ /** env에서 자격을 읽어 DnsProvider를 만든다(미설정 시 throw). */
12
+ fromEnv(zoneName?: string): DnsProvider;
13
+ }
14
+ declare class DnsProviderRegistry {
15
+ private readonly entries;
16
+ register(entry: DnsProviderEntry): void;
17
+ get(name: string): DnsProviderEntry | undefined;
18
+ has(name: string): boolean;
19
+ list(): DnsProviderEntry[];
20
+ names(): string[];
21
+ }
22
+ export declare const dnsProviders: DnsProviderRegistry;
23
+ export {};
24
+ //# sourceMappingURL=dnsRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dnsRegistry.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/dnsRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAMnD,MAAM,WAAW,gBAAgB;IAC/B,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,wDAAwD;IACxD,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,kDAAkD;IAClD,OAAO,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;CACxC;AAED,cAAM,mBAAmB;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAGvC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAG/C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAG1B,IAAI,IAAI,gBAAgB,EAAE;IAG1B,KAAK,IAAI,MAAM,EAAE;CAGlB;AAED,eAAO,MAAM,YAAY,qBAA4B,CAAA"}
@@ -0,0 +1,36 @@
1
+ import { cloudflareDnsFromEnv } from './CloudflareDnsProvider.js';
2
+ import { vercelDnsFromEnv } from './VercelDnsProvider.js';
3
+ class DnsProviderRegistry {
4
+ entries = new Map();
5
+ register(entry) {
6
+ this.entries.set(entry.name, entry);
7
+ }
8
+ get(name) {
9
+ return this.entries.get(name);
10
+ }
11
+ has(name) {
12
+ return this.entries.has(name);
13
+ }
14
+ list() {
15
+ return [...this.entries.values()];
16
+ }
17
+ names() {
18
+ return [...this.entries.keys()];
19
+ }
20
+ }
21
+ export const dnsProviders = new DnsProviderRegistry();
22
+ dnsProviders.register({
23
+ name: 'cloudflare',
24
+ label: 'Cloudflare DNS',
25
+ hint: 'auto-issue & renew via API token (env TAPFLOW_CLOUDFLARE_TOKEN)',
26
+ envVars: ['TAPFLOW_CLOUDFLARE_TOKEN'],
27
+ fromEnv: cloudflareDnsFromEnv,
28
+ });
29
+ dnsProviders.register({
30
+ name: 'vercel',
31
+ label: 'Vercel DNS',
32
+ hint: 'auto-issue & renew via API token (env TAPFLOW_VERCEL_TOKEN)',
33
+ envVars: ['TAPFLOW_VERCEL_TOKEN'],
34
+ fromEnv: vercelDnsFromEnv,
35
+ });
36
+ //# sourceMappingURL=dnsRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dnsRegistry.js","sourceRoot":"","sources":["../../../src/lib/cert/dnsRegistry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAiBzD,MAAM,mBAAmB;IACN,OAAO,GAAG,IAAI,GAAG,EAA4B,CAAA;IAC9D,QAAQ,CAAC,KAAuB;QAC9B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACrC,CAAC;IACD,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;IACD,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;IACD,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACnC,CAAC;IACD,KAAK;QACH,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IACjC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,mBAAmB,EAAE,CAAA;AAErD,YAAY,CAAC,QAAQ,CAAC;IACpB,IAAI,EAAE,YAAY;IAClB,KAAK,EAAE,gBAAgB;IACvB,IAAI,EAAE,iEAAiE;IACvE,OAAO,EAAE,CAAC,0BAA0B,CAAC;IACrC,OAAO,EAAE,oBAAoB;CAC9B,CAAC,CAAA;AACF,YAAY,CAAC,QAAQ,CAAC;IACpB,IAAI,EAAE,QAAQ;IACd,KAAK,EAAE,YAAY;IACnB,IAAI,EAAE,6DAA6D;IACnE,OAAO,EAAE,CAAC,sBAAsB,CAAC;IACjC,OAAO,EAAE,gBAAgB;CAC1B,CAAC,CAAA"}
@@ -0,0 +1,23 @@
1
+ export type { CertProvider, CertMaterial, CertStrategy } from './CertProvider.js';
2
+ export type { DnsProvider } from './DnsProvider.js';
3
+ export { CloudflareDnsProvider, cloudflareDnsFromEnv } from './CloudflareDnsProvider.js';
4
+ export type { CloudflareDnsProviderOptions, FetchLike } from './CloudflareDnsProvider.js';
5
+ export { VercelDnsProvider, vercelDnsFromEnv } from './VercelDnsProvider.js';
6
+ export type { VercelDnsProviderOptions } from './VercelDnsProvider.js';
7
+ export { AcmeCertProvider, InMemoryCertStore } from './AcmeCertProvider.js';
8
+ export type { AcmeIssuer, IssuedCert, CertStore, AcmeCertProviderOptions } from './AcmeCertProvider.js';
9
+ export { AcmeClientIssuer } from './AcmeClientIssuer.js';
10
+ export type { AcmeClientIssuerOptions } from './AcmeClientIssuer.js';
11
+ export { ImportCertProvider } from './ImportCertProvider.js';
12
+ export type { ImportCertProviderOptions } from './ImportCertProvider.js';
13
+ export { parseCertNotAfter } from './parseCert.js';
14
+ export { DiskCertStore } from './DiskCertStore.js';
15
+ export { startCertRenewal, renewalTick } from './renewal.js';
16
+ export type { CertRenewalOptions } from './renewal.js';
17
+ export { startAddressPublisher, detectLanIPv4 } from './addressPublisher.js';
18
+ export type { AddressPublisherTls, AddressPublisherOptions } from './addressPublisher.js';
19
+ export { dnsProviders } from './dnsRegistry.js';
20
+ export type { DnsProviderEntry } from './dnsRegistry.js';
21
+ export { createCertProvider } from './createCertProvider.js';
22
+ export type { TlsConfig, CreateCertProviderDeps } from './createCertProvider.js';
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACjF,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AACxF,YAAY,EAAE,4BAA4B,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AACzF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC5E,YAAY,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AACtE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC3E,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACvG,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,YAAY,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,YAAY,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAA;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC5D,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAC5E,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,YAAY,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA"}
@@ -0,0 +1,12 @@
1
+ export { CloudflareDnsProvider, cloudflareDnsFromEnv } from './CloudflareDnsProvider.js';
2
+ export { VercelDnsProvider, vercelDnsFromEnv } from './VercelDnsProvider.js';
3
+ export { AcmeCertProvider, InMemoryCertStore } from './AcmeCertProvider.js';
4
+ export { AcmeClientIssuer } from './AcmeClientIssuer.js';
5
+ export { ImportCertProvider } from './ImportCertProvider.js';
6
+ export { parseCertNotAfter } from './parseCert.js';
7
+ export { DiskCertStore } from './DiskCertStore.js';
8
+ export { startCertRenewal, renewalTick } from './renewal.js';
9
+ export { startAddressPublisher, detectLanIPv4 } from './addressPublisher.js';
10
+ export { dnsProviders } from './dnsRegistry.js';
11
+ export { createCertProvider } from './createCertProvider.js';
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/cert/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAExF,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAE5E,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAE3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAE5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE5D,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAE5E,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA"}
@@ -0,0 +1,3 @@
1
+ /** PEM 인증서에서 notAfter(만료 시각)를 파싱한다. 잘못된 PEM이면 throw. */
2
+ export declare function parseCertNotAfter(certPem: string): Date;
3
+ //# sourceMappingURL=parseCert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseCert.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/parseCert.ts"],"names":[],"mappings":"AAEA,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEvD"}
@@ -0,0 +1,6 @@
1
+ import { X509Certificate } from 'crypto';
2
+ /** PEM 인증서에서 notAfter(만료 시각)를 파싱한다. 잘못된 PEM이면 throw. */
3
+ export function parseCertNotAfter(certPem) {
4
+ return new Date(new X509Certificate(certPem).validTo);
5
+ }
6
+ //# sourceMappingURL=parseCert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseCert.js","sourceRoot":"","sources":["../../../src/lib/cert/parseCert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAA;AAExC,wDAAwD;AACxD,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,OAAO,IAAI,IAAI,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAA;AACvD,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { CertProvider, CertMaterial } from './CertProvider.js';
2
+ export interface CertRenewalOptions {
3
+ intervalMs?: number;
4
+ /** 갱신이 일어났을 때(새 자료) 호출 — 예: relay TLS 컨텍스트 핫스왑. */
5
+ onRenew?: (material: CertMaterial) => void;
6
+ /** 갱신 중 오류. 미지정 시 경고 로그. 절대 전파하지 않는다. */
7
+ onError?: (err: unknown) => void;
8
+ }
9
+ /** 1회 점검 — renewIfNeeded 호출, 갱신 시 onRenew, 오류는 삼키고 onError/로그. */
10
+ export declare function renewalTick(provider: CertProvider, opts?: CertRenewalOptions): Promise<void>;
11
+ /** 주기 갱신 타이머를 시작하고 정지 함수를 반환한다. */
12
+ export declare function startCertRenewal(provider: CertProvider, opts?: CertRenewalOptions): () => void;
13
+ //# sourceMappingURL=renewal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renewal.d.ts","sourceRoot":"","sources":["../../../src/lib/cert/renewal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAOnE,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,mDAAmD;IACnD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAA;IAC1C,yCAAyC;IACzC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CACjC;AAED,kEAAkE;AAClE,wBAAsB,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQtG;AAED,mCAAmC;AACnC,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,GAAE,kBAAuB,GAAG,MAAM,IAAI,CAOlG"}
@@ -0,0 +1,28 @@
1
+ import { createLogger } from '@tapflowio/agent-core';
2
+ const logger = createLogger('relay:cert');
3
+ // ACME 클라이언트 관례대로 하루 두 번 점검. 실제 발급은 만료 30일 이내일 때만 일어난다.
4
+ const DEFAULT_INTERVAL_MS = 12 * 60 * 60 * 1000;
5
+ /** 1회 점검 — renewIfNeeded 호출, 갱신 시 onRenew, 오류는 삼키고 onError/로그. */
6
+ export async function renewalTick(provider, opts = {}) {
7
+ try {
8
+ const renewed = await provider.renewIfNeeded();
9
+ if (renewed)
10
+ opts.onRenew?.(renewed);
11
+ }
12
+ catch (err) {
13
+ if (opts.onError)
14
+ opts.onError(err);
15
+ else
16
+ logger.warn(`cert renewal failed: ${String(err)}`);
17
+ }
18
+ }
19
+ /** 주기 갱신 타이머를 시작하고 정지 함수를 반환한다. */
20
+ export function startCertRenewal(provider, opts = {}) {
21
+ const interval = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
22
+ const timer = setInterval(() => {
23
+ void renewalTick(provider, opts);
24
+ }, interval);
25
+ timer.unref?.();
26
+ return () => clearInterval(timer);
27
+ }
28
+ //# sourceMappingURL=renewal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renewal.js","sourceRoot":"","sources":["../../../src/lib/cert/renewal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAGpD,MAAM,MAAM,GAAG,YAAY,CAAC,YAAY,CAAC,CAAA;AAEzC,wDAAwD;AACxD,MAAM,mBAAmB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAU/C,kEAAkE;AAClE,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAsB,EAAE,OAA2B,EAAE;IACrF,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,aAAa,EAAE,CAAA;QAC9C,IAAI,OAAO;YAAE,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAA;IACtC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;;YAC9B,MAAM,CAAC,IAAI,CAAC,wBAAwB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACzD,CAAC;AACH,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,gBAAgB,CAAC,QAAsB,EAAE,OAA2B,EAAE;IACpF,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,IAAI,mBAAmB,CAAA;IACvD,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,KAAK,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAClC,CAAC,EAAE,QAAQ,CAAC,CAAA;IACZ,KAAK,CAAC,KAAK,EAAE,EAAE,CAAA;IACf,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;AACnC,CAAC"}