@tapflowio/relay 0.8.2 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/RelayServer.d.ts +10 -0
- package/dist/RelayServer.d.ts.map +1 -1
- package/dist/RelayServer.js +53 -5
- package/dist/RelayServer.js.map +1 -1
- package/dist/api/builds.d.ts.map +1 -1
- package/dist/api/builds.js +14 -13
- package/dist/api/builds.js.map +1 -1
- package/dist/api/comments.d.ts.map +1 -1
- package/dist/api/comments.js +23 -10
- package/dist/api/comments.js.map +1 -1
- package/dist/api/team.d.ts.map +1 -1
- package/dist/api/team.js +4 -2
- package/dist/api/team.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/cert/AcmeCertProvider.d.ts +55 -0
- package/dist/lib/cert/AcmeCertProvider.d.ts.map +1 -0
- package/dist/lib/cert/AcmeCertProvider.js +62 -0
- package/dist/lib/cert/AcmeCertProvider.js.map +1 -0
- package/dist/lib/cert/AcmeClientIssuer.d.ts +23 -0
- package/dist/lib/cert/AcmeClientIssuer.d.ts.map +1 -0
- package/dist/lib/cert/AcmeClientIssuer.js +54 -0
- package/dist/lib/cert/AcmeClientIssuer.js.map +1 -0
- package/dist/lib/cert/CertProvider.d.ts +30 -0
- package/dist/lib/cert/CertProvider.d.ts.map +1 -0
- package/dist/lib/cert/CertProvider.js +4 -0
- package/dist/lib/cert/CertProvider.js.map +1 -0
- package/dist/lib/cert/CloudflareDnsProvider.d.ts +40 -0
- package/dist/lib/cert/CloudflareDnsProvider.d.ts.map +1 -0
- package/dist/lib/cert/CloudflareDnsProvider.js +93 -0
- package/dist/lib/cert/CloudflareDnsProvider.js.map +1 -0
- package/dist/lib/cert/DiskCertStore.d.ts +10 -0
- package/dist/lib/cert/DiskCertStore.d.ts.map +1 -0
- package/dist/lib/cert/DiskCertStore.js +35 -0
- package/dist/lib/cert/DiskCertStore.js.map +1 -0
- package/dist/lib/cert/DnsProvider.d.ts +7 -0
- package/dist/lib/cert/DnsProvider.d.ts.map +1 -0
- package/dist/lib/cert/DnsProvider.js +2 -0
- package/dist/lib/cert/DnsProvider.js.map +1 -0
- package/dist/lib/cert/ImportCertProvider.d.ts +20 -0
- package/dist/lib/cert/ImportCertProvider.d.ts.map +1 -0
- package/dist/lib/cert/ImportCertProvider.js +24 -0
- package/dist/lib/cert/ImportCertProvider.js.map +1 -0
- package/dist/lib/cert/VercelDnsProvider.d.ts +38 -0
- package/dist/lib/cert/VercelDnsProvider.d.ts.map +1 -0
- package/dist/lib/cert/VercelDnsProvider.js +146 -0
- package/dist/lib/cert/VercelDnsProvider.js.map +1 -0
- package/dist/lib/cert/accountKey.d.ts +2 -0
- package/dist/lib/cert/accountKey.d.ts.map +1 -0
- package/dist/lib/cert/accountKey.js +23 -0
- package/dist/lib/cert/accountKey.js.map +1 -0
- package/dist/lib/cert/addressPublisher.d.ts +20 -0
- package/dist/lib/cert/addressPublisher.d.ts.map +1 -0
- package/dist/lib/cert/addressPublisher.js +103 -0
- package/dist/lib/cert/addressPublisher.js.map +1 -0
- package/dist/lib/cert/createCertProvider.d.ts +20 -0
- package/dist/lib/cert/createCertProvider.d.ts.map +1 -0
- package/dist/lib/cert/createCertProvider.js +25 -0
- package/dist/lib/cert/createCertProvider.js.map +1 -0
- package/dist/lib/cert/dnsRegistry.d.ts +24 -0
- package/dist/lib/cert/dnsRegistry.d.ts.map +1 -0
- package/dist/lib/cert/dnsRegistry.js +36 -0
- package/dist/lib/cert/dnsRegistry.js.map +1 -0
- package/dist/lib/cert/index.d.ts +23 -0
- package/dist/lib/cert/index.d.ts.map +1 -0
- package/dist/lib/cert/index.js +12 -0
- package/dist/lib/cert/index.js.map +1 -0
- package/dist/lib/cert/parseCert.d.ts +3 -0
- package/dist/lib/cert/parseCert.d.ts.map +1 -0
- package/dist/lib/cert/parseCert.js +6 -0
- package/dist/lib/cert/parseCert.js.map +1 -0
- package/dist/lib/cert/renewal.d.ts +13 -0
- package/dist/lib/cert/renewal.d.ts.map +1 -0
- package/dist/lib/cert/renewal.js +28 -0
- package/dist/lib/cert/renewal.js.map +1 -0
- package/dist/lib/config.d.ts +23 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +51 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/cors.d.ts +2 -0
- package/dist/lib/cors.d.ts.map +1 -0
- package/dist/lib/cors.js +18 -0
- package/dist/lib/cors.js.map +1 -0
- package/dist/lib/csrf.d.ts +3 -0
- package/dist/lib/csrf.d.ts.map +1 -0
- package/dist/lib/csrf.js +42 -0
- package/dist/lib/csrf.js.map +1 -0
- package/dist/lib/loadEnvFile.d.ts +2 -0
- package/dist/lib/loadEnvFile.d.ts.map +1 -0
- package/dist/lib/loadEnvFile.js +19 -0
- package/dist/lib/loadEnvFile.js.map +1 -0
- package/dist/lib/proxyConfig.d.ts +6 -0
- package/dist/lib/proxyConfig.d.ts.map +1 -0
- package/dist/lib/proxyConfig.js +27 -0
- package/dist/lib/proxyConfig.js.map +1 -0
- package/dist/lib/publicUrl.d.ts +3 -0
- package/dist/lib/publicUrl.d.ts.map +1 -0
- package/dist/lib/publicUrl.js +17 -0
- package/dist/lib/publicUrl.js.map +1 -0
- package/dist/lib/uploads.d.ts +2 -0
- package/dist/lib/uploads.d.ts.map +1 -0
- package/dist/lib/uploads.js +14 -0
- package/dist/lib/uploads.js.map +1 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +13 -1
- package/dist/router.js.map +1 -1
- package/dist/server.js +43 -6
- package/dist/server.js.map +1 -1
- package/package.json +9 -8
- package/public/assets/AppCenter-CH3MXel8.js +16 -0
- package/public/assets/AppCenter-CH3MXel8.js.br +0 -0
- package/public/assets/Default-CK_yhJO6.js +1 -0
- package/public/assets/Default-CK_yhJO6.js.br +0 -0
- package/public/assets/Invite-9g1gI701.js +1 -0
- package/public/assets/Invite-9g1gI701.js.br +0 -0
- package/public/assets/MacResources-41d3_9X2.js +5 -0
- package/public/assets/MacResources-41d3_9X2.js.br +0 -0
- package/public/assets/NotFound-DsMP9eZN.js +1 -0
- package/public/assets/NotFound-DsMP9eZN.js.br +2 -0
- package/public/assets/QASession-B02bILlB.js +130 -0
- package/public/assets/QASession-B02bILlB.js.br +0 -0
- package/public/assets/ResetPassword-DsXsNFTA.js +1 -0
- package/public/assets/ResetPassword-DsXsNFTA.js.br +0 -0
- package/public/assets/Setup-CouJNq4v.js +1 -0
- package/public/assets/Setup-CouJNq4v.js.br +0 -0
- package/public/assets/Team-DUG0YTtb.js +6 -0
- package/public/assets/Team-DUG0YTtb.js.br +0 -0
- package/public/assets/Tokens-BWBEl4ax.js +6 -0
- package/public/assets/Tokens-BWBEl4ax.js.br +0 -0
- package/public/assets/alert-dialog-qlHDtr4B.js +1 -0
- package/public/assets/alert-dialog-qlHDtr4B.js.br +0 -0
- package/public/assets/badge-Cx2-XXcg.js +1 -0
- package/public/assets/badge-Cx2-XXcg.js.br +0 -0
- package/public/assets/build-format-BvNznvYq.js +11 -0
- package/public/assets/build-format-BvNznvYq.js.br +0 -0
- package/public/assets/dialog-DzNUT3NF.js +11 -0
- package/public/assets/dialog-DzNUT3NF.js.br +0 -0
- package/public/assets/index-BI7E3MD-.css +1 -0
- package/public/assets/index-BI7E3MD-.css.br +0 -0
- package/public/assets/index-Dfs85Uiw.js +274 -0
- package/public/assets/index-Dfs85Uiw.js.br +0 -0
- package/public/assets/inter-latin-ext-standard-normal-DpK-iCPk.woff2 +0 -0
- package/public/assets/inter-latin-standard-normal-BwkfbSeq.woff2 +0 -0
- package/public/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/public/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/public/assets/pencil-CrO7Me4O.js +6 -0
- package/public/assets/pencil-CrO7Me4O.js.br +0 -0
- package/public/assets/plus-DnAOz8DL.js +6 -0
- package/public/assets/plus-DnAOz8DL.js.br +0 -0
- package/public/assets/table-BXnp_tRt.js +1 -0
- package/public/assets/table-BXnp_tRt.js.br +0 -0
- package/public/assets/tabs-BTnhanO2.js +1 -0
- package/public/assets/tabs-BTnhanO2.js.br +0 -0
- package/public/assets/tinyh264.worker-DiJ50Hai.js.br +0 -0
- package/public/favicon.svg.br +0 -0
- package/public/index.html +2 -2
- package/public/index.html.br +0 -0
- package/public/logo-dark.svg.br +0 -0
- package/public/logo.svg.br +0 -0
- package/public/assets/index-Bhoumejm.js +0 -520
- package/public/assets/index-DX5Pf9T6.css +0 -1
- package/public/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
- package/public/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
- package/public/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
- package/public/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
- package/public/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
- package/public/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
- package/public/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
- package/public/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
- package/public/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
- package/public/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
- package/public/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
- package/public/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
- package/public/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
- package/public/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
- package/public/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
- package/public/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
- package/public/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
- package/public/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
- package/public/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
- package/public/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
- package/public/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
- package/public/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
- package/public/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
- package/public/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
- package/public/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/public/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
- package/public/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
- package/public/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/public/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
- package/public/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
- package/public/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
- package/public/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
- package/public/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
- package/public/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
- package/public/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
- package/public/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
- package/public/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
- package/public/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
- package/public/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
- package/public/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
- package/public/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
- package/public/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
- package/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|