serverless-ircd 0.6.0 → 0.8.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 (350) hide show
  1. package/.github/workflows/ci.yml +9 -4
  2. package/.gitmodules +4 -1
  3. package/CHANGELOG.md +480 -22
  4. package/README.md +244 -57
  5. package/apps/aws-stack/package.json +1 -1
  6. package/apps/aws-stack/src/aws-stack.ts +186 -18
  7. package/apps/aws-stack/tests/stack.test.ts +400 -56
  8. package/apps/cf-tcp-container/package.json +1 -1
  9. package/apps/cf-worker/package.json +1 -1
  10. package/apps/cf-worker/src/origin-allowlist.ts +99 -0
  11. package/apps/cf-worker/src/worker.ts +89 -9
  12. package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
  13. package/apps/cf-worker/tests/fixtures/web-dist/webclient/index.html +18 -0
  14. package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
  15. package/apps/cf-worker/tests/smoke.test.ts +28 -2
  16. package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
  17. package/apps/cf-worker/wrangler.test.toml +23 -0
  18. package/apps/cf-worker/wrangler.toml +56 -0
  19. package/apps/local-cli/package.json +1 -1
  20. package/apps/local-cli/src/config-loader.ts +8 -0
  21. package/apps/local-cli/src/main.ts +16 -0
  22. package/apps/local-cli/src/server.ts +1 -0
  23. package/apps/local-cli/tests/config-loader.test.ts +14 -0
  24. package/apps/web/landing/index.html +215 -0
  25. package/apps/web/package.json +28 -0
  26. package/apps/web/scripts/build.mjs +132 -0
  27. package/apps/web/src/build-env.ts +43 -0
  28. package/apps/web/src/config-schema.ts +138 -0
  29. package/apps/web/static/config.json +28 -0
  30. package/apps/web/static/config.prod.json +28 -0
  31. package/apps/web/static/config.staging.json +28 -0
  32. package/apps/web/tests/build-env.test.ts +63 -0
  33. package/apps/web/tests/build-smoke.test.ts +103 -0
  34. package/apps/web/tests/config-schema.test.ts +432 -0
  35. package/apps/web/tests/workspace.test.ts +12 -0
  36. package/apps/web/tsconfig.json +10 -0
  37. package/apps/web/upstream/.browserslistrc +3 -0
  38. package/apps/web/upstream/.editorconfig +9 -0
  39. package/apps/web/upstream/.eslintignore +3 -0
  40. package/apps/web/upstream/.eslintrc.js +90 -0
  41. package/apps/web/upstream/.github/FUNDING.yml +12 -0
  42. package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  43. package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  44. package/apps/web/upstream/.github/workflows/push.yml +26 -0
  45. package/apps/web/upstream/.github/workflows/release.yml +33 -0
  46. package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
  47. package/apps/web/upstream/.prettierrc.js +9 -0
  48. package/apps/web/upstream/.stylelintignore +5 -0
  49. package/apps/web/upstream/.stylelintrc.js +21 -0
  50. package/apps/web/upstream/LICENSE +201 -0
  51. package/apps/web/upstream/README.md +76 -0
  52. package/apps/web/upstream/babel.config.js +25 -0
  53. package/apps/web/upstream/crowdin.yml +3 -0
  54. package/apps/web/upstream/index.html +35 -0
  55. package/apps/web/upstream/jest.config.js +7 -0
  56. package/apps/web/upstream/jsdoc.json +22 -0
  57. package/apps/web/upstream/package.json +80 -0
  58. package/apps/web/upstream/scripts/pre-commit +43 -0
  59. package/apps/web/upstream/src/components/App.vue +516 -0
  60. package/apps/web/upstream/src/components/AppSettings.vue +587 -0
  61. package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
  62. package/apps/web/upstream/src/components/Avatar.vue +105 -0
  63. package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
  64. package/apps/web/upstream/src/components/BufferKey.vue +121 -0
  65. package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
  66. package/apps/web/upstream/src/components/Captcha.vue +85 -0
  67. package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
  68. package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
  69. package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
  70. package/apps/web/upstream/src/components/ChannelList.vue +345 -0
  71. package/apps/web/upstream/src/components/Container.vue +401 -0
  72. package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
  73. package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
  74. package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
  75. package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
  76. package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
  77. package/apps/web/upstream/src/components/MessageList.vue +1028 -0
  78. package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
  79. package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
  80. package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
  81. package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
  82. package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
  83. package/apps/web/upstream/src/components/Nicklist.vue +309 -0
  84. package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
  85. package/apps/web/upstream/src/components/NotConnected.vue +271 -0
  86. package/apps/web/upstream/src/components/SelfUser.vue +283 -0
  87. package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
  88. package/apps/web/upstream/src/components/ServerView.vue +126 -0
  89. package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
  90. package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
  91. package/apps/web/upstream/src/components/Sidebar.vue +429 -0
  92. package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
  93. package/apps/web/upstream/src/components/SidebarState.vue +145 -0
  94. package/apps/web/upstream/src/components/StartupError.vue +35 -0
  95. package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
  96. package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
  97. package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
  98. package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
  99. package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
  100. package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
  101. package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
  102. package/apps/web/upstream/src/components/UserBox.vue +728 -0
  103. package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
  104. package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
  105. package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
  106. package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
  107. package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
  108. package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
  109. package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
  110. package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
  111. package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
  112. package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
  113. package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
  114. package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
  115. package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
  116. package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
  117. package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
  118. package/apps/web/upstream/src/helpers/Colours.js +128 -0
  119. package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
  120. package/apps/web/upstream/src/helpers/Md5.js +193 -0
  121. package/apps/web/upstream/src/helpers/Misc.js +401 -0
  122. package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
  123. package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
  124. package/apps/web/upstream/src/libs/AudioManager.js +60 -0
  125. package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
  126. package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
  127. package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
  128. package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
  129. package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
  130. package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
  131. package/apps/web/upstream/src/libs/IPC.js +51 -0
  132. package/apps/web/upstream/src/libs/InputHandler.js +921 -0
  133. package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
  134. package/apps/web/upstream/src/libs/Logger.js +71 -0
  135. package/apps/web/upstream/src/libs/Message.js +169 -0
  136. package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
  137. package/apps/web/upstream/src/libs/MessageParser.js +249 -0
  138. package/apps/web/upstream/src/libs/Notifications.js +78 -0
  139. package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
  140. package/apps/web/upstream/src/libs/ServerSession.js +102 -0
  141. package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
  142. package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
  143. package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
  144. package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
  145. package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
  146. package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
  147. package/apps/web/upstream/src/libs/bufferTools.js +177 -0
  148. package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
  149. package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
  150. package/apps/web/upstream/src/libs/settingTools.js +31 -0
  151. package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
  152. package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
  153. package/apps/web/upstream/src/libs/state/UserState.js +89 -0
  154. package/apps/web/upstream/src/libs/state/common.js +26 -0
  155. package/apps/web/upstream/src/libs/state.js +961 -0
  156. package/apps/web/upstream/src/libs/storage/Local.js +51 -0
  157. package/apps/web/upstream/src/main.js +566 -0
  158. package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
  159. package/apps/web/upstream/src/res/configTemplates.js +373 -0
  160. package/apps/web/upstream/src/res/globalStyle.css +277 -0
  161. package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
  162. package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
  163. package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
  164. package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
  165. package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
  166. package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
  167. package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
  168. package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
  169. package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
  170. package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
  171. package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
  172. package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
  173. package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
  174. package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
  175. package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
  176. package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
  177. package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
  178. package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
  179. package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
  180. package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
  181. package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
  182. package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
  183. package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
  184. package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
  185. package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
  186. package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
  187. package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
  188. package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
  189. package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
  190. package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
  191. package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
  192. package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
  193. package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
  194. package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
  195. package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
  196. package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
  197. package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
  198. package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
  199. package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
  200. package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
  201. package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
  202. package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
  203. package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
  204. package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
  205. package/apps/web/upstream/src/res/localesList.json +27 -0
  206. package/apps/web/upstream/src/res/logo.png +0 -0
  207. package/apps/web/upstream/src/thirdparty/about.html +43 -0
  208. package/apps/web/upstream/src/thirdparty/index.js +5 -0
  209. package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
  210. package/apps/web/upstream/static/config.json +28 -0
  211. package/apps/web/upstream/static/emoticons/smile.png +0 -0
  212. package/apps/web/upstream/static/favicon.png +0 -0
  213. package/apps/web/upstream/static/highlight.mp3 +0 -0
  214. package/apps/web/upstream/static/highlight.ogg +0 -0
  215. package/apps/web/upstream/static/locales/.gitignore +2 -0
  216. package/apps/web/upstream/static/plugins/customise.html.example +10 -0
  217. package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
  218. package/apps/web/upstream/static/themes/common/base.css +1064 -0
  219. package/apps/web/upstream/static/themes/dark/theme.css +196 -0
  220. package/apps/web/upstream/static/themes/default/theme.css +92 -0
  221. package/apps/web/upstream/static/themes/elite/theme.css +248 -0
  222. package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
  223. package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
  224. package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
  225. package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
  226. package/apps/web/upstream/static/themes/sky/theme.css +107 -0
  227. package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
  228. package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
  229. package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
  230. package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
  231. package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
  232. package/apps/web/upstream/vue.config.js +150 -0
  233. package/apps/web/upstream/yarn.lock +9767 -0
  234. package/apps/web/vitest.config.ts +13 -0
  235. package/biome.json +1 -0
  236. package/docs/AWS-Deployment.md +21 -8
  237. package/docs/Cloudflare-Deployment-Guide.md +22 -6
  238. package/docs/Home.md +1 -0
  239. package/docs/PlanExtensions.md +113 -3
  240. package/docs/Release-Process.md +23 -13
  241. package/docs/Services.md +546 -0
  242. package/docs/WebClientGuide.md +536 -0
  243. package/package.json +2 -2
  244. package/packages/aws-adapter/package.json +1 -1
  245. package/packages/aws-adapter/src/aws-runtime.ts +5 -0
  246. package/packages/aws-adapter/src/cdk-table-defs.ts +6 -0
  247. package/packages/aws-adapter/src/config-loader.ts +11 -0
  248. package/packages/aws-adapter/src/connection-counter.ts +89 -0
  249. package/packages/aws-adapter/src/dynamo-services-store.ts +649 -0
  250. package/packages/aws-adapter/src/handlers/connect.ts +55 -51
  251. package/packages/aws-adapter/src/handlers/default.ts +36 -4
  252. package/packages/aws-adapter/src/handlers/index.ts +15 -0
  253. package/packages/aws-adapter/src/handlers/nlb-stream.ts +15 -0
  254. package/packages/aws-adapter/src/handlers/sweeper.ts +5 -1
  255. package/packages/aws-adapter/src/index.ts +4 -0
  256. package/packages/aws-adapter/src/stats.ts +6 -1
  257. package/packages/aws-adapter/src/tables.ts +34 -4
  258. package/packages/aws-adapter/tests/aws-harness.ts +3 -0
  259. package/packages/aws-adapter/tests/config-loader.test.ts +8 -0
  260. package/packages/aws-adapter/tests/connect.test.ts +158 -32
  261. package/packages/aws-adapter/tests/connection-counter.test.ts +127 -0
  262. package/packages/aws-adapter/tests/dynamo-services-store-dynamo.test.ts +183 -0
  263. package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +568 -0
  264. package/packages/aws-adapter/tests/handlers.test.ts +105 -3
  265. package/packages/aws-adapter/tests/tables.test.ts +6 -1
  266. package/packages/cf-adapter/package.json +1 -1
  267. package/packages/cf-adapter/src/config-loader.ts +11 -0
  268. package/packages/cf-adapter/src/connection-do.ts +112 -2
  269. package/packages/cf-adapter/src/d1-services-store.ts +703 -0
  270. package/packages/cf-adapter/src/env.ts +8 -0
  271. package/packages/cf-adapter/src/index.ts +5 -0
  272. package/packages/cf-adapter/tests/cf-harness.ts +12 -1
  273. package/packages/cf-adapter/tests/config-loader.test.ts +19 -0
  274. package/packages/cf-adapter/tests/connection-do-nickserv-d1.test.ts +128 -0
  275. package/packages/cf-adapter/tests/connection-do.test.ts +150 -2
  276. package/packages/cf-adapter/tests/d1-services-store.test.ts +582 -0
  277. package/packages/cf-adapter/tests/serialize.test.ts +1 -0
  278. package/packages/in-memory-runtime/package.json +1 -1
  279. package/packages/irc-core/package.json +1 -1
  280. package/packages/irc-core/reports/mutation/mutation.html +342 -0
  281. package/packages/irc-core/scripts/generate-build-info.mjs +26 -5
  282. package/packages/irc-core/src/commands/account-auth.ts +172 -0
  283. package/packages/irc-core/src/commands/chanserv.ts +882 -0
  284. package/packages/irc-core/src/commands/hostserv.ts +487 -0
  285. package/packages/irc-core/src/commands/index.ts +12 -0
  286. package/packages/irc-core/src/commands/join.ts +164 -8
  287. package/packages/irc-core/src/commands/markread.ts +202 -0
  288. package/packages/irc-core/src/commands/memoserv.ts +319 -0
  289. package/packages/irc-core/src/commands/mode.ts +96 -4
  290. package/packages/irc-core/src/commands/nickserv.ts +390 -0
  291. package/packages/irc-core/src/commands/oper.ts +18 -1
  292. package/packages/irc-core/src/commands/operserv.ts +346 -0
  293. package/packages/irc-core/src/commands/pre-away.ts +3 -1
  294. package/packages/irc-core/src/commands/privmsg.ts +42 -0
  295. package/packages/irc-core/src/commands/read-marker.ts +8 -8
  296. package/packages/irc-core/src/commands/registration.ts +61 -6
  297. package/packages/irc-core/src/commands/sasl.ts +18 -49
  298. package/packages/irc-core/src/commands/tagmsg.ts +41 -6
  299. package/packages/irc-core/src/commands/topic.ts +37 -0
  300. package/packages/irc-core/src/config.ts +36 -5
  301. package/packages/irc-core/src/effects.ts +56 -1
  302. package/packages/irc-core/src/ports.ts +1653 -84
  303. package/packages/irc-core/src/protocol/numerics.ts +8 -0
  304. package/packages/irc-core/src/state/channel.ts +21 -1
  305. package/packages/irc-core/src/state/connection.ts +25 -1
  306. package/packages/irc-core/src/types.ts +48 -12
  307. package/packages/irc-core/tests/batch.test.ts +15 -0
  308. package/packages/irc-core/tests/commands/chanserv.test.ts +1668 -0
  309. package/packages/irc-core/tests/commands/chathistory.test.ts +6 -0
  310. package/packages/irc-core/tests/commands/hostserv.test.ts +935 -0
  311. package/packages/irc-core/tests/commands/join.test.ts +393 -1
  312. package/packages/irc-core/tests/commands/markread.test.ts +361 -0
  313. package/packages/irc-core/tests/commands/memoserv.test.ts +654 -0
  314. package/packages/irc-core/tests/commands/mode.test.ts +381 -2
  315. package/packages/irc-core/tests/commands/nickserv.test.ts +807 -0
  316. package/packages/irc-core/tests/commands/oper.test.ts +13 -0
  317. package/packages/irc-core/tests/commands/operserv.test.ts +656 -0
  318. package/packages/irc-core/tests/commands/privmsg.test.ts +147 -0
  319. package/packages/irc-core/tests/commands/read-marker.test.ts +28 -28
  320. package/packages/irc-core/tests/commands/registration.test.ts +788 -14
  321. package/packages/irc-core/tests/commands/sasl.test.ts +185 -12
  322. package/packages/irc-core/tests/commands/server-info.test.ts +9 -5
  323. package/packages/irc-core/tests/commands/tagmsg.test.ts +73 -33
  324. package/packages/irc-core/tests/commands/topic.test.ts +94 -2
  325. package/packages/irc-core/tests/commands/unified-account.test.ts +416 -0
  326. package/packages/irc-core/tests/config.test.ts +49 -5
  327. package/packages/irc-core/tests/effects.test.ts +19 -0
  328. package/packages/irc-core/tests/message-store.test.ts +63 -0
  329. package/packages/irc-core/tests/numerics.test.ts +13 -0
  330. package/packages/irc-core/tests/parser.test.ts +109 -0
  331. package/packages/irc-core/tests/persistent-services-store.test.ts +582 -0
  332. package/packages/irc-core/tests/services-store.test.ts +1289 -0
  333. package/packages/irc-core/tests/state/channel.test.ts +3 -0
  334. package/packages/irc-server/package.json +1 -1
  335. package/packages/irc-server/src/actor.ts +71 -16
  336. package/packages/irc-server/src/dispatch.ts +94 -7
  337. package/packages/irc-server/src/routing.ts +19 -0
  338. package/packages/irc-server/tests/actor.test.ts +623 -12
  339. package/packages/irc-server/tests/dispatch.test.ts +270 -2
  340. package/packages/irc-server/tests/routing.test.ts +6 -0
  341. package/packages/irc-test-support/package.json +1 -1
  342. package/packages/irc-test-support/src/in-memory-harness.ts +29 -3
  343. package/packages/irc-test-support/src/index.ts +1 -0
  344. package/packages/irc-test-support/src/scenarios.ts +24 -2
  345. package/packages/irc-test-support/tests/in-memory-harness.test.ts +32 -0
  346. package/tools/ci-hardening/package.json +1 -1
  347. package/tools/load-test/package.json +1 -1
  348. package/tools/tcp-ws-forwarder/package.json +1 -1
  349. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +2 -2
  350. package/packages/irc-core/tests/read-marker-store.test.ts +0 -108
