serverless-ircd 0.5.0 → 0.7.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 (394) hide show
  1. package/.github/workflows/ci.yml +40 -3
  2. package/.github/workflows/deploy-aws.yml +1 -2
  3. package/.github/workflows/deploy-cf-tcp.yml +1 -5
  4. package/.github/workflows/deploy-cf.yml +1 -2
  5. package/.gitmodules +3 -0
  6. package/CHANGELOG.md +474 -0
  7. package/README.md +219 -22
  8. package/apps/aws-stack/README.md +33 -0
  9. package/apps/aws-stack/package.json +1 -1
  10. package/apps/aws-stack/src/aws-stack.ts +74 -9
  11. package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
  12. package/apps/aws-stack/tests/stack.test.ts +2 -2
  13. package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
  14. package/apps/aws-stack/tests/vitest.setup.ts +18 -0
  15. package/apps/aws-stack/vitest.config.ts +3 -0
  16. package/apps/cf-tcp-container/package.json +1 -1
  17. package/apps/cf-tcp-container/src/config-loader.ts +6 -7
  18. package/apps/cf-tcp-container/src/container-server.ts +3 -0
  19. package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
  20. package/apps/cf-tcp-container/wrangler.toml +1 -1
  21. package/apps/cf-worker/package.json +2 -1
  22. package/apps/cf-worker/src/origin-allowlist.ts +99 -0
  23. package/apps/cf-worker/src/worker.ts +89 -9
  24. package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +18 -0
  25. package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
  26. package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
  27. package/apps/cf-worker/tests/smoke.test.ts +28 -2
  28. package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
  29. package/apps/cf-worker/vitest.config.ts +1 -1
  30. package/apps/cf-worker/wrangler.test.toml +23 -0
  31. package/apps/cf-worker/wrangler.toml +59 -3
  32. package/apps/local-cli/package.json +1 -1
  33. package/apps/local-cli/src/config-loader.ts +6 -6
  34. package/apps/local-cli/src/main.ts +20 -0
  35. package/apps/local-cli/src/server.ts +9 -7
  36. package/apps/local-cli/tests/e2e.test.ts +112 -28
  37. package/apps/local-cli/tests/tcp.test.ts +71 -19
  38. package/apps/local-cli/tests/ws-subprotocol.test.ts +12 -7
  39. package/apps/web/landing/index.html +217 -0
  40. package/apps/web/package.json +28 -0
  41. package/apps/web/scripts/build.mjs +125 -0
  42. package/apps/web/src/build-env.ts +43 -0
  43. package/apps/web/src/config-schema.ts +138 -0
  44. package/apps/web/static/config.json +28 -0
  45. package/apps/web/static/config.prod.json +28 -0
  46. package/apps/web/static/config.staging.json +28 -0
  47. package/apps/web/tests/build-env.test.ts +63 -0
  48. package/apps/web/tests/build-smoke.test.ts +103 -0
  49. package/apps/web/tests/config-schema.test.ts +432 -0
  50. package/apps/web/tests/workspace.test.ts +12 -0
  51. package/apps/web/tsconfig.json +10 -0
  52. package/apps/web/upstream/.browserslistrc +3 -0
  53. package/apps/web/upstream/.editorconfig +9 -0
  54. package/apps/web/upstream/.eslintignore +3 -0
  55. package/apps/web/upstream/.eslintrc.js +90 -0
  56. package/apps/web/upstream/.github/FUNDING.yml +12 -0
  57. package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  58. package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  59. package/apps/web/upstream/.github/workflows/push.yml +26 -0
  60. package/apps/web/upstream/.github/workflows/release.yml +33 -0
  61. package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
  62. package/apps/web/upstream/.prettierrc.js +9 -0
  63. package/apps/web/upstream/.stylelintignore +5 -0
  64. package/apps/web/upstream/.stylelintrc.js +21 -0
  65. package/apps/web/upstream/LICENSE +201 -0
  66. package/apps/web/upstream/README.md +76 -0
  67. package/apps/web/upstream/babel.config.js +25 -0
  68. package/apps/web/upstream/crowdin.yml +3 -0
  69. package/apps/web/upstream/index.html +35 -0
  70. package/apps/web/upstream/jest.config.js +7 -0
  71. package/apps/web/upstream/jsdoc.json +22 -0
  72. package/apps/web/upstream/package.json +80 -0
  73. package/apps/web/upstream/scripts/pre-commit +43 -0
  74. package/apps/web/upstream/src/components/App.vue +516 -0
  75. package/apps/web/upstream/src/components/AppSettings.vue +587 -0
  76. package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
  77. package/apps/web/upstream/src/components/Avatar.vue +105 -0
  78. package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
  79. package/apps/web/upstream/src/components/BufferKey.vue +121 -0
  80. package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
  81. package/apps/web/upstream/src/components/Captcha.vue +85 -0
  82. package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
  83. package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
  84. package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
  85. package/apps/web/upstream/src/components/ChannelList.vue +345 -0
  86. package/apps/web/upstream/src/components/Container.vue +401 -0
  87. package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
  88. package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
  89. package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
  90. package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
  91. package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
  92. package/apps/web/upstream/src/components/MessageList.vue +1028 -0
  93. package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
  94. package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
  95. package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
  96. package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
  97. package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
  98. package/apps/web/upstream/src/components/Nicklist.vue +309 -0
  99. package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
  100. package/apps/web/upstream/src/components/NotConnected.vue +271 -0
  101. package/apps/web/upstream/src/components/SelfUser.vue +283 -0
  102. package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
  103. package/apps/web/upstream/src/components/ServerView.vue +126 -0
  104. package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
  105. package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
  106. package/apps/web/upstream/src/components/Sidebar.vue +429 -0
  107. package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
  108. package/apps/web/upstream/src/components/SidebarState.vue +145 -0
  109. package/apps/web/upstream/src/components/StartupError.vue +35 -0
  110. package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
  111. package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
  112. package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
  113. package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
  114. package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
  115. package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
  116. package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
  117. package/apps/web/upstream/src/components/UserBox.vue +728 -0
  118. package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
  119. package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
  120. package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
  121. package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
  122. package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
  123. package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
  124. package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
  125. package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
  126. package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
  127. package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
  128. package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
  129. package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
  130. package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
  131. package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
  132. package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
  133. package/apps/web/upstream/src/helpers/Colours.js +128 -0
  134. package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
  135. package/apps/web/upstream/src/helpers/Md5.js +193 -0
  136. package/apps/web/upstream/src/helpers/Misc.js +401 -0
  137. package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
  138. package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
  139. package/apps/web/upstream/src/libs/AudioManager.js +60 -0
  140. package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
  141. package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
  142. package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
  143. package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
  144. package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
  145. package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
  146. package/apps/web/upstream/src/libs/IPC.js +51 -0
  147. package/apps/web/upstream/src/libs/InputHandler.js +921 -0
  148. package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
  149. package/apps/web/upstream/src/libs/Logger.js +71 -0
  150. package/apps/web/upstream/src/libs/Message.js +169 -0
  151. package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
  152. package/apps/web/upstream/src/libs/MessageParser.js +249 -0
  153. package/apps/web/upstream/src/libs/Notifications.js +78 -0
  154. package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
  155. package/apps/web/upstream/src/libs/ServerSession.js +102 -0
  156. package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
  157. package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
  158. package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
  159. package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
  160. package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
  161. package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
  162. package/apps/web/upstream/src/libs/bufferTools.js +177 -0
  163. package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
  164. package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
  165. package/apps/web/upstream/src/libs/settingTools.js +31 -0
  166. package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
  167. package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
  168. package/apps/web/upstream/src/libs/state/UserState.js +89 -0
  169. package/apps/web/upstream/src/libs/state/common.js +26 -0
  170. package/apps/web/upstream/src/libs/state.js +961 -0
  171. package/apps/web/upstream/src/libs/storage/Local.js +51 -0
  172. package/apps/web/upstream/src/main.js +566 -0
  173. package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
  174. package/apps/web/upstream/src/res/configTemplates.js +373 -0
  175. package/apps/web/upstream/src/res/globalStyle.css +277 -0
  176. package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
  177. package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
  178. package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
  179. package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
  180. package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
  181. package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
  182. package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
  183. package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
  184. package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
  185. package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
  186. package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
  187. package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
  188. package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
  189. package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
  190. package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
  191. package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
  192. package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
  193. package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
  194. package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
  195. package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
  196. package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
  197. package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
  198. package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
  199. package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
  200. package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
  201. package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
  202. package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
  203. package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
  204. package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
  205. package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
  206. package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
  207. package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
  208. package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
  209. package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
  210. package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
  211. package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
  212. package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
  213. package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
  214. package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
  215. package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
  216. package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
  217. package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
  218. package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
  219. package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
  220. package/apps/web/upstream/src/res/localesList.json +27 -0
  221. package/apps/web/upstream/src/res/logo.png +0 -0
  222. package/apps/web/upstream/src/thirdparty/about.html +43 -0
  223. package/apps/web/upstream/src/thirdparty/index.js +5 -0
  224. package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
  225. package/apps/web/upstream/static/config.json +28 -0
  226. package/apps/web/upstream/static/emoticons/smile.png +0 -0
  227. package/apps/web/upstream/static/favicon.png +0 -0
  228. package/apps/web/upstream/static/highlight.mp3 +0 -0
  229. package/apps/web/upstream/static/highlight.ogg +0 -0
  230. package/apps/web/upstream/static/locales/.gitignore +2 -0
  231. package/apps/web/upstream/static/plugins/customise.html.example +10 -0
  232. package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
  233. package/apps/web/upstream/static/themes/common/base.css +1064 -0
  234. package/apps/web/upstream/static/themes/dark/theme.css +196 -0
  235. package/apps/web/upstream/static/themes/default/theme.css +92 -0
  236. package/apps/web/upstream/static/themes/elite/theme.css +248 -0
  237. package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
  238. package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
  239. package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
  240. package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
  241. package/apps/web/upstream/static/themes/sky/theme.css +107 -0
  242. package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
  243. package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
  244. package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
  245. package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
  246. package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
  247. package/apps/web/upstream/vue.config.js +150 -0
  248. package/apps/web/upstream/yarn.lock +9767 -0
  249. package/apps/web/vitest.config.ts +13 -0
  250. package/biome.json +2 -0
  251. package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
  252. package/docs/ADR-002-location-of-authority.md +82 -0
  253. package/docs/ADR-003-durable-object-sharding.md +110 -0
  254. package/docs/ADR-004-dynamodb-schema.md +96 -0
  255. package/docs/ADR-005-wss-only-transport-v1.md +83 -0
  256. package/docs/ADR-006-sasl-mechanism-scope.md +96 -0
  257. package/docs/ADR-007-deterministic-ports.md +82 -0
  258. package/docs/ADR-008-monorepo-tooling.md +60 -0
  259. package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
  260. package/docs/ADR-Index.md +15 -0
  261. package/docs/AWS-Adapter-Architecture.md +496 -0
  262. package/docs/AWS-Deployment.md +1275 -0
  263. package/docs/AWS-TCP-Deployment.md +410 -0
  264. package/docs/Cloudflare-Deployment-Guide.md +719 -0
  265. package/docs/Cloudflare-TCP-Deployment.md +510 -0
  266. package/docs/Home.md +16 -0
  267. package/docs/Observability.md +87 -0
  268. package/docs/PlanExtensions.md +812 -0
  269. package/docs/PlanIRCv3Websocket.md +503 -0
  270. package/docs/PlanWebClient.md +451 -0
  271. package/docs/Release-Process.md +443 -0
  272. package/docs/WebClientGuide.md +535 -0
  273. package/package.json +2 -2
  274. package/packages/aws-adapter/package.json +1 -1
  275. package/packages/aws-adapter/src/handlers/default.ts +3 -0
  276. package/packages/aws-adapter/src/handlers/nlb-stream.ts +9 -0
  277. package/packages/aws-adapter/src/serialize.ts +29 -1
  278. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
  279. package/packages/aws-adapter/tests/account-store.test.ts +4 -7
  280. package/packages/aws-adapter/tests/aws-harness.ts +5 -2
  281. package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
  282. package/packages/aws-adapter/tests/aws-runtime.test.ts +79 -0
  283. package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
  284. package/packages/aws-adapter/tests/connect.test.ts +9 -9
  285. package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
  286. package/packages/aws-adapter/tests/global-setup.ts +55 -30
  287. package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
  288. package/packages/aws-adapter/tests/handlers.test.ts +90 -4
  289. package/packages/aws-adapter/tests/message-store.test.ts +6 -5
  290. package/packages/aws-adapter/tests/nlb-stream.test.ts +60 -8
  291. package/packages/cf-adapter/package.json +2 -1
  292. package/packages/cf-adapter/src/connection-do.ts +3 -0
  293. package/packages/cf-adapter/src/serialize.ts +25 -4
  294. package/packages/cf-adapter/tests/cf-harness.ts +12 -1
  295. package/packages/cf-adapter/tests/cf-runtime.test.ts +269 -0
  296. package/packages/cf-adapter/tests/channel-do.test.ts +117 -0
  297. package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
  298. package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
  299. package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
  300. package/packages/cf-adapter/tests/registry-do.test.ts +104 -0
  301. package/packages/cf-adapter/tests/serialize.test.ts +308 -0
  302. package/packages/cf-adapter/vitest.config.ts +1 -1
  303. package/packages/in-memory-runtime/package.json +1 -1
  304. package/packages/irc-core/package.json +1 -1
  305. package/packages/irc-core/reports/mutation/mutation.html +342 -0
  306. package/packages/irc-core/src/caps/capabilities.ts +37 -3
  307. package/packages/irc-core/src/caps/index.ts +1 -0
  308. package/packages/irc-core/src/caps/sts.ts +84 -0
  309. package/packages/irc-core/src/commands/account-notify.ts +53 -0
  310. package/packages/irc-core/src/commands/away.ts +9 -3
  311. package/packages/irc-core/src/commands/cap.ts +23 -5
  312. package/packages/irc-core/src/commands/chathistory.ts +40 -11
  313. package/packages/irc-core/src/commands/index.ts +34 -0
  314. package/packages/irc-core/src/commands/isupport.ts +53 -0
  315. package/packages/irc-core/src/commands/mode.ts +8 -1
  316. package/packages/irc-core/src/commands/monitor.ts +327 -0
  317. package/packages/irc-core/src/commands/multiline.ts +256 -0
  318. package/packages/irc-core/src/commands/pre-away.ts +112 -0
  319. package/packages/irc-core/src/commands/privmsg.ts +19 -12
  320. package/packages/irc-core/src/commands/read-marker.ts +108 -0
  321. package/packages/irc-core/src/commands/registration.ts +8 -0
  322. package/packages/irc-core/src/commands/sasl.ts +24 -1
  323. package/packages/irc-core/src/commands/tagmsg.ts +71 -1
  324. package/packages/irc-core/src/commands/whois.ts +5 -0
  325. package/packages/irc-core/src/config.ts +65 -10
  326. package/packages/irc-core/src/effects.ts +14 -0
  327. package/packages/irc-core/src/ports.ts +389 -0
  328. package/packages/irc-core/src/protocol/index.ts +14 -0
  329. package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
  330. package/packages/irc-core/src/protocol/numerics.ts +15 -0
  331. package/packages/irc-core/src/protocol/outbound.ts +16 -1
  332. package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
  333. package/packages/irc-core/src/state/connection.ts +32 -1
  334. package/packages/irc-core/src/types.ts +112 -0
  335. package/packages/irc-core/stryker.commands.conf.json +1 -2
  336. package/packages/irc-core/tests/away-store.test.ts +73 -0
  337. package/packages/irc-core/tests/batch.test.ts +15 -0
  338. package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
  339. package/packages/irc-core/tests/caps/sts.test.ts +123 -0
  340. package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
  341. package/packages/irc-core/tests/commands/away.test.ts +85 -2
  342. package/packages/irc-core/tests/commands/cap.test.ts +165 -0
  343. package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
  344. package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
  345. package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
  346. package/packages/irc-core/tests/commands/mode.test.ts +72 -0
  347. package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
  348. package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
  349. package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
  350. package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
  351. package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
  352. package/packages/irc-core/tests/commands/registration.test.ts +64 -0
  353. package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
  354. package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
  355. package/packages/irc-core/tests/commands/whois.test.ts +34 -0
  356. package/packages/irc-core/tests/config.test.ts +103 -13
  357. package/packages/irc-core/tests/labeled-response.test.ts +181 -0
  358. package/packages/irc-core/tests/message-tags.test.ts +114 -0
  359. package/packages/irc-core/tests/monitor-store.test.ts +200 -0
  360. package/packages/irc-core/tests/numerics.test.ts +13 -0
  361. package/packages/irc-core/tests/parser.test.ts +109 -0
  362. package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
  363. package/packages/irc-core/tests/standard-replies.test.ts +412 -0
  364. package/packages/irc-core/tests/types.test.ts +27 -0
  365. package/packages/irc-server/package.json +1 -1
  366. package/packages/irc-server/src/actor.ts +450 -3
  367. package/packages/irc-server/src/dispatch.ts +108 -16
  368. package/packages/irc-server/tests/actor.test.ts +1432 -39
  369. package/packages/irc-server/tests/dispatch.test.ts +263 -2
  370. package/packages/irc-test-support/package.json +1 -1
  371. package/packages/irc-test-support/src/index.ts +6 -0
  372. package/packages/irc-test-support/src/scenarios.ts +24 -2
  373. package/packages/irc-test-support/src/test-config.ts +54 -0
  374. package/packages/irc-test-support/tests/test-config.test.ts +51 -0
  375. package/tools/ci-hardening/package.json +1 -1
  376. package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
  377. package/tools/load-test/package.json +33 -0
  378. package/tools/load-test/src/client.ts +351 -0
  379. package/tools/load-test/src/config.ts +313 -0
  380. package/tools/load-test/src/harness.ts +116 -0
  381. package/tools/load-test/src/main.ts +120 -0
  382. package/tools/load-test/src/metrics.ts +168 -0
  383. package/tools/load-test/src/report.ts +106 -0
  384. package/tools/load-test/tests/client.test.ts +212 -0
  385. package/tools/load-test/tests/config.test.ts +152 -0
  386. package/tools/load-test/tests/framing.test.ts +37 -0
  387. package/tools/load-test/tests/harness.test.ts +165 -0
  388. package/tools/load-test/tests/metrics.test.ts +174 -0
  389. package/tools/load-test/tests/report.test.ts +161 -0
  390. package/tools/load-test/tests/smoke.test.ts +67 -0
  391. package/tools/load-test/tsconfig.build.json +12 -0
  392. package/tools/load-test/tsconfig.test.json +10 -0
  393. package/tools/load-test/vitest.config.ts +29 -0
  394. package/tools/tcp-ws-forwarder/package.json +1 -1
