serverless-ircd 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/.github/workflows/ci.yml +40 -3
  2. package/.github/workflows/deploy-aws.yml +1 -2
  3. package/.github/workflows/deploy-cf-tcp.yml +1 -5
  4. package/.github/workflows/deploy-cf.yml +1 -2
  5. package/.gitmodules +3 -0
  6. package/CHANGELOG.md +474 -0
  7. package/README.md +219 -22
  8. package/apps/aws-stack/README.md +33 -0
  9. package/apps/aws-stack/package.json +1 -1
  10. package/apps/aws-stack/src/aws-stack.ts +74 -9
  11. package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
  12. package/apps/aws-stack/tests/stack.test.ts +2 -2
  13. package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
  14. package/apps/aws-stack/tests/vitest.setup.ts +18 -0
  15. package/apps/aws-stack/vitest.config.ts +3 -0
  16. package/apps/cf-tcp-container/package.json +1 -1
  17. package/apps/cf-tcp-container/src/config-loader.ts +6 -7
  18. package/apps/cf-tcp-container/src/container-server.ts +3 -0
  19. package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
  20. package/apps/cf-tcp-container/wrangler.toml +1 -1
  21. package/apps/cf-worker/package.json +2 -1
  22. package/apps/cf-worker/src/origin-allowlist.ts +99 -0
  23. package/apps/cf-worker/src/worker.ts +89 -9
  24. package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +18 -0
  25. package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
  26. package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
  27. package/apps/cf-worker/tests/smoke.test.ts +28 -2
  28. package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
  29. package/apps/cf-worker/vitest.config.ts +1 -1
  30. package/apps/cf-worker/wrangler.test.toml +23 -0
  31. package/apps/cf-worker/wrangler.toml +59 -3
  32. package/apps/local-cli/package.json +1 -1
  33. package/apps/local-cli/src/config-loader.ts +6 -6
  34. package/apps/local-cli/src/main.ts +20 -0
  35. package/apps/local-cli/src/server.ts +9 -7
  36. package/apps/local-cli/tests/e2e.test.ts +112 -28
  37. package/apps/local-cli/tests/tcp.test.ts +71 -19
  38. package/apps/local-cli/tests/ws-subprotocol.test.ts +12 -7
  39. package/apps/web/landing/index.html +217 -0
  40. package/apps/web/package.json +28 -0
  41. package/apps/web/scripts/build.mjs +125 -0
  42. package/apps/web/src/build-env.ts +43 -0
  43. package/apps/web/src/config-schema.ts +138 -0
  44. package/apps/web/static/config.json +28 -0
  45. package/apps/web/static/config.prod.json +28 -0
  46. package/apps/web/static/config.staging.json +28 -0
  47. package/apps/web/tests/build-env.test.ts +63 -0
  48. package/apps/web/tests/build-smoke.test.ts +103 -0
  49. package/apps/web/tests/config-schema.test.ts +432 -0
  50. package/apps/web/tests/workspace.test.ts +12 -0
  51. package/apps/web/tsconfig.json +10 -0
  52. package/apps/web/upstream/.browserslistrc +3 -0
  53. package/apps/web/upstream/.editorconfig +9 -0
  54. package/apps/web/upstream/.eslintignore +3 -0
  55. package/apps/web/upstream/.eslintrc.js +90 -0
  56. package/apps/web/upstream/.github/FUNDING.yml +12 -0
  57. package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  58. package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  59. package/apps/web/upstream/.github/workflows/push.yml +26 -0
  60. package/apps/web/upstream/.github/workflows/release.yml +33 -0
  61. package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
  62. package/apps/web/upstream/.prettierrc.js +9 -0
  63. package/apps/web/upstream/.stylelintignore +5 -0
  64. package/apps/web/upstream/.stylelintrc.js +21 -0
  65. package/apps/web/upstream/LICENSE +201 -0
  66. package/apps/web/upstream/README.md +76 -0
  67. package/apps/web/upstream/babel.config.js +25 -0
  68. package/apps/web/upstream/crowdin.yml +3 -0
  69. package/apps/web/upstream/index.html +35 -0
  70. package/apps/web/upstream/jest.config.js +7 -0
  71. package/apps/web/upstream/jsdoc.json +22 -0
  72. package/apps/web/upstream/package.json +80 -0
  73. package/apps/web/upstream/scripts/pre-commit +43 -0
  74. package/apps/web/upstream/src/components/App.vue +516 -0
  75. package/apps/web/upstream/src/components/AppSettings.vue +587 -0
  76. package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
  77. package/apps/web/upstream/src/components/Avatar.vue +105 -0
  78. package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
  79. package/apps/web/upstream/src/components/BufferKey.vue +121 -0
  80. package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
  81. package/apps/web/upstream/src/components/Captcha.vue +85 -0
  82. package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
  83. package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
  84. package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
  85. package/apps/web/upstream/src/components/ChannelList.vue +345 -0
  86. package/apps/web/upstream/src/components/Container.vue +401 -0
  87. package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
  88. package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
  89. package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
  90. package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
  91. package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
  92. package/apps/web/upstream/src/components/MessageList.vue +1028 -0
  93. package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
  94. package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
  95. package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
  96. package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
  97. package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
  98. package/apps/web/upstream/src/components/Nicklist.vue +309 -0
  99. package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
  100. package/apps/web/upstream/src/components/NotConnected.vue +271 -0
  101. package/apps/web/upstream/src/components/SelfUser.vue +283 -0
  102. package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
  103. package/apps/web/upstream/src/components/ServerView.vue +126 -0
  104. package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
  105. package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
  106. package/apps/web/upstream/src/components/Sidebar.vue +429 -0
  107. package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
  108. package/apps/web/upstream/src/components/SidebarState.vue +145 -0
  109. package/apps/web/upstream/src/components/StartupError.vue +35 -0
  110. package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
  111. package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
  112. package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
  113. package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
  114. package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
  115. package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
  116. package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
  117. package/apps/web/upstream/src/components/UserBox.vue +728 -0
  118. package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
  119. package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
  120. package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
  121. package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
  122. package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
  123. package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
  124. package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
  125. package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
  126. package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
  127. package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
  128. package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
  129. package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
  130. package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
  131. package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
  132. package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
  133. package/apps/web/upstream/src/helpers/Colours.js +128 -0
  134. package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
  135. package/apps/web/upstream/src/helpers/Md5.js +193 -0
  136. package/apps/web/upstream/src/helpers/Misc.js +401 -0
  137. package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
  138. package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
  139. package/apps/web/upstream/src/libs/AudioManager.js +60 -0
  140. package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
  141. package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
  142. package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
  143. package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
  144. package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
  145. package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
  146. package/apps/web/upstream/src/libs/IPC.js +51 -0
  147. package/apps/web/upstream/src/libs/InputHandler.js +921 -0
  148. package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
  149. package/apps/web/upstream/src/libs/Logger.js +71 -0
  150. package/apps/web/upstream/src/libs/Message.js +169 -0
  151. package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
  152. package/apps/web/upstream/src/libs/MessageParser.js +249 -0
  153. package/apps/web/upstream/src/libs/Notifications.js +78 -0
  154. package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
  155. package/apps/web/upstream/src/libs/ServerSession.js +102 -0
  156. package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
  157. package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
  158. package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
  159. package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
  160. package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
  161. package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
  162. package/apps/web/upstream/src/libs/bufferTools.js +177 -0
  163. package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
  164. package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
  165. package/apps/web/upstream/src/libs/settingTools.js +31 -0
  166. package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
  167. package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
  168. package/apps/web/upstream/src/libs/state/UserState.js +89 -0
  169. package/apps/web/upstream/src/libs/state/common.js +26 -0
  170. package/apps/web/upstream/src/libs/state.js +961 -0
  171. package/apps/web/upstream/src/libs/storage/Local.js +51 -0
  172. package/apps/web/upstream/src/main.js +566 -0
  173. package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
  174. package/apps/web/upstream/src/res/configTemplates.js +373 -0
  175. package/apps/web/upstream/src/res/globalStyle.css +277 -0
  176. package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
  177. package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
  178. package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
  179. package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
  180. package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
  181. package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
  182. package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
  183. package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
  184. package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
  185. package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
  186. package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
  187. package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
  188. package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
  189. package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
  190. package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
  191. package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
  192. package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
  193. package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
  194. package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
  195. package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
  196. package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
  197. package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
  198. package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
  199. package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
  200. package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
  201. package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
  202. package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
  203. package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
  204. package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
  205. package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
  206. package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
  207. package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
  208. package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
  209. package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
  210. package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
  211. package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
  212. package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
  213. package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
  214. package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
  215. package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
  216. package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
  217. package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
  218. package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
  219. package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
  220. package/apps/web/upstream/src/res/localesList.json +27 -0
  221. package/apps/web/upstream/src/res/logo.png +0 -0
  222. package/apps/web/upstream/src/thirdparty/about.html +43 -0
  223. package/apps/web/upstream/src/thirdparty/index.js +5 -0
  224. package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
  225. package/apps/web/upstream/static/config.json +28 -0
  226. package/apps/web/upstream/static/emoticons/smile.png +0 -0
  227. package/apps/web/upstream/static/favicon.png +0 -0
  228. package/apps/web/upstream/static/highlight.mp3 +0 -0
  229. package/apps/web/upstream/static/highlight.ogg +0 -0
  230. package/apps/web/upstream/static/locales/.gitignore +2 -0
  231. package/apps/web/upstream/static/plugins/customise.html.example +10 -0
  232. package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
  233. package/apps/web/upstream/static/themes/common/base.css +1064 -0
  234. package/apps/web/upstream/static/themes/dark/theme.css +196 -0
  235. package/apps/web/upstream/static/themes/default/theme.css +92 -0
  236. package/apps/web/upstream/static/themes/elite/theme.css +248 -0
  237. package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
  238. package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
  239. package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
  240. package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
  241. package/apps/web/upstream/static/themes/sky/theme.css +107 -0
  242. package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
  243. package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
  244. package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
  245. package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
  246. package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
  247. package/apps/web/upstream/vue.config.js +150 -0
  248. package/apps/web/upstream/yarn.lock +9767 -0
  249. package/apps/web/vitest.config.ts +13 -0
  250. package/biome.json +2 -0
  251. package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
  252. package/docs/ADR-002-location-of-authority.md +82 -0
  253. package/docs/ADR-003-durable-object-sharding.md +110 -0
  254. package/docs/ADR-004-dynamodb-schema.md +96 -0
  255. package/docs/ADR-005-wss-only-transport-v1.md +83 -0
  256. package/docs/ADR-006-sasl-mechanism-scope.md +96 -0
  257. package/docs/ADR-007-deterministic-ports.md +82 -0
  258. package/docs/ADR-008-monorepo-tooling.md +60 -0
  259. package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
  260. package/docs/ADR-Index.md +15 -0
  261. package/docs/AWS-Adapter-Architecture.md +496 -0
  262. package/docs/AWS-Deployment.md +1275 -0
  263. package/docs/AWS-TCP-Deployment.md +410 -0
  264. package/docs/Cloudflare-Deployment-Guide.md +719 -0
  265. package/docs/Cloudflare-TCP-Deployment.md +510 -0
  266. package/docs/Home.md +16 -0
  267. package/docs/Observability.md +87 -0
  268. package/docs/PlanExtensions.md +812 -0
  269. package/docs/PlanIRCv3Websocket.md +503 -0
  270. package/docs/PlanWebClient.md +451 -0
  271. package/docs/Release-Process.md +443 -0
  272. package/docs/WebClientGuide.md +535 -0
  273. package/package.json +2 -2
  274. package/packages/aws-adapter/package.json +1 -1
  275. package/packages/aws-adapter/src/handlers/default.ts +3 -0
  276. package/packages/aws-adapter/src/handlers/nlb-stream.ts +9 -0
  277. package/packages/aws-adapter/src/serialize.ts +29 -1
  278. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
  279. package/packages/aws-adapter/tests/account-store.test.ts +4 -7
  280. package/packages/aws-adapter/tests/aws-harness.ts +5 -2
  281. package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
  282. package/packages/aws-adapter/tests/aws-runtime.test.ts +79 -0
  283. package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
  284. package/packages/aws-adapter/tests/connect.test.ts +9 -9
  285. package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
  286. package/packages/aws-adapter/tests/global-setup.ts +55 -30
  287. package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
  288. package/packages/aws-adapter/tests/handlers.test.ts +90 -4
  289. package/packages/aws-adapter/tests/message-store.test.ts +6 -5
  290. package/packages/aws-adapter/tests/nlb-stream.test.ts +60 -8
  291. package/packages/cf-adapter/package.json +2 -1
  292. package/packages/cf-adapter/src/connection-do.ts +3 -0
  293. package/packages/cf-adapter/src/serialize.ts +25 -4
  294. package/packages/cf-adapter/tests/cf-harness.ts +12 -1
  295. package/packages/cf-adapter/tests/cf-runtime.test.ts +269 -0
  296. package/packages/cf-adapter/tests/channel-do.test.ts +117 -0
  297. package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
  298. package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
  299. package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
  300. package/packages/cf-adapter/tests/registry-do.test.ts +104 -0
  301. package/packages/cf-adapter/tests/serialize.test.ts +308 -0
  302. package/packages/cf-adapter/vitest.config.ts +1 -1
  303. package/packages/in-memory-runtime/package.json +1 -1
  304. package/packages/irc-core/package.json +1 -1
  305. package/packages/irc-core/reports/mutation/mutation.html +342 -0
  306. package/packages/irc-core/src/caps/capabilities.ts +37 -3
  307. package/packages/irc-core/src/caps/index.ts +1 -0
  308. package/packages/irc-core/src/caps/sts.ts +84 -0
  309. package/packages/irc-core/src/commands/account-notify.ts +53 -0
  310. package/packages/irc-core/src/commands/away.ts +9 -3
  311. package/packages/irc-core/src/commands/cap.ts +23 -5
  312. package/packages/irc-core/src/commands/chathistory.ts +40 -11
  313. package/packages/irc-core/src/commands/index.ts +34 -0
  314. package/packages/irc-core/src/commands/isupport.ts +53 -0
  315. package/packages/irc-core/src/commands/mode.ts +8 -1
  316. package/packages/irc-core/src/commands/monitor.ts +327 -0
  317. package/packages/irc-core/src/commands/multiline.ts +256 -0
  318. package/packages/irc-core/src/commands/pre-away.ts +112 -0
  319. package/packages/irc-core/src/commands/privmsg.ts +19 -12
  320. package/packages/irc-core/src/commands/read-marker.ts +108 -0
  321. package/packages/irc-core/src/commands/registration.ts +8 -0
  322. package/packages/irc-core/src/commands/sasl.ts +24 -1
  323. package/packages/irc-core/src/commands/tagmsg.ts +71 -1
  324. package/packages/irc-core/src/commands/whois.ts +5 -0
  325. package/packages/irc-core/src/config.ts +65 -10
  326. package/packages/irc-core/src/effects.ts +14 -0
  327. package/packages/irc-core/src/ports.ts +389 -0
  328. package/packages/irc-core/src/protocol/index.ts +14 -0
  329. package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
  330. package/packages/irc-core/src/protocol/numerics.ts +15 -0
  331. package/packages/irc-core/src/protocol/outbound.ts +16 -1
  332. package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
  333. package/packages/irc-core/src/state/connection.ts +32 -1
  334. package/packages/irc-core/src/types.ts +112 -0
  335. package/packages/irc-core/stryker.commands.conf.json +1 -2
  336. package/packages/irc-core/tests/away-store.test.ts +73 -0
  337. package/packages/irc-core/tests/batch.test.ts +15 -0
  338. package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
  339. package/packages/irc-core/tests/caps/sts.test.ts +123 -0
  340. package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
  341. package/packages/irc-core/tests/commands/away.test.ts +85 -2
  342. package/packages/irc-core/tests/commands/cap.test.ts +165 -0
  343. package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
  344. package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
  345. package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
  346. package/packages/irc-core/tests/commands/mode.test.ts +72 -0
  347. package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
  348. package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
  349. package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
  350. package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
  351. package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
  352. package/packages/irc-core/tests/commands/registration.test.ts +64 -0
  353. package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
  354. package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
  355. package/packages/irc-core/tests/commands/whois.test.ts +34 -0
  356. package/packages/irc-core/tests/config.test.ts +103 -13
  357. package/packages/irc-core/tests/labeled-response.test.ts +181 -0
  358. package/packages/irc-core/tests/message-tags.test.ts +114 -0
  359. package/packages/irc-core/tests/monitor-store.test.ts +200 -0
  360. package/packages/irc-core/tests/numerics.test.ts +13 -0
  361. package/packages/irc-core/tests/parser.test.ts +109 -0
  362. package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
  363. package/packages/irc-core/tests/standard-replies.test.ts +412 -0
  364. package/packages/irc-core/tests/types.test.ts +27 -0
  365. package/packages/irc-server/package.json +1 -1
  366. package/packages/irc-server/src/actor.ts +450 -3
  367. package/packages/irc-server/src/dispatch.ts +108 -16
  368. package/packages/irc-server/tests/actor.test.ts +1432 -39
  369. package/packages/irc-server/tests/dispatch.test.ts +263 -2
  370. package/packages/irc-test-support/package.json +1 -1
  371. package/packages/irc-test-support/src/index.ts +6 -0
  372. package/packages/irc-test-support/src/scenarios.ts +24 -2
  373. package/packages/irc-test-support/src/test-config.ts +54 -0
  374. package/packages/irc-test-support/tests/test-config.test.ts +51 -0
  375. package/tools/ci-hardening/package.json +1 -1
  376. package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
  377. package/tools/load-test/package.json +33 -0
  378. package/tools/load-test/src/client.ts +351 -0
  379. package/tools/load-test/src/config.ts +313 -0
  380. package/tools/load-test/src/harness.ts +116 -0
  381. package/tools/load-test/src/main.ts +120 -0
  382. package/tools/load-test/src/metrics.ts +168 -0
  383. package/tools/load-test/src/report.ts +106 -0
  384. package/tools/load-test/tests/client.test.ts +212 -0
  385. package/tools/load-test/tests/config.test.ts +152 -0
  386. package/tools/load-test/tests/framing.test.ts +37 -0
  387. package/tools/load-test/tests/harness.test.ts +165 -0
  388. package/tools/load-test/tests/metrics.test.ts +174 -0
  389. package/tools/load-test/tests/report.test.ts +161 -0
  390. package/tools/load-test/tests/smoke.test.ts +67 -0
  391. package/tools/load-test/tsconfig.build.json +12 -0
  392. package/tools/load-test/tsconfig.test.json +10 -0
  393. package/tools/load-test/vitest.config.ts +29 -0
  394. package/tools/tcp-ws-forwarder/package.json +1 -1
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.5.0 (preview).** The pure protocol core, the
9
+ > **Status:** **v0.7.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
@@ -15,14 +15,27 @@ One TypeScript codebase. Two serverless substrates.
15
15
  > clients — Cloudflare via Spectrum + a Container origin, AWS via a