@@ -29,6 +29,7 @@ jobs:
29
29
  verify:
30
30
  name: typecheck / lint / test / coverage
31
31
  runs-on: ubuntu-latest
32
+ timeout-minutes: 30
32
33
  steps:
33
34
  - name: Checkout
34
35
  uses: actions/checkout@v4
@@ -40,7 +41,6 @@ jobs:
40
41
  uses: actions/setup-node@v4
41
42
  with:
42
43
  node-version: 24
43
- cache: pnpm
44
44
 
45
45
  # DynamoDB Local capability for `aws-adapter` coverage.
46
46
  #
@@ -63,7 +63,7 @@ jobs:
63
63
  # (otherwise it blocks for ~60s waiting for Ryuk before falling
64
64
  # back, which is flaky and wastes CI minutes).
65
65
  - name: Setup Java (JRE for DynamoDB Local ZIP fallback)
66
- uses: actions/setup-java@v4
66
+ uses: actions/setup-java@v5
67
67
  with:
68
68
  distribution: temurin
69
69
  java-version: '11'
@@ -134,6 +134,7 @@ jobs:
134
134
  runs-on: ubuntu-latest
135
135
  # Stryker is the slowest single task; it is independent of the
136
136
  # typecheck/lint/coverage chain and runs in parallel.
