@atproto/oauth-provider 0.3.1 → 0.5.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 (404) hide show
  1. package/.linguirc +57 -0
  2. package/CHANGELOG.md +29 -0
  3. package/LICENSE.txt +1 -1
  4. package/dist/account/account-manager.d.ts +17 -3
  5. package/dist/account/account-manager.d.ts.map +1 -1
  6. package/dist/account/account-manager.js +102 -8
  7. package/dist/account/account-manager.js.map +1 -1
  8. package/dist/account/account-store.d.ts +81 -15
  9. package/dist/account/account-store.d.ts.map +1 -1
  10. package/dist/account/account-store.js +70 -19
  11. package/dist/account/account-store.js.map +1 -1
  12. package/dist/account/sign-in-data.d.ts +28 -0
  13. package/dist/account/sign-in-data.d.ts.map +1 -0
  14. package/dist/account/sign-in-data.js +16 -0
  15. package/dist/account/sign-in-data.js.map +1 -0
  16. package/dist/account/sign-up-data.d.ts +26 -0
  17. package/dist/account/sign-up-data.d.ts.map +1 -0
  18. package/dist/account/sign-up-data.js +11 -0
  19. package/dist/account/sign-up-data.js.map +1 -0
  20. package/dist/assets/app/bundle-manifest.json +598 -6
  21. package/dist/assets/app/index-ItwwtJ8r.js +36 -0
  22. package/dist/assets/app/index-ItwwtJ8r.js.map +1 -0
  23. package/dist/assets/app/main-B_dNxQo_.js +4 -0
  24. package/dist/assets/app/main-B_dNxQo_.js.map +1 -0
  25. package/dist/assets/app/main-CSatvmRR.css +3 -0
  26. package/dist/assets/app/main-CSatvmRR.js +306 -0
  27. package/dist/assets/app/main-CSatvmRR.js.map +1 -0
  28. package/dist/assets/app/messages-BQeltXSF.js +4 -0
  29. package/dist/assets/app/messages-BQeltXSF.js.map +1 -0
  30. package/dist/assets/app/messages-BQkEhfjg.js +4 -0
  31. package/dist/assets/app/messages-BQkEhfjg.js.map +1 -0
  32. package/dist/assets/app/messages-BUjKj_UJ.js +4 -0
  33. package/dist/assets/app/messages-BUjKj_UJ.js.map +1 -0
  34. package/dist/assets/app/messages-BWIQa8fO.js +4 -0
  35. package/dist/assets/app/messages-BWIQa8fO.js.map +1 -0
  36. package/dist/assets/app/messages-BaNVb0bp.js +4 -0
  37. package/dist/assets/app/messages-BaNVb0bp.js.map +1 -0
  38. package/dist/assets/app/messages-BaizVXcF.js +4 -0
  39. package/dist/assets/app/messages-BaizVXcF.js.map +1 -0
  40. package/dist/assets/app/messages-BfoClA1Y.js +4 -0
  41. package/dist/assets/app/messages-BfoClA1Y.js.map +1 -0
  42. package/dist/assets/app/messages-BsKGDZnC.js +4 -0
  43. package/dist/assets/app/messages-BsKGDZnC.js.map +1 -0
  44. package/dist/assets/app/messages-Bu-TJhml.js +4 -0
  45. package/dist/assets/app/messages-Bu-TJhml.js.map +1 -0
  46. package/dist/assets/app/messages-BvOKnBQk.js +4 -0
  47. package/dist/assets/app/messages-BvOKnBQk.js.map +1 -0
  48. package/dist/assets/app/messages-BxDzCiWz.js +4 -0
  49. package/dist/assets/app/messages-BxDzCiWz.js.map +1 -0
  50. package/dist/assets/app/messages-CDgFOy4S.js +4 -0
  51. package/dist/assets/app/messages-CDgFOy4S.js.map +1 -0
  52. package/dist/assets/app/messages-CLbTz0o9.js +4 -0
  53. package/dist/assets/app/messages-CLbTz0o9.js.map +1 -0
  54. package/dist/assets/app/messages-CNwSh0t7.js +4 -0
  55. package/dist/assets/app/messages-CNwSh0t7.js.map +1 -0
  56. package/dist/assets/app/messages-CSMNJ6P8.js +4 -0
  57. package/dist/assets/app/messages-CSMNJ6P8.js.map +1 -0
  58. package/dist/assets/app/messages-CZQUw3mp.js +4 -0
  59. package/dist/assets/app/messages-CZQUw3mp.js.map +1 -0
  60. package/dist/assets/app/messages-CZT41oVp.js +4 -0
  61. package/dist/assets/app/messages-CZT41oVp.js.map +1 -0
  62. package/dist/assets/app/messages-C_b-d3t8.js +4 -0
  63. package/dist/assets/app/messages-C_b-d3t8.js.map +1 -0
  64. package/dist/assets/app/messages-C_u3MTc2.js +4 -0
  65. package/dist/assets/app/messages-C_u3MTc2.js.map +1 -0
  66. package/dist/assets/app/messages-Cn8nHZic.js +4 -0
  67. package/dist/assets/app/messages-Cn8nHZic.js.map +1 -0
  68. package/dist/assets/app/messages-CtDywJUm.js +4 -0
  69. package/dist/assets/app/messages-CtDywJUm.js.map +1 -0
  70. package/dist/assets/app/messages-CurtIjBF.js +4 -0
  71. package/dist/assets/app/messages-CurtIjBF.js.map +1 -0
  72. package/dist/assets/app/messages-Cv6zIbaP.js +4 -0
  73. package/dist/assets/app/messages-Cv6zIbaP.js.map +1 -0
  74. package/dist/assets/app/messages-D1eLQuPE.js +4 -0
  75. package/dist/assets/app/messages-D1eLQuPE.js.map +1 -0
  76. package/dist/assets/app/messages-D8vHEaYW.js +4 -0
  77. package/dist/assets/app/messages-D8vHEaYW.js.map +1 -0
  78. package/dist/assets/app/messages-DJ1Q4GeC.js +4 -0
  79. package/dist/assets/app/messages-DJ1Q4GeC.js.map +1 -0
  80. package/dist/assets/app/messages-DRL3exqd.js +4 -0
  81. package/dist/assets/app/messages-DRL3exqd.js.map +1 -0
  82. package/dist/assets/app/messages-DWLPQRTp.js +4 -0
  83. package/dist/assets/app/messages-DWLPQRTp.js.map +1 -0
  84. package/dist/assets/app/messages-DjVaE9YE.js +4 -0
  85. package/dist/assets/app/messages-DjVaE9YE.js.map +1 -0
  86. package/dist/assets/app/messages-DqpMfFJR.js +4 -0
  87. package/dist/assets/app/messages-DqpMfFJR.js.map +1 -0
  88. package/dist/assets/app/messages-ETjhJBEN.js +4 -0
  89. package/dist/assets/app/messages-ETjhJBEN.js.map +1 -0
  90. package/dist/assets/app/messages-EUKrgrGn.js +4 -0
  91. package/dist/assets/app/messages-EUKrgrGn.js.map +1 -0
  92. package/dist/assets/app/messages-QQrOUcPW.js +4 -0
  93. package/dist/assets/app/messages-QQrOUcPW.js.map +1 -0
  94. package/dist/assets/app/messages-e2QGqFL6.js +4 -0
  95. package/dist/assets/app/messages-e2QGqFL6.js.map +1 -0
  96. package/dist/assets/app/messages-p61py7gD.js +4 -0
  97. package/dist/assets/app/messages-p61py7gD.js.map +1 -0
  98. package/dist/assets/asset.d.ts +1 -0
  99. package/dist/assets/asset.d.ts.map +1 -1
  100. package/dist/assets/assets-middleware.d.ts.map +1 -1
  101. package/dist/assets/assets-middleware.js +12 -7
  102. package/dist/assets/assets-middleware.js.map +1 -1
  103. package/dist/assets/index.d.ts +3 -2
  104. package/dist/assets/index.d.ts.map +1 -1
  105. package/dist/assets/index.js +13 -1
  106. package/dist/assets/index.js.map +1 -1
  107. package/dist/client/client-store.d.ts +3 -3
  108. package/dist/client/client-store.d.ts.map +1 -1
  109. package/dist/client/client-store.js +6 -5
  110. package/dist/client/client-store.js.map +1 -1
  111. package/dist/device/device-manager.d.ts +12 -13
  112. package/dist/device/device-manager.d.ts.map +1 -1
  113. package/dist/device/device-manager.js +5 -3
  114. package/dist/device/device-manager.js.map +1 -1
  115. package/dist/device/device-store.d.ts +3 -3
  116. package/dist/device/device-store.d.ts.map +1 -1
  117. package/dist/device/device-store.js +10 -9
  118. package/dist/device/device-store.js.map +1 -1
  119. package/dist/dpop/dpop-manager.d.ts +15 -7
  120. package/dist/dpop/dpop-manager.d.ts.map +1 -1
  121. package/dist/dpop/dpop-manager.js +17 -3
  122. package/dist/dpop/dpop-manager.js.map +1 -1
  123. package/dist/dpop/dpop-nonce.d.ts +11 -5
  124. package/dist/dpop/dpop-nonce.d.ts.map +1 -1
  125. package/dist/dpop/dpop-nonce.js +47 -38
  126. package/dist/dpop/dpop-nonce.js.map +1 -1
  127. package/dist/errors/handle-unavailable-error.d.ts +11 -0
  128. package/dist/errors/handle-unavailable-error.d.ts.map +1 -0
  129. package/dist/errors/handle-unavailable-error.js +19 -0
  130. package/dist/errors/handle-unavailable-error.js.map +1 -0
  131. package/dist/errors/invalid-request-error.d.ts +6 -8
  132. package/dist/errors/invalid-request-error.d.ts.map +1 -1
  133. package/dist/errors/invalid-request-error.js +10 -8
  134. package/dist/errors/invalid-request-error.js.map +1 -1
  135. package/dist/lib/csp/index.d.ts +18 -0
  136. package/dist/lib/csp/index.d.ts.map +1 -0
  137. package/dist/lib/csp/index.js +72 -0
  138. package/dist/lib/csp/index.js.map +1 -0
  139. package/dist/lib/hcaptcha.d.ts +177 -0
  140. package/dist/lib/hcaptcha.d.ts.map +1 -0
  141. package/dist/lib/hcaptcha.js +155 -0
  142. package/dist/lib/hcaptcha.js.map +1 -0
  143. package/dist/lib/html/build-document.d.ts +11 -3
  144. package/dist/lib/html/build-document.d.ts.map +1 -1
  145. package/dist/lib/html/build-document.js +51 -15
  146. package/dist/lib/html/build-document.js.map +1 -1
  147. package/dist/lib/http/middleware.d.ts.map +1 -1
  148. package/dist/lib/http/middleware.js +4 -1
  149. package/dist/lib/http/middleware.js.map +1 -1
  150. package/dist/lib/http/request.d.ts +18 -3
  151. package/dist/lib/http/request.d.ts.map +1 -1
  152. package/dist/lib/http/request.js +56 -23
  153. package/dist/lib/http/request.js.map +1 -1
  154. package/dist/lib/http/response.d.ts +4 -2
  155. package/dist/lib/http/response.d.ts.map +1 -1
  156. package/dist/lib/http/response.js +23 -5
  157. package/dist/lib/http/response.js.map +1 -1
  158. package/dist/lib/locale.d.ts +15 -0
  159. package/dist/lib/locale.d.ts.map +1 -0
  160. package/dist/lib/locale.js +17 -0
  161. package/dist/lib/locale.js.map +1 -0
  162. package/dist/lib/util/function.d.ts +2 -2
  163. package/dist/lib/util/function.d.ts.map +1 -1
  164. package/dist/lib/util/function.js.map +1 -1
  165. package/dist/lib/util/type.d.ts +88 -1
  166. package/dist/lib/util/type.d.ts.map +1 -1
  167. package/dist/lib/util/type.js +41 -0
  168. package/dist/lib/util/type.js.map +1 -1
  169. package/dist/metadata/build-metadata.d.ts +2 -2
  170. package/dist/metadata/build-metadata.d.ts.map +1 -1
  171. package/dist/metadata/build-metadata.js.map +1 -1
  172. package/dist/oauth-errors.d.ts +1 -0
  173. package/dist/oauth-errors.d.ts.map +1 -1
  174. package/dist/oauth-errors.js +3 -1
  175. package/dist/oauth-errors.js.map +1 -1
  176. package/dist/oauth-hooks.d.ts +60 -3
  177. package/dist/oauth-hooks.d.ts.map +1 -1
  178. package/dist/oauth-hooks.js +3 -3
  179. package/dist/oauth-hooks.js.map +1 -1
  180. package/dist/oauth-provider.d.ts +28 -22
  181. package/dist/oauth-provider.d.ts.map +1 -1
  182. package/dist/oauth-provider.js +212 -211
  183. package/dist/oauth-provider.js.map +1 -1
  184. package/dist/oauth-verifier.d.ts +1 -1
  185. package/dist/oauth-verifier.d.ts.map +1 -1
  186. package/dist/oauth-verifier.js +2 -1
  187. package/dist/oauth-verifier.js.map +1 -1
  188. package/dist/output/build-authorize-data.d.ts +0 -1
  189. package/dist/output/build-authorize-data.d.ts.map +1 -1
  190. package/dist/output/build-authorize-data.js +0 -1
  191. package/dist/output/build-authorize-data.js.map +1 -1
  192. package/dist/output/build-customization-data.d.ts +232 -0
  193. package/dist/output/build-customization-data.d.ts.map +1 -0
  194. package/dist/output/build-customization-data.js +145 -0
  195. package/dist/output/build-customization-data.js.map +1 -0
  196. package/dist/output/output-manager.d.ts +16 -9
  197. package/dist/output/output-manager.d.ts.map +1 -1
  198. package/dist/output/output-manager.js +78 -42
  199. package/dist/output/output-manager.js.map +1 -1
  200. package/dist/output/send-authorize-redirect.d.ts +9 -6
  201. package/dist/output/send-authorize-redirect.d.ts.map +1 -1
  202. package/dist/output/send-authorize-redirect.js +20 -14
  203. package/dist/output/send-authorize-redirect.js.map +1 -1
  204. package/dist/output/send-web-page.d.ts +7 -2
  205. package/dist/output/send-web-page.d.ts.map +1 -1
  206. package/dist/output/send-web-page.js +37 -21
  207. package/dist/output/send-web-page.js.map +1 -1
  208. package/dist/request/request-manager.d.ts +1 -1
  209. package/dist/request/request-manager.d.ts.map +1 -1
  210. package/dist/request/request-manager.js +4 -4
  211. package/dist/request/request-manager.js.map +1 -1
  212. package/dist/request/request-store.d.ts +3 -3
  213. package/dist/request/request-store.d.ts.map +1 -1
  214. package/dist/request/request-store.js +11 -10
  215. package/dist/request/request-store.js.map +1 -1
  216. package/dist/token/token-store.d.ts +4 -4
  217. package/dist/token/token-store.d.ts.map +1 -1
  218. package/dist/token/token-store.js +13 -12
  219. package/dist/token/token-store.js.map +1 -1
  220. package/package.json +46 -21
  221. package/rollup.config.js +61 -17
  222. package/src/account/account-manager.ts +159 -8
  223. package/src/account/account-store.ts +127 -32
  224. package/src/account/sign-in-data.ts +15 -0
  225. package/src/account/sign-up-data.ts +11 -0
  226. package/src/assets/app/app.tsx +31 -16
  227. package/src/assets/app/backend-data.ts +15 -60
  228. package/src/assets/app/backend-types.ts +66 -0
  229. package/src/assets/app/components/forms/button-toggle-visibility.tsx +43 -0
  230. package/src/assets/app/components/forms/button.tsx +60 -0
  231. package/src/assets/app/components/forms/fieldset.tsx +55 -0
  232. package/src/assets/app/components/forms/form-card-async.tsx +103 -0
  233. package/src/assets/app/components/forms/form-card.tsx +49 -0
  234. package/src/assets/app/components/forms/input-checkbox.tsx +73 -0
  235. package/src/assets/app/components/forms/input-container.tsx +107 -0
  236. package/src/assets/app/components/forms/input-email-address.tsx +66 -0
  237. package/src/assets/app/components/forms/input-new-password.tsx +62 -0
  238. package/src/assets/app/components/forms/input-password.tsx +88 -0
  239. package/src/assets/app/components/forms/input-text.tsx +76 -0
  240. package/src/assets/app/components/forms/input-token.tsx +94 -0
  241. package/src/assets/app/components/forms/wizard-card.tsx +116 -0
  242. package/src/assets/app/components/layouts/layout-title-page.tsx +77 -0
  243. package/src/assets/app/components/layouts/layout-welcome.tsx +73 -0
  244. package/src/assets/app/components/utils/account-identifier.tsx +23 -0
  245. package/src/assets/app/components/utils/account-image.tsx +33 -0
  246. package/src/assets/app/components/utils/admonition.tsx +52 -0
  247. package/src/assets/app/components/utils/client-name.tsx +45 -0
  248. package/src/assets/app/components/utils/error-card.tsx +93 -0
  249. package/src/assets/app/components/utils/error-message.tsx +62 -0
  250. package/src/assets/app/components/utils/help-card.tsx +46 -0
  251. package/src/assets/app/components/utils/icons.tsx +88 -0
  252. package/src/assets/app/components/utils/link-anchor.tsx +28 -0
  253. package/src/assets/app/components/utils/link-title.tsx +26 -0
  254. package/src/assets/app/components/utils/multi-lang-string.tsx +56 -0
  255. package/src/assets/app/components/utils/password-strength-label.tsx +37 -0
  256. package/src/assets/app/components/utils/password-strength-meter.tsx +58 -0
  257. package/src/assets/app/components/{url-viewer.tsx → utils/url-viewer.tsx} +9 -6
  258. package/src/assets/app/hooks/use-api.ts +128 -55
  259. package/src/assets/app/hooks/use-async-action.ts +120 -0
  260. package/src/assets/app/hooks/use-browser-color-scheme.ts +31 -0
  261. package/src/assets/app/hooks/use-csrf-token.ts +1 -1
  262. package/src/assets/app/hooks/use-random-string.ts +37 -0
  263. package/src/assets/app/hooks/use-stepper.ts +87 -0
  264. package/src/assets/app/index.html +182 -0
  265. package/src/assets/app/lib/api.ts +248 -79
  266. package/src/assets/app/lib/clsx.ts +5 -8
  267. package/src/assets/app/lib/json-client.ts +94 -0
  268. package/src/assets/app/lib/password.ts +98 -0
  269. package/src/assets/app/lib/ref.ts +17 -0
  270. package/src/assets/app/locales/an/messages.po +492 -0
  271. package/src/assets/app/locales/ast/messages.po +492 -0
  272. package/src/assets/app/locales/ca/messages.po +492 -0
  273. package/src/assets/app/locales/da/messages.po +492 -0
  274. package/src/assets/app/locales/de/messages.po +492 -0
  275. package/src/assets/app/locales/el/messages.po +492 -0
  276. package/src/assets/app/locales/en/messages.po +492 -0
  277. package/src/assets/app/locales/en-GB/messages.po +492 -0
  278. package/src/assets/app/locales/es/messages.po +492 -0
  279. package/src/assets/app/locales/eu/messages.po +492 -0
  280. package/src/assets/app/locales/fi/messages.po +492 -0
  281. package/src/assets/app/locales/fr/messages.po +492 -0
  282. package/src/assets/app/locales/ga/messages.po +492 -0
  283. package/src/assets/app/locales/gl/messages.po +492 -0
  284. package/src/assets/app/locales/hi/messages.po +492 -0
  285. package/src/assets/app/locales/hu/messages.po +492 -0
  286. package/src/assets/app/locales/ia/messages.po +492 -0
  287. package/src/assets/app/locales/id/messages.po +492 -0
  288. package/src/assets/app/locales/it/messages.po +492 -0
  289. package/src/assets/app/locales/ja/messages.po +492 -0
  290. package/src/assets/app/locales/km/messages.po +492 -0
  291. package/src/assets/app/locales/ko/messages.po +492 -0
  292. package/src/assets/app/locales/load.ts +8 -0
  293. package/src/assets/app/locales/locale-context.ts +19 -0
  294. package/src/assets/app/locales/locale-provider.tsx +112 -0
  295. package/src/assets/app/locales/locale-selector.tsx +58 -0
  296. package/src/assets/app/locales/locales.ts +168 -0
  297. package/src/assets/app/locales/ne/messages.po +492 -0
  298. package/src/assets/app/locales/nl/messages.po +492 -0
  299. package/src/assets/app/locales/pl/messages.po +492 -0
  300. package/src/assets/app/locales/pt-BR/messages.po +492 -0
  301. package/src/assets/app/locales/ro/messages.po +492 -0
  302. package/src/assets/app/locales/ru/messages.po +492 -0
  303. package/src/assets/app/locales/sv/messages.po +492 -0
  304. package/src/assets/app/locales/th/messages.po +492 -0
  305. package/src/assets/app/locales/tr/messages.po +492 -0
  306. package/src/assets/app/locales/uk/messages.po +492 -0
  307. package/src/assets/app/locales/vi/messages.po +492 -0
  308. package/src/assets/app/locales/zh-CN/messages.po +492 -0
  309. package/src/assets/app/locales/zh-HK/messages.po +492 -0
  310. package/src/assets/app/locales/zh-TW/messages.po +492 -0
  311. package/src/assets/app/main.css +23 -2
  312. package/src/assets/app/main.tsx +24 -8
  313. package/src/assets/app/views/authorize/accept/accept-form.tsx +150 -0
  314. package/src/assets/app/views/authorize/accept/accept-view.tsx +70 -0
  315. package/src/assets/app/views/authorize/authorize-view.tsx +180 -0
  316. package/src/assets/app/views/authorize/reset-password/reset-password-confirm-form.tsx +88 -0
  317. package/src/assets/app/views/authorize/reset-password/reset-password-request-form.tsx +80 -0
  318. package/src/assets/app/views/authorize/reset-password/reset-password-view.tsx +127 -0
  319. package/src/assets/app/views/authorize/sign-in/sign-in-form.tsx +244 -0
  320. package/src/assets/app/views/authorize/sign-in/sign-in-picker.tsx +116 -0
  321. package/src/assets/app/views/authorize/sign-in/sign-in-view.tsx +145 -0
  322. package/src/assets/app/views/authorize/sign-up/sign-up-account-form.tsx +140 -0
  323. package/src/assets/app/views/authorize/sign-up/sign-up-disclaimer.tsx +51 -0
  324. package/src/assets/app/views/authorize/sign-up/sign-up-handle-form.tsx +289 -0
  325. package/src/assets/app/views/authorize/sign-up/sign-up-hcaptcha-form.tsx +108 -0
  326. package/src/assets/app/views/authorize/sign-up/sign-up-view.tsx +158 -0
  327. package/src/assets/app/views/authorize/welcome/welcome-view.tsx +56 -0
  328. package/src/assets/app/views/error/error-view.tsx +31 -0
  329. package/src/assets/asset.ts +1 -0
  330. package/src/assets/assets-middleware.ts +13 -8
  331. package/src/assets/index.ts +15 -2
  332. package/src/client/client-store.ts +10 -12
  333. package/src/device/device-manager.ts +14 -15
  334. package/src/device/device-store.ts +9 -15
  335. package/src/dpop/dpop-manager.ts +20 -8
  336. package/src/dpop/dpop-nonce.ts +58 -40
  337. package/src/errors/handle-unavailable-error.ts +18 -0
  338. package/src/errors/invalid-request-error.ts +10 -8
  339. package/src/lib/csp/index.ts +98 -0
  340. package/src/lib/hcaptcha.ts +182 -0
  341. package/src/lib/html/build-document.ts +60 -16
  342. package/src/lib/http/middleware.ts +4 -3
  343. package/src/lib/http/request.ts +81 -28
  344. package/src/lib/http/response.ts +22 -9
  345. package/src/lib/locale.ts +21 -0
  346. package/src/lib/util/function.ts +0 -3
  347. package/src/lib/util/type.ts +130 -1
  348. package/src/metadata/build-metadata.ts +2 -1
  349. package/src/oauth-errors.ts +1 -0
  350. package/src/oauth-hooks.ts +69 -3
  351. package/src/oauth-provider.ts +410 -315
  352. package/src/oauth-verifier.ts +3 -1
  353. package/src/output/build-authorize-data.ts +1 -3
  354. package/src/output/build-customization-data.ts +189 -0
  355. package/src/output/output-manager.ts +111 -48
  356. package/src/output/send-authorize-redirect.ts +43 -36
  357. package/src/output/send-web-page.ts +40 -26
  358. package/src/request/request-manager.ts +4 -4
  359. package/src/request/request-store.ts +12 -16
  360. package/src/token/token-store.ts +14 -18
  361. package/tailwind.config.js +5 -0
  362. package/tsconfig.backend.tsbuildinfo +1 -1
  363. package/tsconfig.frontend.tsbuildinfo +1 -1
  364. package/tsconfig.tools.tsbuildinfo +1 -1
  365. package/vite.config.mjs +16 -0
  366. package/.postcssrc.yml +0 -3
  367. package/dist/assets/app/main.css +0 -3
  368. package/dist/assets/app/main.js +0 -20
  369. package/dist/assets/app/main.js.map +0 -1
  370. package/dist/output/customization.d.ts +0 -27
  371. package/dist/output/customization.d.ts.map +0 -1
  372. package/dist/output/customization.js +0 -88
  373. package/dist/output/customization.js.map +0 -1
  374. package/src/assets/app/components/accept-form.tsx +0 -137
  375. package/src/assets/app/components/account-identifier.tsx +0 -18
  376. package/src/assets/app/components/account-picker.tsx +0 -127
  377. package/src/assets/app/components/button.tsx +0 -34
  378. package/src/assets/app/components/client-name.tsx +0 -37
  379. package/src/assets/app/components/fieldset.tsx +0 -26
  380. package/src/assets/app/components/form-card.tsx +0 -47
  381. package/src/assets/app/components/help-card.tsx +0 -42
  382. package/src/assets/app/components/icons/alert-icon.tsx +0 -5
  383. package/src/assets/app/components/icons/at-symbol-icon.tsx +0 -5
  384. package/src/assets/app/components/icons/caret-right-icon.tsx +0 -5
  385. package/src/assets/app/components/icons/lock-icon.tsx +0 -5
  386. package/src/assets/app/components/icons/token-icon.tsx +0 -5
  387. package/src/assets/app/components/icons/util.tsx +0 -17
  388. package/src/assets/app/components/info-card.tsx +0 -45
  389. package/src/assets/app/components/input-checkbox.tsx +0 -47
  390. package/src/assets/app/components/input-container.tsx +0 -37
  391. package/src/assets/app/components/input-layout.tsx +0 -47
  392. package/src/assets/app/components/input-text.tsx +0 -69
  393. package/src/assets/app/components/layout-title-page.tsx +0 -60
  394. package/src/assets/app/components/layout-welcome.tsx +0 -74
  395. package/src/assets/app/components/sign-in-form.tsx +0 -337
  396. package/src/assets/app/components/sign-up-account-form.tsx +0 -194
  397. package/src/assets/app/components/sign-up-disclaimer.tsx +0 -44
  398. package/src/assets/app/views/accept-view.tsx +0 -55
  399. package/src/assets/app/views/authorize-view.tsx +0 -106
  400. package/src/assets/app/views/error-view.tsx +0 -36
  401. package/src/assets/app/views/sign-in-view.tsx +0 -111
  402. package/src/assets/app/views/sign-up-view.tsx +0 -86
  403. package/src/assets/app/views/welcome-view.tsx +0 -54
  404. package/src/output/customization.ts +0 -118