16
16
  > Network Load Balancer + Lambda streaming — alongside the WebSocket
17
17
  > path, plus **SASL `EXTERNAL` via mTLS** and a **Cloudflare D1-backed
18
- > SASL account store**. v0.5.0 completes the **protocol surface**: the
18
+ > SASL account store**. v0.5.0 completed the **protocol surface**: the
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 every standard verb a modern client expects is
23
- > now implemented. Remaining 0.x work: the load/compat sweep
24
- > (10k-connection load test + client compatibility matrix) and a
25
- > required-`serverName` config gate. See `CHANGELOG.md` for the
22
+ > are formally dropped. v0.6.0 landed the **IRCv3 extension sweep
23
+ > (Phase 12)** ten caps (`account-notify`, `msgid`,
24
+ > `standard-replies`, `MONITOR`, `labeled-response`, `sts`,
25
+ > `draft/typing`, `draft/multiline`, `draft/read-marker`,
26
+ > `draft/pre-away`), the ISUPPORT tokens for the new caps, and the
27
+ > read-only user mode `S` (TLS connected). **v0.7.0 lands the web
28
+ > client (Phase 14)**: a vendored **Kiwi IRC** SPA served at `/app/`
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. Remaining 0.x work: the client compatibility sweep
35
+ > (WeeChat / HexChat / IRCCloud / TheLounge), the Playwright
36
+ > browser-driven e2e for the SPA, IRC services (NickServ / ChanServ /
37
+ > HostServ / OperServ / MemoServ), and driving the remaining
38
+ > sub-100% packages to full coverage. See `CHANGELOG.md` for the
26
39
  > per-release manifests.