@@ -0,0 +1,535 @@
1
+ # Web Client Guide
2
+
3
+ End-to-end contributor/operator doc for the ServerlessIRCd web client: a
4
+ vendored **Kiwi IRC** SPA served at `/app/` and a static project landing
5
+ page served at `/` by the Cloudflare Worker. The browser opens a native
6
+ `wss://` WebSocket straight to the Worker's existing IRC-over-WebSocket
7
+ endpoint — no proxy, gateway, or transport adaptation in the hot path.
8
+
9
+ Cross-reference: `PLAN.md` §6.1 (CF mapping), `docs/PlanWebClient.md`
10
+ (design), `docs/Cloudflare-Deployment-Guide.md` (Worker deploy),
11
+ `apps/cf-worker/wrangler.toml`, `apps/web/`.
12
+
13
+ **Acceptance criterion (TICKET-139):** a new contributor can
14
+ `git submodule update --init`, `pnpm install`,
15
+ `pnpm --filter web build:staging`, and `wrangler deploy --env staging`
16
+ following only this doc.
17
+
18
+ ---
19
+
20
+ ## 1. How the pieces fit
21
+
22
+ ```
23
+ ┌──────────────────────────────────────────────────────────────┐
24
+ │ Browser │
25
+ │ GET / → landing page (project front door) │
26
+ │ GET /app/ → Kiwi IRC SPA (index.html + JS bundle) │
27
+ │ wss://.../ → one IRC message per WS text frame │
28
+ └──────────────────────────────────────────────────────────────┘
29
+ │ │
30
+ │ HTTPS (static assets) │ wss (IRC-over-WebSocket)
31
+ ▼ ▼
32
+ ┌──────────────────────────────────────────────────────────────┐
33
+ │ apps/cf-worker (Cloudflare Worker) │
34
+ │ fetch(request, env): │
35
+ │ Upgrade: websocket → Origin check → ConnectionDO │
36
+ │ GET /health → plaintext liveness string │
37
+ │ else → env.ASSETS (apps/web/dist) │
38
+ └──────────────────────────────────────────────────────────────┘
39
+ ```
40
+
41
+ The Worker is the single origin for the landing page, the SPA, **and**
42
+ the WebSocket. Same-origin by default — which is what makes the CSWSH
43
+ defense in §5 almost zero-config. A Cloudflare Pages deployment of the
44
+ same `dist/` is a documented alternative for prod (see §7).
45
+
46
+ Path layout on the Worker:
47
+
48
+ | Path | Served by | Content |
49
+ |------------|--------------------------|----------------------------------|
50
+ | `/` | `[assets]` binding | `dist/index.html` (landing page) |
51
+ | `/app/` | `[assets]` binding | `dist/app/index.html` (Kiwi SPA) |
52
+ | `/app/static/...` | `[assets]` binding | Kiwi JS/CSS/themes + baked config |
53
+ | `/health` | `worker.ts` handler | Plaintext liveness string |
54
+ | `/` (WS upgrade) | `worker.ts` → `ConnectionDO` | IRC session |
55
+
56
+ The WS endpoint and the landing page share path `/`. The Worker runs
57
+ first for every request (`run_worker_first = true` in `wrangler.toml`)
58
+ so the `Upgrade: websocket` check fires before the asset platform
59
+ serves `/` — otherwise an upgrade would get a `200 OK` landing page
60
+ instead of a `101` switch.
61
+
62
+ ---
63
+
64
+ ## 2. Prerequisites
65
+
66
+ | Requirement | Version / detail |
67
+ |---|---|
68
+ | Node.js | ≥ 24 (matches CI; `engines.node` in root `package.json`) |
69
+ | pnpm | 11.x (`packageManager` pin in root `package.json`) |
70
+ | yarn | Required **only** to build the SPA. Kiwi ships a `yarn.lock`; `corepack enable` provides yarn on any Node. |
71
+ | `wrangler` CLI | Comes from `apps/cf-worker/devDependencies`; no global install needed. |
72
+ | Cloudflare account | For deploy only (see `docs/Cloudflare-Deployment-Guide.md`). Local dev needs nothing. |
73
+
74
+ Confirm the local environment:
75
+
76
+ ```bash
77
+ node --version # v24.x or newer
78
+ corepack enable # lets the pinned pnpm + yarn run
79
+ pnpm --version # 11.x
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 3. Quickstart (clean checkout → deployed staging SPA)
85
+
86
+ ```bash
87
+ # 1. Pull submodules (docs wiki + Kiwi IRC upstream sources).
88
+ git submodule update --init
89
+
90
+ # 2. Install workspace deps.
91
+ pnpm install
92
+
93
+ # 3. Build the SPA + landing page (default config = staging target).
94
+ pnpm --filter web build:staging
95
+ # Equivalent: pnpm --filter @serverless-ircd/web build:staging
96
+ # → apps/web/dist/index.html (landing page)
97
+ # → apps/web/dist/app/index.html (Kiwi SPA)
98
+ # → apps/web/dist/app/static/config.json (baked, env-specific)
99
+
100
+ # 4. Build the rest of the workspace (Worker imports compiled dist/).
101
+ pnpm build
102
+
103
+ # 5. Serve everything locally (Worker + SPA + landing page).
104
+ pnpm --filter @serverless-ircd/cf-worker dev
105
+ # → http://localhost:8787/ landing page
106
+ # → http://localhost:8787/app/ Kiwi SPA
107
+ # → ws://localhost:8787/ IRC-over-WebSocket (same endpoint)
108
+
109
+ # 6. Deploy staging (Worker + assets in one command).
110
+ pnpm deploy:cf:staging
111
+ # → wrangler deploy --env staging
112
+ ```
113
+
114
+ Open `http://localhost:8787/app/` in a browser — Kiwi boots, reads
115
+ `/app/static/config.json`, and opens a `wss://` (or `ws://` in local
116
+ dev) connection to the same origin.
117
+
118
+ > **Forgot the submodule?** `apps/web/scripts/build.mjs` fails fast:
119
+ > `Kiwi upstream missing at .../upstream. Run
120
+ > 'git submodule update --init apps/web/upstream' first.`
121
+
122
+ ---
123
+
124
+ ## 4. The build pipeline (`apps/web/scripts/build.mjs`)
125
+
126
+ The build is an orchestrated layering over the upstream Kiwi build —
127
+ **we own no IRC logic**, only config + asset placement.
128
+
129
+ ### 4.1 Per-environment config matrix
130
+
131
+ | `pnpm ...` command | Reads | Bakes into `dist/app/static/config.json` |
132
+ |---|---|---|
133
+ | `--filter web build` | `static/config.json` | default (staging-shaped) config |
134
+ | `--filter web build:staging` | `static/config.staging.json` | staging config |
135
+ | `--filter web build:prod` | `static/config.prod.json` | prod config |
136
+
137
+ `--env` is parsed by `src/build-env.ts`; an unknown value aborts the
138
+ build with a readable error. The selected file is validated against the
139
+ Zod schema (`src/config-schema.ts`) **before** it is baked, so a
140
+ malformed config fails the build rather than shipping a broken SPA.
141
+
142
+ ### 4.2 What the build does, in order
143
+
144
+ 1. **Verify the submodule** exists (`upstream/`), or fail with the
145
+ hint quoted above.
146
+ 2. **`yarn install --frozen-lockfile`** inside `upstream/` (only if
147
+ `upstream/node_modules` is missing). `COREPACK_ROOT` is set so yarn
148
+ classic doesn't refuse on the pnpm-managed workspace root. On
149
+ Node ≥ 17 the OpenSSL legacy provider is enabled for webpack 5's md4
150
+ hashing.
151
+ 3. **Patch `upstream/vue.config.js`** `publicPath: ''` →
152
+ `publicPath: '/app/'`, run `yarn build`, then **restore** the file
153
+ in `finally` so the submodule stays clean. This is why every hashed
154
+ asset URL comes out `/app/static/js/app.<hash>.js` instead of
155
+ `static/js/...`, which would 404 at root.
156
+ 4. **Copy `upstream/dist/` → `apps/web/dist/app/`.**
157
+ 5. **Rewrite bare `static/...` refs** in the baked `index.html` (e.g.
158
+ the favicon, which webpack doesn't run through `publicPath`) to
159
+ `/app/static/...`.
160
+ 6. **Validate + bake the per-env config**: read
161
+ `static/config.<env>.json`, run `parseKiwiConfig`, re-serialise
162
+ (stable key order), write to `dist/app/static/config.json`.
163
+ 7. **Copy the landing page** `landing/index.html` → `dist/index.html`.
164
+
165
+ Output is reproducible: same checkout + same `--env` → byte-identical
166
+ `dist/` (modulo upstream webpack hash stability).
167
+
168
+ ### 4.3 The config schema (`src/config-schema.ts`)
169
+
170
+ The schema models only the fields ServerlessIRCd cares about; the rest
171
+ of Kiwi's surface is passed through untouched (`.passthrough()`).
172
+
173
+ ```jsonc
174
+ // static/config.staging.json
175
+ {
176
+ "windowTitle": "ServerlessIRCd (staging)",
177
+ "startupOptions": {
178
+ "server": "{{hostname}}", // resolved to window.location.hostname at boot
179
+ "direct": true, // MUST be true — direct WS, no kiwiServer gateway
180
+ "tls": true, // MUST be true — wss:// (page is HTTPS)
181
+ "port": 443,
182
+ "channel": "#welcome",
183
+ "nick": "Guest"
184
+ }
185
+ }
186
+ ```
187
+
188
+ Field notes (see the schema docstrings for the full rationale):
189
+
190
+ - **`startupOptions.server`** — `{{hostname}}` (or `{{host}}` to keep
191
+ a non-default port) is a Kiwi template resolved by the browser to
192
+ `window.location.hostname` at load time. This is what lets one build
193
+ artifact target staging, prod, preview, and PR deploys without a
194
+ per-env hostname. A literal hostname is also accepted.
195
+ - **`startupOptions.direct`** — MUST be `true`. ServerlessIRCd is a
196
+ direct IRC-over-WebSocket server; the Kiwi default (`false`) routes
197
+ through a `kiwiServer` WebIRC gateway that nothing serves here.
198
+ - **`startupOptions.tls`** — MUST be `true` in any HTTPS deployment
199
+ (browsers refuse `ws://` on an HTTPS page).
200
+ - **`startupOptions.direct_path`** — the WS path. Kiwi reads
201
+ `direct_path` (NOT `path`); the Worker exposes its WS upgrade at
202
+ root, so this MUST be `/` if supplied. Optional — when omitted,
203
+ irc-framework builds `wss://host:port` (no path), which the browser
204
+ normalises to `wss://host:port/`.
205
+ - **`startupOptions.channel`** — must begin with `#` (matches
206
+ `CHANTYPES=#`); comma-separated lists are accepted.
207
+
208
+ A malformed config aborts the build with an aggregated error, e.g.:
209
+
210
+ ```
211
+ config.staging.json: Invalid Kiwi web config:
212
+ - startupOptions.port: port must be a valid TCP port (1-65535)
213
+ - startupOptions.channel: must begin with '#'
214
+ ```
215
+
216
+ ### 4.4 Tests
217
+
218
+ ```bash
219
+ pnpm --filter web test # schema + build-env + workspace tests
220
+ pnpm --filter web typecheck
221
+ ```
222
+
223
+ The schema and build-env tests are pure (no Kiwi build needed). The
224
+ build-smoke tests (`tests/build-smoke.test.ts`) assert the **landing
225
+ page** source unconditionally and the `dist/` output only when a build
226
+ is present (`describe.skipIf(!built)`) — the Kiwi build is too heavy
227
+ for the unit suite, so run `pnpm --filter web build` first to exercise
228
+ those assertions.
229
+
230
+ ---
231
+
232
+ ## 5. CSWSH defense and the `WEB_ORIGINS` var
233
+
234
+ WebSocket upgrades do **not** follow the same-origin policy. A
235
+ malicious page can `new WebSocket('wss://irc.example.com/')` from the
236
+ victim's browser and drive an IRC session with the victim's
237
+ credentials (Cross-Site WebSocket Hijacking, CSWSH). The Worker
238
+ rejects upgrades whose `Origin` is not allowed — see
239
+ `apps/cf-worker/src/origin-allowlist.ts`.
240
+
241
+ ### 5.1 Two enforcement modes (evaluated in order)
242
+
243
+ 1. **Explicit allowlist** — when `WEB_ORIGINS` is set, only listed
244
+ origins proceed. Use this for cross-origin deployments (SPA on a
245
+ different domain than the Worker).
246
+ 2. **Auto-derive (same-origin)** — when `WEB_ORIGINS` is unset/empty
247
+ (the default), the Worker compares the browser's `Origin` against
248
+ the request's own origin (`${protocol}//${host}`). If they match,
249
+ the page opening the WS is the same origin as the server → safe.
250
+
251
+ Non-browser clients (curl, WeeChat, the `tcp-ws-forwarder`, scripted
252
+ test harnesses) never send `Origin`; their upgrades pass through
253
+ unchanged in **both** modes, so existing tooling keeps working.
254
+
255
+ ### 5.2 `WEB_ORIGINS` is optional
256
+
257
+ Because mode 2 (same-origin auto-derive) is the default and requires
258
+ zero per-env configuration, **`WEB_ORIGINS` is optional**. Most
259
+ deployments never need to set it: the SPA and the Worker are on the
260
+ same origin, so the auto-derive check passes for every browser and
261
+ rejects every cross-origin attempt. It works for `*.workers.dev`,
262
+ custom domains, and preview URLs alike.
263
+
264
+ Set `WEB_ORIGINS` only when the SPA is served from a **different**
265
+ origin than the Worker (e.g. a Cloudflare Pages deployment — see §7 —
266
+ or a separate static host). It is a comma-separated list of origins,
267
+ lowercased and whitespace-trimmed:
268
+
269
+ ```toml
270
+ # apps/cf-worker/wrangler.toml — cross-origin example
271
+ [vars]
272
+ WEB_ORIGINS = "https://irc.example.com,https://web.example.com"
273
+ ```
274
+
275
+ ```bash
276
+ # or per-deploy via the secret store / dashboard env vars:
277
+ wrangler secret put WEB_ORIGINS --env staging
278
+ ```
279
+
280
+ ### 5.3 Responses
281
+
282
+ | Scenario | Response |
283
+ |---|---|
284
+ | `Origin` allowed (allowlist or same-origin) | `101 Switching Protocols` → `ConnectionDO` |
285
+ | `Origin` header absent (non-browser client) | `101` (unchanged) |
286
+ | `Origin` present but not allowed | `403 Forbidden` (`WebSocket upgrade refused: the request Origin is not allowed.`) |
287
+ | No allowlist AND `Host` undeterminable | `403 Forbidden` (defensive fail-closed; does not fire on Cloudflare, which always sets `Host`) |
288
+
289
+ The 403 path is unit-tested in `apps/cf-worker/tests/ws-origin.test.ts`
290
+ and the pure policy in `origin-allowlist.test.ts`. There is no
291
+ fail-open path.
292
+
293
+ ---
294
+
295
+ ## 6. Local development
296
+
297
+ ### 6.1 Serve the SPA from the Worker (`wrangler dev`)
298
+
299
+ This is the canonical local loop — same-origin, real `workerd`, real
300
+ Durable Objects:
301
+
302
+ ```bash
303
+ pnpm --filter web build # build the SPA + landing page
304
+ pnpm build # build the rest of the workspace
305
+ pnpm --filter @serverless-ircd/cf-worker dev
306
+ ```
307
+
308
+ Then:
309
+
310
+ - `http://localhost:8787/` — landing page.
311
+ - `http://localhost:8787/app/` — Kiwi SPA (reads
312
+ `/app/static/config.json`, opens `ws://localhost:8787/`).
313
+ - `http://localhost:8787/health` — plaintext liveness.
314
+
315
+ Smoke the WS path directly (no browser needed):
316
+
317
+ ```bash
318
+ node apps/cf-worker/scripts/smoke.mjs
319
+ # → replays CONNECT/NICK/USER/JOIN/PRIVMSG/QUIT, asserts 001/376/353/366
320
+ ```
321
+
322
+ ### 6.2 Iterate on the SPA config without rebuilding Kiwi
323
+
324
+ The Kiwi upstream build is the slow step. For a config-only change,
325
+ edit `static/config.staging.json`, then re-run
326
+ `pnpm --filter web build:staging` — step 2 of the build reuses
327
+ `upstream/node_modules` and skips straight to re-baking the config
328
+ (steps 4–6 are fast).
329
+
330
+ ### 6.3 Iterate on the landing page
331
+
332
+ `landing/index.html` is pure static HTML + inline CSS, no JavaScript,
333
+ no build step. Edit it and re-run the build (it is copied verbatim to
334
+ `dist/index.html`); or just preview it directly in a browser — it has
335
+ no runtime dependencies.
336
+
337
+ ---
338
+
339
+ ## 7. Optional: Cloudflare Pages instead of Worker assets
340
+
341
+ The build output (`apps/web/dist/`) is identical for both hosting
342
+ options; the choice is a deploy-time knob, not a build-time one. Use
343
+ Pages when you want to decouple static deploys from Worker deploys
344
+ (e.g. the frontend changes more often than the IRCd), or when prod
345
+ favors a dedicated static edge.
346
+
347
+ ### 7.1 Set up Pages
348
+
349
+ 1. Push the repo (or just `apps/web/dist/`) to a Pages-connected
350
+ branch, or use `wrangler pages deploy apps/web/dist`.
351
+ 2. Set the Pages project's output directory to `apps/web/dist` and the
352
+ build command to `pnpm --filter web build:prod` (plus `pnpm build`
353
+ if the Worker is deployed from the same repo).
354
+ 3. The Pages project is reachable at
355
+ `https://<project>.pages.dev` (or a custom domain).
356
+
357
+ ### 7.2 Allow the Pages origin on the Worker
358
+
359
+ Because the SPA is now on a **different** origin than the Worker, the
360
+ same-origin auto-derive (§5.1 mode 2) will reject the browser's WS
361
+ upgrade. Set `WEB_ORIGINS` on the Worker to the Pages origin:
362
+
363
+ ```toml
364
+ # apps/cf-worker/wrangler.toml
365
+ [vars]
366
+ WEB_ORIGINS = "https://<project>.pages.dev"
367
+ ```
368
+
369
+ The SPA config's `startupOptions.server` still resolves to the
370
+ **Worker's** hostname (where the WS endpoint lives), not the Pages
371
+ hostname — so set it to a literal if you've pinned it, or keep
372
+ `{{hostname}}` only if Pages and the Worker share a domain.
373
+
374
+ ---
375
+
376
+ ## 8. Optional: `webircgateway` SockJS fallback
377
+
378
+ ServerlessIRCd already speaks the wire format browser Kiwi expects:
379
+ native WebSocket text frames, one IRC message per frame, with
380
+ `\r\n`-joined-frame tolerance. The Worker's WS endpoint at `/` is all
381
+ Kiwi needs. **You do not need `webircgateway` for normal operation.**
382
+
383
+ When is it useful? Corporate proxies and some mobile carriers block
384
+ raw `wss://` (or upgrade it in ways that break long-lived frames).
385
+ `webircgateway` provides a SockJS + Kiwi multi-server transport that
386
+ falls back to HTTP polling when raw WS is unavailable. If your users
387
+ report "can't connect from the office Wi-Fi", that's the trigger.
388
+
389
+ The gateway is vendored separately (see the `webircgateway/` submodule
390
+ if present in your checkout). Running it as a sidecar is a deployment
391
+ concern outside this doc's scope; the key point is that it is
392
+ **strictly opt-in**. The default SPA build does not reference it, and
393
+ no `kiwiServer` / gateway URL is baked into the config.
394
+
395
+ To wire it up when needed, point Kiwi's `startupOptions.kiwiServer`
396
+ at the gateway and set `startupOptions.direct: false` — the opposite
397
+ of the ServerlessIRCd default. Document this only for the affected
398
+ deployment; don't change the default config.
399
+
400
+ ---
401
+
402
+ ## 9. Customising the landing page
403
+
404
+ The landing page (`apps/web/landing/index.html`) is the project's front
405
+ door: a visitor hitting `/` learns what ServerlessIRCd is and clicks
406
+ through to `/app/` to launch the client. It is intentionally
407
+ framework-free (pure HTML + inline CSS, no JS) so it renders without
408
+ JavaScript and costs nothing to serve.
409
+
410
+ To customise:
411
+
412
+ - **Copy/branding** — edit the `<header>`, `<main>`, and `<footer>`
413
+ blocks directly. The CSS variables at the top of the `<style>` block
414
+ (`--bg`, `--surface`, `--accent`, …) theme the whole page in one
415
+ place.
416
+ - **Links** — the page links to `/app/` (the SPA), `docs/`, and the
417
+ source repo. Update the repo URLs and the docs path to match your
418
+ deployment.
419
+ - **Accessibility** — keep the declared viewport meta tag and the
420
+ no-`<script>` invariant; both are asserted by
421
+ `tests/build-smoke.test.ts`. The build copies the file verbatim to
422
+ `dist/index.html`, so what you author is exactly what is served.
423
+
424
+ Re-run `pnpm --filter web build` (any `--env`) after edits; the build
425
+ test suite will fail loudly if you accidentally remove the `/app/`
426
+ link, the docs link, or the viewport tag.
427
+
428
+ ---
429
+
430
+ ## 10. Troubleshooting
431
+
432
+ ### 10.1 `Kiwi upstream missing at .../upstream`
433
+
434
+ The submodule wasn't initialised. Run:
435
+
436
+ ```bash
437
+ git submodule update --init apps/web/upstream
438
+ ```
439
+
440
+ The build fails fast with this hint rather than a confusing webpack
441
+ error.
442
+
443
+ ### 10.2 SPA loads but never connects ("connection refused" / spinning)
444
+
445
+ Almost always a config issue. Open the browser devtools Network tab
446
+ and check the WS upgrade:
447
+
448
+ - **`ws://` on an HTTPS page** — `startupOptions.tls` must be `true`
449
+ (browsers refuse insecure WS from secure pages). The default configs
450
+ set it; a hand-edited config may not.
451
+ - **`direct: false`** — Kiwi is probing a `kiwiServer` gateway that
452
+ nothing serves. `startupOptions.direct` MUST be `true`.
453
+ - **Wrong server** — if you pinned a literal `server`, it must be the
454
+ Worker's hostname. `{{hostname}}` resolves to
455
+ `window.location.hostname` and is the safest default.
456
+ - **403 on upgrade** — the `Origin` was rejected (§5). Check
457
+ `WEB_ORIGINS` if you're cross-origin, or confirm the SPA and Worker
458
+ are same-origin.
459
+
460
+ ### 10.3 Asset 404s at `/static/...` (instead of `/app/static/...`)
461
+
462
+ The `publicPath` patch in step 3 of the build didn't take. Confirm
463
+ `upstream/vue.config.js` still contains a `publicPath: ''` literal to
464
+ patch against (the build restores it after building). If upstream
465
+ changed the phrasing, the patch regex in `scripts/build.mjs` needs an
466
+ update.
467
+
468
+ ### 10.4 `200 OK` instead of `101 Switching Protocols` on WS upgrade
469
+
470
+ The asset platform served the landing page for the upgrade request.
471
+ This means `run_worker_first = true` is missing or false in the
472
+ `[assets]` block of `wrangler.toml`. The WS endpoint and the landing
473
+ page share path `/`, so the Worker MUST run first for everything and
474
+ fall through to assets only for non-WS requests.
475
+
476
+ ### 10.5 `Cannot find module '@serverless-ircd/cf-adapter'`
477
+
478
+ The workspace packages weren't built. Run `pnpm build` from the repo
479
+ root before `pnpm --filter web build` or `wrangler dev` — the build
480
+ script and the Worker entry import compiled `dist/` from the workspace
481
+ packages.
482
+
483
+ ### 10.6 Build-smoke tests fail in CI but pass locally
484
+
485
+ The `dist/`-dependent assertions in `tests/build-smoke.test.ts` are
486
+ gated on `existsSync(dist/app/index.html)` and are skipped when the
487
+ SPA hasn't been built. CI runs `pnpm --filter web build` (or
488
+ `pnpm build`) before `pnpm --filter web test`, so the assertions fire
489
+ there. If you run `pnpm --filter web test` locally without building,
490
+ only the landing-page source assertions run — that's expected, not a
491
+ regression.
492
+
493
+ ---
494
+
495
+ ## 11. Quick reference
496
+
497
+ ```bash
498
+ # One-time / per-clone
499
+ corepack enable
500
+ git submodule update --init # docs + apps/web/upstream (Kiwi)
501
+ pnpm install
502
+
503
+ # Build the SPA + landing page (pick the env)
504
+ pnpm --filter web build # default config (staging-shaped)
505
+ pnpm --filter web build:staging # static/config.staging.json
506
+ pnpm --filter web build:prod # static/config.prod.json
507
+
508
+ # Local dev (Worker serves SPA + landing page + WS on one origin)
509
+ pnpm build # build the rest of the workspace
510
+ pnpm --filter @serverless-ircd/cf-worker dev # http://localhost:8787
511
+ node apps/cf-worker/scripts/smoke.mjs # WS smoke
512
+
513
+ # Deploy staging (Worker + assets in one command)
514
+ pnpm deploy:cf:staging # wrangler deploy --env staging
515
+
516
+ # Tests / lint
517
+ pnpm --filter web test
518
+ pnpm --filter web typecheck
519
+ pnpm lint
520
+ ```
521
+
522
+ Key files:
523
+
524
+ | Path | What |
525
+ |---|---|
526
+ | `apps/web/scripts/build.mjs` | Orchestrates the upstream Kiwi build + layers our config. |
527
+ | `apps/web/src/config-schema.ts` | Zod schema for `static/config.*.json` (the only TS we own). |
528
+ | `apps/web/src/build-env.ts` | `--env` → config-file resolution (unit-tested). |
529
+ | `apps/web/static/config.{,staging,prod}.json` | Per-env Kiwi configs (validated at build time). |
530
+ | `apps/web/landing/index.html` | Static landing page (project front door), copied to `dist/index.html`. |
531
+ | `apps/web/tests/build-smoke.test.ts` | Landing-page + build-output assertions. |
532
+ | `apps/cf-worker/src/worker.ts` | Edge handler: WS → `ConnectionDO`, `/health`, else `env.ASSETS`. |
533
+ | `apps/cf-worker/src/origin-allowlist.ts` | CSWSH `Origin` policy (pure, unit-tested). |
534
+ | `apps/cf-worker/wrangler.toml` | `[assets]` binding + per-env blocks + `WEB_ORIGINS` var. |
535
+ | `apps/web/upstream/` | Kiwi IRC source (git submodule, tag-pinned). |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverless-ircd",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "description": "Serverless IRC daemon with a platform-agnostic core and Cloudflare Workers + AWS adapters",
6
6
  "license": "BSD-3-Clause",