@@ -0,0 +1,289 @@
1
+ import { Trans, useLingui } from '@lingui/react/macro'
2
+ import { JSX, ReactNode, useCallback, useEffect, useRef, useState } from 'react'
3
+ import {
4
+ AsyncActionController,
5
+ FormCardAsync,
6
+ FormCardAsyncProps,
7
+ } from '../../../components/forms/form-card-async.tsx'
8
+ import { InputText } from '../../../components/forms/input-text.tsx'
9
+ import { Admonition } from '../../../components/utils/admonition.tsx'
10
+ import {
11
+ AtSymbolIcon,
12
+ CheckMarkIcon,
13
+ XMarkIcon,
14
+ } from '../../../components/utils/icons.tsx'
15
+ import { clsx } from '../../../lib/clsx.ts'
16
+ import { mergeRefs } from '../../../lib/ref.ts'
17
+ import { Override } from '../../../lib/util.ts'
18
+
19
+ /**
20
+ * Spec limit is 63, but in practice, we've limited it to 18 in our implementations.
21
+ *
22
+ * @see {@link https://atproto.com/specs/handle | ATProto Handle Spec}
23
+ */
24
+ const MAX_LENGTH = 18
25
+
26
+ /**
27
+ * Spec limit is 1, but in practice, we've targeted at least 3 characters in handles.
28
+ *
29
+ * @see {@link https://atproto.com/specs/handle | ATProto Handle Spec}
30
+ */
31
+ const MIN_LENGTH = 3
32
+
33
+ /**
34
+ * Spec limit is 253, but in practice, we've targeted 30 characters in handles.
35
+ *
36
+ * @see {@link https://atproto.com/specs/handle | ATProto Handle Spec}
37
+ */
38
+ const MAX_FULL_LENGTH = 30
39
+
40
+ type ValidDomain = `.${string}`
41
+ const isValidDomain = (domain: string): domain is ValidDomain =>
42
+ // Ignore domains that are so long that they would make the handle smaller
43
+ // than MIN_LENGTH characters
44
+ MIN_LENGTH + domain.length <= MAX_FULL_LENGTH &&
45
+ // Basic validation here
46
+ domain.startsWith('.') &&
47
+ !domain.endsWith('.')
48
+
49
+ function useSegmentValidator(domain: ValidDomain) {
50
+ const minLen = MIN_LENGTH
51
+ const maxLen = Math.min(MAX_LENGTH, MAX_FULL_LENGTH - domain.length)
52
+
53
+ const validateSegment = useCallback(
54
+ (segment: string) => {
55
+ const validLength = segment.length >= minLen && segment.length <= maxLen
56
+ const validCharset = /^[a-z0-9][a-z0-9-]+[a-z0-9]$/g.test(segment)
57
+
58
+ return { validLength, validCharset, valid: validLength && validCharset }
59
+ },
60
+ [maxLen, minLen],
61
+ )
62
+
63
+ return {
64
+ minLength: minLen,
65
+ maxLength: maxLen,
66
+ validateSegment,
67
+ }
68
+ }
69
+
70
+ export type SignUpHandleFormProps = Override<
71
+ Omit<
72
+ FormCardAsyncProps,
73
+ 'append' | 'onCancel' | 'cancelLabel' | 'onSubmit' | 'submitLabel'
74
+ >,
75
+ {
76
+ domains: string[]
77
+
78
+ onNext: (signal: AbortSignal) => void | PromiseLike<void>
79
+ nextLabel?: ReactNode
80
+
81
+ onPrev?: () => void
82
+ prevLabel?: ReactNode
83
+
84
+ handle?: string
85
+ onHandle?: (handle: string | undefined) => void
86
+ }
87
+ >
88
+
89
+ export function SignUpHandleForm({
90
+ domains: availableDomains,
91
+
92
+ onNext,
93
+ nextLabel,
94
+
95
+ onPrev,
96
+ prevLabel,
97
+
98
+ handle: handleInit,
99
+ onHandle,
100
+
101
+ // FormCardProps
102
+ invalid,
103
+ children,
104
+ ref,
105
+ ...props
106
+ }: SignUpHandleFormProps) {
107
+ const { t } = useLingui()
108
+ const domains = availableDomains.filter(isValidDomain)
109
+
110
+ const formRef = useRef<AsyncActionController>(null)
111
+
112
+ const [domainIdx, setDomainIdx] = useState(() => {
113
+ const idx = domains.findIndex((d) => handleInit?.endsWith(d))
114
+ return idx === -1 ? 0 : idx
115
+ })
116
+ const [segment, setSegment] = useState(() => handleInit?.split('.')[0] || '')
117
+
118
+ // Automatically update the domain index when the list length changes
119
+ useEffect(() => {
120
+ setDomainIdx((v) => Math.min(v, domains.length - 1))
121
+ }, [domains.length])
122
+
123
+ const domain: ValidDomain | null = domains[domainIdx] || domains[0] || null
124
+
125
+ const { minLength, maxLength, validateSegment } = useSegmentValidator(domain)
126
+
127
+ const validity = validateSegment(segment)
128
+ const handle = domain && validity.valid ? `${segment}${domain}` : undefined
129
+ useEffect(() => {
130
+ // Whenever the user changes the handle, abort any pending form action
131
+ formRef.current?.reset()
132
+ onHandle?.(handle)
133
+ }, [onHandle, handle])
134
+
135
+ const inputRef = useRef<HTMLInputElement>(null)
136
+
137
+ const preview = `@${segment}${domain}`
138
+
139
+ return (
140
+ <FormCardAsync
141
+ {...props}
142
+ ref={mergeRefs([ref, formRef])}
143
+ onCancel={onPrev}
144
+ cancelLabel={prevLabel}
145
+ onSubmit={onNext}
146
+ submitLabel={nextLabel}
147
+ invalid={invalid || !handle}
148
+ append={children}
149
+ >
150
+ <div>
151
+ <ValidationMessage hasValue={!!segment} valid={validity.validLength}>
152
+ <Trans>
153
+ Between {minLength} and {maxLength} characters
154
+ </Trans>
155
+ </ValidationMessage>
156
+ <ValidationMessage hasValue={!!segment} valid={validity.validCharset}>
157
+ <Trans>Only letters, numbers, and hyphens</Trans>
158
+ </ValidationMessage>
159
+ </div>
160
+
161
+ <InputText
162
+ ref={inputRef}
163
+ icon={<AtSymbolIcon className="w-5" />}
164
+ name="handle"
165
+ type="text"
166
+ placeholder={t`Type your desired username`}
167
+ aria-label={t`Type your desired username`}
168
+ title={t`Type your desired username`}
169
+ pattern="[a-z0-9][a-z0-9\-]+[a-z0-9]"
170
+ minLength={minLength}
171
+ maxLength={maxLength}
172
+ autoCapitalize="none"
173
+ autoCorrect="off"
174
+ autoComplete="off"
175
+ dir="auto"
176
+ enterKeyHint="done"
177
+ autoFocus
178
+ required
179
+ value={segment}
180
+ onChange={(event) => {
181
+ const segment = event.target.value.toLowerCase()
182
+
183
+ // Ensure the input is always lowercase
184
+ const selectionStart = event.target.selectionStart
185
+ const selectionEnd = event.target.selectionEnd
186
+ event.target.value = segment
187
+ event.target.setSelectionRange(selectionStart, selectionEnd)
188
+
189
+ setSegment(segment)
190
+ }}
191
+ append={
192
+ // @TODO refactor this to a separate component
193
+ domains.length > 1 && (
194
+ <select
195
+ onClick={(event) => event.stopPropagation()}
196
+ onMouseDown={(event) => event.stopPropagation()}
197
+ value={domainIdx}
198
+ aria-label={t`Select domain`}
199
+ onChange={(event) => {
200
+ setDomainIdx(Number(event.target.value))
201
+ inputRef.current?.focus()
202
+ }}
203
+ className={clsx(
204
+ 'block w-full',
205
+ 'text-sm',
206
+ 'rounded-lg p-2',
207
+ 'bg-white dark:bg-slate-600',
208
+ )}
209
+ >
210
+ {domains.map((domain, idx) => (
211
+ <option key={domain} value={idx}>
212
+ {domain}
213
+ </option>
214
+ ))}
215
+ </select>
216
+ )
217
+ }
218
+ bellow={
219
+ <Trans>
220
+ Your full username will be:{' '}
221
+ {segment.length ? (
222
+ <strong className="text-gray-800 dark:text-gray-200">
223
+ {preview}
224
+ </strong>
225
+ ) : (
226
+ <span
227
+ aria-hidden
228
+ className="bg-gray-300 dark:bg-slate-600 rounded-md p-2 w-24"
229
+ />
230
+ )}
231
+ </Trans>
232
+ }
233
+ />
234
+
235
+ <Admonition role="status">
236
+ <p className="text-md">
237
+ <Trans>
238
+ You can change this username to any domain name you control after
239
+ your account is set up.
240
+ </Trans>
241
+ </p>
242
+ </Admonition>
243
+ </FormCardAsync>
244
+ )
245
+ }
246
+
247
+ type ValidationMessageProps = JSX.IntrinsicElements['div'] & {
248
+ valid: boolean
249
+ hasValue: boolean
250
+ }
251
+
252
+ function ValidationMessage({
253
+ valid,
254
+ hasValue,
255
+
256
+ // div
257
+ children,
258
+ className,
259
+ ...props
260
+ }: ValidationMessageProps) {
261
+ const { t } = useLingui()
262
+ return (
263
+ <div
264
+ {...props}
265
+ className={clsx('flex flex-row items-center gap-2', className)}
266
+ >
267
+ {hasValue ? (
268
+ <>
269
+ {valid ? (
270
+ <CheckMarkIcon
271
+ className="inline-block w-4 h-4 text-success"
272
+ title={t`Valid`}
273
+ />
274
+ ) : (
275
+ <XMarkIcon
276
+ className="inline-block w-4 h-4 text-error"
277
+ title={t`Invalid`}
278
+ />
279
+ )}
280
+ </>
281
+ ) : (
282
+ <div aria-hidden className="w-4 h-4 flex items-center justify-center">
283
+ <div className="bg-gray-300 dark:bg-slate-600 rounded-full w-2 h-2" />
284
+ </div>
285
+ )}
286
+ <div className="text-sm">{children}</div>
287
+ </div>
288
+ )
289
+ }
@@ -0,0 +1,108 @@
1
+ import type HCaptcha from '@hcaptcha/react-hcaptcha'
2
+ import {
3
+ ForwardedRef,
4
+ ReactNode,
5
+ lazy,
6
+ useCallback,
7
+ useRef,
8
+ useState,
9
+ } from 'react'
10
+ import {
11
+ FormCardAsync,
12
+ FormCardAsyncProps,
13
+ } from '../../../components/forms/form-card-async.tsx'
14
+ import { useBrowserColorScheme } from '../../../hooks/use-browser-color-scheme.ts'
15
+ import { mergeRefs } from '../../../lib/ref.ts'
16
+ import { Override } from '../../../lib/util.ts'
17
+
18
+ export type SignUpHcaptchaFormProps = Override<
19
+ Omit<
20
+ FormCardAsyncProps,
21
+ 'append' | 'onSubmit' | 'submitLabel' | 'onCancel' | 'cancelLabel'
22
+ >,
23
+ {
24
+ siteKey: string
25
+
26
+ token?: string
27
+ onToken: (token: string, ekey: string) => void
28
+
29
+ prevLabel?: ReactNode
30
+ onPrev?: () => void
31
+
32
+ nextLabel?: ReactNode
33
+ onNext: (signal: AbortSignal) => void | PromiseLike<void>
34
+
35
+ ref?: ForwardedRef<HCaptcha>
36
+ }
37
+ >
38
+
39
+ const HCaptchaLazy = lazy(() => import('@hcaptcha/react-hcaptcha'))
40
+
41
+ export function SignUpHcaptchaForm({
42
+ siteKey,
43
+
44
+ token: tokenInit,
45
+ onToken,
46
+
47
+ prevLabel,
48
+ onPrev,
49
+
50
+ nextLabel,
51
+ onNext,
52
+
53
+ ref,
54
+
55
+ // FormCardProps
56
+ invalid,
57
+ children,
58
+ ...props
59
+ }: SignUpHcaptchaFormProps) {
60
+ const captchaRef = useRef<HCaptcha>(null)
61
+ const theme = useBrowserColorScheme()
62
+ const [token, setToken] = useState<string | undefined>(tokenInit)
63
+
64
+ const onLoad = useCallback(() => {
65
+ // this reaches out to the hCaptcha JS API and runs the
66
+ // execute function on it. you can use other functions as
67
+ // documented here:
68
+ // https://docs.hcaptcha.com/configuration#jsapi
69
+ captchaRef.current?.execute()
70
+ }, [])
71
+
72
+ const onVerify = useCallback(
73
+ (token: string, ekey: string) => {
74
+ setToken(token)
75
+ onToken(token, ekey)
76
+ },
77
+ [onToken],
78
+ )
79
+
80
+ const doSubmit = useCallback(
81
+ (signal: AbortSignal) => {
82
+ if (token) return onNext(signal)
83
+ else if (captchaRef.current) captchaRef.current.execute()
84
+ else throw new Error('Unable to load hCaptcha')
85
+ },
86
+ [token, onNext],
87
+ )
88
+
89
+ return (
90
+ <FormCardAsync
91
+ {...props}
92
+ cancelLabel={prevLabel}
93
+ onCancel={onPrev}
94
+ submitLabel={nextLabel}
95
+ onSubmit={doSubmit}
96
+ append={children}
97
+ invalid={invalid || !token}
98
+ >
99
+ <HCaptchaLazy
100
+ theme={theme}
101
+ sitekey={siteKey}
102
+ onLoad={onLoad}
103
+ onVerify={onVerify}
104
+ ref={mergeRefs([ref, captchaRef])}
105
+ />
106
+ </FormCardAsync>
107
+ )
108
+ }
@@ -0,0 +1,158 @@
1
+ import { Trans, useLingui } from '@lingui/react/macro'
2
+ import { useCallback, useState } from 'react'
3
+ import { CustomizationData } from '../../../backend-types.ts'
4
+ import { WizardCard } from '../../../components/forms/wizard-card.tsx'
5
+ import {
6
+ LayoutTitlePage,
7
+ LayoutTitlePageProps,
8
+ } from '../../../components/layouts/layout-title-page.tsx'
9
+ import { HelpCard } from '../../../components/utils/help-card.tsx'
10
+ import { Override } from '../../../lib/util.ts'
11
+ import {
12
+ SignUpAccountForm,
13
+ SignUpAccountFormOutput,
14
+ } from './sign-up-account-form.tsx'
15
+ import { SignUpDisclaimer } from './sign-up-disclaimer.tsx'
16
+ import { SignUpHandleForm } from './sign-up-handle-form.tsx'
17
+ import { SignUpHcaptchaForm } from './sign-up-hcaptcha-form.tsx'
18
+
19
+ export type SignUpViewProps = Override<
20
+ LayoutTitlePageProps,
21
+ {
22
+ customizationData?: CustomizationData
23
+
24
+ onBack?: () => void
25
+ onValidateNewHandle: (
26
+ data: { handle: string },
27
+ signal?: AbortSignal,
28
+ ) => void | PromiseLike<void>
29
+ onDone: (
30
+ data: SignUpAccountFormOutput & {
31
+ handle: string
32
+ hcaptchaToken?: string
33
+ },
34
+ signal?: AbortSignal,
35
+ ) => void | PromiseLike<void>
36
+ }
37
+ >
38
+
39
+ export function SignUpView({
40
+ customizationData: {
41
+ availableUserDomains = [],
42
+ hcaptchaSiteKey = undefined,
43
+ inviteCodeRequired = true,
44
+ links,
45
+ } = {},
46
+
47
+ onValidateNewHandle,
48
+ onDone,
49
+ onBack,
50
+
51
+ // LayoutTitlePage
52
+ ...props
53
+ }: SignUpViewProps) {
54
+ const { t } = useLingui()
55
+ const [credentials, setCredentials] = useState<
56
+ undefined | SignUpAccountFormOutput
57
+ >(undefined)
58
+ const [handle, setHandle] = useState<undefined | string>(undefined)
59
+ const [hcaptcha, setHcaptcha] = useState<undefined | string>(undefined)
60
+
61
+ /**
62
+ * "false" indicates that the hcaptcha token is invalid (required but not provided)
63
+ */
64
+ const hcaptchaToken = hcaptchaSiteKey == null ? undefined : hcaptcha || false
65
+
66
+ const doDone = useCallback(
67
+ (signal: AbortSignal) => {
68
+ if (credentials && handle && hcaptchaToken !== false) {
69
+ return onDone({ ...credentials, handle, hcaptchaToken }, signal)
70
+ }
71
+ },
72
+ [credentials, handle, hcaptchaToken, onDone],
73
+ )
74
+
75
+ return (
76
+ <LayoutTitlePage
77
+ {...props}
78
+ title={props.title ?? t`Create Account`}
79
+ subtitle={
80
+ props.subtitle ?? <Trans>We're so excited to have you join us!</Trans>
81
+ }
82
+ >
83
+ <WizardCard
84
+ doneLabel={<Trans>Sign up</Trans>}
85
+ onBack={onBack}
86
+ onDone={doDone}
87
+ steps={[
88
+ // We use the handle input first since the "onValidateNewHandle" check
89
+ // will make it less likely that the actual signup call will fail, and
90
+ // will result in a better user experience, especially if there is an
91
+ // issue with the email address (e.g. already in use).
92
+ {
93
+ invalid: !handle,
94
+ titleRender: () => <Trans>Choose a username</Trans>,
95
+ contentRender: ({ prev, prevLabel, next, nextLabel, invalid }) => (
96
+ <SignUpHandleForm
97
+ className="flex-grow"
98
+ invalid={invalid}
99
+ domains={availableUserDomains}
100
+ handle={handle}
101
+ onHandle={setHandle}
102
+ prevLabel={prevLabel}
103
+ onPrev={prev}
104
+ nextLabel={nextLabel}
105
+ onNext={async (signal) => {
106
+ if (handle) await onValidateNewHandle({ handle }, signal)
107
+ if (!signal.aborted) return next(signal)
108
+ }}
109
+ >
110
+ <SignUpDisclaimer links={links} />
111
+ </SignUpHandleForm>
112
+ ),
113
+ },
114
+ {
115
+ invalid: !credentials,
116
+ titleRender: () => <Trans>Your account</Trans>,
117
+ contentRender: ({ prev, prevLabel, next, nextLabel, invalid }) => (
118
+ <SignUpAccountForm
119
+ className="flex-grow"
120
+ invalid={invalid}
121
+ prevLabel={prevLabel}
122
+ onPrev={prev}
123
+ nextLabel={nextLabel}
124
+ onNext={next}
125
+ inviteCodeRequired={inviteCodeRequired}
126
+ credentials={credentials}
127
+ onCredentials={setCredentials}
128
+ >
129
+ <SignUpDisclaimer links={links} />
130
+ </SignUpAccountForm>
131
+ ),
132
+ },
133
+ hcaptchaSiteKey != null && {
134
+ invalid: hcaptchaToken === false,
135
+ titleRender: () => <Trans>Verify you are human</Trans>,
136
+ contentRender: ({ prev, prevLabel, next, nextLabel, invalid }) => (
137
+ <SignUpHcaptchaForm
138
+ className="flex-grow"
139
+ invalid={invalid}
140
+ siteKey={hcaptchaSiteKey}
141
+ token={hcaptcha}
142
+ onToken={setHcaptcha}
143
+ prevLabel={prevLabel}
144
+ onPrev={prev}
145
+ nextLabel={nextLabel}
146
+ onNext={next}
147
+ >
148
+ <SignUpDisclaimer links={links} />
149
+ </SignUpHcaptchaForm>
150
+ ),
151
+ },
152
+ ]}
153
+ />
154
+
155
+ <HelpCard className="mt-4" links={links} />
156
+ </LayoutTitlePage>
157
+ )
158
+ }
@@ -0,0 +1,56 @@
1
+ import { Trans, useLingui } from '@lingui/react/macro'
2
+ import { Button } from '../../../components/forms/button.tsx'
3
+ import {
4
+ LayoutWelcome,
5
+ LayoutWelcomeProps,
6
+ } from '../../../components/layouts/layout-welcome.tsx'
7
+ import { Override } from '../../../lib/util.ts'
8
+
9
+ export type WelcomeViewParams = Override<
10
+ LayoutWelcomeProps,
11
+ {
12
+ onSignIn?: () => void
13
+ onSignUp?: () => void
14
+ onCancel?: () => void
15
+ }
16
+ >
17
+
18
+ export function WelcomeView({
19
+ onSignUp,
20
+ onSignIn,
21
+ onCancel,
22
+
23
+ // LayoutWelcome
24
+ ...props
25
+ }: WelcomeViewParams) {
26
+ const { t } = useLingui()
27
+ return (
28
+ <LayoutWelcome {...props} title={props.title ?? t`Authenticate`}>
29
+ {onSignUp && (
30
+ <Button
31
+ className={'m-1 w-60 max-w-full min-w-min'}
32
+ color={onSignIn ? 'brand' : undefined}
33
+ onClick={onSignUp}
34
+ >
35
+ <Trans>Create a new account</Trans>
36
+ </Button>
37
+ )}
38
+
39
+ {onSignIn && (
40
+ <Button
41
+ className={'m-1 w-60 max-w-full min-w-min'}
42
+ color={onSignUp ? undefined : 'brand'}
43
+ onClick={onSignIn}
44
+ >
45
+ <Trans>Sign in</Trans>
46
+ </Button>
47
+ )}
48
+
49
+ {onCancel && (
50
+ <Button className="m-1 w-60 max-w-full min-w-min" onClick={onCancel}>
51
+ <Trans>Cancel</Trans>
52
+ </Button>
53
+ )}
54
+ </LayoutWelcome>
55
+ )
56
+ }
@@ -0,0 +1,31 @@
1
+ import { useLingui } from '@lingui/react/macro'
2
+ import { memo } from 'react'
3
+ import {
4
+ LayoutWelcome,
5
+ LayoutWelcomeProps,
6
+ } from '../../components/layouts/layout-welcome.tsx'
7
+ import { ErrorCard } from '../../components/utils/error-card.tsx'
8
+ import { Override } from '../../lib/util.ts'
9
+
10
+ export type ErrorViewProps = Override<
11
+ LayoutWelcomeProps,
12
+ {
13
+ error: unknown
14
+ }
15
+ >
16
+
17
+ export const ErrorView = memo(function ErrorView({
18
+ error,
19
+
20
+ // LayoutWelcome
21
+ title,
22
+ ...props
23
+ }: ErrorViewProps) {
24
+ const { t } = useLingui()
25
+
26
+ return (
27
+ <LayoutWelcome {...props} title={title ?? t`Error`}>
28
+ <ErrorCard error={error} />
29
+ </LayoutWelcome>
30
+ )
31
+ })
@@ -3,6 +3,7 @@ import type { Readable } from 'node:stream'
3
3
  export type Asset = {
4
4
  url: string
5
5
  type?: string
6
+ isEntry: boolean
6
7
  sha256: string
7
8
  createStream: () => Readable
8
9
  }