27
40
 
28
41
  ---
@@ -124,12 +137,14 @@ ServerlessIRCd/
124
137
  │ ├── cf-adapter/ Durable Objects + CfRuntime
125
138
  │ └── aws-adapter/ Lambda + DynamoDB + AwsRuntime
126
139
  ├── apps/
127
- │ ├── cf-worker/ worker entry, DO migrations, bindings
140
+ │ ├── cf-worker/ worker entry, DO migrations, bindings, [assets] + CSWSH gate
128
141
  │ ├── cf-tcp-container/ Spectrum + Container origin for irc+tls :6697
129
142
  │ ├── aws-stack/ CDK stack (APIGW WS + NLB + Lambda streaming + DynamoDB)
143
+ │ ├── web/ vendored Kiwi IRC SPA (→ /app/) + static landing page (→ /)
130
144
  │ └── local-cli/ runnable WS + TCP server using in-memory-runtime
131
145
  ├── tools/
132
146
  │ ├── tcp-ws-forwarder/ local TCP↔ws/wss bridge for stock IRC clients
147
+ │ ├── load-test/ synthetic WebSocket IRC client farm (10k conns, p50/p95/p99, drop rate)
133
148
  │ ├── ci-hardening/ coverage-gate + mutation-config validators
134
149
  │ ├── seed-aws-accounts.ts scrypt-hash SASL PLAIN accounts into DynamoDB
