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
package/README.md CHANGED
@@ -6,7 +6,7 @@ platform-agnostic core, and two thin adapters run it on **Cloudflare Workers**
6
6
 
7
7
  One TypeScript codebase. Two serverless substrates.
8
8
 
9
- > **Status:** **v0.6.0 (preview).** The pure protocol core, the
9
+ > **Status:** **v0.8.0 (preview).** The pure protocol core, the
10
10
  > `IrcRuntime` port + in-memory runtime, a runnable local CLI server,
11
11
  > the **Cloudflare Workers** adapter, and the **AWS** (API Gateway
12
12
  > WebSocket + Lambda + DynamoDB + CDK) adapter are all functional and
@@ -19,30 +19,44 @@ One TypeScript codebase. Two serverless substrates.
19
19
  > deferred IRC verbs (`KILL`, `REHASH`, `LUSERS` + `STATS`, `TRACE`,
20
20
  > `WALLOPS`, `SETNAME`) all land, and the S2S (`CONNECT` / `SQUIT` /
21
21
  > `LINKS`) and obsolete RFC 2812 (`SERVICE` / `SUMMON` / `USERS`) verbs
22
- > are formally dropped. **v0.6.0 lands the IRCv3 extension sweep
23
- > (Phase 12)** — ten caps (`account-notify`, `msgid`,
22
+ > are formally dropped. v0.6.0 landed the **IRCv3 extension sweep**
23
+ > — ten caps (`account-notify`, `msgid`,
24
24
  > `standard-replies`, `MONITOR`, `labeled-response`, `sts`,
25
25
  > `draft/typing`, `draft/multiline`, `draft/read-marker`,
26
26
  > `draft/pre-away`), the ISUPPORT tokens for the new caps, and the
27
- > read-only user mode `S` (TLS connected) closes the
28
- > required-`serverName` config gate, ships a `tools/load-test`
29
- > 10k-connection harness, and lifts `aws-adapter` / `aws-stack` over
30
- > the 90% coverage gate (CF packages moved to istanbul coverage,
31
- > clearing the v0.5.0 workerd-incompatibility caveat). Remaining 0.x
32
- > work: the client compatibility sweep (WeeChat / HexChat / IRCCloud /
33
- > TheLounge), IRC services (NickServ / ChanServ / HostServ /
34
- > OperServ / MemoServ), a web client (vendored Kiwi IRC), and driving
35
- > the remaining sub-100% packages to full coverage. See
36
- > `CHANGELOG.md` for the per-release manifests.
27
+ > read-only user mode `S` (TLS connected). **v0.7.0 lands the web
28
+ > client**: a vendored **Kiwi IRC** SPA served at `/webclient/`
29
+ > and a static landing page at `/` directly by the Cloudflare Worker,
30
+ > with **Cross-Site WebSocket Hijacking (CSWSH) defense** wired into
31
+ > the WS upgrade path (same-origin auto-derive by default, optional
32
+ > `WEB_ORIGINS` allowlist for cross-origin deploys) the browser
33
+ > opens a native `wss://` straight to the existing IRC-over-WebSocket
34
+ > endpoint. **v0.8.0 lands integrated IRC services** NickServ
35
+ > (`REGISTER` / `IDENTIFY` / `DROP` / `INFO` / `SET ENFORCE` +
36
+ > nick enforcement), ChanServ (`REGISTER` / `DROP` / `SET` /
37
+ > `ACCESS` / `LEVELS` + the `+r` / `+R` / `+M` channel modes),
38
+ > HostServ (`ON` / `OFF` / `REQUEST` + oper `SET` / `APPROVE`),
39
+ > OperServ (`AKILL` / `JUPE` / `RAW`), and MemoServ
40
+ > (`SEND` / `LIST` / `READ` / `DEL`) — backed by a `ServicesStore`
41
+ > port with **persistent D1 (Cloudflare) and DynamoDB (AWS)
42
+ > backends** (write-behind, surviving redeploys). The SASL
43
+ > `AccountStore` and NickServ accounts are **unified** under one
44
+ > scrypt-hashed credential store, `PASS <nick>:<password>` identifies
45
+ > at registration, and `draft/read-marker` gains the timestamp-based
46
+ > `MARKREAD` verb. Remaining 0.x work: the client compatibility sweep
47
+ > (WeeChat / HexChat / IRCCloud / TheLounge), the Playwright
48
+ > browser-driven e2e for the SPA, and driving the remaining
49
+ > sub-100% packages to full coverage. See `CHANGELOG.md` for the
50
+ > per-release manifests.
37
51
 
38
52
  ---
39
53
 
40
54
  ## Why
41
55
 
42
56
  IRC servers have historically been long-running stateful processes. This
43
- project explores what happens when the protocol brain is a set of **pure
44
- reducers** and the only platform-specific code is the side-effect plumbing
45
- (transports, registries, fanout). The payoff:
57
+ project moves the protocol logic into a set of **pure reducers** so that
58
+ the only platform-specific code is the side-effect layer (transports,
59
+ registries, fanout). The consequences:
46
60
 
47
61
  - Every command handler is a trivial unit test: arrange state, apply a message,
48
62
  assert the new state and emitted effects.
@@ -54,7 +68,7 @@ reducers** and the only platform-specific code is the side-effect plumbing
54
68
 
55
69
  ## Architecture
56
70
 
57
- Hexagonal / ports-and-adapters. The core reasons about IRC; adapters move bytes.
71
+ Hexagonal / ports-and-adapters. The core implements the IRC protocol; adapters handle I/O.
58
72
 
59
73
  ```
60
74
  ┌─────────────────────────────────────────────────────────────────┐
@@ -94,7 +108,7 @@ function. Both feed the same `ConnectionActor` through a `Transport`
94
108
  seam (`WsTextFrameTransport` vs. `TcpByteStreamTransport`); the
95
109
  parser/reducer/dispatch pipeline is identical.
96
110
 
97
- ### The key idea: pure reducers + location-of-authority
111
+ ### Design: pure reducers + location-of-authority
98
112
 
99
113
  Each command handler is a pure function:
100
114
 
@@ -119,6 +133,7 @@ Each reducer runs in whichever authority **owns** the state it mutates:
119
133
  | PRIVMSG/NOTICE to channel | Channel entity | Fanout (reads roster) |
120
134
  | Nick collision check | Registry entity | Nick → Connection map |
121
135
  | NAMES / WHO / WHOIS | Reads snapshots | (no mutation) |
136
+ | Services (`PRIVMSG NickServ`/`ChanServ`/…) | Connection entity | `ServicesStore` (account/channel/vhost/memo state) |
122
137
 
123
138
  ---
124
139
 
@@ -127,20 +142,21 @@ Each reducer runs in whichever authority **owns** the state it mutates:
127
142
  ```
128
143
  ServerlessIRCd/
129
144
  ├── packages/
130
- │ ├── irc-core/ pure protocol + reducers (the brain)
145
+ │ ├── irc-core/ pure protocol + reducers
131
146
  │ ├── irc-server/ orchestration, IrcRuntime port, dispatch
132
147
  │ ├── in-memory-runtime/ reference runtime (used by tests + local CLI)
133
148
  │ ├── irc-test-support/ parametrized IRC scenario suite + harness seam
134
149
  │ ├── cf-adapter/ Durable Objects + CfRuntime
135
150
  │ └── aws-adapter/ Lambda + DynamoDB + AwsRuntime
136
151
  ├── apps/
137
- │ ├── cf-worker/ worker entry, DO migrations, bindings
152
+ │ ├── cf-worker/ worker entry, DO migrations, bindings, [assets] + CSWSH gate
138
153
  │ ├── cf-tcp-container/ Spectrum + Container origin for irc+tls :6697
139
154
  │ ├── aws-stack/ CDK stack (APIGW WS + NLB + Lambda streaming + DynamoDB)
155
+ │ ├── web/ vendored Kiwi IRC SPA (→ /webclient/) + static landing page (→ /)
140
156
  │ └── local-cli/ runnable WS + TCP server using in-memory-runtime
141
157
  ├── tools/
142
158
  │ ├── tcp-ws-forwarder/ local TCP↔ws/wss bridge for stock IRC clients
143
- │ ├── load-test/ synthetic WebSocket IRC client farm (10k conns, p50/p95/p99, drop rate)
159
+ │ ├── load-test/ synthetic WebSocket IRC client pool (10k conns, p50/p95/p99, drop rate)
144
160
  │ ├── ci-hardening/ coverage-gate + mutation-config validators
145
161
  │ ├── seed-aws-accounts.ts scrypt-hash SASL PLAIN accounts into DynamoDB
146
162
  │ └── seed-cf-accounts.ts scrypt-hash SASL PLAIN accounts into Cloudflare D1
@@ -174,6 +190,7 @@ pin is resolved automatically:
174
190
  ```bash
175
191
  corepack enable # lets the pinned pnpm@11 run on any Node ≥ 24
176
192
 
193
+ git submodule update --init # docs + apps/web Kiwi upstream sources
177
194
  pnpm install # install workspace deps
178
195
 
179
196
  pnpm build # build all packages (turbo)
@@ -191,6 +208,14 @@ pnpm mutation # Stryker spot-check on irc-core (protocol + commands)
191
208
  pnpm clean # remove dist/coverage/.turbo + node_modules
192
209
  ```
193
210
 
211
+ The web client (`apps/web`) vendors Kiwi IRC as a git submodule. Building it
212
+ needs the upstream sources (`git submodule update --init apps/web/upstream`)
213
+ and `yarn` (Kiwi ships a `yarn.lock`; `corepack enable` provides it):
214
+
215
+ ```bash
216
+ pnpm --filter web build # builds the Kiwi SPA into apps/web/dist/webclient/ (/webclient/)
217
+ ```
218
+
194
219
  Coverage reports are written to `packages/*/coverage/`. CI (`.github/workflows/ci.yml`)
195
220
  runs lint, typecheck, the coverage gate, the parametrized contract suite, and a
196
221
  Stryker mutation spot-check on every push and pull request. Coverage thresholds
@@ -210,7 +235,7 @@ that share one runtime, so a TCP client and a WS client can see each other
210
235
 
211
236
  - a **WebSocket** listener (the serverless transport) on `--port`, and
212
237
  - an **RFC-style TCP** listener on `--tcp-port` (default `<port> + 1`) so
213
- real IRC clients (WeeChat, HexChat, irssi, …) can dial in directly.
238
+ real IRC clients (WeeChat, HexChat, irssi, …) can connect directly.
214
239
 
215
240
  First build the workspace (the CLI runs from compiled `dist/`):
216
241
 
@@ -243,11 +268,55 @@ On startup it logs a JSON line like:
243
268
  | `--motd-file <p>` | built-in | Read MOTD lines from this file (one per line). |
244
269
  | `--server-name <h>`| `irc.localhost` | Server hostname advertised in `001`/`005`. Production MUST override. |
245
270
  | `--network-name <n>`| `LocalNet` | Network name advertised in `005 NETWORK=…`. |
271
+ | `--server-password <p>`| | Server-password gate. When set, every connection must supply `PASS <p>` (or SASL-identify) before `001`. Treat as a secret. |
246
272
  | `-h, --help` | | Show help and exit. |
247
273
 
248
274
  Pass `--host 0.0.0.0` to expose the server on all interfaces. `SIGINT` /
249
275
  `SIGTERM` perform a graceful shutdown (closes active sockets, then exits).
250
276
 
277
+ ### Server password (`SERVER_PASSWORD` / `--server-password`)
278
+
279
+ All three adapters (Cloudflare Worker, AWS Lambda, local CLI) support an
280
+ optional **server-password gate**. When the knob is set, every connection
281
+ must supply the matching `PASS <value>` before `001 RPL_WELCOME` is
282
+ emitted; when unset or empty the gate is disabled (the default).
283
+
284
+ | Adapter | Knob |
285
+ |----------------|-------------------------------------------------------------------------------|
286
+ | Cloudflare | `wrangler secret put SERVER_PASSWORD` (Workers secret; never a `[vars]` entry). |
287
+ | AWS | `SERVER_PASSWORD` Lambda env var (sourced from Secrets Manager / SSM). |
288
+ | local CLI | `--server-password <p>` flag, or `serverPassword` on `StartServerOptions`. |
289
+
290
+ On a mismatched/missing `PASS` the server emits `464 ERR_PASSWDMISMATCH`
291
+ and disconnects with reason `Bad Password`. **SASL short-circuit:** a
292
+ connection that has authenticated an account via SASL
293
+ (`AUTHENTICATE PLAIN` / `EXTERNAL`) is exempt — the reducer's gate
294
+ treats `state.account !== undefined` as already authorised, so a
295
+ deployment with both `SERVER_PASSWORD` and configured SASL accounts
296
+ does not need to hand the shared password to identified users. The
297
+ server-wide gate is a shared deployment secret (never log it); per-user
298
+ credentials still go through the hashed SASL account store
299
+ (`HashedAccountStore` / D1 / DynamoDB), which is unaffected.
300
+
301
+ **PASS-based account login:** in addition to SASL, a client may identify
302
+ to its NickServ account at registration by sending
303
+ `PASS <nick>:<password>` (the same `<nick>:<password>` literal the
304
+ `SASL_ACCOUNTS` seed tooling uses). When an `AccountStore` is configured,
305
+ `NICK alice` + `PASS alice:hunter2` + `USER …` verifies the credentials
306
+ against the account store and, on success, logs the connection in as
307
+ `alice` — emitting `900 RPL_LOGGEDIN` + `903 RPL_SASLSUCCESS` (the same
308
+ "account is set" numerics SASL uses) before `001 RPL_WELCOME`, stamping
309
+ user mode `+r`, and running the same read-marker / away / memo replay as
310
+ a SASL login. The nick left of the `:` must match the `NICK` sent; a bare
311
+ `PASS <value>` with no colon is always treated as a server-password
312
+ candidate, never as account credentials. A wrong password or unknown nick
313
+ is indistinguishable (no `904`/`464` from the auth path) and the
314
+ connection simply proceeds un-identified when no server password is set.
315
+ This composes with the gate above by precedence: an already-identified
316
+ connection (SASL or PASS-auth) satisfies the gate; a `<nick>:<password>`
317
+ that fails verify never matches a bare shared secret, so a server with
318
+ both configured still rejects it with `464`.
319
+
251
320
  ### Connecting
252
321
 
253
322
  The WebSocket listener speaks WebSocket text frames (one IRC message per
@@ -270,9 +339,9 @@ given.
270
339
  The local CLI ships a built-in TCP listener, so no bridge is needed for local
271
340
  development. Deployed stacks (Cloudflare Workers and AWS API Gateway) are
272
341
  WebSocket-only, so a stock TCP IRC client (WeeChat, HexChat, irssi, …) cannot
273
- dial them directly. `tools/tcp-ws-forwarder` is a local bridge for that case:
342
+ connect to them directly. `tools/tcp-ws-forwarder` is a local bridge for that case:
274
343
  it listens on a TCP port and, for each connection, opens one WebSocket to a
275
- `ws://` / `wss://` target and pumps IRC lines both ways — reassembling the TCP
344
+ `ws://` / `wss://` target and forwards IRC lines both directions — reassembling the TCP
276
345
  byte stream into one message per WS frame outbound, and normalizing inbound
277
346
  frames back to canonical CRLF.
278
347
 
@@ -317,9 +386,9 @@ against any line-oriented WebSocket endpoint.
317
386
 
318
387
  ## Load testing (`tools/load-test`)
319
388
 
320
- A synthetic WebSocket IRC client farm (PLAN §7, Phase 7). It opens N
389
+ A synthetic WebSocket IRC client pool (PLAN §7). It opens N
321
390
  connections to a `ws://` / `wss://` target, registers each (NICK/USER),
322
- joins a channel, and optionally chats. Per-phase **p50 / p95 / p99
391
+ joins a channel, and optionally chats. Per-stage **p50 / p95 / p99
323
392
  latency** (connect / register / join / message) and the **drop rate**
324
393
  are captured, then printed as a markdown report to stdout (and written
325
394
  to `--report <path>` when given).
@@ -364,6 +433,66 @@ report).
364
433
 
365
434
  ---
366
435
 
436
+ ## Web client (`apps/web`)
437
+
438
+ A vendored **Kiwi IRC** SPA served at `/webclient/` and a static project
439
+ landing page served at `/`, both baked out of `apps/web/dist/` and
440
+ served directly by the Cloudflare Worker via its `[assets]` binding.
441
+ The browser opens a native `wss://` directly to the Worker's
442
+ IRC-over-WebSocket endpoint — no proxy, gateway, or transport
443
+ adaptation in the request path. See `docs/WebClientGuide.md` for the
444
+ end-to-end contributor/operator guide.
445
+
446
+ Build the SPA + landing page (needs the submodule + `yarn`, provided by
447
+ `corepack enable`):
448
+
449
+ ```bash
450
+ git submodule update --init apps/web/upstream # one-time per clone
451
+ pnpm --filter web build:staging # or :prod / default
452
+ # → apps/web/dist/index.html (landing page, served at /)
453
+ # → apps/web/dist/webclient/index.html (Kiwi SPA, served at /webclient/)
454
+ # → apps/web/dist/webclient/static/config.json (baked, env-specific)
455
+ ```
456
+
457
+ Then run everything locally (Worker serves SPA + landing page + WS on
458
+ one origin):
459
+
460
+ ```bash
461
+ pnpm build # workspace packages
462
+ pnpm --filter @serverless-ircd/cf-worker dev # http://localhost:8787
463
+ # / → landing page
464
+ # /webclient/ → Kiwi SPA (opens ws://localhost:8787/)
465
+ # /health → plaintext liveness
466
+ ```
467
+
468
+ Deploy staging (Worker + assets in one command):
469
+
470
+ ```bash
471
+ pnpm deploy:cf:staging # wrangler deploy --env staging
472
+ ```
473
+
474
+ ### WebSocket Origin policy (CSWSH defense)
475
+
476
+ WebSocket upgrades do **not** follow the same-origin policy, so the
477
+ Worker rejects browser upgrades whose `Origin` is not allowed — see
478
+ `apps/cf-worker/src/origin-allowlist.ts` and the SPA guide §5. Two
479
+ modes, evaluated in order:
480
+
481
+ 1. **Explicit allowlist** — set `WEB_ORIGINS` (comma-separated) for
482
+ cross-origin deploys (SPA on a different domain than the Worker,
483
+ e.g. Cloudflare Pages). Only listed origins proceed.
484
+ 2. **Same-origin auto-derive (the default)** — when `WEB_ORIGINS` is
485
+ unset/empty, the Worker compares the browser's `Origin` against the
486
+ request's own origin. Match → proceed; mismatch → `403 Forbidden`.
487
+
488
+ `WEB_ORIGINS` is **optional** — same-origin auto-derive needs zero
489
+ per-env config and works for `*.workers.dev`, custom domains, and
490
+ preview URLs alike. Non-browser clients (curl, WeeChat, the
491
+ `tcp-ws-forwarder`, scripted harnesses) never send `Origin` and pass
492
+ through unchanged in both modes.
493
+
494
+ ---
495
+
367
496
  ## WebSocket transport (IRCv3 subprotocols)
368
497
 
369
498
  The WebSocket entry points (the local CLI, the Cloudflare Worker, and the
@@ -428,49 +557,49 @@ suite passes against every runtime, both adapters are deployed to staging,
428
557
  and ≥3 reference clients (WeeChat, HexChat, IRCCloud, TheLounge) connect
429
558
  cleanly. Per-release manifests live in `CHANGELOG.md`.
430
559
 
431
- - **Phase 0** — Foundation (monorepo, turbo, vitest, biome, CI). ✅
432
- - **Phase 1** — Pure protocol engine: reducers, IRCv3 caps, isupport,
560
+ - **Foundation** (monorepo, turbo, vitest, biome, CI). ✅
561
+ - **Pure protocol engine** reducers, IRCv3 caps, isupport,
433
562
  SASL (`PLAIN`), token-bucket flood control, chat history. ✅ (most in
434
563
  **v0.1.0**; SASL/flood/chathistory landed in **v0.2.0**).
435
- - **Phase 2** — `IrcRuntime` port, in-memory runtime, `ConnectionActor`,
564
+ - **`IrcRuntime` port** — in-memory runtime, `ConnectionActor`,
436
565
  local CLI, parametrized contract suite. ✅ landed in **v0.1.0**.
437
- - **Phase 3** — Cloudflare adapter (ConnectionDO / ChannelDO / RegistryDO).
566
+ - **Cloudflare adapter** (ConnectionDO / ChannelDO / RegistryDO).
438
567
  ✅ landed in **v0.1.0** (staging deploy via CI; prod deploy is manual).
439
- - **Phase 4** — AWS adapter (APIGW WS + Lambda + DynamoDB + CDK).
568
+ - **AWS adapter** (APIGW WS + Lambda + DynamoDB + CDK).
440
569
  ✅ landed in **v0.2.0** (staging deploy via CI; prod deploy is manual).
441
- - **Phase 5** — Observability, security hardening, config, CI gates.
570
+ - **Observability, security hardening, config, CI gates.**
442
571
  ✅ landed across v0.2.0–v0.4.0 (logger port, server-password, cloaking,
443
572
  admission limits, coverage + mutation gates, OPER credential auth, the
444
573
  Cloudflare config loader, and AWS max-clients admission); the
445
574
  required-`serverName` gate closed in **v0.6.0**.
446
- - **Phase 6** — TLS hardening & raw TCP transport (`irc+tls :6697`).
575
+ - **TLS hardening & raw TCP transport** (`irc+tls :6697`).
447
576
  ✅ landed in **v0.4.0**: generalized `ConnectionActor` transport seam,
448
577
  Cloudflare Spectrum + Container origin, AWS NLB + Lambda streaming,
449
578
  mTLS → SASL `EXTERNAL`, transport-parametrized contract suite, ADR-009.
450
- - **Phase 7 — Load testing & compatibility sweep.** 🔄 partial as of
579
+ - **Load testing & compatibility sweep.** 🔄 partial as of
451
580
  **v0.6.0**: the `tools/load-test` 10k-connection harness is in-tree;
452
581
  the formal 10k-connection report per platform and the client
453
582
  compatibility matrix (WeeChat / HexChat / IRCCloud / TheLounge) are
454
583
  still pending.
455
- - **Phase 8 — PLAN-FIXES remediation.** ✅ landed across v0.3.0–v0.4.0
584
+ - **PLAN-FIXES remediation.** ✅ landed across v0.3.0–v0.4.0
456
585
  (MessageStore wired into all three adapters, AccountStore end-to-end,
457
586
  the DynamoDB `Accounts` table decided, CF enumeration RPCs, AWS QUIT
458
587
  fanout, `OPER` / `TAGMSG` / `WHOWAS`, RFC 1459 case-mapping, real
459
588
  MOTD, config-driven server identity, AWS max-clients admission,
460
589
  CF D1 SASL store, and a sweep of dead-code / stale-comment cleanup).
461
- - **Phase 9 — Runtime & tooling baseline.** ✅ landed in **v0.4.0**
590
+ - **Runtime & tooling baseline.** ✅ landed in **v0.4.0**
462
591
  (Node ≥ 24 / pnpm 11).
463
- - **Phase 10 — IRCv3 WebSocket extension.** ✅ landed in **v0.4.0**:
592
+ - **IRCv3 WebSocket extension.** ✅ landed in **v0.4.0**:
464
593
  `text.ircv3.net` / `binary.ircv3.net` subprotocol negotiation +
465
594
  per-message framing across the CF Worker, AWS APIGW, local CLI, and
466
595
  `tcp-ws-forwarder` (510-byte budget, `1009`/`1003` close codes,
467
596
  legacy fallback).
468
- - **Phase 11 — Deferred IRC verbs.** ✅ landed in **v0.5.0**: `KILL`,
597
+ - **Deferred IRC verbs.** ✅ landed in **v0.5.0**: `KILL`,
469
598
  `REHASH`, `LUSERS` + `STATS`, `TRACE`, `WALLOPS`, `SETNAME`, plus
470
599
  the formal drop of the S2S (`CONNECT`/`SQUIT`/`LINKS`) and obsolete
471
600
  RFC 2812 (`SERVICE`/`SUMMON`/`USERS`) verbs. The protocol surface is
472
601
  feature-complete for modern clients.
473
- - **Phase 12 — IRCv3 extension sweep.** ✅ landed in **v0.6.0**: ten
602
+ - **IRCv3 extension sweep.** ✅ landed in **v0.6.0**: ten
474
603
  negotiated caps (`account-notify`, `msgid`, `standard-replies`,
475
604
  `MONITOR`, `labeled-response`, `sts`, `draft/typing`,
476
605
  `draft/multiline`, `draft/read-marker`, `draft/pre-away`), the
@@ -479,19 +608,32 @@ cleanly. Per-release manifests live in `CHANGELOG.md`.
479
608
  (TLS connected), and the async push fanout seam (Registry hooks →
480
609
  `MONITOR` online/offline, `BroadcastWallops`-style caps-gated
481
610
  delivery).
482
- - **Phase 13 — IRC services.** pending: `ServicesStore` port +
483
- NickServ / ChanServ / HostServ / OperServ / MemoServ. Adapter
484
- backends (D1 / DynamoDB) ship alongside; until then
485
- `draft/read-marker` and `draft/pre-away` use in-memory reference
486
- stores and do not survive an adapter restart.
487
- - **Phase 14 — Web client.** ⬳ pending: vendored Kiwi IRC SPA served
488
- from the Worker, WS Origin allowlist (CSWSH defense), landing page,
489
- headless browser e2e.
490
- - **Coverage hardening.** 🔄 partial as of **v0.6.0**: `aws-adapter`
491
- and `aws-stack` now clear the 90% gate (CI boots DynamoDB Local via
492
- a JRE; the CDK synth no longer re-bundles under coverage), and the
493
- CF packages moved to istanbul to clear the v0.5.0 workerd-incompat.
494
- Follow-ups drive each remaining sub-100% package to 100%.
611
+ - **IRC services.** landed in **v0.8.0**: `ServicesStore` port
612
+ + integrated NickServ (`REGISTER`/`IDENTIFY`/`DROP`/`INFO`/`SET
613
+ ENFORCE` + nick enforcement), ChanServ (`REGISTER`/`DROP`/`SET`/
614
+ `ACCESS`/`LEVELS` + `+r`/`+R`/`+M`), HostServ
615
+ (`ON`/`OFF`/`REQUEST` + oper `SET`/`APPROVE`), OperServ
616
+ (`AKILL`/`JUPE`/`RAW`), MemoServ (`SEND`/`LIST`/`READ`/`DEL`).
617
+ Persistent backends ship alongside **D1** on Cloudflare,
618
+ **DynamoDB** on AWS — both write-behind so registrations survive
619
+ redeploys. The SASL `AccountStore` and NickServ accounts are
620
+ **unified** under one scrypt-hashed credential store, `PASS
621
+ <nick>:<password>` identifies at registration, and `draft/read-marker`
622
+ / `draft/pre-away` now persist through the `ServicesStore` (no longer
623
+ in-memory-only). See `docs/Services.md`.
624
+ - **Web client.** 🔄 partial as of **v0.7.0**: a vendored
625
+ Kiwi IRC SPA is served at `/webclient/` and a static landing page at `/`
626
+ by the Cloudflare Worker via an `[assets]` binding, with a Zod-
627
+ validated per-env config matrix and **CSWSH defense** (same-origin
628
+ auto-derive + optional `WEB_ORIGINS` allowlist). The Playwright
629
+ headless-browser e2e is the remaining open ticket.
630
+ See `docs/WebClientGuide.md` for the contributor/operator guide.
631
+ - **Coverage hardening.** 🔄 partial as of **v0.7.0**: `aws-adapter`
632
+ and `aws-stack` clear the 90% gate (v0.6.0); the CF packages run
633
+ under istanbul (v0.6.0). The remaining packages (`cf-adapter`,
634
+ `local-cli`, `load-test`, `cf-tcp-container`, `tcp-ws-forwarder`,
635
+ `irc-test-support`, `web`) are **above the gate but below 100%** —
636
+ follow-ups drive each to 100%.
495
637
 
496
638
  ---
497
639
 
@@ -516,10 +658,34 @@ RFC 2812 verbs `SERVICE`/`SUMMON`/`USERS` are **formally dropped** as of
516
658
  v0.5.0 — they return `421 ERR_UNKNOWNCOMMAND`, and their reserved
517
659
  numerics have been removed. S2S linking is a PLAN non-goal.)
518
660
 
519
- **Channel modes:** `o v b i k l t n m s p`.
661
+ **Channel modes:** `o v b i k l t n m s p`, plus the
662
+ services-derived modes `r` (registered), `R` (block unidentified
663
+ join/message), `M` (moderated-identified) — settable only via ChanServ,
664
+ not via `MODE`.
520
665
  **User modes:** `i`, `o` (local only), `w`, `s`, and the read-only `S`
521
666
  (TLS connected — set by the transport at registration, surfaced in WHOIS
522
- via `276 RPL_WHOISSECURE`, not settable via `MODE`).
667
+ via `276 RPL_WHOISSECURE`, not settable via `MODE`) and the read-only
668
+ `r` (registered — set by NickServ `IDENTIFY` / SASL login, surfaced as
669
+ `+r`, not settable via `MODE`).
670
+
671
+ **Integrated IRC services** (v0.8.0): the service nicks `NickServ`,
672
+ `ChanServ`, `HostServ`, `OperServ`, and `MemoServ` are reserved and
673
+ routed to dedicated pure reducers when a `ServicesStore` is bound —
674
+ there is **no separate services process and no S2S link**. Reached via
675
+ `PRIVMSG <Service> :<subcommand>`:
676
+ NickServ (`REGISTER` / `IDENTIFY` / `DROP` / `INFO` / `SET ENFORCE` +
677
+ nick enforcement on the `NICK` path), ChanServ (`REGISTER` / `DROP` /
678
+ `SET` / `INFO` / `ACCESS` / `LEVELS` + auto-op on `JOIN`), HostServ
679
+ (`ON` / `OFF` / `REQUEST` + oper `SET` / `APPROVE` / `LIST`, with
680
+ `CHGHOST` fanout), OperServ (`AKILL` / `JUPE` / `UNJUPE` / `RAW`), and
681
+ MemoServ (`SEND` / `LIST` / `READ` / `DEL` with queue delivery at
682
+ identify). The SASL `AccountStore` and NickServ accounts share one
683
+ scrypt-hashed credential store, so a registered nick is also a SASL
684
+ login and vice versa. Backends: D1 on Cloudflare, DynamoDB on AWS,
685
+ in-memory for the local CLI / tests (all write-behind; registrations
686
+ survive redeploys). When no store is bound, services commands reply
687
+ `501` and the rest of the daemon is unaffected. See
688
+ `docs/Services.md` for the full reference.
523
689
 