@@ -4,6 +4,7 @@ import {
4
4
  validateFetchSite,
5
5
  writeStream,
6
6
  } from '../lib/http/index.js'
7
+ import { Asset } from './asset.js'
7
8
  import { ASSETS_URL_PREFIX, getAsset } from './index.js'
8
9
 
9
10
  export function authorizeAssetsMiddleware(): Middleware {
@@ -15,17 +16,24 @@ export function authorizeAssetsMiddleware(): Middleware {
15
16
  string,
16
17
  string | undefined,
17
18
  ]
19
+ if (query) return next()
20
+
18
21
  const filename = pathname.slice(ASSETS_URL_PREFIX.length)
19
22
  if (!filename) return next()
20
23
 
21
- const asset = await getAsset(filename).catch(() => null)
22
- if (!asset) return next()
24
+ let asset: Asset
25
+ try {
26
+ asset = getAsset(filename)
27
+ } catch {
28
+ // Filename not found or not valid
29
+ return next()
30
+ }
23
31
 
24
32
  try {
25
33
  // Allow "null" (ie. no header) to allow loading assets outside of a
26
34
  // fetch context (not from a web page).
27
- validateFetchSite(req, res, [null, 'same-origin'])
28
- validateFetchDest(req, res, [null, 'style', 'script'])
35
+ validateFetchSite(req, res, [null, 'none', 'cross-site', 'same-origin'])
36
+ validateFetchDest(req, res, [null, 'document', 'style', 'script'])
29
37
  } catch (err) {
30
38
  return next(err)
31
39
  }
@@ -35,10 +43,7 @@ export function authorizeAssetsMiddleware(): Middleware {
35
43
  }
36
44
 
37
45
  res.setHeader('ETag', asset.sha256)
38
-
39
- if (query === asset.sha256) {
40
- res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
41
- }
46
+ res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
42
47
 
43
48
  writeStream(res, asset.createStream(), { contentType: asset.type })
44
49
  }