135
150
  │ └── seed-cf-accounts.ts scrypt-hash SASL PLAIN accounts into Cloudflare D1
@@ -163,6 +178,7 @@ pin is resolved automatically:
163
178
  ```bash
164
179
  corepack enable # lets the pinned pnpm@11 run on any Node ≥ 24
165
180
 
181
+ git submodule update --init # docs + apps/web Kiwi upstream sources
166
182
  pnpm install # install workspace deps
167
183
 
168
184
  pnpm build # build all packages (turbo)
@@ -180,6 +196,14 @@ pnpm mutation # Stryker spot-check on irc-core (protocol + commands)
180
196
  pnpm clean # remove dist/coverage/.turbo + node_modules
181
197
  ```
182
198
 
199
+ The web client (`apps/web`) vendors Kiwi IRC as a git submodule. Building it
200
+ needs the upstream sources (`git submodule update --init apps/web/upstream`)
201
+ and `yarn` (Kiwi ships a `yarn.lock`; `corepack enable` provides it):
202
+
203
+ ```bash
204
+ pnpm --filter web build # builds the Kiwi SPA into apps/web/dist/app/ (/app/)
205
+ ```
206
+
183
207
  Coverage reports are written to `packages/*/coverage/`. CI (`.github/workflows/ci.yml`)
184
208
  runs lint, typecheck, the coverage gate, the parametrized contract suite, and a
185
209
  Stryker mutation spot-check on every push and pull request. Coverage thresholds
@@ -230,6 +254,8 @@ On startup it logs a JSON line like:
230
254
  | `--tcp-port <n>` | `<port> + 1` | TCP port for RFC-style IRC clients. |
231
255
  | `--no-tcp` | | Disable the TCP listener (WebSocket only). |
232
256
  | `--motd-file <p>` | built-in | Read MOTD lines from this file (one per line). |
257
+ | `--server-name <h>`| `irc.localhost` | Server hostname advertised in `001`/`005`. Production MUST override. |
258
+ | `--network-name <n>`| `LocalNet` | Network name advertised in `005 NETWORK=…`. |
233
259
  | `-h, --help` | | Show help and exit. |
234
260
 
235
261
  Pass `--host 0.0.0.0` to expose the server on all interfaces. `SIGINT` /
@@ -302,6 +328,115 @@ against any line-oriented WebSocket endpoint.
302
328
 
303
329
  ---
304
330
 