137
+ timeout-minutes: 45
137
138
  steps:
138
139
  - name: Checkout
139
140
  uses: actions/checkout@v4
@@ -145,13 +146,17 @@ jobs:
145
146
  uses: actions/setup-node@v4
146
147
  with:
147
148
  node-version: 24
148
- cache: pnpm
149
149
 
150
150
  - name: Install dependencies
151
151
  run: pnpm install --frozen-lockfile
152
152
 
153
153
  - name: Build workspace (irc-core mutation targets)
154
- run: pnpm build
154
+ # Scope the build to irc-core and its workspace deps only. The
155
+ # mutation spot-check mutates irc-core/protocol and irc-core/commands,
156
+ # so building the rest of the workspace (notably @serverless-ircd/web,
157
+ # which spawns `yarn` to build the Kiwi upstream SPA) is unnecessary
158
+ # and fails on runner images that don't ship yarn.
159
+ run: pnpm turbo run build --filter=@serverless-ircd/irc-core...
155
160
 
156
161
  - name: Stryker spot-check on irc-core/protocol
157
162
  # Mutation score threshold >=80% is enforced by the
package/.gitmodules CHANGED
@@ -1,3 +1,6 @@
1
1
  [submodule "docs"]
2
2
  path = docs
3
- url = https://gitea.com/fowlmouth/ServerlessIRCd.wiki.git
3
+ url = https://gitea.com/ServerlessIRCd/ServerlessIRCd.wiki.git
4
+ [submodule "apps/web/upstream"]
5
+ path = apps/web/upstream
6
+ url = https://github.com/kiwiirc/kiwiirc.git
package/CHANGELOG.md CHANGED
@@ -7,25 +7,483 @@ and this project adheres to [Semantic Versioning](https://semver.org/) with
7
7
  the 0.x convention (any release may change anything).
8
8
 
9
9
  For the release process itself — versioning policy, pre-release checklist,
10
- cutting a tag, rolling back — see [`docs/release.md`](docs/release.md).
10
+ cutting a tag, rolling back — see [`docs/Release-Process.md`](docs/Release-Process.md).
11
11
  Cross-reference `progress.md` / `tickets.md` for per-ticket detail.
12
12
 
13
+ ## [0.8.0] - 2026-08-08
14
+
15
+ The main change in this release is **integrated IRC services**: NickServ,
16
+ ChanServ, HostServ, OperServ, and MemoServ ship as part of the daemon
17
+ itself — there is **no separate services process and no S2S link**. The
18
+ service nicks are reserved and routed to dedicated pure reducers backed
19
+ by a new `ServicesStore` port, with **persistent D1 (Cloudflare) and
20
+ DynamoDB (AWS) backends** so registrations survive redeploys. Alongside
21
+ it: the SASL `AccountStore` and NickServ accounts are **unified** under
22
+ one scrypt-hashed credential store, `PASS <nick>:<password>` identifies
23
+ at registration, the `draft/read-marker` `MARKREAD` verb lands, and a
24
+ round of **AWS adapter hardening** (O(1) admission counter, strongly-
25
+ consistent `$default` reads, WebSocket subprotocol echo fix).
26
+
27
+ ### Added — Integrated IRC services (`@serverless-ircd/irc-core`, `@serverless-ircd/irc-server`)
28
+
29
+ The ten services tickets add the full
30
+ services surface. Service names are reserved nicks recognized at
31
+ `PRIVMSG` time and routed to dedicated pure reducers when a
32
+ `ServicesStore` is bound; when no store is bound every services command
33
+ replies `501` and the rest of the daemon is unaffected. See
34
+ `docs/Services.md` for the operator reference.
35
+
36
+ - **`ServicesStore` port + in-memory reference impl**: a synchronous,
37
+ injectable store holding NickServ / ChanServ / HostServ / MemoServ /
38
+ OperServ / read-marker state. Reducers stay pure — all side effects
39
+ flow through the store port. The `ReadMarkerStore` from v0.6.0 is
40
+ **folded into** `ServicesStore` so read-markers, `draft/pre-away`,
41
+ and account state share one persistence path.
42
+ - **NickServ** (`REGISTER` / `IDENTIFY` / `DROP` / `INFO` / `SET`):
43
+ registers the current nick, identifies to an account (setting the
44
+ read-only user mode **`+r`**, fanning `ACCOUNT` to `account-notify`
45
+ peers, then delivering unread MemoServ memos), and supports
46
+ `SET ENFORCE` — a registered nick held by a non-identified
47
+ connection is reclaimed on the `NICK` path (enforcement policy
48
+ mirrors Atheme/Anope). `IDENTIFY` falls back to SASL account
49
+ credentials when no NickServ row exists.
50
+ - **ChanServ** (`REGISTER` / `DROP` / `SET` / `INFO` / `ACCESS` /
51
+ `LEVELS`): channel registration with a founder, the services-only
52
+ channel modes **`+r`** (registered), **`+R`** (block unidentified
53
+ join/message), and **`+M`** (moderated-identified), `ACCESS`/`LEVELS`
54
+ (SOP/AOP/VOP/HOP access lists), and auto-op on `JOIN` per access
55
+ level. The three services modes are settable only via ChanServ, not
56
+ via `MODE` (`472 ERR_UNKNOWNMODE` / `501`).
57
+ - **HostServ** (`ON` / `OFF` / `REQUEST` + oper `SET` / `APPROVE` /
58
+ `REJECT` / `LIST`): per-account vhost with `CHGHOST` fanout to shared
59
+ channels. `REQUEST` enters an **oper approval queue**; opers `APPROVE`
60
+ to activate or `REJECT` to drop it.
61
+ - **OperServ** (`AKILL` / `JUPE` / `UNJUPE` / `RAW`): oper-gated
62
+ network management. `AKILL` records an `*@host` mask, `JUPE` reserves
63
+ a juped nick (rejected at `NICK` with `432 ERR_ERRONEUSNICKNAME`),
64
+ `RAW` is the escape hatch. Wired end-to-end: `PRIVMSG OperServ :…`
65
+ routes to the OperServ reducer.
66
+ - **MemoServ** (`SEND` / `LIST` / `READ` / `DEL`): per-account memos
67
+ with queue delivery — unread memos are replayed at identify (NickServ
68
+ `IDENTIFY` / SASL login / PASS-based login), the same path that
69
+ replays `draft/read-marker` and `draft/pre-away`.
70
+
71
+ ### Added — Persistent services backends (`@serverless-ircd/cf-adapter`, `@serverless-ircd/aws-adapter`)
72
+
73
+ Both adapters ship write-behind persistence for the `ServicesStore` so
74
+ registrations, channel access lists, vhosts, memos, and read-markers
75
+ survive a redeploy/reconnect.
76
+
77
+ - **Cloudflare (D1)**: `D1ServicesStore` over the shared `ACCOUNTS_DB`
78
+ binding (the same D1 database used for SASL accounts since v0.4.0).
79
+ The ten services tables **auto-provision on cold start** (`CREATE
80
+ TABLE IF NOT EXISTS` inside `loadD1ServicesStore`), so a fresh deploy
81
+ self-provisions and an existing deploy re-runs the batch as a cheap
82
+ no-op. `ConnectionDO` load/flush hooks wrap each frame and teardown.
83
+ - **AWS (DynamoDB)**: `DynamoServicesStore` plus a new `Services`
84
+ table definition and handler wiring so NLB-stream / `$default`
85
+ invocations hydrate + flush services alongside the existing account
86
+ store. Enabled when the `SERVICES_TABLE` env var / table name is
87
+ supplied.
88
+ - **`PersistentServicesStore` base** (`irc-core`): the shared
89
+ write-behind base — mutates the inherited cache synchronously and
90
+ enqueues matching writes for `flush()`, so reducers stay free of IO.
91
+
92
+ ### Added — Unified accounts & registration login (`@serverless-ircd/irc-core`)
93
+
94
+ - **Unified SASL + NickServ account store**: the SASL `AccountStore`
95
+ and NickServ `ServicesStore` now share one **scrypt-hashed**
96
+ credential shape. A NickServ registration *is* a SASL account —
97
+ SASL `PLAIN`'s services fallback verifies against a row created by
98
+ NickServ `REGISTER` without a second registration, and vice versa.
99
+ The plaintext password on `ServicesNickRow` is replaced with a
100
+ `HashedAccountCredential` (scrypt + random salt). `ingestAccountCredential`
101
+ adopts a pre-hashed SASL credential as a registered nick (idempotent;
102
+ existing records win).
103
+ - **PASS-based account login**: a client may identify to its NickServ
104
+ account at registration by sending `PASS <nick>:<password>` (the same
105
+ literal the `SASL_ACCOUNTS` seed tooling uses). On success the
106
+ connection is logged in — emitting `900`/`903`, stamping `+r`, and
107
+ running the same read-marker / away / memo replay as SASL. A bare
108
+ `PASS <value>` (no colon) is always treated as a server-password
109
+ candidate. The shared login reduction (`applyAccountSuccess`) is
110
+ extracted and reused from SASL.
111
+ - **Server-password gate with SASL short-circuit**: an optional gate
112
+ enforced in the registration reducer at `001` — when
113
+ `ServerConfig.serverPassword` is set, every connection must supply a
114
+ matching `PASS` (or authenticate via SASL / PASS-based account login)
115
+ before `RPL_WELCOME`, else `464 ERR_PASSWDMISMATCH` + disconnect.
116
+ Wired through all three adapters (`SERVER_PASSWORD` Worker secret /
117
+ Lambda env var / `--server-password` CLI flag).
118
+
119
+ ### Added — `draft/read-marker` `MARKREAD` verb (`@serverless-ircd/irc-core`)
120
+
121
+ - The timestamp-based `MARKREAD` command from the read-marker draft
122
+ lands alongside the existing `+draft/read-marker=<msgid>` tag path.
123
+ `MARKREAD <target>` (GET) replies with the stored marker's timestamp;
124
+ `MARKREAD <target> <timestamp>` (SET) advances the marker, with
125
+ timestamp↔msgid translation against the message store. Failure modes
126
+ use `standard-replies` `FAIL MARKREAD …`. The mark fanout is
127
+ **scoped to the originator's account** (spec compliance), not
128
+ broadcast to every cap-enabled peer.
129
+
130
+ ### Added — Cloudflare adapter (`@serverless-ircd/cf-adapter`)
131
+
132
+ - **Client source IP capture at WebSocket upgrade**: reads
133
+ `CF-Connecting-IP` (with `X-Real-IP` fallback) from the upgrade
134
+ request and seeds `ConnectionState.host`, so WHOIS / hostmasks carry
135
+ a real `@host` segment instead of an `undefined` fallback.
136
+ - **Ghost-member pruning via alarm**: when a WebSocket `close` event is
137
+ lost (client drop, edge hiccup), the DO's alarm prunes the ghost
138
+ member from its channels on the next sweep, preventing stale roster
139
+ entries.
140
+
141
+ ### Changed — AWS adapter (`@serverless-ircd/aws-adapter`, `@serverless-ircd/aws-stack`)
142
+
143
+ - **O(1) admission counter**: `$connect`'s full-table `Scan COUNT` is
144
+ replaced with an atomic increment/decrement on a single meta row,
145
+ dropping admission cost from O(connections) to O(1). Reservations
146
+ roll back on rejection or a failed `Put`; the sweeper and stats
147
+ exclude the meta row.
148
+ - **Strongly-consistent `$default` reads + observability**: `$default`
149
+ now reads the `Connections` row strongly consistent (closes a
150
+ read-after-write `410` window), warns on a missing row, logs
151
+ `postToConnection` failures (previously silently swallowed), and
152
+ emits a summary log per invocation.
153
+ - **`ServerName` / `NetworkName` as CFN parameters**: server identity
154
+ is now a CloudFormation parameter (was env-var-only), and the
155
+ sweeper Lambda's `loadServerConfigFromLambdaEnv` is wired with the
156
+ server-identity env vars it requires. `SweeperScheduleExpression`,
157
+ `PingCheckerScheduleExpression`, and `ApiGatewayLoggingLevel` are
158
+ also exposed as parameters.
159
+ - **WebSocket subprotocol echo fixed**: API Gateway WebSocket
160
+ `IntegrationResponse` uses `route.response.header.*` (not
161
+ `method.response.header.*`, which is REST API only), so the
162
+ `Sec-WebSocket-Protocol` header was not being echoed back to clients.
163
+ AWS now correctly negotiates the IRCv3 subprotocol. Subprotocol
164
+ advertisement is forced text-only (`text.ircv3.net`) to match the
165
+ Lambda response-stream path.
166
+ - **APIGW logging role corrected** and Biome lint/format applied.
167
+
168
+ ### Changed
169
+
170
+ - **`MODE +o` echo on oper-up**: the connection that grants oper now
171
+ receives a `MODE` echo, matching the cross-connection oper-up path.
172
+ - **NICK change broadcast fix**: nick changes now broadcast to every
173
+ shared-channel peer (a code path that dropped peers under certain
174
+ roster shapes is corrected).
175
+ - **Server version from `package.json`**: the default `serverVersion`
176
+ advertised in `002`/`003` is derived from the workspace
177
+ `package.json` at build time (was a hardcoded placeholder when the
178
+ config omitted it).
179
+ - **Web client SPA path `/app/` → `/webclient/`**: the vendored Kiwi
180
+ IRC SPA is now served at `/webclient/` (the `[assets]` publicPath
181
+ and config are repointed). Operators with custom Kiwi configs or
182
+ bookmarks must update to `/webclient/`.
183
+ - **Source / issue links migrated to Gitea**; landing-page copy
184
+ tightened and the upstream-install guard hardened.
185
+
186
+ ### Changed — CI / tooling
187
+
188
+ - `actions/setup-java` bumped to v5; pnpm pinned to 11.20.0. CI jobs
189
+ are bounded by explicit timeouts and the unreachable pnpm cache step
190
+ is dropped.
191
+
192
+ ### ⚠️ Migration required
193
+
194
+ - **Cloudflare: services are enabled when `ACCOUNTS_DB` is bound.** The
195
+ D1 binding is the same one introduced in v0.4.0 for SASL accounts;
196
+ the ten services tables **self-provision on cold start** (no manual
197
+ `wrangler d1 execute` needed). If `ACCOUNTS_DB` is already bound,
198
+ services are now live after deploy — no action. If it is unset,
199
+ services commands reply `501` and the daemon is otherwise unchanged.
200
+ - **AWS: new `Services` DynamoDB table + `SERVICES_TABLE`.** To enable
201
+ services, supply a services table name (`SERVICES_TABLE` env var /
202
+ CDK `servicesTableName` prop); the table is created by the CDK stack.
203
+ Without it, services are unbound.
204
+ - **NickServ / SASL credentials are now scrypt-hashed at rest.** A
205
+ NickServ `REGISTER` creates the same hashed credential shape SASL
206
+ uses; the `password` column on the NickServ row is replaced by
207
+ `algorithm` / `salt` / `hash`. Existing seeded SASL accounts are
208
+ adopted as registered nicks idempotently. Operators re-seeding via
209
+ `tools/seed-*-accounts.ts` see no change (the tools already hashed).
210
+ - **Web client SPA moved to `/webclient/`.** Update any bookmarks,
211
+ custom Kiwi configs, or `WEB_ORIGINS` entries pointing at `/app/`.
212
+ - **No persisted-DO-state schema change.** `PERSISTED_STATE_VERSION`
213
+ stays `1`. The new read-only user mode `r` (registered) and the
214
+ services channel modes `r`/`R`/`M` are runtime state, not persisted
215
+ DO fields; the `ReadMarkerStore` fold moves read-markers into the
216
+ services backend (D1/DynamoDB), out of DO storage. **Code rollback
217
+ across this release is safe.**
218
+ - No toolchain change. Node ≥ 24 / pnpm 11 (from v0.4.0) still apply.
219
+
220
+ ### Known limitations
221
+
222
+ - **Services are not binary-compatible with Atheme or Anope.** There is
223
+ no S2S protocol, so an existing Atheme/Anope services process cannot
224
+ connect, and existing services databases cannot be imported.
225
+ Operators migrating from a services-aware network must re-register
226
+ nicks and channels. See `docs/Services.md` §12.
227
+ - **The web client remains partial.** The Playwright headless-browser
228
+ e2e against the deployed staging frontend is still pending; the SPA
229
+ is exercised via the WS smoke (`scripts/smoke.mjs`) and the `apps/web`
230
+ unit / build-smoke suite. The web client remains Cloudflare-only (the
231
+ `[assets]` binding is a Worker feature).
232
+ - Same transport limits as v0.7.0: the **CF TCP path requires
233
+ Cloudflare Spectrum (Enterprise tier)** plus a stateful Container
234
+ origin; the **AWS TCP path** uses NLB + Lambda streaming and is
235
+ subject to Lambda idle-timeout / stream-duration limits. The
236
+ WebSocket path remains the zero-extra-deps default.
237
+ - **mTLS (for SASL `EXTERNAL`) requires a custom domain + uploaded
238
+ CA/trust-store** on both platforms and is not enabled by default.
239
+ - **Client compatibility sweep** (WeeChat / HexChat / IRCCloud /
240
+ TheLounge / matrix-IRC bridge) remains pending.
241
+ - **Coverage hardening is partial.** `aws-adapter` and `aws-stack`
242
+ clear the 90% gate; the remaining packages (`cf-adapter`,
243
+ `local-cli`, `load-test`, `cf-tcp-container`, `tcp-ws-forwarder`,
244
+ `irc-test-support`, `web`) are **above the gate but below 100%** —
245
+ follow-ups drive each to 100%.
246
+
247
+ ### Security
248
+
249
+ - **NickServ / SASL credentials are now uniformly scrypt-hashed.** The
250
+ unified account store means a NickServ-registered nick and a SASL
251
+ account share one hashed credential — never plaintext, in memory or
252
+ at rest. The server-password gate and CSWSH defense are unchanged
253
+ from v0.7.0; no other auth path changed.
254
+
255
+ ---
256
+
257
+ ## [0.7.0] - 2026-08-04
258
+
259
+ The main change in this release is the **web client**: a vendored **Kiwi IRC**
260
+ SPA served at `/app/` and a static project landing page served at `/`
261
+ directly by the Cloudflare Worker, with **Cross-Site WebSocket
262
+ Hijacking (CSWSH) defense** wired into the WS upgrade path. The browser
263
+ opens a native `wss://` directly to the IRC-over-WebSocket
264
+ endpoint — no proxy, gateway, or transport adaptation in the request path.
265
+ Alongside it: a round of **test hardening** (mutation-killing tests for
266
+ `irc-core`'s parser / batch / numerics, and CF-harness / scenario fixes
267
+ for slow transports).
268
+
269
+ ### Added — Web client (`@serverless-ircd/web`, `@serverless-ircd/cf-worker`)
270
+
271
+ Six tickets add the project's browser surface. The Worker
272
+ becomes the single origin for the landing page, the SPA, **and** the
273
+ WebSocket. Same-origin by default — which is what makes the CSWSH
274
+ defense almost zero-config.
275
+
276
+ - **`apps/web` workspace package (`@serverless-ircd/web`)**: a new
277
+ pnpm workspace package owning the Kiwi build orchestration and config
278
+ schema. The package layout reserves `dist/app/` for the Kiwi SPA
279
+ (served at `/app/`) and `dist/index.html` for the landing page
280
+ (served at `/`). No `@serverless-ircd/*` runtime dependency —
281
+ Kiwi talks to the Worker over plain WebSocket.
282
+ - **Kiwi IRC vendored as a git submodule** (`apps/web/upstream/`,
283
+ tag-pinned): `scripts/build.mjs` patches the upstream `publicPath`
284
+ to `/app/`, runs `yarn build` inside the submodule, copies
285
+ `upstream/dist/` → `apps/web/dist/app/`, then layers our validated
286
+ `static/config.json` over the upstream default. The patch runs in
287
+ `finally` so the submodule stays clean; every hashed asset URL comes
288
+ out `/app/static/...` instead of root-relative. `.gitmodules` updated;
289
+ `biome.json` ignores `apps/web/upstream/**`. `git submodule update
290
+ --init` is now part of the contributor quickstart.
291
+ - **Per-env config matrix + Zod schema** (`apps/web/src/config-schema.ts`,
292
+ `apps/web/src/build-env.ts`): `static/config.json`,
293
+ `static/config.staging.json`, `static/config.prod.json` are validated
294
+ against a Zod schema **before** being baked into
295
+ `dist/app/static/config.json`. `pnpm --filter web build` accepts a
296
+ `--env <staging|prod>` flag (unit-tested in isolation from the slow
297
+ Kiwi build). A malformed config (missing `server`, out-of-range
298
+ `port`, non-`#` channel, `direct: false`, …) aborts the build with
299
+ an aggregated, field-listing error rather than shipping a broken SPA.
300
+ The Kiwi `startupOptions.server` uses the `{{hostname}}` template
301
+ (resolved by the browser to `window.location.hostname` at load time),
302
+ so **one build artifact targets staging, prod, and preview deploys
303
+ without a per-env hostname**.
304
+ - **Cloudflare Worker `[assets]` binding** (`apps/cf-worker/wrangler.toml`,
305
+ `apps/cf-worker/src/worker.ts`): the non-WS, non-`/health` branch of
306
+ the worker's `fetch` falls through to `env.ASSETS`, which serves
307
+ `apps/web/dist`. Per-env (`[env.staging.assets]`) blocks ship so
308
+ `wrangler deploy --env staging` deploys the Worker and the SPA in
309
+ one command. `run_worker_first = true` is set so WS upgrades aren't
310
+ intercepted by the asset platform serving `dist/index.html` at `/`
311
+ with a `200 OK` (the WS endpoint and the landing page share path
312
+ `/`; only the Worker can tell them apart).
313
+ - **Static landing page** (`apps/web/landing/index.html`): framework-
314
+ free HTML + inline CSS, no JavaScript. The project's entry page —
315
+ explains what ServerlessIRCd is, links to `/app/` (the IRC client),
316
+ the docs, and the source repo. The build copies it verbatim to
317
+ `dist/index.html`. Renders without JS; responsive; the build-smoke
318
+ suite asserts the `/app/` link, the docs link, and the viewport meta
319
+ tag so they cannot regress.
320
+ - **`/health` is an explicit route**: the previous catch-all plaintext
321
+ liveness string now lives at `GET /health` so `/` is free for the
322
+ landing page. WS upgrades still route to `ConnectionDO`.
323
+
324
+ ### Added — CSWSH defense (`@serverless-ircd/cf-worker`)
325
+
326
+ WebSocket upgrades do **not** follow the same-origin policy. A
327
+ malicious page can `new WebSocket('wss://irc.example.com/')` from the
328
+ victim's browser and drive an IRC session with the victim's
329
+ credentials. The Worker now rejects upgrades whose `Origin` is not
330
+ allowed — see `apps/cf-worker/src/origin-allowlist.ts` and
331
+ `docs/WebClientGuide.md` §5.
332
+
333
+ - **Two enforcement modes, evaluated in order:**
334
+ 1. **Explicit allowlist** — when the `WEB_ORIGINS` var is set, only
335
+ listed origins proceed. Use this for cross-origin deployments
336
+ (SPA on a different domain than the Worker, e.g. Cloudflare
337
+ Pages).
338
+ 2. **Auto-derive (same-origin)** — when `WEB_ORIGINS` is unset/empty
339
+ (the default), the Worker compares the browser's `Origin` against
340
+ the request's own origin (`${protocol}//${host}`). If they match,
341
+ the page opening the WS is the same origin as the server → safe.
342
+ - **`WEB_ORIGINS` is optional.** Because mode 2 is the default and
343
+ requires zero per-env configuration, most deployments never need to
344
+ set it: the SPA and the Worker are on the same origin. It works for
345
+ `*.workers.dev`, custom domains, and preview URLs alike.
346
+ - **Non-browser clients pass through unchanged.** curl, WeeChat, the
347
+ `tcp-ws-forwarder`, and scripted test harnesses never send `Origin`;
348
+ their upgrades are allowed in both modes so existing tooling keeps
349
+ working.
350
+ - **No fail-open path.** A browser-sent `Origin` that is not allowed
351
+ → `403 Forbidden`. The extreme edge case (no allowlist AND
352
+ `Host` undeterminable, which never fires on Cloudflare) is
353
+ fail-closed `403`. The pure policy is exhaustively unit-tested in
354
+ `apps/cf-worker/tests/origin-allowlist.test.ts`; the worker
355
+ integration is covered by `tests/ws-origin.test.ts`.
356
+
357
+ ### Added — Documentation (`docs/WebClientGuide.md`)
358
+
359
+ End-to-end contributor/operator doc covering: how the SPA, landing
360
+ page, and WS endpoint fit on one origin; prerequisites (incl. `yarn`
361
+ for the Kiwi build only); the quickstart (`git submodule update --init`
362
+ → `pnpm --filter web build:staging` → `pnpm deploy:cf:staging`); a
363
+ walkthrough of the build pipeline and the per-env config matrix; the
364
+ CSWSH rationale and the optional `WEB_ORIGINS` var; local dev
365
+ (`wrangler dev` serves everything on `http://localhost:8787`); an
366
+ optional Cloudflare Pages alternative for cross-origin prod deploys;
367
+ the optional `webircgateway` SockJS fallback (strictly opt-in, not
368
+ needed for normal operation); landing-page customisation; and a
369
+ troubleshooting section. The acceptance criterion is that a new
370
+ contributor can deploy a staging SPA following only this doc.
371
+
372
+ ### Added — Test hardening (`@serverless-ircd/irc-core`, `@serverless-ircd/cf-adapter`, `@serverless-ircd/irc-test-support`)
373
+
374
+ - **Mutation-killing tests for `irc-core`**: the parser, batch, and
375
+ numerics suites were expanded to kill surviving Stryker mutants —
376
+ command-token anchors, strict source-prefix shape, malformed-input
377
+ diagnostic text, middle-param whitespace collapsing,
378
+ `unescapeTagValue` boundary conditions; empty-batch framing metadata
379
+ surviving elision; and `numericToName` round-trip parity against the
380
+ registry. A generated Stryker mutation report is committed under
381
+ `packages/irc-core/reports/mutation/`.
382
+ - **CF harness / scenario fixes for slow transports**: the CF test
383
+ harness now waits for the end of the registration burst (`376` /
384
+ `422`) instead of `001`, so late MOTD frames do not pollute
385
+ `received` after a test clears it. The NOTICE scenario now only
386
+ rejects NOTICE-relevant error numerics (rather than any numeric),
387
+ preventing delayed welcome / MOTD lines from false-positiving the
388
+ RFC no-error-replies check. No production code changed.
389
+
390
+ ### Changed
391
+
392
+ - **Cloudflare Worker routing.** `/health` is the new liveness route;
393
+ the root `/` now serves the landing page from the `[assets]`
394
+ binding. WS upgrades still route to `ConnectionDO`. Operators who
395
+ scraped the root `/` for the plaintext health string must update
396
+ to `/health`.
397
+
398
+ ### ⚠️ Migration required
399
+
400
+ - **New optional runtime var: `WEB_ORIGINS`.** Optional — same-origin
401
+ auto-derive is the default. Set it only for cross-origin SPA
402
+ deployments (SPA on Cloudflare Pages, Worker serving the WS only,
403
+ etc.):
404
+ ```toml
405
+ # apps/cf-worker/wrangler.toml
406
+ [vars]
407
+ WEB_ORIGINS = "https://irc.example.com,https://web.example.com"
408
+ ```
409
+ - **New build prerequisite for the SPA.** Building `apps/web` needs
410
+ the Kiwi submodule (`git submodule update --init apps/web/upstream`)
411
+ and `yarn` (provided by `corepack enable`). The CF deploy workflow
412
+ runs `pnpm build` (which builds the SPA + landing page) before
413
+ `wrangler deploy`. If you deploy manually and skip the SPA build,
414
+ the Worker still works but `/app/` and `/` return 404 from the asset
415
+ layer.
416
+ - **`/health` is the new liveness route.** Operators who scraped `/`
417
+ for the plaintext health string must update to `/health`.
418
+ - **New workspace package.** `apps/web` is a new private package
419
+ (`@serverless-ircd/web`); `pnpm install` picks it up automatically
420
+ from the existing `pnpm-workspace.yaml` glob.
421
+ - **No persisted-state schema change.** `PERSISTED_STATE_VERSION`
422
+ stays `1`. **Code rollback across this release is safe** — the
423
+ web-client work adds the `[assets]` binding and the `/health` route
424
+ but touches no Durable Object state.
425
+ - No toolchain change. Node ≥ 24 / pnpm 11 (from v0.4.0) still apply.
426
+
427
+ ### Known limitations
428
+
429
+ - **The web client is partial.** The Playwright headless-browser e2e
430
+ against the deployed staging frontend remains pending
431
+ — the SPA is exercised via the existing WS smoke
432
+ (`scripts/smoke.mjs`) and the new `apps/web` unit / build-smoke
433
+ suite, but no browser-driven end-to-end test runs in CI yet.
434
+ - **The web client is Cloudflare-only.** The `[assets]` binding is a
435
+ Cloudflare Worker feature; AWS deployments continue to require an
436
+ external static host for the SPA (and must add the SPA's origin to
437
+ the Worker-style `WEB_ORIGINS` equivalent if/when an AWS-side
438
+ CSWSH check lands).
439
+ - Same transport limits as v0.6.0: the **CF TCP path requires
440
+ Cloudflare Spectrum (Enterprise tier)** plus a stateful Container
441
+ origin; the **AWS TCP path** uses NLB + Lambda streaming and is
442
+ subject to Lambda idle-timeout / stream-duration limits. The
443
+ WebSocket path remains the zero-extra-deps default.
444
+ - **mTLS (for SASL `EXTERNAL`) requires a custom domain + uploaded
445
+ CA/trust-store** on both platforms and is not enabled by default.
446
+ - **Client compatibility sweep** (WeeChat / HexChat / IRCCloud /
447
+ TheLounge / matrix-IRC bridge) remains pending.
448
+ - **Coverage hardening is partial.** `aws-adapter` and `aws-stack`
449
+ clear the 90% gate (v0.6.0); the remaining packages
450
+ (`cf-adapter`, `local-cli`, `load-test`, `cf-tcp-container`,
451
+ `tcp-ws-forwarder`, `irc-test-support`, and now `web`) are **above
452
+ the gate but below 100%** — follow-ups drive each to 100%.
453
+ - The IRC services work (NickServ / ChanServ / HostServ /
454
+ OperServ / MemoServ) is pending; `draft/read-marker` and
455
+ `draft/pre-away` ship with in-memory reference stores, so marker /
456
+ away persistence does not survive an adapter restart until a
457
+ services backend lands.
458
+
459
+ ### Security
460
+
461
+ - **CSWSH defense added.** WebSocket upgrades from browsers now
462
+ require an allowed `Origin`. Same-origin auto-derive is the default
463
+ (zero config); explicit `WEB_ORIGINS` allowlist for cross-origin
464
+ setups. Non-browser clients (no `Origin`) pass through unchanged.
465
+ There is no fail-open path. See `docs/WebClientGuide.md` §5. No
466
+ other auth path changed (SASL `PLAIN` / `EXTERNAL`, oper gate,
467
+ admission limits, and cloaking are unchanged from v0.6.0).
468
+
469
+ ---
470
+
13
471
  ## [0.6.0] - 2026-08-03
14
472
 
15
- The headline is the **IRCv3 extension sweep (Phase 12)**: ten negotiated
473
+ The main change in this release is the **IRCv3 extension sweep**: ten negotiated
16
474
  caps and the user-mode-`S` TLS indicator land in one release, taking the
17
475
  negotiated capability surface from "modern baseline" to "draft-leading-
18
476
  edge". Alongside it: the **required-`serverName` config gate** (the
19
- last open v0.5.0 follow-up), a new **`tools/load-test`** client farm
20
- (Phase 7's 10k-connection harness), and the **coverage / CI unblocks**
477
+ last open v0.5.0 follow-up), a new **`tools/load-test`** client pool
478
+ (the 10k-connection harness), and the **coverage / CI unblocks**
21
479
  that lift `aws-adapter` over the 90% gate and switch `cf-adapter` /
