@tapflowio/relay 0.8.1 → 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 (226) hide show
  1. package/dist/RelayServer.d.ts +13 -0
  2. package/dist/RelayServer.d.ts.map +1 -1
  3. package/dist/RelayServer.js +80 -9
  4. package/dist/RelayServer.js.map +1 -1
  5. package/dist/api/auth.d.ts +3 -2
  6. package/dist/api/auth.d.ts.map +1 -1
  7. package/dist/api/auth.js +37 -3
  8. package/dist/api/auth.js.map +1 -1
  9. package/dist/api/builds.d.ts.map +1 -1
  10. package/dist/api/builds.js +14 -13
  11. package/dist/api/builds.js.map +1 -1
  12. package/dist/api/comments.d.ts.map +1 -1
  13. package/dist/api/comments.js +23 -10
  14. package/dist/api/comments.js.map +1 -1
  15. package/dist/api/team.d.ts.map +1 -1
  16. package/dist/api/team.js +4 -2
  17. package/dist/api/team.js.map +1 -1
  18. package/dist/index.d.ts +7 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/lib/cert/AcmeCertProvider.d.ts +55 -0
  23. package/dist/lib/cert/AcmeCertProvider.d.ts.map +1 -0
  24. package/dist/lib/cert/AcmeCertProvider.js +62 -0
  25. package/dist/lib/cert/AcmeCertProvider.js.map +1 -0
  26. package/dist/lib/cert/AcmeClientIssuer.d.ts +23 -0
  27. package/dist/lib/cert/AcmeClientIssuer.d.ts.map +1 -0
  28. package/dist/lib/cert/AcmeClientIssuer.js +54 -0
  29. package/dist/lib/cert/AcmeClientIssuer.js.map +1 -0
  30. package/dist/lib/cert/CertProvider.d.ts +30 -0
  31. package/dist/lib/cert/CertProvider.d.ts.map +1 -0
  32. package/dist/lib/cert/CertProvider.js +4 -0
  33. package/dist/lib/cert/CertProvider.js.map +1 -0
  34. package/dist/lib/cert/CloudflareDnsProvider.d.ts +40 -0
  35. package/dist/lib/cert/CloudflareDnsProvider.d.ts.map +1 -0
  36. package/dist/lib/cert/CloudflareDnsProvider.js +93 -0
  37. package/dist/lib/cert/CloudflareDnsProvider.js.map +1 -0
  38. package/dist/lib/cert/DiskCertStore.d.ts +10 -0
  39. package/dist/lib/cert/DiskCertStore.d.ts.map +1 -0
  40. package/dist/lib/cert/DiskCertStore.js +35 -0
  41. package/dist/lib/cert/DiskCertStore.js.map +1 -0
  42. package/dist/lib/cert/DnsProvider.d.ts +7 -0
  43. package/dist/lib/cert/DnsProvider.d.ts.map +1 -0
  44. package/dist/lib/cert/DnsProvider.js +2 -0
  45. package/dist/lib/cert/DnsProvider.js.map +1 -0
  46. package/dist/lib/cert/ImportCertProvider.d.ts +20 -0
  47. package/dist/lib/cert/ImportCertProvider.d.ts.map +1 -0
  48. package/dist/lib/cert/ImportCertProvider.js +24 -0
  49. package/dist/lib/cert/ImportCertProvider.js.map +1 -0
  50. package/dist/lib/cert/VercelDnsProvider.d.ts +38 -0
  51. package/dist/lib/cert/VercelDnsProvider.d.ts.map +1 -0
  52. package/dist/lib/cert/VercelDnsProvider.js +146 -0
  53. package/dist/lib/cert/VercelDnsProvider.js.map +1 -0
  54. package/dist/lib/cert/accountKey.d.ts +2 -0
  55. package/dist/lib/cert/accountKey.d.ts.map +1 -0
  56. package/dist/lib/cert/accountKey.js +23 -0
  57. package/dist/lib/cert/accountKey.js.map +1 -0
  58. package/dist/lib/cert/addressPublisher.d.ts +20 -0
  59. package/dist/lib/cert/addressPublisher.d.ts.map +1 -0
  60. package/dist/lib/cert/addressPublisher.js +103 -0
  61. package/dist/lib/cert/addressPublisher.js.map +1 -0
  62. package/dist/lib/cert/createCertProvider.d.ts +20 -0
  63. package/dist/lib/cert/createCertProvider.d.ts.map +1 -0
  64. package/dist/lib/cert/createCertProvider.js +25 -0
  65. package/dist/lib/cert/createCertProvider.js.map +1 -0
  66. package/dist/lib/cert/dnsRegistry.d.ts +24 -0
  67. package/dist/lib/cert/dnsRegistry.d.ts.map +1 -0
  68. package/dist/lib/cert/dnsRegistry.js +36 -0
  69. package/dist/lib/cert/dnsRegistry.js.map +1 -0
  70. package/dist/lib/cert/index.d.ts +23 -0
  71. package/dist/lib/cert/index.d.ts.map +1 -0
  72. package/dist/lib/cert/index.js +12 -0
  73. package/dist/lib/cert/index.js.map +1 -0
  74. package/dist/lib/cert/parseCert.d.ts +3 -0
  75. package/dist/lib/cert/parseCert.d.ts.map +1 -0
  76. package/dist/lib/cert/parseCert.js +6 -0
  77. package/dist/lib/cert/parseCert.js.map +1 -0
  78. package/dist/lib/cert/renewal.d.ts +13 -0
  79. package/dist/lib/cert/renewal.d.ts.map +1 -0
  80. package/dist/lib/cert/renewal.js +28 -0
  81. package/dist/lib/cert/renewal.js.map +1 -0
  82. package/dist/lib/clientAddress.d.ts +12 -0
  83. package/dist/lib/clientAddress.d.ts.map +1 -0
  84. package/dist/lib/clientAddress.js +46 -0
  85. package/dist/lib/clientAddress.js.map +1 -0
  86. package/dist/lib/config.d.ts +25 -0
  87. package/dist/lib/config.d.ts.map +1 -1
  88. package/dist/lib/config.js +69 -3
  89. package/dist/lib/config.js.map +1 -1
  90. package/dist/lib/cors.d.ts +2 -0
  91. package/dist/lib/cors.d.ts.map +1 -0
  92. package/dist/lib/cors.js +18 -0
  93. package/dist/lib/cors.js.map +1 -0
  94. package/dist/lib/csrf.d.ts +3 -0
  95. package/dist/lib/csrf.d.ts.map +1 -0
  96. package/dist/lib/csrf.js +42 -0
  97. package/dist/lib/csrf.js.map +1 -0
  98. package/dist/lib/loadEnvFile.d.ts +2 -0
  99. package/dist/lib/loadEnvFile.d.ts.map +1 -0
  100. package/dist/lib/loadEnvFile.js +17 -0
  101. package/dist/lib/loadEnvFile.js.map +1 -0
  102. package/dist/lib/proxyConfig.d.ts +6 -0
  103. package/dist/lib/proxyConfig.d.ts.map +1 -0
  104. package/dist/lib/proxyConfig.js +27 -0
  105. package/dist/lib/proxyConfig.js.map +1 -0
  106. package/dist/lib/publicUrl.d.ts +3 -0
  107. package/dist/lib/publicUrl.d.ts.map +1 -0
  108. package/dist/lib/publicUrl.js +17 -0
  109. package/dist/lib/publicUrl.js.map +1 -0
  110. package/dist/lib/uploads.d.ts +2 -0
  111. package/dist/lib/uploads.d.ts.map +1 -0
  112. package/dist/lib/uploads.js +14 -0
  113. package/dist/lib/uploads.js.map +1 -0
  114. package/dist/middleware/rateLimit.d.ts +18 -0
  115. package/dist/middleware/rateLimit.d.ts.map +1 -0
  116. package/dist/middleware/rateLimit.js +55 -0
  117. package/dist/middleware/rateLimit.js.map +1 -0
  118. package/dist/router.d.ts.map +1 -1
  119. package/dist/router.js +13 -1
  120. package/dist/router.js.map +1 -1
  121. package/dist/server.js +44 -5
  122. package/dist/server.js.map +1 -1
  123. package/package.json +6 -5
  124. package/public/assets/AppCenter-CaY73Tvf.js +16 -0
  125. package/public/assets/AppCenter-CaY73Tvf.js.br +0 -0
  126. package/public/assets/Default-DQiKj0NT.js +1 -0
  127. package/public/assets/Default-DQiKj0NT.js.br +0 -0
  128. package/public/assets/Invite-B4MMcQtw.js +1 -0
  129. package/public/assets/Invite-B4MMcQtw.js.br +0 -0
  130. package/public/assets/MacResources-BfZAJ5BP.js +5 -0
  131. package/public/assets/MacResources-BfZAJ5BP.js.br +0 -0
  132. package/public/assets/NotFound-D85RKzwv.js +1 -0
  133. package/public/assets/NotFound-D85RKzwv.js.br +0 -0
  134. package/public/assets/QASession-B9FSUtZo.js +130 -0
  135. package/public/assets/QASession-B9FSUtZo.js.br +0 -0
  136. package/public/assets/ResetPassword-Degmb6sl.js +1 -0
  137. package/public/assets/ResetPassword-Degmb6sl.js.br +0 -0
  138. package/public/assets/Setup-DKOFxDo2.js +1 -0
  139. package/public/assets/Setup-DKOFxDo2.js.br +0 -0
  140. package/public/assets/Team-Bgspan2B.js +6 -0
  141. package/public/assets/Team-Bgspan2B.js.br +0 -0
  142. package/public/assets/Tokens-C3u48FUG.js +6 -0
  143. package/public/assets/Tokens-C3u48FUG.js.br +0 -0
  144. package/public/assets/alert-dialog-Ci_cWoVo.js +7 -0
  145. package/public/assets/alert-dialog-Ci_cWoVo.js.br +0 -0
  146. package/public/assets/badge-Cktrrdvf.js +1 -0
  147. package/public/assets/badge-Cktrrdvf.js.br +0 -0
  148. package/public/assets/build-format-Blzidb1D.js +11 -0
  149. package/public/assets/build-format-Blzidb1D.js.br +0 -0
  150. package/public/assets/dialog-BuZjQWBQ.js +11 -0
  151. package/public/assets/dialog-BuZjQWBQ.js.br +0 -0
  152. package/public/assets/index-BI7E3MD-.css +1 -0
  153. package/public/assets/index-BI7E3MD-.css.br +0 -0
  154. package/public/assets/index-ClAB0eKw.js +258 -0
  155. package/public/assets/index-ClAB0eKw.js.br +0 -0
  156. package/public/assets/inter-latin-ext-standard-normal-DpK-iCPk.woff2 +0 -0
  157. package/public/assets/inter-latin-standard-normal-BwkfbSeq.woff2 +0 -0
  158. package/public/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  159. package/public/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  160. package/public/assets/pencil-BoxokH2R.js +6 -0
  161. package/public/assets/pencil-BoxokH2R.js.br +0 -0
  162. package/public/assets/plus-C0Y4Yk9P.js +6 -0
  163. package/public/assets/plus-C0Y4Yk9P.js.br +0 -0
  164. package/public/assets/table-NBRnBazr.js +1 -0
  165. package/public/assets/table-NBRnBazr.js.br +0 -0
  166. package/public/assets/tabs-CB9a8dpY.js +1 -0
  167. package/public/assets/tabs-CB9a8dpY.js.br +0 -0
  168. package/public/assets/tinyh264.worker-DiJ50Hai.js.br +0 -0
  169. package/public/favicon.svg.br +0 -0
  170. package/public/index.html +2 -2
  171. package/public/index.html.br +0 -0
  172. package/public/logo-dark.svg.br +0 -0
  173. package/public/logo.svg.br +0 -0
  174. package/public/assets/index-Bhoumejm.js +0 -520
  175. package/public/assets/index-DX5Pf9T6.css +0 -1
  176. package/public/assets/inter-cyrillic-400-normal-HOLc17fK.woff +0 -0
  177. package/public/assets/inter-cyrillic-400-normal-obahsSVq.woff2 +0 -0
  178. package/public/assets/inter-cyrillic-500-normal-BasfLYem.woff2 +0 -0
  179. package/public/assets/inter-cyrillic-500-normal-CxZf_p3X.woff +0 -0
  180. package/public/assets/inter-cyrillic-600-normal-4D_pXhcN.woff +0 -0
  181. package/public/assets/inter-cyrillic-600-normal-CWCymEST.woff2 +0 -0
  182. package/public/assets/inter-cyrillic-ext-400-normal-BQZuk6qB.woff2 +0 -0
  183. package/public/assets/inter-cyrillic-ext-400-normal-DQukG94-.woff +0 -0
  184. package/public/assets/inter-cyrillic-ext-500-normal-B0yAr1jD.woff2 +0 -0
  185. package/public/assets/inter-cyrillic-ext-500-normal-BmqWE9Dz.woff +0 -0
  186. package/public/assets/inter-cyrillic-ext-600-normal-Bcila6Z-.woff +0 -0
  187. package/public/assets/inter-cyrillic-ext-600-normal-Dfes3d0z.woff2 +0 -0
  188. package/public/assets/inter-greek-400-normal-B4URO6DV.woff2 +0 -0
  189. package/public/assets/inter-greek-400-normal-q2sYcFCs.woff +0 -0
  190. package/public/assets/inter-greek-500-normal-BIZE56-Y.woff2 +0 -0
  191. package/public/assets/inter-greek-500-normal-Xzm54t5V.woff +0 -0
  192. package/public/assets/inter-greek-600-normal-BZpKdvQh.woff +0 -0
  193. package/public/assets/inter-greek-600-normal-plRanbMR.woff2 +0 -0
  194. package/public/assets/inter-greek-ext-400-normal-DGGRlc-M.woff2 +0 -0
  195. package/public/assets/inter-greek-ext-400-normal-KugGGMne.woff +0 -0
  196. package/public/assets/inter-greek-ext-500-normal-2j5mBUwD.woff +0 -0
  197. package/public/assets/inter-greek-ext-500-normal-C4iEst2y.woff2 +0 -0
  198. package/public/assets/inter-greek-ext-600-normal-B8X0CLgF.woff +0 -0
  199. package/public/assets/inter-greek-ext-600-normal-DRtmH8MT.woff2 +0 -0
  200. package/public/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
  201. package/public/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
  202. package/public/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  203. package/public/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
  204. package/public/assets/inter-latin-600-normal-CiBQ2DWP.woff +0 -0
  205. package/public/assets/inter-latin-600-normal-LgqL8muc.woff2 +0 -0
  206. package/public/assets/inter-latin-ext-400-normal-77YHD8bZ.woff +0 -0
  207. package/public/assets/inter-latin-ext-400-normal-C1nco2VV.woff2 +0 -0
  208. package/public/assets/inter-latin-ext-500-normal-BxGbmqWO.woff +0 -0
  209. package/public/assets/inter-latin-ext-500-normal-CV4jyFjo.woff2 +0 -0
  210. package/public/assets/inter-latin-ext-600-normal-CIVaiw4L.woff +0 -0
  211. package/public/assets/inter-latin-ext-600-normal-D2bJ5OIk.woff2 +0 -0
  212. package/public/assets/inter-vietnamese-400-normal-Bbgyi5SW.woff +0 -0
  213. package/public/assets/inter-vietnamese-400-normal-DMkecbls.woff2 +0 -0
  214. package/public/assets/inter-vietnamese-500-normal-DOriooB6.woff2 +0 -0
  215. package/public/assets/inter-vietnamese-500-normal-mJboJaSs.woff +0 -0
  216. package/public/assets/inter-vietnamese-600-normal-BuLX-rYi.woff +0 -0
  217. package/public/assets/inter-vietnamese-600-normal-Cc8MFFhd.woff2 +0 -0
  218. package/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  219. package/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  220. package/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  221. package/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  222. package/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  223. package/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  224. package/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  225. package/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  226. package/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadEnvFile.d.ts","sourceRoot":"","sources":["../../src/lib/loadEnvFile.ts"],"names":[],"mappings":"AAIA,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU7D"}