331
+ ## Load testing (`tools/load-test`)
332
+
333
+ A synthetic WebSocket IRC client farm (PLAN §7, Phase 7). It opens N
334
+ connections to a `ws://` / `wss://` target, registers each (NICK/USER),
335
+ joins a channel, and optionally chats. Per-phase **p50 / p95 / p99
336
+ latency** (connect / register / join / message) and the **drop rate**
337
+ are captured, then printed as a markdown report to stdout (and written
338
+ to `--report <path>` when given).
339
+
340
+ Build the workspace, then start a run (from the repo root):
341
+
342
+ ```bash
343
+ pnpm build
344
+
345
+ pnpm --filter load-test start -- \
346
+ --target wss://irc.staging.example.com/ \
347
+ --connections 10000 --concurrency 200 --ramp-ms 60000 \
348
+ --channel '#loadtest' --messages 3 --platform cf-staging \
349
+ --report reports/cf-staging-2026-08-03.md
350
+ ```
351
+
352
+ The harness drives the WebSocket transport only (the serverless default);
353
+ a TCP IRC client is not what a serverless load test exercises. Each
354
+ connection is independent — the package ships no `@serverless-ircd/*`
355
+ runtime dependency (only the IRC line framing shared with the
356
+ forwarder), so it can be pointed at any WebSocket IRC endpoint.
357
+
358
+ ### CLI flags
359
+
360
+ | Flag | Default | Description |
361
+ |-------------------------|-------------|----------------------------------------------------------|
362
+ | `--target <url>` | (required) | Upstream `ws://` or `wss://` URL to load test. |
363
+ | `--connections <n>` | `1000` | Total connections to open. |
364
+ | `--concurrency <n>` | `50` | Concurrent in-flight connection attempts during ramp-up. |
365
+ | `--ramp-ms <ms>` | `30000` | Ramp-up duration over which connections are opened. |
366
+ | `--channel <c>` | `#loadtest` | Channel every client JOINs. |
367
+ | `--messages <n>` | `0` | PRIVMSGs each client sends after JOIN (`0` = connect only). |
368
+ | `--echo-message` | | Negotiate `echo-message` so sent PRIVMSGs round-trip. |
369
+ | `--platform <name>` | `unknown` | Label baked into the report header. |
370
+ | `--report <path>` | | Write the markdown report to this path. |
371
+ | `-h, --help` | | Show help and exit. |
372
+
373
+ `SIGINT` / `SIGTERM` cancel an in-flight run gracefully (drains live
374
+ sockets, prints the partial report). The package ships its own vitest
375
+ suite at ≥90% coverage (config parsing, framing, harness, metrics,
376
+ report).
377
+
378
+ ---
379
+
380
+ ## Web client (`apps/web`)
381
+
382
+ A vendored **Kiwi IRC** SPA served at `/app/` and a static project
383
+ landing page served at `/`, both baked out of `apps/web/dist/` and
384
+ served directly by the Cloudflare Worker via its `[assets]` binding.
385
+ The browser opens a native `wss://` straight to the Worker's existing
386
+ IRC-over-WebSocket endpoint — no proxy, gateway, or transport
387
+ adaptation in the hot path. See `docs/WebClientGuide.md` for the
388
+ end-to-end contributor/operator guide.
389
+
390
+ Build the SPA + landing page (needs the submodule + `yarn`, provided by
391
+ `corepack enable`):
392
+
393
+ ```bash
394
+ git submodule update --init apps/web/upstream # one-time per clone
395
+ pnpm --filter web build:staging # or :prod / default
396
+ # → apps/web/dist/index.html (landing page, served at /)
397
+ # → apps/web/dist/app/index.html (Kiwi SPA, served at /app/)
398
+ # → apps/web/dist/app/static/config.json (baked, env-specific)
399
+ ```
400
+
401
+ Then run everything locally (Worker serves SPA + landing page + WS on
402
+ one origin):
403
+
404
+ ```bash
405
+ pnpm build # workspace packages
406
+ pnpm --filter @serverless-ircd/cf-worker dev # http://localhost:8787
407
+ # / → landing page
408
+ # /app/ → Kiwi SPA (opens ws://localhost:8787/)
409
+ # /health → plaintext liveness
410
+ ```
411
+
412
+ Deploy staging (Worker + assets in one command):
413
+
414
+ ```bash
415
+ pnpm deploy:cf:staging # wrangler deploy --env staging
416
+ ```
417
+
418
+ ### WebSocket Origin policy (CSWSH defense)
419
+
420
+ WebSocket upgrades do **not** follow the same-origin policy, so the
421
+ Worker rejects browser upgrades whose `Origin` is not allowed — see
422
+ `apps/cf-worker/src/origin-allowlist.ts` and the SPA guide §5. Two
423
+ modes, evaluated in order:
424
+
425
+ 1. **Explicit allowlist** — set `WEB_ORIGINS` (comma-separated) for
426
+ cross-origin deploys (SPA on a different domain than the Worker,
427
+ e.g. Cloudflare Pages). Only listed origins proceed.
428
+ 2. **Same-origin auto-derive (the default)** — when `WEB_ORIGINS` is
429
+ unset/empty, the Worker compares the browser's `Origin` against the
430
+ request's own origin. Match → proceed; mismatch → `403 Forbidden`.
431
+
432
+ `WEB_ORIGINS` is **optional** — same-origin auto-derive needs zero
433
+ per-env config and works for `*.workers.dev`, custom domains, and
434
+ preview URLs alike. Non-browser clients (curl, WeeChat, the
435
+ `tcp-ws-forwarder`, scripted harnesses) never send `Origin` and pass
436
+ through unchanged in both modes.
437
+
438
+ ---
439
+
305
440
  ## WebSocket transport (IRCv3 subprotocols)
306
441
 
307
442
  The WebSocket entry points (the local CLI, the Cloudflare Worker, and the
@@ -373,21 +508,23 @@ cleanly. Per-release manifests live in `CHANGELOG.md`.
373
508
  - **Phase 2** — `IrcRuntime` port, in-memory runtime, `ConnectionActor`,
374
509
  local CLI, parametrized contract suite. ✅ landed in **v0.1.0**.
375
510
  - **Phase 3** — Cloudflare adapter (ConnectionDO / ChannelDO / RegistryDO).
376
- ✅ landed in **v0.1.0** (staging auto-deploy via CI; prod deploy is manual).
511
+ ✅ landed in **v0.1.0** (staging deploy via CI; prod deploy is manual).
377
512
  - **Phase 4** — AWS adapter (APIGW WS + Lambda + DynamoDB + CDK).
378
- ✅ landed in **v0.2.0** (staging auto-deploy via CI; prod deploy is manual).
513
+ ✅ landed in **v0.2.0** (staging deploy via CI; prod deploy is manual).
379
514
  - **Phase 5** — Observability, security hardening, config, CI gates.
380
515
  ✅ landed across v0.2.0–v0.4.0 (logger port, server-password, cloaking,
381
516
  admission limits, coverage + mutation gates, OPER credential auth, the
382
- Cloudflare config loader, and AWS max-clients admission).
517
+ Cloudflare config loader, and AWS max-clients admission); the
518
+ required-`serverName` gate closed in **v0.6.0**.
383
519
  - **Phase 6** — TLS hardening & raw TCP transport (`irc+tls :6697`).
384
520
  ✅ landed in **v0.4.0**: generalized `ConnectionActor` transport seam,
385
521
  Cloudflare Spectrum + Container origin, AWS NLB + Lambda streaming,
386
522
  mTLS → SASL `EXTERNAL`, transport-parametrized contract suite, ADR-009.
387
- - **Phase 7** — Load testing (10k concurrent connections per platform)
388
- and client compatibility sweep (WeeChat, HexChat, IRCCloud, TheLounge).
389
- pending; the dual-transport surface it needs
390
- landed in v0.4.0.
523
+ - **Phase 7 — Load testing & compatibility sweep.** 🔄 partial as of
524
+ **v0.6.0**: the `tools/load-test` 10k-connection harness is in-tree;
525
+ the formal 10k-connection report per platform and the client
526
+ compatibility matrix (WeeChat / HexChat / IRCCloud / TheLounge) are
527
+ still pending.
391
528
  - **Phase 8 — PLAN-FIXES remediation.** ✅ landed across v0.3.0–v0.4.0