@@ -17,7 +17,7 @@
17
17
  "typescript": "^5.9.3",
18
18
  "vite": "^7.3.6",
19
19
  "vitest": "^4.1.10",
20
- "@serverless-ircd/aws-adapter": "0.5.0"
20
+ "@serverless-ircd/aws-adapter": "0.7.0"
21
21
  },
22
22
  "scripts": {
23
23
  "build": "turbo run build",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serverless-ircd/aws-adapter",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "private": true,
5
5
  "description": "AWS Lambda + DynamoDB adapter: AwsRuntime implementing IrcRuntime + $connect/$disconnect/$default handlers",
6
6
  "license": "BSD-3-Clause",
@@ -194,6 +194,9 @@ export async function handleDefault(params: DefaultParams): Promise<{ statusCode
194
194
  ...(params.accounts !== undefined ? { accounts: params.accounts } : {}),
195
195
  ...(params.mtlsIdentity !== undefined ? { mtlsIdentity: params.mtlsIdentity } : {}),
196
196
  ...(params.history !== undefined ? { history: params.history } : {}),
197
+ // API Gateway terminates TLS before the Lambda is invoked, so every
198
+ // WebSocket connection is secure → user mode `S`.
199
+ secure: true,
197
200
  });
198
201
 
199
202
  try {
@@ -158,6 +158,13 @@ export interface NlbStreamParams {
158
158
  * graceful error-suffixed `382`.
159
159
  */
160
160
  configLoader?: () => Promise<ServerConfig>;
161
+ /**
162
+ * Whether the NLB listener terminates TLS for this flow. Set to `true`
163
+ * when the target group fronts a TLS listener (the `irc+tls://` port) so
164
+ * the connection surfaces user mode `S`; omit / set `false` for a plain
165
+ * TCP listener. The stack code sets this from the listener protocol.
166
+ */
167
+ secure?: boolean;
161
168
  }
162
169
 
163
170
  /**
@@ -253,6 +260,8 @@ export async function handleNlbStream(
253
260
  ...(params.accounts !== undefined ? { accounts: params.accounts } : {}),
254
261
  ...(params.mtlsIdentity !== undefined ? { mtlsIdentity: params.mtlsIdentity } : {}),
255
262
  ...(params.history !== undefined ? { history: params.history } : {}),
263
+ // Surface TLS fact when the NLB listener terminates TLS (irc+tls port).
264
+ ...(params.secure === true ? { secure: true } : {}),
256
265
  });
257
266
 
258
267
  if (lines.length > 0) {
@@ -56,6 +56,13 @@ export interface MarshalledConnection {
56
56
  connectionId: ConnId;
57
57
  registration: RegistrationState;
58
58
  capNegotiating: boolean;
59
+ /**
60
+ * Transport TLS fact (user mode `S`). Optional on the persisted shape so
61
+ * rows written before the field shipped deserialize with a `false`
62
+ * default via the coalescing in {@link unmarshalConnection}. Always
63
+ * written by {@link marshalConnection}.
64
+ */
65
+ secure?: boolean;
59
66
  caps: string[];
60
67
  /**
61
68
  * Present (as a JS `Set`, auto-marshalled to `SS`) only when the
@@ -102,6 +109,7 @@ export function marshalConnection(state: ConnectionState, idleSince: number): Ma
102
109
  connectionId: state.id,
103
110
  registration: state.registration,
104
111
  capNegotiating: state.capNegotiating,
112
+ secure: state.secure,
105
113
  caps: [...state.caps],
106
114
  userModes: { ...state.userModes },
107
115
  lastSeen: state.lastSeen,
@@ -140,13 +148,20 @@ export function unmarshalConnection(row: MarshalledConnection): ConnectionState
140
148
  id: row.connectionId,
141
149
  registration: row.registration,
142
150
  capNegotiating: row.capNegotiating,
151
+ // `secure` is absent on rows persisted before user mode `S` shipped;
152
+ // default to false (plain transport) so the state shape stays satisfied.
153
+ secure: row.secure ?? false,
143
154
  caps: new Set<string>(row.caps),
144
155
  // `joinedChannels` is absent on rows that never joined a channel
145
156
  // (DynamoDB rejects empty sets); default to an empty Set. The SDK
146
157
  // unmarshalls `SS` back to a native Set, which the Set constructor
147
158
  // copies; arrays (older rows) also iterate fine.
148
159
  joinedChannels: new Set<ChanName>(row.joinedChannels ?? []),
149
- userModes: { ...row.userModes },
160
+ // `userModes.tls` is absent on rows persisted before user mode `S`
161
+ // shipped; default to false so the field is a real boolean rather than
162
+ // `undefined`. Cast to Partial so TypeScript accepts the explicit
163
+ // default without flagging the spread overwrite (TS2783).
164
+ userModes: normalizeUserModes(row.userModes),
150
165
  lastSeen: row.lastSeen,
151
166
  connectedSince: row.connectedSince,
152
167
  };
@@ -162,6 +177,19 @@ export function unmarshalConnection(row: MarshalledConnection): ConnectionState
162
177
  return state;
163
178
  }
164
179
 
180
+ /**
181
+ * Defaults `tls` to false on a deserialized `UserModes`. Rows persisted
182
+ * before user mode `S` shipped lack the field; the DynamoDB SDK round-trip
183
+ * yields `undefined`, which would leak into runtime code expecting a real
184
+ * boolean. Casting to `Partial` lets the explicit default apply without
185
+ * TypeScript flagging the spread overwrite (TS2783).
186
+ */
187
+ function normalizeUserModes(um: UserModes): UserModes {
188
+ const out: Partial<UserModes> = { ...um };
189
+ if (out.tls === undefined) out.tls = false;
190
+ return out as UserModes;
191
+ }
192
+
165
193
  // ---------------------------------------------------------------------------
166
194
  // Nicks
167
195
  // ---------------------------------------------------------------------------