22
480
  `cf-worker` to istanbul (clearing the v0.5.0 workerd-incompatibility
23
481
  caveat). Deploy workflows are now **manual-only** (`workflow_dispatch`).
24
482
 
25
483
  ### Added — IRCv3 extensions (`@serverless-ircd/irc-core`, `@serverless-ircd/irc-server`)
26
484
 
27
- The ten Phase 12 cap tickets light up the remaining negotiated-cap
28
- surface, and the two non-cap Phase 12 tickets (ISUPPORT tokens and
485
+ The ten cap tickets add the remaining negotiated-cap
486
+ surface, and the two non-cap tickets (ISUPPORT tokens and
29
487
  user mode `S`) round out the wire-visible advertisement. Each cap
30
488
  ships a pure reducer (where applicable), an actor route case,
31
489
  advertisement in `SUPPORTED_CAPABILITIES` (or conditional
@@ -135,10 +593,10 @@ with a readable, field-listing error.
135
593
 
136
594
  ### Added — Load-test harness (`@serverless-ircd/load-test`)
137
595
 
138
- PLAN §7 / Phase 7's 10k-connection harness. A synthetic WebSocket IRC
139
- client farm: opens N connections, registers (NICK/USER), joins a
596
+ The PLAN §7 10k-connection harness. A synthetic WebSocket IRC
597
+ client pool: opens N connections, registers (NICK/USER), joins a
140
598
  channel, and optionally chats. Captures **p50 / p95 / p99 latency per
141
- phase** (connect / register / join / message) plus the **drop rate**,
599
+ stage** (connect / register / join / message) plus the **drop rate**,
142
600
  and prints a markdown report to stdout (or `--report <path>`).
143
601
 
144
602
  ```bash
@@ -253,11 +711,11 @@ resolves it.
253
711
  `irc-test-support`) are **above the gate but below 100%** —
254
712
  follow-ups drive each to 100%.
255
713
  - The IRC services (NickServ / ChanServ / HostServ / OperServ /
256
- MemoServ) Phase 13 work is pending; `draft/read-marker` and
714
+ MemoServ) work is pending; `draft/read-marker` and
257
715
  `draft/pre-away` ship with in-memory reference stores, so marker /
258
716
  away persistence does not survive an adapter restart until a
259
717
  services backend lands.
260
- - The web client Phase 14 work (vendor Kiwi IRC, serve SPA from the
718
+ - The web client work (vendor Kiwi IRC, serve SPA from the
261
719
  Worker, WS Origin allowlist) is pending.
262
720
 
263
721
  ### Security
@@ -272,8 +730,8 @@ resolves it.
272
730
 
273
731
  ## [0.5.0] - 2026-08-02
274
732
 
275
- The headline is **protocol completion**: the deferred IRC verbs land
276
- (Phase 11), and the S2S / obsolete RFC 2812 verbs are **formally
733
+ The main change in this release is **protocol completion**: the deferred IRC verbs land,
734
+ and the S2S / obsolete RFC 2812 verbs are **formally
277
735
  dropped** rather than left "deferred". With this release every standard
278
736
  IRC verb a modern client expects is implemented — the only remaining
279
737
  `421 ERR_UNKNOWNCOMMAND` paths are the deliberately-unimplemented S2S
@@ -283,10 +741,10 @@ client compatibility matrix) and the required-`serverName` config gate.
283
741
 
284
742
  ### Added — Deferred IRC verbs (`@serverless-ircd/irc-core`, `@serverless-ircd/irc-server`)
285
743
 
286
- Six Phase 11 tickets light up the remaining command surface. Each ships
744
+ Six tickets add the remaining command surface. Each ships
287
745
  a pure reducer, an actor route case (no more `421`), new numerics in
288
746
  `protocol/numerics.ts`, and unit + actor coverage. (The remaining two
289
- Phase 11 tickets are drops — see below.)
747
+ tickets are drops — see below.)
290
748
 
291
749
  - **`KILL`** (oper-gated force-disconnect): `KILL <nick>
292
750
  <comment>` resolves the target via the nick registry + a live
@@ -340,7 +798,7 @@ Phase 11 tickets are drops — see below.)
340
798
  channel-scoped). The originator is always excluded (skip-self,
341
799
  matching NOTICE/PRIVMSG semantics). Empty message → `461`. The `+w`
342
800
  user mode was already accepted by `MODE` and advertised in `004
343
- RPL_MYINFO`; this lights up the consumer.
801
+ RPL_MYINFO`; this enables the consumer.
344
802
  - **`SETNAME`** (realname change): `SETNAME :<realname>`
345
803
  updates `state.realname` so a subsequent `WHOIS` reflects the new
346
804
  value in `311 RPL_WHOISUSER`. No broadcast is emitted in v1 (there
@@ -350,7 +808,7 @@ Phase 11 tickets are drops — see below.)
350
808
 
351
809
  ### Changed — S2S & obsolete verbs formally dropped (`@serverless-ircd/irc-core`)
352
810
 
353
- The remaining two Phase 11 tickets convert the "deferred" framing into
811
+ The remaining two tickets convert the "deferred" framing into
354
812
  an explicit **drop**. There is no behaviour change on the wire — the verbs
355
813
  already returned `421 ERR_UNKNOWNCOMMAND` — but the framing, comments,
356
814
  and reserved numerics are cleaned up so the codebase no longer carries
@@ -447,13 +905,13 @@ dead scaffolding.
447
905
 
448
906
  ## [0.4.0] - 2026-07-31
449
907
 
450
- The headline is **dual transport**: both adapters now speak a real
908
+ The main change in this release is **dual transport**: both adapters now speak a real
451
909
  `irc+tls :6697` (TLS-over-TCP) surface for stock IRC clients in
452
910
  production — not just the local CLI — alongside the WebSocket path. This
453
911
  lands **SASL `EXTERNAL` via mTLS**, the **remaining standard IRC verbs**
454
912
  (WHOWAS, VERSION, TIME, ADMIN, INFO, USERHOST, ISON), a **Cloudflare
455
913
  D1-backed SASL account store**, config-driven server identity, and a
456
- **Node 24 / pnpm 11** toolchain. Phase 7 (TLS hardening & raw TCP) is
914
+ **Node 24 / pnpm 11** toolchain. TLS hardening & raw TCP is
457
915
  complete.
458
916
 
459
917
  ### Added — Dual transport: WebSocket + `irc+tls :6697`
@@ -523,7 +981,7 @@ pipeline is unchanged.
523
981
 
524
982
  - The `002` / `003` / `004` registration replies now reflect
525
983
  `ServerConfig.serverVersion` / `createdAt` — the hardcoded
526
- `'0.1.0'` / `'Phase 1'` placeholders are gone. A build step
984
+ `'0.1.0'` version / placeholder strings are gone. A build step
527
985
  (`scripts/generate-build-info.mjs`) bakes the package version and a
528
986
  build timestamp into `RPL_CREATED` (`003`) when the config omits them.
529
987
 
@@ -791,7 +1249,7 @@ AWS are now both deployable.
791
1249
  ### Added — Shared test harness (`@serverless-ircd/irc-test-support`)
792
1250
 
793
1251
  - New workspace package owning the parametrized IRC scenario suite
794
- (`runIrcScenarios`, 31 Phase 2 scenarios) and the
1252
+ (`runIrcScenarios`, 31 scenarios) and the
795
1253
  `IrcHarnessFactory` / `IrcHarness` / `ClientHarness` seam each
796
1254
  adapter implements. Ships the reference `inMemoryHarnessFactory`.
797
1255
  cf-adapter and aws-adapter integration tests both consume it, so the
@@ -822,7 +1280,7 @@ AWS are now both deployable.
822
1280
  end-to-end rather than advertised-only.
823
1281
  - The local CLI now binds a **second RFC-style TCP listener** by default
824
1282
  (`--tcp-port`, default `<port> + 1`) alongside the WebSocket listener,
825
- so stock IRC clients can dial in directly without the forwarder.
1283
+ so stock IRC clients can connect directly without the forwarder.
826
1284
  Pass `--no-tcp` for WebSocket-only. `--motd-file` loads the MOTD from
827
1285
  disk.
828
1286