392
529
  (MessageStore wired into all three adapters, AccountStore end-to-end,
393
530
  the DynamoDB `Accounts` table decided, CF enumeration RPCs, AWS QUIT
@@ -406,6 +543,33 @@ cleanly. Per-release manifests live in `CHANGELOG.md`.
406
543
  the formal drop of the S2S (`CONNECT`/`SQUIT`/`LINKS`) and obsolete
407
544
  RFC 2812 (`SERVICE`/`SUMMON`/`USERS`) verbs. The protocol surface is
408
545
  feature-complete for modern clients.
546
+ - **Phase 12 — IRCv3 extension sweep.** ✅ landed in **v0.6.0**: ten
547
+ negotiated caps (`account-notify`, `msgid`, `standard-replies`,
548
+ `MONITOR`, `labeled-response`, `sts`, `draft/typing`,
549
+ `draft/multiline`, `draft/read-marker`, `draft/pre-away`), the
550
+ ISUPPORT tokens for the new caps (`MONITOR`/`MULTILINE`/`TYPING`/
551
+ `STATUSMSG`/`EXTBAN`/`ACCOUNTEXTBAN`), the read-only user mode `S`
552
+ (TLS connected), and the async push fanout seam (Registry hooks →
553
+ `MONITOR` online/offline, `BroadcastWallops`-style caps-gated
554
+ delivery).
555
+ - **Phase 13 — IRC services.** ⬳ pending: `ServicesStore` port +
556
+ NickServ / ChanServ / HostServ / OperServ / MemoServ. Adapter
557
+ backends (D1 / DynamoDB) ship alongside; until then
558
+ `draft/read-marker` and `draft/pre-away` use in-memory reference
559
+ stores and do not survive an adapter restart.
560
+ - **Phase 14 — Web client.** 🔄 partial as of **v0.7.0**: a vendored
561
+ Kiwi IRC SPA is served at `/app/` and a static landing page at `/`
562
+ by the Cloudflare Worker via an `[assets]` binding, with a Zod-
563
+ validated per-env config matrix and **CSWSH defense** (same-origin
564
+ auto-derive + optional `WEB_ORIGINS` allowlist). The Playwright
565
+ headless-browser e2e is the remaining open ticket.
566
+ See `docs/WebClientGuide.md` for the contributor/operator guide.
567
+ - **Coverage hardening.** 🔄 partial as of **v0.7.0**: `aws-adapter`
568
+ and `aws-stack` clear the 90% gate (v0.6.0); the CF packages run
569
+ under istanbul (v0.6.0). The remaining packages (`cf-adapter`,
570
+ `local-cli`, `load-test`, `cf-tcp-container`, `tcp-ws-forwarder`,
571
+ `irc-test-support`, `web`) are **above the gate but below 100%** —
572
+ follow-ups drive each to 100%.
409
573
 
410
574
  ---
411
575
 
@@ -416,6 +580,8 @@ cleanly. Per-release manifests live in `CHANGELOG.md`.
416
580
  channel), `TOPIC`, `KICK`, `INVITE`, `NAMES`, `LIST`, `WHO`, `WHOIS`,
417
581
  `WHOWAS`, `MOTD`, `AWAY`, `OPER` (credential auth → `o` user mode), the
418
582
  query verbs `VERSION`, `TIME`, `ADMIN`, `INFO`, `USERHOST`, `ISON`, the
583
+ modern-online-presence verb `MONITOR` (`+`/`-`/`C`/`L`/`S` with the
584
+ `730`/`731`/`732`/`734` numerics and async online/offline push), the
419
585
  network-statistics verbs `LUSERS` and `STATS` (`u` uptime + `l` link-info
420
586
  letters; unknown letters get the charybdis-style empty body), the
421
587
  routing-diagnostic verb `TRACE` (oper-gated per-connection detail), the
@@ -429,15 +595,31 @@ v0.5.0 — they return `421 ERR_UNKNOWNCOMMAND`, and their reserved
429
595
  numerics have been removed. S2S linking is a PLAN non-goal.)
430
596
 
431
597
  **Channel modes:** `o v b i k l t n m s p`.
432
- **User modes:** `i`, `o` (local only), `w`, `s`.
598
+ **User modes:** `i`, `o` (local only), `w`, `s`, and the read-only `S`
599
+ (TLS connected — set by the transport at registration, surfaced in WHOIS
600
+ via `276 RPL_WHOISSECURE`, not settable via `MODE`).
433
601
 
434
602
  **IRCv3 extensions (negotiated via `CAP`):** `message-tags` (incl. the
435
- `TAGMSG` command), `server-time`, `account-tag`, `echo-message`, `batch`,
603
+ `TAGMSG` command), `server-time`, `account-tag`, `account-notify`
604
+ (pushes `ACCOUNT` on SASL login/logout), `echo-message`, `batch`,
436
605
  `sasl` (`PLAIN` always; `EXTERNAL` via mTLS when a client-cert trust store
437
- is bound),
438
- `multi-prefix`, `away-notify`, `chghost`, `invite-notify`, `extended-join`,
439
- `draft/chathistory`, `safelist`. Case-insensitive nick/channel comparison
440
- uses **RFC 1459** case-mapping (advertised via `005 CASEMAPPING=rfc1459`).
606
+ is bound), `multi-prefix`, `away-notify`, `chghost`, `invite-notify`,
607
+ `extended-join`, `msgid` (`@+msgid=<id>` on every PRIVMSG/NOTICE/TAGMSG,
608
+ shared between live and `draft/chathistory` replay), `standard-replies`
609
+ (`FAIL`/`WARN`/`NOTE` replacements for a curated numeric subset),
610
+ `MONITOR=<n>` (config-driven ceiling, default 30), `labeled-response`
611
+ (`+label=<id>` → `BATCH +id labeled-response … BATCH -id` wrapping),
612
+ `sts` (Strict Transport Security — conditionally advertised with
613
+ `duration`/`port`/optional `preload` from `ServerConfig` when configured,
614
+ so compliant clients upgrade from plaintext to TLS and pin the secure
615
+ listener), `draft/chathistory`, `safelist`, `draft/typing` (typing-indicator
616
+ broadcast), `draft/multiline=<n>` (multi-line `BATCH`, default 4096-byte
617
+ budget), `draft/read-marker` (per-account persisted last-read), and
618
+ `draft/pre-away` (per-account persisted away reason, replayed at
619
+ identify). Case-insensitive nick/channel comparison uses **RFC 1459**
620
+ case-mapping (advertised via `005 CASEMAPPING=rfc1459`). The new
621
+ `MONITOR` / `MULTILINE` / `TYPING` / `STATUSMSG` / `EXTBAN` /
622
+ `ACCOUNTEXTBAN` ISUPPORT tokens are advertised from `ServerConfig`.
441
623
 
442
624
  **Transport:** deployed stacks (Cloudflare Workers, AWS API Gateway) speak