@@ -0,0 +1,17 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ // #287 — gitignore된 <dataDir>/.env 에서 DNS/ACME 자격 증명을 로드(loadEnvFile 은 기존 값을 안 덮으므로 ambient 우선).
4
+ export function loadDataDirEnv(dataDir) {
5
+ const envPath = path.join(dataDir, '.env');
6
+ if (!fs.existsSync(envPath))
7
+ return null;
8
+ try {
9
+ process.loadEnvFile(envPath);
10
+ return envPath;
11
+ }
12
+ catch {
13
+ // 손상된 파일은 무시 — 자격 증명이 없으면 cert 발급 시점에 명확히 실패한다.
14
+ return null;
15
+ }
16
+ }
17
+ //# sourceMappingURL=loadEnvFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadEnvFile.js","sourceRoot":"","sources":["../../src/lib/loadEnvFile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB,iGAAiG;AACjG,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC1C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAA;IACxC,IAAI,CAAC;QACH,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;QAC5B,OAAO,OAAO,CAAA;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;QAChD,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { TapflowConfig } from './config.js';
2
+ type ProxyCfg = Pick<TapflowConfig, 'tunnel' | 'relay' | 'local'>;
3
+ export declare function buildCorsOrigins(cfg: ProxyCfg, port: number): string[];
4
+ export declare function proxyWithoutPublicUrlWarning(cfg: ProxyCfg): string | null;
5
+ export {};
6
+ //# sourceMappingURL=proxyConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxyConfig.d.ts","sourceRoot":"","sources":["../../src/lib/proxyConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGhD,KAAK,QAAQ,GAAG,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC,CAAA;AAGjE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAStE;AAGD,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAQzE"}
@@ -0,0 +1,27 @@
1
+ import { buildInviteBaseUrl } from './publicUrl.js';
2
+ // Allow cross-origin PAT use only from the configured public origin + loopback (LAN is same-origin).
3
+ export function buildCorsOrigins(cfg, port) {
4
+ // Skip buildInviteBaseUrl's localhost fallback — it would allowlist a stale loopback at config.local.port under a different --port.
5
+ const configuredOrigin = (() => {
6
+ if (!cfg.tunnel?.publicUrl && !cfg.relay.url)
7
+ return null;
8
+ try {
9
+ return new URL(buildInviteBaseUrl(cfg)).origin;
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ })();
15
+ const origins = [configuredOrigin, `http://localhost:${port}`, `http://127.0.0.1:${port}`]
16
+ .filter((o) => o !== null);
17
+ return [...new Set(origins)];
18
+ }
19
+ // A proxied/tunneled relay needs a public URL, or the CSRF/CORS allowlist stays loopback-only and blocks proxied POSTs.
20
+ export function proxyWithoutPublicUrlWarning(cfg) {
21
+ if (cfg.local.trustedProxies.length > 0 && !cfg.tunnel?.publicUrl && !cfg.relay.url) {
22
+ return ('TAPFLOW_TRUSTED_PROXIES is set but no public URL (tunnel.publicUrl / relay.url) is configured. ' +
23
+ 'Cross-origin dashboard requests may be blocked by the CSRF guard — set the public URL for proxied deployments.');
24
+ }
25
+ return null;
26
+ }
27
+ //# sourceMappingURL=proxyConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxyConfig.js","sourceRoot":"","sources":["../../src/lib/proxyConfig.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAInD,qGAAqG;AACrG,MAAM,UAAU,gBAAgB,CAAC,GAAa,EAAE,IAAY;IAC1D,oIAAoI;IACpI,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE;QAC7B,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG;YAAE,OAAO,IAAI,CAAA;QACzD,IAAI,CAAC;YAAC,OAAO,IAAI,GAAG,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAA;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,IAAI,CAAA;QAAC,CAAC;IAC9E,CAAC,CAAC,EAAE,CAAA;IACJ,MAAM,OAAO,GAAG,CAAC,gBAAgB,EAAE,oBAAoB,IAAI,EAAE,EAAE,oBAAoB,IAAI,EAAE,CAAC;SACvF,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAA;IACzC,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAA;AAC9B,CAAC;AAED,wHAAwH;AACxH,MAAM,UAAU,4BAA4B,CAAC,GAAa;IACxD,IAAI,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QACpF,OAAO,CACL,iGAAiG;YACjG,gHAAgH,CACjH,CAAA;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { TapflowConfig } from './config.js';
2
+ export declare function buildInviteBaseUrl(cfg: Pick<TapflowConfig, 'tunnel' | 'relay' | 'local'>): string;
3
+ //# sourceMappingURL=publicUrl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publicUrl.d.ts","sourceRoot":"","sources":["../../src/lib/publicUrl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAMhD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC,GAAG,MAAM,CAOjG"}
@@ -0,0 +1,17 @@
1
+ // #6 — 초대/공개 링크의 신뢰 base URL.
2
+ // `req.headers.host`/`x-forwarded-proto`는 클라이언트가 조작할 수 있어(Host 인젝션) 피싱 링크가
3
+ // 정상 메일로 발송될 수 있다. 따라서 Host 헤더 대신 설정값에서만 base를 도출한다.
4
+ // 우선순위: 터널 공개 URL → 설정된 relay URL(ws→http 변환) → localhost 폴백.
5
+ export function buildInviteBaseUrl(cfg) {
6
+ if (cfg.tunnel?.publicUrl)
7
+ return stripTrailingSlash(cfg.tunnel.publicUrl);
8
+ if (cfg.relay.url) {
9
+ const http = cfg.relay.url.replace(/^ws:\/\//i, 'http://').replace(/^wss:\/\//i, 'https://');
10
+ return stripTrailingSlash(http);
11
+ }
12
+ return `http://localhost:${cfg.local.port}`;
13
+ }
14
+ function stripTrailingSlash(u) {
15
+ return u.replace(/\/+$/, '');
16
+ }
17
+ //# sourceMappingURL=publicUrl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publicUrl.js","sourceRoot":"","sources":["../../src/lib/publicUrl.ts"],"names":[],"mappings":"AAEA,8BAA8B;AAC9B,2EAA2E;AAC3E,qDAAqD;AACrD,8DAA8D;AAC9D,MAAM,UAAU,kBAAkB,CAAC,GAAsD;IACvF,IAAI,GAAG,CAAC,MAAM,EAAE,SAAS;QAAE,OAAO,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IAC1E,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QAClB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;QAC5F,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAA;IACjC,CAAC;IACD,OAAO,oBAAoB,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;AAC7C,CAAC;AAED,SAAS,kBAAkB,CAAC,CAAS;IACnC,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;AAC9B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function unlinkSafe(filePath: string, label: string): void;
2
+ //# sourceMappingURL=uploads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploads.d.ts","sourceRoot":"","sources":["../../src/lib/uploads.ts"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAQhE"}
@@ -0,0 +1,14 @@
1
+ import fs from 'fs';
2
+ // 업로드 정리: 파일을 삭제하되 이미 없으면(ENOENT) 조용히 넘기고, 그 외 에러만 경고한다.
3
+ // builds/comments 업로드 핸들러가 거부·만료된 파일을 정리할 때 공유한다.
4
+ export function unlinkSafe(filePath, label) {
5
+ try {
6
+ fs.unlinkSync(filePath);
7
+ }
8
+ catch (err) {
9
+ if (err.code !== 'ENOENT') {
10
+ console.warn(`[tapflow] failed to delete ${label}`, err.message);
11
+ }
12
+ }
13
+ }
14
+ //# sourceMappingURL=uploads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploads.js","sourceRoot":"","sources":["../../src/lib/uploads.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAA;AAEnB,yDAAyD;AACzD,kDAAkD;AAClD,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,KAAa;IACxD,IAAI,CAAC;QACH,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrD,OAAO,CAAC,IAAI,CAAC,8BAA8B,KAAK,EAAE,EAAG,GAAa,CAAC,OAAO,CAAC,CAAA;QAC7E,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,18 @@
1
+ export interface RateLimitDecision {
2
+ allowed: boolean;
3
+ retryAfterMs: number;
4
+ }
5
+ export interface RateLimiter {
6
+ check(key: string, now?: number): RateLimitDecision;
7
+ recordFailure(key: string, now?: number): void;
8
+ reset(key: string): void;
9
+ }
10
+ export interface RateLimiterOptions {
11
+ maxAttempts?: number;
12
+ baseDelayMs?: number;
13
+ maxDelayMs?: number;
14
+ retentionMs?: number;
15
+ maxEntries?: number;
16
+ }
17
+ export declare function createRateLimiter(opts?: RateLimiterOptions): RateLimiter;
18
+ //# sourceMappingURL=rateLimit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimit.d.ts","sourceRoot":"","sources":["../../src/middleware/rateLimit.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAAA;IACnD,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9C,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,kBAAkB;IAEjC,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,UAAU,CAAC,EAAE,MAAM,CAAA;IAEnB,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,kBAAuB,GAAG,WAAW,CAkD5E"}
@@ -0,0 +1,55 @@
1
+ // P0-1 — 인증 엔드포인트의 무차별 대입 방어. 인메모리 IP+계정 실패 카운터 + 지수 백오프.
2
+ // 단일 인스턴스(t3.small) 전제이므로 영속화 없이 프로세스 메모리에 둔다.
3
+ export function createRateLimiter(opts = {}) {
4
+ const maxAttempts = opts.maxAttempts ?? 5;
5
+ const baseDelayMs = opts.baseDelayMs ?? 1_000;
6
+ const maxDelayMs = opts.maxDelayMs ?? 15 * 60 * 1_000;
7
+ const retentionMs = opts.retentionMs ?? maxDelayMs;
8
+ const maxEntries = opts.maxEntries ?? 10_000;
9
+ const store = new Map();
10
+ function check(key, now = Date.now()) {
11
+ const a = store.get(key);
12
+ if (!a)
13
+ return { allowed: true, retryAfterMs: 0 };
14
+ if (a.expiresAt <= now) {
15
+ store.delete(key);
16
+ return { allowed: true, retryAfterMs: 0 };
17
+ }
18
+ if (a.lockedUntil > now)
19
+ return { allowed: false, retryAfterMs: a.lockedUntil - now };
20
+ return { allowed: true, retryAfterMs: 0 };
21
+ }
22
+ function evictIfNeeded(now) {
23
+ if (store.size <= maxEntries)
24
+ return;
25
+ for (const [k, v] of store)
26
+ if (v.expiresAt <= now)
27
+ store.delete(k);
28
+ // 만료만으로 부족하면 삽입 순서상 가장 오래된 항목부터 폐기.
29
+ while (store.size > maxEntries) {
30
+ const oldest = store.keys().next().value;
31
+ if (oldest === undefined)
32
+ break;
33
+ store.delete(oldest);
34
+ }
35
+ }
36
+ function recordFailure(key, now = Date.now()) {
37
+ const a = store.get(key) ?? { failures: 0, lockedUntil: 0, expiresAt: 0 };
38
+ a.failures += 1;
39
+ if (a.failures >= maxAttempts) {
40
+ const over = a.failures - maxAttempts;
41
+ a.lockedUntil = now + Math.min(baseDelayMs * 2 ** over, maxDelayMs);
42
+ }
43
+ a.expiresAt = now + retentionMs;
44
+ // delete 후 set으로 갱신된 키를 삽입 순서 맨 뒤로 보낸다(LRU). overflow eviction은 삽입
45
+ // 순서상 가장 오래된 것부터 폐기하므로, 활발한 키가 정크에 밀려 리셋되는 우회를 막는다.
46
+ store.delete(key);
47
+ store.set(key, a);
48
+ evictIfNeeded(now);
49
+ }
50
+ function reset(key) {
51
+ store.delete(key);
52
+ }
53
+ return { check, recordFailure, reset };
54
+ }
55
+ //# sourceMappingURL=rateLimit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimit.js","sourceRoot":"","sources":["../../src/middleware/rateLimit.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAE5B,KAAK,OAAO,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAS5H,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAc;IAE5B,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAQxD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAClC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IACnC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IACpC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAE/B,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;CAmBpF;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAIlF;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAOnE;AAED,wBAAsB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,CAGjF"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAa5B,KAAK,OAAO,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAS5H,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAc;IAE5B,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAQxD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAClC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IACnC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IACpC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAE/B,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;CAsBpF;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAIlF;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAOnE;AAED,wBAAsB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,CAGjF"}
package/dist/router.js CHANGED
@@ -1,3 +1,12 @@
1
+ import { createLogger } from '@tapflowio/agent-core';
2
+ const logger = createLogger('relay:router');
3
+ // 로그에 토큰이 새지 않도록 마스킹: PAT(tflw_pat_…)와 세션 JWT(eyJ….….…).
4
+ // 메시지·스택뿐 아니라 method/url(경로 파라미터에 토큰이 섞인 경우)까지 포함해 로그 라인 전체에 적용한다.
5
+ function redactSecrets(s) {
6
+ return s
7
+ .replace(/tflw_pat_[A-Za-z0-9_-]+/g, 'tflw_pat_***')
8
+ .replace(/eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, '[jwt]');
9
+ }
1
10
  export class Router {
2
11
  routes = [];
3
12
  on(method, path, handler) {
@@ -25,7 +34,10 @@ export class Router {
25
34
  try {
26
35
  await route.handler(req, res, params);
27
36
  }
28
- catch (_err) {
37
+ catch (err) {
38
+ // 관측성: 스택을 삼키지 말고 기록한다. 단 응답 본문에는 상세를 노출하지 않는다.
39
+ const detail = err instanceof Error ? (err.stack ?? err.message) : String(err);
40
+ logger.error(redactSecrets(`${method} ${url} — handler error: ${detail}`));
29
41
  json(res, 500, { error: 'Internal server error' });
30
42
  }
31
43
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
package/dist/server.js CHANGED
@@ -2,16 +2,55 @@ import path from 'path';
2
2
  import { initDb } from './db.js';
3
3
  import { RelayServer } from './RelayServer.js';
4
4
  import { config } from './lib/config.js';
5
+ import { buildCorsOrigins, proxyWithoutPublicUrlWarning } from './lib/proxyConfig.js';
6
+ import { createCertProvider, startCertRenewal, startAddressPublisher } from './lib/cert/index.js';
7
+ import { loadDataDirEnv } from './lib/loadEnvFile.js';
5
8
  import { createLogger } from '@tapflowio/agent-core';
6
9
  const logger = createLogger('relay');
7
10
  const { port, dataDir } = config.local;
11
+ // Load gitignored credentials (DNS/ACME tokens) before any cert issuance reads process.env.
12
+ const envPath = loadDataDirEnv(dataDir);
13
+ if (envPath)
14
+ logger.info(`Loaded credentials from ${envPath}`);
8
15
  const dbPath = path.join(dataDir, 'tapflow.db');
9
16
  const uploadsDir = path.join(dataDir, 'uploads');
10
17
  initDb(dbPath);
11
- const server = new RelayServer({ port, uploadsDir, wsBackpressureBytes: config.local.wsBackpressureBytes });
12
- void server.start().then(() => {
13
- logger.info(`tapflow relay running on port ${port}`);
18
+ const corsOrigins = buildCorsOrigins(config, port);
19
+ const proxyWarning = proxyWithoutPublicUrlWarning(config);
20
+ if (proxyWarning)
21
+ logger.warn(proxyWarning);
22
+ async function main() {
23
+ let tls;
24
+ let provider = null;
25
+ if (config.tls) {
26
+ provider = createCertProvider(config.tls, { dataDir });
27
+ const material = await provider.ensureCert();
28
+ tls = { cert: material.cert, key: material.key };
29
+ }
30
+ else {
31
+ logger.info('TLS disabled — serving HTTP. Secure-context features (e.g. WebCodecs hardware decode) require HTTPS; ' +
32
+ 'configure tls in tapflow.config.json to enable.');
33
+ }
34
+ const server = new RelayServer({ port, uploadsDir, wsBackpressureBytes: config.local.wsBackpressureBytes, trustedProxies: config.local.trustedProxies, corsOrigins, tls });
35
+ await server.start();
36
+ logger.info(`tapflow relay running on port ${port} (${tls ? 'https' : 'http'})`);
37
+ const stopRenewal = provider
38
+ ? startCertRenewal(provider, { onRenew: (m) => server.updateTlsContext({ cert: m.cert, key: m.key }) })
39
+ : null;
40
+ // byo-api-token: publish the relay's LAN IP to the domain's A record so teammates just open the URL.
41
+ const stopPublish = config.tls?.mode === 'byo-api-token' && config.tls.publishAddress !== false
42
+ ? startAddressPublisher(config.tls)
43
+ : null;
44
+ const shutdown = () => {
45
+ stopRenewal?.();
46
+ stopPublish?.();
47
+ void server.stop().then(() => process.exit(0));
48
+ };
49
+ process.on('SIGTERM', shutdown);
50
+ process.on('SIGINT', shutdown);
51
+ }
52
+ void main().catch((err) => {
53
+ logger.error(`relay failed to start: ${String(err)}`);
54
+ process.exit(1);
14
55
  });
15
- process.on('SIGTERM', () => server.stop().then(() => process.exit(0)));
16
- process.on('SIGINT', () => server.stop().then(() => process.exit(0)));
17
56
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAEpD,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;AAEpC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,KAAK,CAAA;AACtC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;AAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;AAEhD,MAAM,CAAC,MAAM,CAAC,CAAA;AAEd,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,CAAC,KAAK,CAAC,mBAAmB,EAAE,CAAC,CAAA;AAE3G,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;IAC5B,MAAM,CAAC,IAAI,CAAC,iCAAiC,IAAI,EAAE,CAAC,CAAA;AACtD,CAAC,CAAC,CAAA;AAEF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACtE,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tapflowio/relay",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "WebSocket relay server for tapflow — NAT traversal, session routing, JWT auth, bundled dashboard",
5
5
  "keywords": [
6
6
  "tapflow",
@@ -39,19 +39,20 @@
39
39
  "LICENSE"
40
40
  ],
41
41
  "engines": {
42
- "node": ">=20"
42
+ "node": ">=20.12.0"
43
43
  },
44
44
  "publishConfig": {
45
45
  "access": "public"
46
46
  },
47
47
  "dependencies": {
48
+ "acme-client": "^5.4.0",
48
49
  "better-sqlite3": "^12.9.0",
49
50
  "busboy": "^1.6.0",
50
51
  "jsonwebtoken": "^9.0.3",
51
- "nodemailer": "^8.0.7",
52
- "ws": "^8.0.0",
52
+ "nodemailer": "^8.0.11",
53
+ "ws": "^8.21.0",
53
54
  "zod": "^4.4.3",
54
- "@tapflowio/agent-core": "0.8.1"
55
+ "@tapflowio/agent-core": "0.9.0"
55
56
  },
56
57
  "devDependencies": {
57
58
  "@types/better-sqlite3": "^7.6.13",
@@ -0,0 +1,16 @@
1
+ import{Z as T,a9 as n,a6 as e,B as b,L as y,ab as _,J as G,I as L,S as E,g as J,an as Q,ao as Z}from"./index-ClAB0eKw.js";import{d as K,S as W,P as X,g as ee,f as se,h as ae,b as te,u as le}from"./build-format-Blzidb1D.js";import{D as U,f as z,a as M,d as V,e as q,S as k,i as D,j as A,g as C,h as c,C as ne}from"./dialog-BuZjQWBQ.js";import{P as re}from"./plus-C0Y4Yk9P.js";import{B as O}from"./badge-Cktrrdvf.js";import{A as ie,c as ce,f as de,g as oe,d as xe,e as ue,b as pe,a as he}from"./alert-dialog-Ci_cWoVo.js";/**
2
+ * @license lucide-react v0.468.0 - ISC
3
+ *
4
+ * This source code is licensed under the ISC license.
5
+ * See the LICENSE file in the root directory of this source tree.
6
+ */const me=T("Layers",[["path",{d:"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z",key:"zw3jo"}],["path",{d:"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12",key:"1wduqc"}],["path",{d:"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17",key:"kqbvx6"}]]);/**
7
+ * @license lucide-react v0.468.0 - ISC
8
+ *
9
+ * This source code is licensed under the ISC license.
10
+ * See the LICENSE file in the root directory of this source tree.
11
+ */const fe=T("Package",[["path",{d:"M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z",key:"1a0edw"}],["path",{d:"M12 22V12",key:"d0xqtd"}],["path",{d:"m3.3 7 7.703 4.734a2 2 0 0 0 1.994 0L20.7 7",key:"yx3hmr"}],["path",{d:"m7.5 4.27 9 5.15",key:"1c824w"}]]);/**
12
+ * @license lucide-react v0.468.0 - ISC
13
+ *
14
+ * This source code is licensed under the ISC license.
15
+ * See the LICENSE file in the root directory of this source tree.
16
+ */const ge=T("Upload",[["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["polyline",{points:"17 8 12 3 7 8",key:"t8dd8p"}],["line",{x1:"12",x2:"12",y1:"3",y2:"15",key:"widbto"}]]);function je({onSuccess:s,appId:l}){const[u,r]=n.useState(!1),[a,h]=n.useState(null),[d,p]=n.useState("none"),g=n.useRef(null);function f(i){if(i.preventDefault(),!a)return;const o=new FormData;o.append("file",a),d!=="none"&&o.append("status",d),l&&o.append("app_id",String(l));const N=fetch("/api/v1/builds",{method:"POST",credentials:"include",body:o}).then(async x=>{if(!x.ok){const m=await x.json().catch(()=>({}));throw new Error(m.error??"Upload failed. Check the file format.")}s()});_.promise(N,{loading:"Uploading build…",success:"Build uploaded",error:x=>x instanceof Error?x.message:"Failed to upload build"}),r(!1),h(null),p("none")}function v(i){r(i),i||(h(null),p("none"))}return e.jsxs(U,{open:u,onOpenChange:v,children:[e.jsx(z,{asChild:!0,children:e.jsxs(b,{size:"sm",children:[e.jsx(ge,{className:"mr-2 h-4 w-4"}),"Upload build"]})}),e.jsxs(M,{className:"sm:max-w-md",children:[e.jsx(V,{children:e.jsx(q,{children:"Upload build"})}),e.jsxs("form",{onSubmit:f,className:"flex flex-col gap-4 pt-2",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(y,{children:"File"}),e.jsx("div",{className:"flex h-24 cursor-pointer items-center justify-center rounded-md border-2 border-dashed text-sm text-muted-foreground hover:border-primary",onClick:()=>{var i;return(i=g.current)==null?void 0:i.click()},onDragOver:i=>i.preventDefault(),onDrop:i=>{i.preventDefault();const o=i.dataTransfer.files[0];o&&h(o)},children:a?a.name:"Click or drag to upload"}),e.jsx("input",{ref:g,type:"file",accept:".zip,.apk",className:"hidden",onChange:i=>{var o;return h(((o=i.target.files)==null?void 0:o[0])??null)}}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:["iOS: ",e.jsx("code",{children:".app.zip"})," (zip the ",e.jsx("code",{children:".app"})," folder after ",e.jsx("code",{children:"xcodebuild -sdk iphonesimulator"}),")  · Android: ",e.jsx("code",{children:".apk"})]})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(y,{children:["Status ",e.jsx("span",{className:"text-muted-foreground",children:"(optional)"})]}),e.jsxs(k,{value:d,onValueChange:p,children:[e.jsx(D,{children:e.jsx(A,{})}),e.jsxs(C,{children:[e.jsx(c,{value:"none",children:"None"}),e.jsx(c,{value:"Backlog",children:"Backlog"}),e.jsx(c,{value:"In Progress",children:"In Progress"}),e.jsx(c,{value:"Done",children:"Done"}),e.jsx(c,{value:"Rejected",children:"Rejected"})]})]})]}),e.jsxs("div",{className:"flex justify-end gap-2",children:[e.jsx(b,{type:"button",variant:"outline",className:"w-24",onClick:()=>r(!1),children:"Cancel"}),e.jsx(b,{type:"submit",className:"w-24",disabled:!a,children:"Upload"})]})]})]})]})}function w({className:s,...l}){return e.jsx("span",{className:G("font-mono text-xs tabular-nums tracking-tight",s),...l})}function ve({onSuccess:s}){const[l,u]=n.useState(!1),[r,a]=n.useState(""),[h,d]=n.useState(""),[p,g]=n.useState("ios"),[f,v]=n.useState(""),[i,o]=n.useState(!1);async function N(x){x.preventDefault(),v(""),o(!0);try{const m=await K({name:r.trim(),bundle_id_key:h.trim(),platform:p});if(!m){v("Failed to create app");return}if("error"in m){v(m.error);return}u(!1),a(""),d(""),g("ios"),_.success("App created"),s()}catch{_.error("Failed to create app — check your network")}finally{o(!1)}}return e.jsxs(U,{open:l,onOpenChange:u,children:[e.jsx(z,{asChild:!0,children:e.jsxs("button",{className:"flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-left text-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors",children:[e.jsx(re,{className:"size-3.5 shrink-0"}),e.jsx("span",{children:"Add App"})]})}),e.jsxs(M,{className:"sm:max-w-sm",children:[e.jsx(V,{children:e.jsx(q,{children:"Add App"})}),e.jsxs("form",{onSubmit:N,className:"flex flex-col gap-4 pt-2",children:[e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx(y,{htmlFor:"app-name",children:"Name"}),e.jsx(L,{id:"app-name",value:r,onChange:x=>a(x.target.value),placeholder:"My App",required:!0})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx(y,{htmlFor:"bundle-id",children:"Bundle ID"}),e.jsx(L,{id:"bundle-id",value:h,onChange:x=>d(x.target.value),placeholder:"com.example.myapp",required:!0})]}),e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx(y,{htmlFor:"platform",children:"Platform"}),e.jsxs(k,{value:p,onValueChange:x=>g(x),children:[e.jsx(D,{id:"platform",children:e.jsx(A,{})}),e.jsxs(C,{children:[e.jsx(c,{value:"ios",children:"iOS"}),e.jsx(c,{value:"android",children:"Android"}),e.jsx(c,{value:"both",children:"Both"})]})]})]}),f&&e.jsx("p",{className:"text-sm text-destructive",children:f}),e.jsx(b,{type:"submit",disabled:i,className:"w-full",children:i?"Creating…":"Create App"})]})]})]})}function Ne({apps:s,selectedAppId:l,onSelect:u,onAdd:r}){return e.jsxs("aside",{className:"w-64 shrink-0 border-r flex flex-col gap-1 p-3 overflow-y-auto",children:[e.jsx("span",{className:"px-2 pb-1 font-mono text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Apps"}),s.length===0&&e.jsx("span",{className:"px-2 text-sm text-muted-foreground",children:"No apps yet"}),s.map(a=>e.jsxs("button",{onClick:()=>u(a.id),className:["flex flex-col gap-0.5 rounded-md px-2 py-2 text-left text-sm hover:bg-accent overflow-hidden",a.id===l?"bg-accent font-medium":""].join(" "),children:[e.jsx("span",{className:"truncate",children:a.name}),e.jsx(w,{className:"text-muted-foreground truncate",children:a.bundle_id_key})]},a.id)),e.jsx("div",{className:"mt-1 border-t pt-1",children:e.jsx(ve,{onSuccess:r})})]})}function Se(s){const r=new Date(s).getTime()+6048e5-Date.now();if(r<=0)return{label:"Expired",urgent:!0};const a=Math.floor(r/36e5);return a<1?{label:"< 1 hour left",urgent:!0}:a<24?{label:`Expires in ${a}h`,urgent:a<6}:{label:`Expires in ${Math.floor(a/24)}d`,urgent:!1}}function be({build:s,isLast:l,onNavigate:u,onStatusChange:r}){const[a,h]=n.useState(!1),d=s.status_label==="Done",p=d&&s.completed_at?Se(s.completed_at):null;function g(f){if(f==="Done"){h(!0);return}r(s.id,f==="none"?null:f)}return e.jsxs(e.Fragment,{children:[e.jsx(ie,{open:a,onOpenChange:h,children:e.jsxs(ce,{children:[e.jsxs(de,{children:[e.jsx(oe,{children:"Mark as Done?"}),e.jsx(xe,{children:"Build files will be automatically deleted after 7 days. Reverting the status will cancel the scheduled deletion."})]}),e.jsxs(ue,{children:[e.jsx(pe,{children:"Cancel"}),e.jsx(he,{onClick:()=>{r(s.id,"Done"),h(!1)},children:"Confirm"})]})]})}),e.jsxs("div",{className:["flex items-center gap-3 px-4 py-3",l?"":"border-b"].join(" "),children:[e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[e.jsxs("span",{className:"text-sm font-medium",children:["build ",e.jsx(w,{children:s.build_number??"—"})]}),e.jsx(O,{tone:s.platform==="ios"?"ios":"android",className:"text-xs capitalize",children:s.platform}),s.status_label&&e.jsx(O,{tone:W[s.status_label],className:"text-xs",children:s.status_label})]}),e.jsxs("div",{style:{display:"flex",flexDirection:"row",gap:"14px",marginTop:"0.7rem",alignItems:"center"},className:"text-xs text-muted-foreground",children:[s.uploader&&e.jsxs(e.Fragment,{children:[e.jsx("span",{children:s.uploader}),e.jsx(E,{orientation:"vertical",className:"h-3"})]}),e.jsx(w,{children:new Date(s.uploaded_at).toLocaleDateString()}),p&&e.jsxs(e.Fragment,{children:[e.jsx(E,{orientation:"vertical",className:"h-3"}),e.jsx("span",{className:p.urgent?"text-destructive":"",children:p.label})]})]})]}),e.jsxs(k,{value:s.status_label??"none",onValueChange:g,children:[e.jsx(D,{className:"h-9 w-32 text-xs",children:e.jsx(A,{})}),e.jsxs(C,{children:[e.jsx(c,{value:"none",children:e.jsx("span",{className:"text-muted-foreground",children:"—"})}),e.jsx(c,{value:"Backlog",children:"Backlog"}),e.jsx(c,{value:"In Progress",children:"In Progress"}),e.jsx(c,{value:"Done",children:"Done"}),e.jsx(c,{value:"Rejected",children:"Rejected"})]})]}),e.jsxs(b,{size:"sm",onClick:()=>u(s.id),disabled:d,children:[e.jsx(X,{className:"mr-1.5 h-3.5 w-3.5"}),"Start QA"]})]})]})}function ye({versionName:s,builds:l,isOpen:u,onToggle:r,onNavigate:a,onStatusChange:h}){return e.jsxs("div",{className:"rounded-lg shadow-card-2 overflow-hidden",children:[e.jsxs("button",{className:"flex w-full items-center gap-2 px-4 py-3 text-left hover:bg-accent/50 bg-card",onClick:r,children:[u?e.jsx(ne,{className:"h-4 w-4 shrink-0 text-muted-foreground"}):e.jsx(J,{className:"h-4 w-4 shrink-0 text-muted-foreground"}),e.jsx(w,{className:"font-medium text-sm text-foreground",children:s}),e.jsxs("span",{className:"text-xs text-muted-foreground",children:["(",l.length," build",l.length>1?"s":"",")"]})]}),u&&e.jsx("div",{className:"border-t bg-card",children:l.map((d,p)=>e.jsx(be,{build:d,isLast:p===l.length-1,onNavigate:a,onStatusChange:h},d.id))})]})}function Ie(){const s=Q(),[l,u]=Z(),[r,a]=n.useState([]),[h,d]=n.useState([]),[p,g]=n.useState(""),[f,v]=n.useState("all"),[i,o]=n.useState(!0),[N,x]=n.useState(new Set),m=l.get("appId")?Number(l.get("appId")):null,R=r.find(t=>t.id===m)??null,B=n.useCallback(async()=>{const t=await ee();a(t),!l.get("appId")&&t.length>0&&u({appId:String(t[0].id)},{replace:!0})},[l,u]),I=n.useCallback(async()=>{if(!m){d([]),o(!1);return}o(!0);try{const t=await se({appId:m,search:p,statusFilter:f});d(t),t.length>0&&N.size===0&&x(new Set([t[0].version_name??"Unversioned"]))}finally{o(!1)}},[m,p,f]);n.useEffect(()=>{B()},[]),n.useEffect(()=>{I()},[I]);function H(t){t!==m&&(d([]),x(new Set)),u({appId:String(t)})}function $(t){x(S=>{const j=new Set(S);return j.has(t)?j.delete(t):j.add(t),j})}async function Y(t,S){await le(t,S),d(j=>j.map(P=>P.id===t?{...P,status_label:S}:P))}const F=ae(h);return e.jsxs("div",{className:"flex h-full gap-0",children:[e.jsx(Ne,{apps:r,selectedAppId:m,onSelect:H,onAdd:B}),e.jsxs("main",{className:"flex-1 flex flex-col gap-4 p-4 overflow-y-auto min-w-0",children:[e.jsxs("div",{className:"flex items-center justify-between flex-wrap gap-2",children:[e.jsx("h1",{className:"text-xl font-semibold tracking-display-sm",children:R?R.name:"App Center"}),e.jsx(je,{onSuccess:()=>{B(),I()},appId:m})]}),m?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsx(te,{placeholder:"Search version…",value:p,onChange:g}),e.jsxs(k,{value:f,onValueChange:v,children:[e.jsx(D,{className:"h-8 w-36",children:e.jsx(A,{placeholder:"Status"})}),e.jsxs(C,{children:[e.jsx(c,{value:"all",children:"All statuses"}),e.jsx(c,{value:"Backlog",children:"Backlog"}),e.jsx(c,{value:"In Progress",children:"In Progress"}),e.jsx(c,{value:"Done",children:"Done"}),e.jsx(c,{value:"Rejected",children:"Rejected"})]})]})]}),i?e.jsx("p",{className:"text-sm text-muted-foreground",children:"Loading…"}):F.length===0?e.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center gap-2 text-center",children:[e.jsx(fe,{className:"w-8 h-8 text-muted-foreground/40"}),e.jsx("p",{className:"text-sm font-medium",children:"No builds yet"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:"Upload the first build to get started."})]}):e.jsx("div",{className:"flex flex-col gap-2",children:F.map(({versionName:t,builds:S})=>e.jsx(ye,{versionName:t,builds:S,isOpen:N.has(t),onToggle:()=>$(t),onNavigate:j=>s(`/app-center/build?id=${j}`),onStatusChange:Y},t))})]}):e.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center gap-2 text-center",children:[e.jsx(me,{className:"w-8 h-8 text-muted-foreground/40"}),e.jsx("p",{className:"text-sm font-medium",children:"No app selected"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:"Choose an app from the sidebar to view its builds."})]})]})]})}export{Ie as AppCenter};
@@ -0,0 +1 @@
1
+ import{ar as X,ad as Z,a9 as i,ak as S,ac as y,aq as I,a6 as e,C as g,d as j,e as v,b as w,L as m,I as u,S as M,k as O,ab as n,B as h,E as ee,J as se,H as ae,a7 as A,_ as $,aa as p}from"./index-ClAB0eKw.js";import{B as re}from"./badge-Cktrrdvf.js";import{A as te,h as oe,c as ne,f as ie,g as le,d as ce,e as de,b as me,a as ue}from"./alert-dialog-Ci_cWoVo.js";import{P as W}from"./pencil-BoxokH2R.js";const he=A({teamName:p().min(1),logo:$(File).nullable().optional()}),pe=A({displayName:p().min(1),avatar:$(File).nullable().optional()}),fe=A({currentPassword:p().min(1),newPassword:p().min(8),confirmPassword:p()}).refine(f=>f.newPassword===f.confirmPassword,{message:"Passwords do not match",path:["confirmPassword"]});function we(){const{resolvedTheme:f}=X(),J=f==="dark"?"/logo-dark.svg":"/logo.svg",{user:o}=Z(),b=(o==null?void 0:o.role)==="Admin",N=(o==null?void 0:o.role)==="Admin"||(o==null?void 0:o.role)==="Developer",[V,P]=i.useState(null),k=i.useRef(null),d=S({resolver:y(he),mode:"onBlur",defaultValues:{teamName:"",logo:null}});i.useEffect(()=>{b&&fetch("/api/v1/settings",{credentials:"include"}).then(s=>s.ok?s.json():null).then(s=>{s&&(d.reset({teamName:s.team_name,logo:null}),P(s.logo_url))})},[b]);async function q(s){const a=new FormData;a.append("team_name",s.teamName),s.logo&&a.append("logo",s.logo);try{if(!(await fetch("/api/v1/settings",{method:"PATCH",credentials:"include",body:a})).ok){n.error("Failed to update workspace");return}n.success("Workspace updated")}catch{n.error("Failed to update workspace")}}const[C,F]=i.useState(null),D=i.useRef(null),c=S({resolver:y(pe),mode:"onBlur",defaultValues:{displayName:"",avatar:null}});i.useEffect(()=>{o&&(c.reset({displayName:o.displayName??"",avatar:null}),F(o.avatarUrl??null))},[o]);async function Q(s){const a=new FormData;a.append("display_name",s.displayName),s.avatar&&a.append("avatar",s.avatar);try{if(!(await fetch("/api/v1/profile",{method:"PATCH",credentials:"include",body:a})).ok){n.error("Failed to update profile");return}n.success("Profile updated")}catch{n.error("Failed to update profile")}}const t=S({resolver:y(fe),mode:"onBlur"});async function Y(s){try{const a=await fetch("/api/v1/auth/change-password",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({currentPassword:s.currentPassword,newPassword:s.newPassword})});if(!a.ok){const r=await a.json();t.setError("root",{message:r.error??"Failed to change password"});return}t.reset(),n.success("Password changed")}catch{t.setError("root",{message:"Network error"})}}const[T,E]=i.useState([]),[B,L]=i.useState({}),[U,z]=i.useState({}),[R,_]=i.useState({});i.useEffect(()=>{N&&fetch("/api/v1/apps",{credentials:"include"}).then(s=>s.ok?s.json():null).then(s=>{if(!s)return;E(s.items);const a={};s.items.forEach(r=>{a[r.id]=r.name}),L(a)})},[N]);async function G(s){_(a=>({...a,[s]:!0}));try{if(!(await fetch(`/api/v1/apps/${s}`,{method:"DELETE",credentials:"include"})).ok){n.error("Failed to delete app");return}E(r=>r.filter(l=>l.id!==s)),n.success("App deleted")}catch{n.error("Failed to delete app")}finally{_(a=>({...a,[s]:!1}))}}async function K(s){z(a=>({...a,[s]:!0}));try{if(!(await fetch(`/api/v1/apps/${s}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:B[s]})})).ok){n.error("Failed to update app");return}n.success("App updated")}catch{n.error("Failed to update app")}finally{z(a=>({...a,[s]:!1}))}}const x=I({control:c.control,name:"displayName"});return e.jsxs("div",{className:"flex flex-col gap-6 max-w-[900px] mx-auto w-full p-6",children:[e.jsx("h1",{className:"text-xl font-semibold tracking-display-sm",children:"Settings"}),b&&e.jsxs(g,{children:[e.jsx(j,{children:e.jsx(v,{children:"Workspace"})}),e.jsx(w,{children:e.jsxs("form",{onSubmit:d.handleSubmit(q),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"team-name",children:"Team name"}),e.jsx(u,{id:"team-name",placeholder:"My QA Team",...d.register("teamName")}),d.formState.errors.teamName&&e.jsx("p",{className:"text-sm text-destructive",children:d.formState.errors.teamName.message})]}),e.jsx(M,{}),e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(m,{children:["Logo ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(png · jpg, max 2MB)"})]}),e.jsx(O,{name:"logo",control:d.control,render:({field:s})=>e.jsxs("div",{className:"relative w-16 h-16",children:[e.jsx("img",{src:V??J,alt:"logo",className:"w-16 h-16 rounded-lg object-contain"}),e.jsx("button",{type:"button",onClick:()=>{var a;return(a=k.current)==null?void 0:a.click()},className:"absolute bottom-0 right-0 w-6 h-6 rounded-full bg-background border border-border shadow-sm flex items-center justify-center hover:bg-accent transition-colors",children:e.jsx(W,{className:"w-3 h-3"})}),e.jsx("input",{ref:k,type:"file",accept:".png,.jpg,.jpeg",className:"hidden",onChange:a=>{var l;const r=(l=a.target.files)==null?void 0:l[0];if(r&&r.size>2*1024*1024){n.error("Image must be 2MB or less");return}r&&(s.onChange(r),P(URL.createObjectURL(r)))}})]})})]}),e.jsx("div",{className:"flex justify-end",children:e.jsx(h,{type:"submit",size:"sm",disabled:d.formState.isSubmitting,children:d.formState.isSubmitting?"Saving…":"Save changes"})})]})})]}),e.jsxs(g,{children:[e.jsx(j,{children:e.jsx(v,{children:"Profile"})}),e.jsx(w,{children:e.jsxs("form",{onSubmit:c.handleSubmit(Q),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"display-name",children:"Nickname"}),e.jsx(u,{id:"display-name",placeholder:"Your name",...c.register("displayName")}),c.formState.errors.displayName&&e.jsx("p",{className:"text-sm text-destructive",children:c.formState.errors.displayName.message})]}),e.jsx(M,{}),e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(m,{children:["Avatar ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(png · jpg, max 2MB)"})]}),e.jsx(O,{name:"avatar",control:c.control,render:({field:s})=>{var a;return e.jsxs("div",{className:"relative w-14 h-14",children:[C?e.jsx("img",{src:C,alt:"avatar",className:"w-14 h-14 rounded-full object-cover border"}):e.jsx("div",{className:"w-14 h-14 rounded-full flex items-center justify-center text-lg font-medium",style:(()=>{const r=ee(x||(o==null?void 0:o.email)||"");return{backgroundColor:r.bg,color:r.fg}})(),children:((a=x==null?void 0:x[0])==null?void 0:a.toUpperCase())??"?"}),e.jsx("button",{type:"button",onClick:()=>{var r;return(r=D.current)==null?void 0:r.click()},className:"absolute bottom-0 right-0 w-6 h-6 rounded-full bg-background border border-border shadow-sm flex items-center justify-center hover:bg-accent transition-colors",children:e.jsx(W,{className:"w-3 h-3"})}),e.jsx("input",{ref:D,type:"file",accept:".png,.jpg,.jpeg",className:"hidden",onChange:r=>{var H;const l=(H=r.target.files)==null?void 0:H[0];if(l&&l.size>2*1024*1024){n.error("Image must be 2MB or less");return}l&&(s.onChange(l),F(URL.createObjectURL(l)))}})]})}})]}),e.jsx("div",{className:"flex justify-end",children:e.jsx(h,{type:"submit",size:"sm",disabled:c.formState.isSubmitting,children:c.formState.isSubmitting?"Saving…":"Save changes"})})]})})]}),e.jsxs(g,{children:[e.jsx(j,{children:e.jsx(v,{children:"Password"})}),e.jsx(w,{children:e.jsxs("form",{onSubmit:t.handleSubmit(Y),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"current-password",children:"Current password"}),e.jsx(u,{id:"current-password",type:"password",...t.register("currentPassword")}),t.formState.errors.currentPassword&&e.jsx("p",{className:"text-sm text-destructive",children:t.formState.errors.currentPassword.message})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"new-password",children:"New password"}),e.jsx(u,{id:"new-password",type:"password",...t.register("newPassword")}),t.formState.errors.newPassword&&e.jsx("p",{className:"text-sm text-destructive",children:t.formState.errors.newPassword.message})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"confirm-password",children:"Confirm new password"}),e.jsx(u,{id:"confirm-password",type:"password",...t.register("confirmPassword")}),t.formState.errors.confirmPassword&&e.jsx("p",{className:"text-sm text-destructive",children:t.formState.errors.confirmPassword.message})]}),t.formState.errors.root&&e.jsx("p",{className:"text-sm text-destructive",children:t.formState.errors.root.message}),e.jsx("div",{className:"flex justify-end",children:e.jsx(h,{type:"submit",size:"sm",disabled:t.formState.isSubmitting,children:t.formState.isSubmitting?"Saving…":"Change password"})})]})})]}),N&&T.length>0&&e.jsxs(g,{children:[e.jsx(j,{children:e.jsx(v,{children:"Apps"})}),e.jsx(w,{children:e.jsx("div",{className:"flex flex-col divide-y divide-border",children:T.map(s=>e.jsxs("div",{className:"flex flex-col gap-2 py-4 first:pt-0 last:pb-0",children:[e.jsx(m,{htmlFor:`app-${s.id}`,children:"App Name"}),e.jsx(u,{id:`app-${s.id}`,value:B[s.id]??"",onChange:a=>L(r=>({...r,[s.id]:a.target.value}))}),e.jsxs("div",{className:"flex flex-col gap-1.5 mt-1",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-xs text-muted-foreground font-mono",children:s.bundle_id_key}),e.jsx(re,{tone:s.platform==="ios"?"ios":s.platform==="android"?"android":void 0,variant:s.platform==="both"?"secondary":void 0,className:"capitalize",children:s.platform})]}),e.jsxs("div",{className:"flex items-center justify-end gap-2",children:[e.jsx(h,{size:"sm",variant:"outline",disabled:U[s.id],onClick:()=>K(s.id),children:U[s.id]?"Saving…":"Save"}),e.jsxs(te,{children:[e.jsx(oe,{asChild:!0,children:e.jsx(h,{size:"sm",variant:"destructive",disabled:R[s.id],children:R[s.id]?"Deleting…":"Delete"})}),e.jsxs(ne,{children:[e.jsxs(ie,{children:[e.jsx(le,{children:"Delete app?"}),e.jsxs(ce,{children:["This will permanently delete ",e.jsx("strong",{children:s.name})," and all its builds. This action cannot be undone."]})]}),e.jsxs(de,{children:[e.jsx(me,{className:"w-24",children:"Cancel"}),e.jsx(ue,{className:se(ae({variant:"destructive"}),"w-24"),onClick:()=>G(s.id),children:"Delete"})]})]})]})]})]})]},s.id))})})]})]})}export{we as DefaultSettings};
@@ -0,0 +1 @@
1
+ import{an as I,ao as L,a9 as i,ak as A,ac as M,aq as T,a6 as e,C as b,d as y,e as C,c as k,b as U,L as d,I as x,k as _,E as D,S as O,B as Y,a7 as q,aa as h,_ as z}from"./index-ClAB0eKw.js";import{P as H}from"./pencil-BoxokH2R.js";const V=q({displayName:h().optional(),avatar:z(File).nullable().optional(),password:h().min(8),confirm:h()}).refine(o=>o.password===o.confirm,{message:"Passwords do not match",path:["confirm"]});function G(){const o=I(),[S]=L(),c=S.get("token")??"",[f,m]=i.useState("loading"),[P,F]=i.useState(""),[v,R]=i.useState(null),j=i.useRef(null),{register:p,handleSubmit:B,control:g,setError:u,formState:{errors:t,isSubmitting:w}}=A({resolver:M(V),mode:"onBlur",defaultValues:{displayName:"",avatar:null}}),l=T({control:g,name:"displayName"})??"";i.useEffect(()=>{if(!c){m("invalid");return}fetch(`/api/v1/invitations/verify?token=${c}`).then(a=>a.ok?a.json():Promise.reject()).then(a=>{F(a.role),m("valid")}).catch(()=>m("invalid"))},[c]);async function E(a){var r;try{const s=new FormData;if(s.append("token",c),s.append("password",a.password),(r=a.displayName)!=null&&r.trim()&&s.append("display_name",a.displayName.trim()),a.avatar&&s.append("avatar",a.avatar),!(await fetch("/api/v1/invitations/accept",{method:"POST",body:s})).ok){u("root",{message:"Failed to accept invitation"});return}o("/app-center",{replace:!0})}catch{u("root",{message:"Network error. Please try again."})}}return f==="loading"?e.jsx("div",{className:"flex min-h-svh items-center justify-center"}):f==="invalid"?e.jsx("div",{className:"bg-mesh-gradient flex min-h-svh items-center justify-center overflow-hidden p-4",children:e.jsx(b,{level:4,className:"w-full max-w-sm text-center",children:e.jsxs(y,{children:[e.jsx(C,{children:"Invitation expired"}),e.jsx(k,{children:"This invite link is invalid or has expired. Ask your admin for a new one."})]})})}):e.jsx("div",{className:"bg-mesh-gradient flex min-h-svh items-center justify-center overflow-hidden p-4",children:e.jsxs(b,{level:4,className:"w-full max-w-sm",children:[e.jsxs(y,{className:"text-center",children:[e.jsx(C,{children:"Set up your account"}),e.jsxs(k,{children:["You're joining as ",e.jsx("strong",{children:P})]})]}),e.jsx(U,{children:e.jsxs("form",{onSubmit:B(E),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(d,{htmlFor:"display-name",children:["Nickname ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(optional)"})]}),e.jsx(x,{id:"display-name",placeholder:"Your name",...p("displayName")})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(d,{children:["Avatar ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(optional, png · jpg, max 2MB)"})]}),e.jsx(_,{name:"avatar",control:g,render:({field:a})=>{var r;return e.jsxs("div",{className:"relative w-14 h-14",children:[v?e.jsx("img",{src:v,alt:"avatar",className:"w-14 h-14 rounded-full object-cover border"}):e.jsx("div",{className:"w-14 h-14 rounded-full flex items-center justify-center text-lg font-medium",style:(()=>{const s=D(l);return{backgroundColor:s.bg,color:s.fg}})(),children:((r=l==null?void 0:l[0])==null?void 0:r.toUpperCase())??"?"}),e.jsx("button",{type:"button",onClick:()=>{var s;return(s=j.current)==null?void 0:s.click()},className:"absolute bottom-0 right-0 w-6 h-6 rounded-full bg-background border border-border shadow-sm flex items-center justify-center hover:bg-accent transition-colors",children:e.jsx(H,{className:"w-3 h-3"})}),e.jsx("input",{ref:j,type:"file",accept:"image/png,image/jpeg",className:"hidden",onChange:s=>{var N;const n=(N=s.target.files)==null?void 0:N[0];if(n){if(n.size>2*1024*1024){u("avatar",{message:"Max 2MB for avatar"});return}a.onChange(n),R(URL.createObjectURL(n))}}})]})}}),t.avatar&&e.jsx("p",{className:"text-sm text-destructive",children:t.avatar.message})]}),e.jsx(O,{}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(d,{htmlFor:"password",children:"Password"}),e.jsx(x,{id:"password",type:"password",...p("password")}),t.password&&e.jsx("p",{className:"text-sm text-destructive",children:t.password.message})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(d,{htmlFor:"confirm",children:"Confirm password"}),e.jsx(x,{id:"confirm",type:"password",...p("confirm")}),t.confirm&&e.jsx("p",{className:"text-sm text-destructive",children:t.confirm.message})]}),t.root&&e.jsx("p",{className:"text-sm text-destructive",children:t.root.message}),e.jsx(Y,{type:"submit",disabled:w,className:"w-full",children:w?"Creating account…":"Create account"})]})})]})})}export{G as Invite};