524
690
  **IRCv3 extensions (negotiated via `CAP`):** `message-tags` (incl. the
525
691
  `TAGMSG` command), `server-time`, `account-tag`, `account-notify`
@@ -536,7 +702,9 @@ shared between live and `draft/chathistory` replay), `standard-replies`
536
702
  so compliant clients upgrade from plaintext to TLS and pin the secure
537
703
  listener), `draft/chathistory`, `safelist`, `draft/typing` (typing-indicator
538
704
  broadcast), `draft/multiline=<n>` (multi-line `BATCH`, default 4096-byte
539
- budget), `draft/read-marker` (per-account persisted last-read), and
705
+ budget), `draft/read-marker` (per-account persisted last-read, with the
706
+ timestamp-based `MARKREAD <target> [timestamp]` verb plus the
707
+ `+draft/read-marker` tag fanout), and
540
708
  `draft/pre-away` (per-account persisted away reason, replayed at
541
709
  identify). Case-insensitive nick/channel comparison uses **RFC 1459**
542
710
  case-mapping (advertised via `005 CASEMAPPING=rfc1459`). The new
@@ -563,13 +731,32 @@ bridges a stock TCP client to a deployed WebSocket endpoint.
563
731
  `irc+tls :6697`, SASL `EXTERNAL`/mTLS, WHOWAS + remaining verbs, D1 account