443
625
  WebSocket text frames by default (one IRC message per frame, with tolerance
@@ -457,9 +639,24 @@ bridges a stock TCP client to a deployed WebSocket endpoint.
457
639
  gates), v0.3.0 (OPER, TAGMSG, RFC 1459 case-mapping, real MOTD,
458
640
  ChannelRegistryDO, SASL account persistence), v0.4.0 (dual transport
459
641
  `irc+tls :6697`, SASL `EXTERNAL`/mTLS, WHOWAS + remaining verbs, D1 account
460
- store, Node 24/pnpm 11), and v0.5.0 (deferred IRC verbs `KILL` / `REHASH`
642
+ store, Node 24/pnpm 11), v0.5.0 (deferred IRC verbs `KILL` / `REHASH`
461
643
  / `LUSERS` + `STATS` / `TRACE` / `WALLOPS` / `SETNAME`, plus the formal
462
- drop of the S2S and obsolete RFC 2812 verbs).
644
+ drop of the S2S and obsolete RFC 2812 verbs), v0.6.0 (the IRCv3
645
+ extension sweep — `account-notify`, `msgid`, `standard-replies`,
646
+ `MONITOR`, `labeled-response`, `sts`, `draft/typing`, `draft/multiline`,
647
+ `draft/read-marker`, `draft/pre-away`, plus user mode `S`; the
648
+ required-`serverName` gate; `tools/load-test`; aws-adapter / aws-stack
649
+ coverage unblocks; CF packages moved to istanbul; deploys now
650
+ manual-only), and **v0.7.0** (the web client — vendored Kiwi IRC SPA
651
+ at `/app/`, static landing page at `/`, Cloudflare Worker `[assets]`
652
+ binding, per-env Zod-validated config matrix, and CSWSH defense via
653
+ same-origin auto-derive + optional `WEB_ORIGINS`; `/health` becomes
654
+ an explicit route; mutation-killing tests for `irc-core`'s parser /
655
+ batch / numerics).
656
+ - `docs/WebClientGuide.md` — end-to-end contributor/operator doc for the
657
+ web client: build pipeline, per-env config matrix, CSWSH rationale and
658
+ the optional `WEB_ORIGINS` var, local dev, optional Cloudflare Pages
659
+ alternative, and troubleshooting.
463
660
  - `docs/Cloudflare-TCP-Deployment.md` and `docs/AWS-TCP-Deployment.md` —
464
661
  end-to-end guides for the `:6697` TCP+TLS variants (Spectrum/Container on
465
662
  CF, NLB + Lambda streaming on AWS), including mTLS trust-store setup.
@@ -60,6 +60,39 @@ export AWS_DEFAULT_REGION=us-east-1
60
60
  LOCALSTACK=1 pnpm test
