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
@@ -0,0 +1,536 @@
1
+ # Web Client Guide
2
+
3
+ End-to-end contributor/operator doc for the ServerlessIRCd web client: a
4
+ vendored **Kiwi IRC** SPA served at `/webclient/` and a static project
5
+ landing page served at `/` by the Cloudflare Worker. The browser opens a
6
+ native `wss://` WebSocket straight to the Worker's existing
7
+ IRC-over-WebSocket endpoint — no proxy, gateway, or transport adaptation
8
+ in the hot path.
9
+
10
+ Cross-reference: `PLAN.md` §6.1 (CF mapping), `docs/PlanWebClient.md`
11
+ (design), `docs/Cloudflare-Deployment-Guide.md` (Worker deploy),
12
+ `apps/cf-worker/wrangler.toml`, `apps/web/`.
13
+
14
+ **Acceptance criterion (TICKET-139):** a new contributor can
15
+ `git submodule update --init`, `pnpm install`,
16
+ `pnpm --filter web build:staging`, and `wrangler deploy --env staging`
17
+ following only this doc.
18
+
19
+ ---
20
+
21
+ ## 1. How the pieces fit
22
+
23
+ ```
24
+ ┌──────────────────────────────────────────────────────────────┐
25
+ │ Browser │
26
+ │ GET / → landing page (project front door) │
27
+ │ GET /webclient/ → Kiwi IRC SPA (index.html + JS bundle) │
28
+ │ wss://.../ → one IRC message per WS text frame │
29
+ └──────────────────────────────────────────────────────────────┘
30
+ │ │
31
+ │ HTTPS (static assets) │ wss (IRC-over-WebSocket)
32
+ ▼ ▼
33
+ ┌──────────────────────────────────────────────────────────────┐
34
+ │ apps/cf-worker (Cloudflare Worker) │
35
+ │ fetch(request, env): │
36
+ │ Upgrade: websocket → Origin check → ConnectionDO │
37
+ │ GET /health → plaintext liveness string │
38
+ │ else → env.ASSETS (apps/web/dist) │
39
+ └──────────────────────────────────────────────────────────────┘
40
+ ```
41
+
42
+ The Worker is the single origin for the landing page, the SPA, **and**
43
+ the WebSocket. Same-origin by default — which is what makes the CSWSH
44
+ defense in §5 almost zero-config. A Cloudflare Pages deployment of the
45
+ same `dist/` is a documented alternative for prod (see §7).
46
+
47
+ Path layout on the Worker:
48
+
49
+ | Path | Served by | Content |
50
+ |------------|--------------------------|----------------------------------|
51
+ | `/` | `[assets]` binding | `dist/index.html` (landing page) |
52
+ | `/webclient/` | `[assets]` binding | `dist/webclient/index.html` (Kiwi SPA) |
53
+ | `/webclient/static/...` | `[assets]` binding | Kiwi JS/CSS/themes + baked config |
54
+ | `/health` | `worker.ts` handler | Plaintext liveness string |
55
+ | `/` (WS upgrade) | `worker.ts` → `ConnectionDO` | IRC session |
56
+
57
+ The WS endpoint and the landing page share path `/`. The Worker runs
58
+ first for every request (`run_worker_first = true` in `wrangler.toml`)
59
+ so the `Upgrade: websocket` check fires before the asset platform
60
+ serves `/` — otherwise an upgrade would get a `200 OK` landing page
61
+ instead of a `101` switch.
62
+
63
+ ---
64
+
65
+ ## 2. Prerequisites
66
+
67
+ | Requirement | Version / detail |
68
+ |---|---|
69
+ | Node.js | ≥ 24 (matches CI; `engines.node` in root `package.json`) |
70
+ | pnpm | 11.x (`packageManager` pin in root `package.json`) |
71
+ | yarn | Required **only** to build the SPA. Kiwi ships a `yarn.lock`; `corepack enable` provides yarn on any Node. |
72
+ | `wrangler` CLI | Comes from `apps/cf-worker/devDependencies`; no global install needed. |
73
+ | Cloudflare account | For deploy only (see `docs/Cloudflare-Deployment-Guide.md`). Local dev needs nothing. |
74
+
75
+ Confirm the local environment:
76
+
77
+ ```bash
78
+ node --version # v24.x or newer
79
+ corepack enable # lets the pinned pnpm + yarn run
80
+ pnpm --version # 11.x
81
+ ```
82
+
83
+ ---
84
+
85
+ ## 3. Quickstart (clean checkout → deployed staging SPA)
86
+
87
+ ```bash
88
+ # 1. Pull submodules (docs wiki + Kiwi IRC upstream sources).
89
+ git submodule update --init
90
+
91
+ # 2. Install workspace deps.
92
+ pnpm install
93
+
94
+ # 3. Build the SPA + landing page (default config = staging target).
95
+ pnpm --filter web build:staging
96
+ # Equivalent: pnpm --filter @serverless-ircd/web build:staging
97
+ # → apps/web/dist/index.html (landing page)
98
+ # → apps/web/dist/webclient/index.html (Kiwi SPA)
99
+ # → apps/web/dist/webclient/static/config.json (baked, env-specific)
100
+
101
+ # 4. Build the rest of the workspace (Worker imports compiled dist/).
102
+ pnpm build
103
+
104
+ # 5. Serve everything locally (Worker + SPA + landing page).
105
+ pnpm --filter @serverless-ircd/cf-worker dev
106
+ # → http://localhost:8787/ landing page
107
+ # → http://localhost:8787/webclient/ Kiwi SPA
108
+ # → ws://localhost:8787/ IRC-over-WebSocket (same endpoint)
109
+
110
+ # 6. Deploy staging (Worker + assets in one command).
111
+ pnpm deploy:cf:staging
112
+ # → wrangler deploy --env staging
113
+ ```
114
+
115
+ Open `http://localhost:8787/webclient/` in a browser — Kiwi boots, reads
116
+ `/webclient/static/config.json`, and opens a `wss://` (or `ws://` in
117
+ local dev) connection to the same origin.
118
+
119
+ > **Forgot the submodule?** `apps/web/scripts/build.mjs` fails fast:
120
+ > `Kiwi upstream missing at .../upstream. Run
121
+ > 'git submodule update --init apps/web/upstream' first.`
122
+
123
+ ---
124
+
125
+ ## 4. The build pipeline (`apps/web/scripts/build.mjs`)
126
+
127
+ The build is an orchestrated layering over the upstream Kiwi build —
128
+ **we own no IRC logic**, only config + asset placement.
129
+
130
+ ### 4.1 Per-environment config matrix
131
+
132
+ | `pnpm ...` command | Reads | Bakes into `dist/webclient/static/config.json` |
133
+ |---|---|---|
134
+ | `--filter web build` | `static/config.json` | default (staging-shaped) config |
135
+ | `--filter web build:staging` | `static/config.staging.json` | staging config |
136
+ | `--filter web build:prod` | `static/config.prod.json` | prod config |
137
+
138
+ `--env` is parsed by `src/build-env.ts`; an unknown value aborts the
139
+ build with a readable error. The selected file is validated against the
140
+ Zod schema (`src/config-schema.ts`) **before** it is baked, so a
141
+ malformed config fails the build rather than shipping a broken SPA.
142
+
143
+ ### 4.2 What the build does, in order
144
+
145
+ 1. **Verify the submodule** exists (`upstream/`), or fail with the
146
+ hint quoted above.
147
+ 2. **`yarn install --frozen-lockfile`** inside `upstream/` (only if
148
+ `upstream/node_modules` is missing). `COREPACK_ROOT` is set so yarn
149
+ classic doesn't refuse on the pnpm-managed workspace root. On
150
+ Node ≥ 17 the OpenSSL legacy provider is enabled for webpack 5's md4
151
+ hashing.
152
+ 3. **Patch `upstream/vue.config.js`** `publicPath: ''` →
153
+ `publicPath: '/webclient/'`, run `yarn build`, then **restore** the
154
+ file in `finally` so the submodule stays clean. This is why every
155
+ hashed asset URL comes out `/webclient/static/js/app.<hash>.js`
156
+ instead of `static/js/...`, which would 404 at root.
157
+ 4. **Copy `upstream/dist/` → `apps/web/dist/webclient/`.**
158
+ 5. **Rewrite bare `static/...` refs** in the baked `index.html` (e.g.
159
+ the favicon, which webpack doesn't run through `publicPath`) to
160
+ `/webclient/static/...`.
161
+ 6. **Validate + bake the per-env config**: read
162
+ `static/config.<env>.json`, run `parseKiwiConfig`, re-serialise
163
+ (stable key order), write to `dist/webclient/static/config.json`.
164
+ 7. **Copy the landing page** `landing/index.html` → `dist/index.html`.
165
+
166
+ Output is reproducible: same checkout + same `--env` → byte-identical
167
+ `dist/` (modulo upstream webpack hash stability).
168
+
169
+ ### 4.3 The config schema (`src/config-schema.ts`)
170
+
171
+ The schema models only the fields ServerlessIRCd cares about; the rest
172
+ of Kiwi's surface is passed through untouched (`.passthrough()`).
173
+
174
+ ```jsonc
175
+ // static/config.staging.json
176
+ {
177
+ "windowTitle": "ServerlessIRCd (staging)",
178
+ "startupOptions": {
179
+ "server": "{{hostname}}", // resolved to window.location.hostname at boot
180
+ "direct": true, // MUST be true — direct WS, no kiwiServer gateway
181
+ "tls": true, // MUST be true — wss:// (page is HTTPS)
182
+ "port": 443,
183
+ "channel": "#welcome",
184
+ "nick": "Guest"
185
+ }
186
+ }
187
+ ```
188
+
189
+ Field notes (see the schema docstrings for the full rationale):
190
+
191
+ - **`startupOptions.server`** — `{{hostname}}` (or `{{host}}` to keep
192
+ a non-default port) is a Kiwi template resolved by the browser to
193
+ `window.location.hostname` at load time. This is what lets one build
194
+ artifact target staging, prod, preview, and PR deploys without a
195
+ per-env hostname. A literal hostname is also accepted.
196
+ - **`startupOptions.direct`** — MUST be `true`. ServerlessIRCd is a
197
+ direct IRC-over-WebSocket server; the Kiwi default (`false`) routes
198
+ through a `kiwiServer` WebIRC gateway that nothing serves here.
199
+ - **`startupOptions.tls`** — MUST be `true` in any HTTPS deployment
200
+ (browsers refuse `ws://` on an HTTPS page).
201
+ - **`startupOptions.direct_path`** — the WS path. Kiwi reads
202
+ `direct_path` (NOT `path`); the Worker exposes its WS upgrade at
203
+ root, so this MUST be `/` if supplied. Optional — when omitted,
204
+ irc-framework builds `wss://host:port` (no path), which the browser
205
+ normalises to `wss://host:port/`.
206
+ - **`startupOptions.channel`** — must begin with `#` (matches
207
+ `CHANTYPES=#`); comma-separated lists are accepted.
208
+
209
+ A malformed config aborts the build with an aggregated error, e.g.:
210
+
211
+ ```
212
+ config.staging.json: Invalid Kiwi web config:
213
+ - startupOptions.port: port must be a valid TCP port (1-65535)
214
+ - startupOptions.channel: must begin with '#'
215
+ ```
216
+
217
+ ### 4.4 Tests
218
+
219
+ ```bash
220
+ pnpm --filter web test # schema + build-env + workspace tests
221
+ pnpm --filter web typecheck
222
+ ```
223
+
224
+ The schema and build-env tests are pure (no Kiwi build needed). The
225
+ build-smoke tests (`tests/build-smoke.test.ts`) assert the **landing
226
+ page** source unconditionally and the `dist/` output only when a build
227
+ is present (`describe.skipIf(!built)`) — the Kiwi build is too heavy
228
+ for the unit suite, so run `pnpm --filter web build` first to exercise
229
+ those assertions.
230
+
231
+ ---
232
+
233
+ ## 5. CSWSH defense and the `WEB_ORIGINS` var
234
+
235
+ WebSocket upgrades do **not** follow the same-origin policy. A
236
+ malicious page can `new WebSocket('wss://irc.example.com/')` from the
237
+ victim's browser and drive an IRC session with the victim's
238
+ credentials (Cross-Site WebSocket Hijacking, CSWSH). The Worker
239
+ rejects upgrades whose `Origin` is not allowed — see
240
+ `apps/cf-worker/src/origin-allowlist.ts`.
241
+
242
+ ### 5.1 Two enforcement modes (evaluated in order)
243
+
244
+ 1. **Explicit allowlist** — when `WEB_ORIGINS` is set, only listed
245
+ origins proceed. Use this for cross-origin deployments (SPA on a
246
+ different domain than the Worker).
247
+ 2. **Auto-derive (same-origin)** — when `WEB_ORIGINS` is unset/empty
248
+ (the default), the Worker compares the browser's `Origin` against
249
+ the request's own origin (`${protocol}//${host}`). If they match,
250
+ the page opening the WS is the same origin as the server → safe.
251
+
252
+ Non-browser clients (curl, WeeChat, the `tcp-ws-forwarder`, scripted
253
+ test harnesses) never send `Origin`; their upgrades pass through
254
+ unchanged in **both** modes, so existing tooling keeps working.
255
+
256
+ ### 5.2 `WEB_ORIGINS` is optional
257
+
258
+ Because mode 2 (same-origin auto-derive) is the default and requires
259
+ zero per-env configuration, **`WEB_ORIGINS` is optional**. Most
260
+ deployments never need to set it: the SPA and the Worker are on the
261
+ same origin, so the auto-derive check passes for every browser and
262
+ rejects every cross-origin attempt. It works for `*.workers.dev`,
263
+ custom domains, and preview URLs alike.
264
+
265
+ Set `WEB_ORIGINS` only when the SPA is served from a **different**
266
+ origin than the Worker (e.g. a Cloudflare Pages deployment — see §7 —
267
+ or a separate static host). It is a comma-separated list of origins,
268
+ lowercased and whitespace-trimmed:
269
+
270
+ ```toml
271
+ # apps/cf-worker/wrangler.toml — cross-origin example
272
+ [vars]
273
+ WEB_ORIGINS = "https://irc.example.com,https://web.example.com"
274
+ ```
275
+
276
+ ```bash
277
+ # or per-deploy via the secret store / dashboard env vars:
278
+ wrangler secret put WEB_ORIGINS --env staging
279
+ ```
280
+
281
+ ### 5.3 Responses
282
+
283
+ | Scenario | Response |
284
+ |---|---|
285
+ | `Origin` allowed (allowlist or same-origin) | `101 Switching Protocols` → `ConnectionDO` |
286
+ | `Origin` header absent (non-browser client) | `101` (unchanged) |
287
+ | `Origin` present but not allowed | `403 Forbidden` (`WebSocket upgrade refused: the request Origin is not allowed.`) |
288
+ | No allowlist AND `Host` undeterminable | `403 Forbidden` (defensive fail-closed; does not fire on Cloudflare, which always sets `Host`) |
289
+
290
+ The 403 path is unit-tested in `apps/cf-worker/tests/ws-origin.test.ts`
291
+ and the pure policy in `origin-allowlist.test.ts`. There is no
292
+ fail-open path.
293
+
294
+ ---
295
+
296
+ ## 6. Local development
297
+
298
+ ### 6.1 Serve the SPA from the Worker (`wrangler dev`)
299
+
300
+ This is the canonical local loop — same-origin, real `workerd`, real
301
+ Durable Objects:
302
+
303
+ ```bash
304
+ pnpm --filter web build # build the SPA + landing page
305
+ pnpm build # build the rest of the workspace
306
+ pnpm --filter @serverless-ircd/cf-worker dev
307
+ ```
308
+
309
+ Then:
310
+
311
+ - `http://localhost:8787/` — landing page.
312
+ - `http://localhost:8787/webclient/` — Kiwi SPA (reads
313
+ `/webclient/static/config.json`, opens `ws://localhost:8787/`).
314
+ - `http://localhost:8787/health` — plaintext liveness.
315
+
316
+ Smoke the WS path directly (no browser needed):
317
+
318
+ ```bash
319
+ node apps/cf-worker/scripts/smoke.mjs
320
+ # → replays CONNECT/NICK/USER/JOIN/PRIVMSG/QUIT, asserts 001/376/353/366
321
+ ```
322
+
323
+ ### 6.2 Iterate on the SPA config without rebuilding Kiwi
324
+
325
+ The Kiwi upstream build is the slow step. For a config-only change,
326
+ edit `static/config.staging.json`, then re-run
327
+ `pnpm --filter web build:staging` — step 2 of the build reuses
328
+ `upstream/node_modules` and skips straight to re-baking the config
329
+ (steps 4–6 are fast).
330
+
331
+ ### 6.3 Iterate on the landing page
332
+
333
+ `landing/index.html` is pure static HTML + inline CSS, no JavaScript,
334
+ no build step. Edit it and re-run the build (it is copied verbatim to
335
+ `dist/index.html`); or just preview it directly in a browser — it has
336
+ no runtime dependencies.
337
+
338
+ ---
339
+
340
+ ## 7. Optional: Cloudflare Pages instead of Worker assets
341
+
342
+ The build output (`apps/web/dist/`) is identical for both hosting
343
+ options; the choice is a deploy-time knob, not a build-time one. Use
344
+ Pages when you want to decouple static deploys from Worker deploys
345
+ (e.g. the frontend changes more often than the IRCd), or when prod
346
+ favors a dedicated static edge.
347
+
348
+ ### 7.1 Set up Pages
349
+
350
+ 1. Push the repo (or just `apps/web/dist/`) to a Pages-connected
351
+ branch, or use `wrangler pages deploy apps/web/dist`.
352
+ 2. Set the Pages project's output directory to `apps/web/dist` and the
353
+ build command to `pnpm --filter web build:prod` (plus `pnpm build`
354
+ if the Worker is deployed from the same repo).
355
+ 3. The Pages project is reachable at
356
+ `https://<project>.pages.dev` (or a custom domain).
357
+
358
+ ### 7.2 Allow the Pages origin on the Worker
359
+
360
+ Because the SPA is now on a **different** origin than the Worker, the
361
+ same-origin auto-derive (§5.1 mode 2) will reject the browser's WS
362
+ upgrade. Set `WEB_ORIGINS` on the Worker to the Pages origin:
363
+
364
+ ```toml
365
+ # apps/cf-worker/wrangler.toml
366
+ [vars]
367
+ WEB_ORIGINS = "https://<project>.pages.dev"
368
+ ```
369
+
370
+ The SPA config's `startupOptions.server` still resolves to the
371
+ **Worker's** hostname (where the WS endpoint lives), not the Pages
372
+ hostname — so set it to a literal if you've pinned it, or keep
373
+ `{{hostname}}` only if Pages and the Worker share a domain.
374
+
375
+ ---
376
+
377
+ ## 8. Optional: `webircgateway` SockJS fallback
378
+
379
+ ServerlessIRCd already speaks the wire format browser Kiwi expects:
380
+ native WebSocket text frames, one IRC message per frame, with
381
+ `\r\n`-joined-frame tolerance. The Worker's WS endpoint at `/` is all
382
+ Kiwi needs. **You do not need `webircgateway` for normal operation.**
383
+
384
+ When is it useful? Corporate proxies and some mobile carriers block
385
+ raw `wss://` (or upgrade it in ways that break long-lived frames).
386
+ `webircgateway` provides a SockJS + Kiwi multi-server transport that
387
+ falls back to HTTP polling when raw WS is unavailable. If your users
388
+ report "can't connect from the office Wi-Fi", that's the trigger.
389
+
390
+ The gateway is vendored separately (see the `webircgateway/` submodule
391
+ if present in your checkout). Running it as a sidecar is a deployment
392
+ concern outside this doc's scope; the key point is that it is
393
+ **strictly opt-in**. The default SPA build does not reference it, and
394
+ no `kiwiServer` / gateway URL is baked into the config.
395
+
396
+ To wire it up when needed, point Kiwi's `startupOptions.kiwiServer`
397
+ at the gateway and set `startupOptions.direct: false` — the opposite
398
+ of the ServerlessIRCd default. Document this only for the affected
399
+ deployment; don't change the default config.
400
+
401
+ ---
402
+
403
+ ## 9. Customising the landing page
404
+
405
+ The landing page (`apps/web/landing/index.html`) is the project's front
406
+ door: a visitor hitting `/` learns what ServerlessIRCd is and clicks
407
+ through to `/webclient/` to launch the client. It is intentionally
408
+ framework-free (pure HTML + inline CSS, no JS) so it renders without
409
+ JavaScript and costs nothing to serve.
410
+
411
+ To customise:
412
+
413
+ - **Copy/branding** — edit the `<header>`, `<main>`, and `<footer>`
414
+ blocks directly. The CSS variables at the top of the `<style>` block
415
+ (`--bg`, `--surface`, `--accent`, …) theme the whole page in one
416
+ place.
417
+ - **Links** — the page links to `/webclient/` (the SPA), `docs/`, and
418
+ the source repo. Update the repo URLs and the docs path to match
419
+ your deployment.
420
+ - **Accessibility** — keep the declared viewport meta tag and the
421
+ no-`<script>` invariant; both are asserted by
422
+ `tests/build-smoke.test.ts`. The build copies the file verbatim to
423
+ `dist/index.html`, so what you author is exactly what is served.
424
+
425
+ Re-run `pnpm --filter web build` (any `--env`) after edits; the build
426
+ test suite will fail loudly if you accidentally remove the `/webclient/`
427
+ link, the docs link, or the viewport tag.
428
+
429
+ ---
430
+
431
+ ## 10. Troubleshooting
432
+
433
+ ### 10.1 `Kiwi upstream missing at .../upstream`
434
+
435
+ The submodule wasn't initialised. Run:
436
+
437
+ ```bash
438
+ git submodule update --init apps/web/upstream
439
+ ```
440
+
441
+ The build fails fast with this hint rather than a confusing webpack
442
+ error.
443
+
444
+ ### 10.2 SPA loads but never connects ("connection refused" / spinning)
445
+
446
+ Almost always a config issue. Open the browser devtools Network tab
447
+ and check the WS upgrade:
448
+
449
+ - **`ws://` on an HTTPS page** — `startupOptions.tls` must be `true`
450
+ (browsers refuse insecure WS from secure pages). The default configs
451
+ set it; a hand-edited config may not.
452
+ - **`direct: false`** — Kiwi is probing a `kiwiServer` gateway that
453
+ nothing serves. `startupOptions.direct` MUST be `true`.
454
+ - **Wrong server** — if you pinned a literal `server`, it must be the
455
+ Worker's hostname. `{{hostname}}` resolves to
456
+ `window.location.hostname` and is the safest default.
457
+ - **403 on upgrade** — the `Origin` was rejected (§5). Check
458
+ `WEB_ORIGINS` if you're cross-origin, or confirm the SPA and Worker
459
+ are same-origin.
460
+
461
+ ### 10.3 Asset 404s at `/static/...` (instead of `/webclient/static/...`)
462
+
463
+ The `publicPath` patch in step 3 of the build didn't take. Confirm
464
+ `upstream/vue.config.js` still contains a `publicPath: ''` literal to
465
+ patch against (the build restores it after building). If upstream
466
+ changed the phrasing, the patch regex in `scripts/build.mjs` needs an
467
+ update.
468
+
469
+ ### 10.4 `200 OK` instead of `101 Switching Protocols` on WS upgrade
470
+
471
+ The asset platform served the landing page for the upgrade request.
472
+ This means `run_worker_first = true` is missing or false in the
473
+ `[assets]` block of `wrangler.toml`. The WS endpoint and the landing
474
+ page share path `/`, so the Worker MUST run first for everything and
475
+ fall through to assets only for non-WS requests.
476
+
477
+ ### 10.5 `Cannot find module '@serverless-ircd/cf-adapter'`
478
+
479
+ The workspace packages weren't built. Run `pnpm build` from the repo
480
+ root before `pnpm --filter web build` or `wrangler dev` — the build
481
+ script and the Worker entry import compiled `dist/` from the workspace
482
+ packages.
483
+
484
+ ### 10.6 Build-smoke tests fail in CI but pass locally
485
+
486
+ The `dist/`-dependent assertions in `tests/build-smoke.test.ts` are
487
+ gated on `existsSync(dist/webclient/index.html)` and are skipped when
488
+ the SPA hasn't been built. CI runs `pnpm --filter web build` (or
489
+ `pnpm build`) before `pnpm --filter web test`, so the assertions fire
490
+ there. If you run `pnpm --filter web test` locally without building,
491
+ only the landing-page source assertions run — that's expected, not a
492
+ regression.
493
+
494
+ ---
495
+
496
+ ## 11. Quick reference
497
+
498
+ ```bash
499
+ # One-time / per-clone
500
+ corepack enable
501
+ git submodule update --init # docs + apps/web/upstream (Kiwi)
502
+ pnpm install
503
+
504
+ # Build the SPA + landing page (pick the env)
505
+ pnpm --filter web build # default config (staging-shaped)
506
+ pnpm --filter web build:staging # static/config.staging.json
507
+ pnpm --filter web build:prod # static/config.prod.json
508
+
509
+ # Local dev (Worker serves SPA + landing page + WS on one origin)
510
+ pnpm build # build the rest of the workspace
511
+ pnpm --filter @serverless-ircd/cf-worker dev # http://localhost:8787
512
+ node apps/cf-worker/scripts/smoke.mjs # WS smoke
513
+
514
+ # Deploy staging (Worker + assets in one command)
515
+ pnpm deploy:cf:staging # wrangler deploy --env staging
516
+
517
+ # Tests / lint
518
+ pnpm --filter web test
519
+ pnpm --filter web typecheck
520
+ pnpm lint
521
+ ```
522
+
523
+ Key files:
524
+
525
+ | Path | What |
526
+ |---|---|
527
+ | `apps/web/scripts/build.mjs` | Orchestrates the upstream Kiwi build + layers our config. |
528
+ | `apps/web/src/config-schema.ts` | Zod schema for `static/config.*.json` (the only TS we own). |
529
+ | `apps/web/src/build-env.ts` | `--env` → config-file resolution (unit-tested). |
530
+ | `apps/web/static/config.{,staging,prod}.json` | Per-env Kiwi configs (validated at build time). |
531
+ | `apps/web/landing/index.html` | Static landing page (project front door), copied to `dist/index.html`. |
532
+ | `apps/web/tests/build-smoke.test.ts` | Landing-page + build-output assertions. |
533
+ | `apps/cf-worker/src/worker.ts` | Edge handler: WS → `ConnectionDO`, `/health`, else `env.ASSETS`. |
534
+ | `apps/cf-worker/src/origin-allowlist.ts` | CSWSH `Origin` policy (pure, unit-tested). |
535
+ | `apps/cf-worker/wrangler.toml` | `[assets]` binding + per-env blocks + `WEB_ORIGINS` var. |
536
+ | `apps/web/upstream/` | Kiwi IRC source (git submodule, tag-pinned). |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverless-ircd",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "description": "Serverless IRC daemon with a platform-agnostic core and Cloudflare Workers + AWS adapters",
6
6
  "license": "BSD-3-Clause",