564
732
  store, Node 24/pnpm 11), v0.5.0 (deferred IRC verbs `KILL` / `REHASH`
565
733
  / `LUSERS` + `STATS` / `TRACE` / `WALLOPS` / `SETNAME`, plus the formal
566
- drop of the S2S and obsolete RFC 2812 verbs), and **v0.6.0** (the IRCv3
734
+ drop of the S2S and obsolete RFC 2812 verbs), v0.6.0 (the IRCv3
567
735
  extension sweep — `account-notify`, `msgid`, `standard-replies`,
568
736
  `MONITOR`, `labeled-response`, `sts`, `draft/typing`, `draft/multiline`,
569
737
  `draft/read-marker`, `draft/pre-away`, plus user mode `S`; the
570
738
  required-`serverName` gate; `tools/load-test`; aws-adapter / aws-stack
571
739
  coverage unblocks; CF packages moved to istanbul; deploys now
572
- manual-only).
740
+ manual-only), and **v0.7.0** (the web client — vendored Kiwi IRC SPA
741
+ at `/webclient/`, static landing page at `/`, Cloudflare Worker
742
+ `[assets]` binding, per-env Zod-validated config matrix, and CSWSH
743
+ defense via same-origin auto-derive + optional `WEB_ORIGINS`;
744
+ `/health` becomes an explicit route; mutation-killing tests for
745
+ `irc-core`'s parser / batch / numerics), and **v0.8.0** (integrated
746
+ IRC services — NickServ / ChanServ / HostServ / OperServ / MemoServ
747
+ backed by a `ServicesStore` port with persistent D1 + DynamoDB
748
+ backends; unified SASL + NickServ scrypt-hashed account store;
749
+ `PASS <nick>:<password>` registration login; the `draft/read-marker`
750
+ `MARKREAD` verb).
751
+ - `docs/Services.md` — operator and contributor reference for the
752
+ integrated IRC services: architecture (integrated vs. pseudo-client),
753
+ the per-service command tables, the `+r`/`+R`/`+M` modes, nick
754
+ enforcement, adapter backends, and an end-to-end registration
755
+ walkthrough.
756
+ - `docs/WebClientGuide.md` — end-to-end contributor/operator doc for the
757
+ web client: build pipeline, per-env config matrix, CSWSH rationale and
758
+ the optional `WEB_ORIGINS` var, local dev, optional Cloudflare Pages
759
+ alternative, and troubleshooting.
573
760
  - `docs/Cloudflare-TCP-Deployment.md` and `docs/AWS-TCP-Deployment.md` —
574
761
  end-to-end guides for the `:6697` TCP+TLS variants (Spectrum/Container on
575
762
  CF, NLB + Lambda streaming on AWS), including mTLS trust-store setup.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serverless-ircd/aws-stack",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "private": true,
5
5
  "description": "AWS CDK v2 stack: API Gateway v2 WebSocket API, Lambda, DynamoDB tables, least-privilege IAM",
6
6
  "license": "BSD-3-Clause",