61
61
  ```
62
62
 
63
+ ## Test-mode bundle stub (why coverage does not re-bundle)
64
+
65
+ The stack ships four `NodejsFunction` constructs (the wss `IrcHandler`,
66
+ `IrcSweeper`, `IrcPingChecker`, and the optional NLB `IrcNlbHandler`).
67
+ Each one invokes esbuild at synth time to bundle the shared handler
68
+ entry. Repeated across the ~80 synth assertions in `tests/stack.test.ts`,
69
+ that meant the bundler ran **dozens to hundreds of times per coverage
70
+ invocation**, exhausting CI memory before vitest could print its
71
+ coverage table — the failure mode presented as a wall of
72
+ `Bundling asset TestStack/IrcHandler/Code/Stage…` lines followed by
73
+ `[ELIFECYCLE] Command failed with exit code N` and no report.
74
+
75
+ The fix is a single env-var toggle consumed by `makeLambda()` in
76
+ `src/aws-stack.ts`:
77
+
78
+ | Env var | Effect on the synth |
79
+ |-----------------------------------|-----------------------------------------------------------------------|
80
+ | `IRC_AWS_STACK_TEST_NO_BUNDLE=1` | Every Lambda is emitted as an inline `Code.fromInline('void 0')` zip; esbuild is not invoked at all. CloudFormation shape (runtime, env vars, IAM grants, route wiring, …) is identical to the production synth. |
81
+ | _(unset)_ | Production path: every Lambda is a real `NodejsFunction` whose asset is esbuild-bundled at synth time. This is what `cdk synth` / `cdk deploy` run. |
82
+
83
+ `tests/vitest.setup.ts` sets the flag once per test file so the entire
84
+ `pnpm test` / `pnpm coverage` run stays on the stub path. Production
85
+ synth (`bin/aws.ts` via the CDK CLI) never loads vitest, so it cannot
86
+ accidentally inherit the flag.
87
+
88
+ A small `tests/synth-no-bundle.test.ts` locks both paths in place: it
89
+ asserts the stub emits `Code: { ZipFile }` when the flag is set, and
90
+ exercises the production `NodejsFunction` return once (with the flag
91
+ explicitly unset) so the production branch stays covered. The
92
+ end-to-end guard lives in `tests/coverage-command.test.ts`, which
93
+ spawns `pnpm --filter @serverless-ircd/aws-stack coverage` in a
94
+ subprocess and asserts the report prints an `All files` row.
95
+
63
96
  ## Stack outputs
64
97
 
65
98
  | Output | Description |
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serverless-ircd/aws-stack",
3
- "version": "0.5.0",
3
+ "version": "0.7.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",
@@ -37,8 +37,8 @@ import {
37
37
  import { LambdaTarget } from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets';
38
38
  import { Rule, Schedule } from 'aws-cdk-lib/aws-events';
39
39
  import { LambdaFunction } from 'aws-cdk-lib/aws-events-targets';
40
- import { Runtime } from 'aws-cdk-lib/aws-lambda';
41
- import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
40
+ import { Code, Function as Lambda, Runtime } from 'aws-cdk-lib/aws-lambda';
41
+ import { NodejsFunction, type NodejsFunctionProps } from 'aws-cdk-lib/aws-lambda-nodejs';
42
42
  import type { Construct } from 'constructs';
43
43
 
44
44
  /** Stage name served by the auto-deploying WebSocket stage. */
@@ -64,8 +64,14 @@ const PING_CHECKER_SCHEDULE_RATE = Duration.minutes(1);
64
64
  */
65
65
  const DEFAULT_ENVIRONMENT_NAME = 'staging';
66
66
 
67
- /** Default server identity, used when the corresponding prop is omitted. */
68
- const DEFAULT_SERVER_NAME = 'irc.example.com';
67
+ /**
68
+ * Fallback server identity, used when the corresponding prop is omitted.
69
+ * Deliberately not `irc.example.com` (the retired placeholder) so a
70
+ * deployment that forgets to override advertises an obviously-local name
71
+ * instead of a deceptive example domain. Production stacks MUST pass
72
+ * `serverName` explicitly.
73
+ */
74
+ const DEFAULT_SERVER_NAME = 'irc.localhost';
69
75
  const DEFAULT_NETWORK_NAME = 'ExampleNet';
70
76
  const DEFAULT_MOTD_LINES = [
71
77
  'Welcome to the ServerlessIRCd deployment.',
@@ -74,6 +80,22 @@ const DEFAULT_MOTD_LINES = [
74
80
 
75
81
  const HERE = fileURLToPath(new URL('.', import.meta.url));
76
82
 
83
+ /**
84
+ * Env-var flag that flips {@link makeLambda} onto a stub code path which
85
+ * emits an inline Lambda zip and skips esbuild entirely. The IrcAwsStack
86
+ * test suite synthesizes the stack dozens of times per coverage run; if
87
+ * each synth invoked esbuild the runner exhausted its memory budget
88
+ * before the vitest coverage table could print. The stub preserves every
89
+ * CloudFormation shape the rest of the test suite asserts on (runtime,
90
+ * env vars, IAM grants, route wiring, …) while making the bundling step
91
+ * a no-op.
92
+ *
93
+ * Intentionally a plain `process.env` read (not a CDK context var or
94
+ * construct prop) so the toggle lives in one place — `tests/vitest.setup.ts`
95
+ * — and cannot leak into a production synth driven by `bin/aws.ts`.
96
+ */
97
+ const TEST_NO_BUNDLE_FLAG = 'IRC_AWS_STACK_TEST_NO_BUNDLE';
98
+
77
99
  /**
78
100
  * Construct props for {@link IrcAwsStack}. Acts as the single
79
101
  * configuration bag extended by deploy-time knobs (environment name,
@@ -93,7 +115,8 @@ export interface IrcStackProps extends StackProps {
93
115
 
94
116
  /**
95
117
  * Server name advertised to clients (`SERVER_NAME` / the `001`
96
- * welcome source). Defaults to `irc.example.com`.
118
+ * welcome source). Falls back to `irc.localhost` when omitted; production
119
+ * stacks MUST pass an explicit value.
97
120
  */
98
121
  readonly serverName?: string;
99
122
 
@@ -150,7 +173,7 @@ export class IrcAwsStack extends Stack {
150
173
  }),
151
174
  );
152
175
 
153
- const handler = new NodejsFunction(this, 'IrcHandler', {
176
+ const handler = makeLambda(this, 'IrcHandler', {
154
177
  runtime: Runtime.NODEJS_24_X,
155
178
  entry: handlerEntry(),
156
179
  handler: 'handler',
@@ -166,7 +189,7 @@ export class IrcAwsStack extends Stack {
166
189
  // bundle (tree-shaken to the sweeper path by esbuild at synth time).
167
190
  // Catches connections that vanished without APIGW emitting $disconnect;
168
191
  // see packages/aws-adapter/src/handlers/sweeper.ts.
169
- const sweeper = new NodejsFunction(this, 'IrcSweeper', {
192
+ const sweeper = makeLambda(this, 'IrcSweeper', {
170
193
  runtime: Runtime.NODEJS_24_X,
171
194
  entry: handlerEntry(),
172
195
  handler: 'sweeperHandler',
@@ -180,7 +203,7 @@ export class IrcAwsStack extends Stack {
180
203
  // those that never answer; see
181
204
  // packages/aws-adapter/src/handlers/ping-checker.ts. The Cloudflare
182
205
  // adapter implements the equivalent flow inside `ConnectionDO.alarm()`.
183
- const pingChecker = new NodejsFunction(this, 'IrcPingChecker', {
206
+ const pingChecker = makeLambda(this, 'IrcPingChecker', {
184
207
  runtime: Runtime.NODEJS_24_X,
185
208
  entry: handlerEntry(),
186
209
  handler: 'pingCheckerHandler',
@@ -318,7 +341,7 @@ export class IrcAwsStack extends Stack {
318
341
  // Shares the bundled aws-adapter code (tree-shaken to the nlb-stream
319
342
  // path by esbuild at synth time). Reuses the same DynamoDB tables,
320
343
  // server identity, and MANAGEMENT_URL as the wss handler.
321
- const nlbHandler = new NodejsFunction(this, 'IrcNlbHandler', {
344
+ const nlbHandler = makeLambda(this, 'IrcNlbHandler', {
322
345
  runtime: Runtime.NODEJS_24_X,
323
346
  entry: handlerEntry(),
324
347
  handler: 'nlbStreamHandler',
@@ -391,6 +414,48 @@ function handlerEntry(): string {
391
414
  return resolve(HERE, '..', '..', '..', 'packages', 'aws-adapter', 'src', 'handlers', 'index.ts');
392
415
  }
393
416
 
417
+ /**
418
+ * Internal Lambda-construct props: `NodejsFunctionProps` narrowed so the
419
+ * runtime and handler are required. Every call site in the stack already
420
+ * passes both explicitly; this type just makes that invariant visible to
421
+ * the compiler so {@link makeLambda} can hand them straight through to
422
+ * the stub `Function` constructor without a non-null assertion.
423
+ */
424
+ type StrictLambdaProps = NodejsFunctionProps & {
425
+ readonly runtime: Runtime;
426
+ readonly handler: string;
427
+ };
428
+
429
+ /**
430
+ * Builds a Lambda function for the IRC stack, selecting between the real
431
+ * esbuild-bundled `NodejsFunction` and a no-op stub `Function` based on
432
+ * {@link TEST_NO_BUNDLE_FLAG}.
433
+ *
434
+ * Production synth (`bin/aws.ts`) never sets the flag, so it always lands
435
+ * on the `NodejsFunction` branch and produces a real bundled asset. The
436
+ * test suite sets the flag via `tests/vitest.setup.ts`, which routes
437
+ * every Lambda onto the inline-zip stub so the synth does not invoke
438
+ * esbuild and the coverage pipeline can complete.
439
+ *
440
+ * The stub mirrors only the fields the rest of the stack mutates
441
+ * post-construction (`runtime`, `handler`, env vars, IAM grants) — it
442
+ * deliberately ignores `entry` and `bundling` because both are
443
+ * meaningless once esbuild is bypassed.
444
+ *
445
+ * Returns the base `Function` type (not `NodejsFunction`) so callers
446
+ * cannot reach for bundler-only APIs on the stub path.
447
+ */
448
+ function makeLambda(scope: Construct, id: string, props: StrictLambdaProps): Lambda {
449
+ if (process.env[TEST_NO_BUNDLE_FLAG] === '1') {
450
+ return new Lambda(scope, id, {
451
+ runtime: props.runtime,
452
+ handler: props.handler,
453
+ code: Code.fromInline('void 0'),
454
+ });
455
+ }
456
+ return new NodejsFunction(scope, id, props);
457
+ }
458
+
394
459
  /**
395
460
  * Builds the physical DynamoDB table name for a logical id under the
396
461
  * given environment. Capitalises the environment's first letter so it