@@ -17,7 +17,7 @@
17
17
  "typescript": "^5.9.3",
18
18
  "vite": "^7.3.6",
19
19
  "vitest": "^4.1.10",
20
- "@serverless-ircd/aws-adapter": "0.6.0"
20
+ "@serverless-ircd/aws-adapter": "0.8.0"
21
21
  },
22
22
  "scripts": {
23
23
  "build": "turbo run build",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serverless-ircd/aws-adapter",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "private": true,
5
5
  "description": "AWS Lambda + DynamoDB adapter: AwsRuntime implementing IrcRuntime + $connect/$disconnect/$default handlers",
6
6
  "license": "BSD-3-Clause",
@@ -57,6 +57,7 @@ import {
57
57
  toSnapshot as toConnSnapshot,
58
58
  } from '@serverless-ircd/irc-core';
59
59
  import type { IrcRuntime } from '@serverless-ircd/irc-server';
60
+ import { decrementConnectionCount } from './connection-counter.js';
60
61
  import {
61
62
  type MarshalledChannelMember,
62
63
  type MarshalledChannelMeta,
@@ -733,6 +734,10 @@ export async function cleanupConnection(
733
734
  await dynamo.send(
734
735
  new DeleteCommand({ TableName: tables.Connections, Key: { connectionId: connId } }),
735
736
  );
737
+ // Release the admission slot this connection reserved at `$connect`. The
738
+ // meta counter is best-effort (decrement floors at 0; a missing/zero
739
+ // counter is a silent no-op), so a drifted count never fails teardown.
740
+ await decrementConnectionCount(dynamo, tables.Connections);
736
741
 
737
742
  if (item.nick !== undefined) {
738
743
  try {
@@ -70,4 +70,10 @@ export const TABLE_DEFS: Record<TableName, TableProps> = {
70
70
  partitionKey: { name: 'account', type: AttributeType.STRING },
71
71
  billingMode: BillingMode.PAY_PER_REQUEST,
72
72
  },
73
+ Services: {
74
+ tableName: 'Services',
75
+ partitionKey: { name: 'pk', type: AttributeType.STRING },
76
+ sortKey: { name: 'sk', type: AttributeType.STRING },
77
+ billingMode: BillingMode.PAY_PER_REQUEST,
78
+ },
73
79
  };
@@ -35,6 +35,14 @@ export interface LambdaConfigEnv {
35
35
  CHANNEL_PREFIXES?: string;
36
36
  OPER_USER?: string;
37
37
  OPER_PASSWORD?: string;
38
+ /**
39
+ * Server-password gate. Treat as an AWS secret (Secrets Manager or
40
+ * SSM `SecureString`); the loader threads it through to
41
+ * `ServerConfig.serverPassword` so the registration reducer requires
42
+ * `PASS <value>` (or a SASL-identified connection) before
43
+ * `001 RPL_WELCOME`. Undefined / empty disables the gate.
44
+ */
45
+ SERVER_PASSWORD?: string;
38
46
  MAX_CHANNELS_PER_USER?: string;
39
47
  MAX_TARGETS_PER_COMMAND?: string;
40
48
  NICK_LEN?: string;
@@ -87,6 +95,9 @@ export function buildLambdaConfigInput(env: LambdaConfigEnv): Record<string, unk
87
95
  },
88
96
  ];
89
97
  }
98
+ if (env.SERVER_PASSWORD !== undefined) {
99
+ input.serverPassword = env.SERVER_PASSWORD;
100
+ }
90
101
  if (env.MAX_CHANNELS_PER_USER !== undefined) {
91
102
  input.maxChannelsPerUser = Number.parseInt(env.MAX_CHANNELS_PER_USER, 10);
92
103
  }