serverless-ircd 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/.github/workflows/ci.yml +40 -3
  2. package/.github/workflows/deploy-aws.yml +1 -2
  3. package/.github/workflows/deploy-cf-tcp.yml +1 -5
  4. package/.github/workflows/deploy-cf.yml +1 -2
  5. package/.gitmodules +3 -0
  6. package/CHANGELOG.md +474 -0
  7. package/README.md +219 -22
  8. package/apps/aws-stack/README.md +33 -0
  9. package/apps/aws-stack/package.json +1 -1
  10. package/apps/aws-stack/src/aws-stack.ts +74 -9
  11. package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
  12. package/apps/aws-stack/tests/stack.test.ts +2 -2
  13. package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
  14. package/apps/aws-stack/tests/vitest.setup.ts +18 -0
  15. package/apps/aws-stack/vitest.config.ts +3 -0
  16. package/apps/cf-tcp-container/package.json +1 -1
  17. package/apps/cf-tcp-container/src/config-loader.ts +6 -7
  18. package/apps/cf-tcp-container/src/container-server.ts +3 -0
  19. package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
  20. package/apps/cf-tcp-container/wrangler.toml +1 -1
  21. package/apps/cf-worker/package.json +2 -1
  22. package/apps/cf-worker/src/origin-allowlist.ts +99 -0
  23. package/apps/cf-worker/src/worker.ts +89 -9
  24. package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +18 -0
  25. package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
  26. package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
  27. package/apps/cf-worker/tests/smoke.test.ts +28 -2
  28. package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
  29. package/apps/cf-worker/vitest.config.ts +1 -1
  30. package/apps/cf-worker/wrangler.test.toml +23 -0
  31. package/apps/cf-worker/wrangler.toml +59 -3
  32. package/apps/local-cli/package.json +1 -1
  33. package/apps/local-cli/src/config-loader.ts +6 -6
  34. package/apps/local-cli/src/main.ts +20 -0
  35. package/apps/local-cli/src/server.ts +9 -7
  36. package/apps/local-cli/tests/e2e.test.ts +112 -28
  37. package/apps/local-cli/tests/tcp.test.ts +71 -19
  38. package/apps/local-cli/tests/ws-subprotocol.test.ts +12 -7
  39. package/apps/web/landing/index.html +217 -0
  40. package/apps/web/package.json +28 -0
  41. package/apps/web/scripts/build.mjs +125 -0
  42. package/apps/web/src/build-env.ts +43 -0
  43. package/apps/web/src/config-schema.ts +138 -0
  44. package/apps/web/static/config.json +28 -0
  45. package/apps/web/static/config.prod.json +28 -0
  46. package/apps/web/static/config.staging.json +28 -0
  47. package/apps/web/tests/build-env.test.ts +63 -0
  48. package/apps/web/tests/build-smoke.test.ts +103 -0
  49. package/apps/web/tests/config-schema.test.ts +432 -0
  50. package/apps/web/tests/workspace.test.ts +12 -0
  51. package/apps/web/tsconfig.json +10 -0
  52. package/apps/web/upstream/.browserslistrc +3 -0
  53. package/apps/web/upstream/.editorconfig +9 -0
  54. package/apps/web/upstream/.eslintignore +3 -0
  55. package/apps/web/upstream/.eslintrc.js +90 -0
  56. package/apps/web/upstream/.github/FUNDING.yml +12 -0
  57. package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  58. package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  59. package/apps/web/upstream/.github/workflows/push.yml +26 -0
  60. package/apps/web/upstream/.github/workflows/release.yml +33 -0
  61. package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
  62. package/apps/web/upstream/.prettierrc.js +9 -0
  63. package/apps/web/upstream/.stylelintignore +5 -0
  64. package/apps/web/upstream/.stylelintrc.js +21 -0
  65. package/apps/web/upstream/LICENSE +201 -0
  66. package/apps/web/upstream/README.md +76 -0
  67. package/apps/web/upstream/babel.config.js +25 -0
  68. package/apps/web/upstream/crowdin.yml +3 -0
  69. package/apps/web/upstream/index.html +35 -0
  70. package/apps/web/upstream/jest.config.js +7 -0
  71. package/apps/web/upstream/jsdoc.json +22 -0
  72. package/apps/web/upstream/package.json +80 -0
  73. package/apps/web/upstream/scripts/pre-commit +43 -0
  74. package/apps/web/upstream/src/components/App.vue +516 -0
  75. package/apps/web/upstream/src/components/AppSettings.vue +587 -0
  76. package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
  77. package/apps/web/upstream/src/components/Avatar.vue +105 -0
  78. package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
  79. package/apps/web/upstream/src/components/BufferKey.vue +121 -0
  80. package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
  81. package/apps/web/upstream/src/components/Captcha.vue +85 -0
  82. package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
  83. package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
  84. package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
  85. package/apps/web/upstream/src/components/ChannelList.vue +345 -0
  86. package/apps/web/upstream/src/components/Container.vue +401 -0
  87. package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
  88. package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
  89. package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
  90. package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
  91. package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
  92. package/apps/web/upstream/src/components/MessageList.vue +1028 -0
  93. package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
  94. package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
  95. package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
  96. package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
  97. package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
  98. package/apps/web/upstream/src/components/Nicklist.vue +309 -0
  99. package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
  100. package/apps/web/upstream/src/components/NotConnected.vue +271 -0
  101. package/apps/web/upstream/src/components/SelfUser.vue +283 -0
  102. package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
  103. package/apps/web/upstream/src/components/ServerView.vue +126 -0
  104. package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
  105. package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
  106. package/apps/web/upstream/src/components/Sidebar.vue +429 -0
  107. package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
  108. package/apps/web/upstream/src/components/SidebarState.vue +145 -0
  109. package/apps/web/upstream/src/components/StartupError.vue +35 -0
  110. package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
  111. package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
  112. package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
  113. package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
  114. package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
  115. package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
  116. package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
  117. package/apps/web/upstream/src/components/UserBox.vue +728 -0
  118. package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
  119. package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
  120. package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
  121. package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
  122. package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
  123. package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
  124. package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
  125. package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
  126. package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
  127. package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
  128. package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
  129. package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
  130. package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
  131. package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
  132. package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
  133. package/apps/web/upstream/src/helpers/Colours.js +128 -0
  134. package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
  135. package/apps/web/upstream/src/helpers/Md5.js +193 -0
  136. package/apps/web/upstream/src/helpers/Misc.js +401 -0
  137. package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
  138. package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
  139. package/apps/web/upstream/src/libs/AudioManager.js +60 -0
  140. package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
  141. package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
  142. package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
  143. package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
  144. package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
  145. package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
  146. package/apps/web/upstream/src/libs/IPC.js +51 -0
  147. package/apps/web/upstream/src/libs/InputHandler.js +921 -0
  148. package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
  149. package/apps/web/upstream/src/libs/Logger.js +71 -0
  150. package/apps/web/upstream/src/libs/Message.js +169 -0
  151. package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
  152. package/apps/web/upstream/src/libs/MessageParser.js +249 -0
  153. package/apps/web/upstream/src/libs/Notifications.js +78 -0
  154. package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
  155. package/apps/web/upstream/src/libs/ServerSession.js +102 -0
  156. package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
  157. package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
  158. package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
  159. package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
  160. package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
  161. package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
  162. package/apps/web/upstream/src/libs/bufferTools.js +177 -0
  163. package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
  164. package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
  165. package/apps/web/upstream/src/libs/settingTools.js +31 -0
  166. package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
  167. package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
  168. package/apps/web/upstream/src/libs/state/UserState.js +89 -0
  169. package/apps/web/upstream/src/libs/state/common.js +26 -0
  170. package/apps/web/upstream/src/libs/state.js +961 -0
  171. package/apps/web/upstream/src/libs/storage/Local.js +51 -0
  172. package/apps/web/upstream/src/main.js +566 -0
  173. package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
  174. package/apps/web/upstream/src/res/configTemplates.js +373 -0
  175. package/apps/web/upstream/src/res/globalStyle.css +277 -0
  176. package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
  177. package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
  178. package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
  179. package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
  180. package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
  181. package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
  182. package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
  183. package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
  184. package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
  185. package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
  186. package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
  187. package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
  188. package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
  189. package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
  190. package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
  191. package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
  192. package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
  193. package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
  194. package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
  195. package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
  196. package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
  197. package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
  198. package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
  199. package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
  200. package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
  201. package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
  202. package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
  203. package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
  204. package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
  205. package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
  206. package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
  207. package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
  208. package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
  209. package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
  210. package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
  211. package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
  212. package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
  213. package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
  214. package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
  215. package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
  216. package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
  217. package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
  218. package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
  219. package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
  220. package/apps/web/upstream/src/res/localesList.json +27 -0
  221. package/apps/web/upstream/src/res/logo.png +0 -0
  222. package/apps/web/upstream/src/thirdparty/about.html +43 -0
  223. package/apps/web/upstream/src/thirdparty/index.js +5 -0
  224. package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
  225. package/apps/web/upstream/static/config.json +28 -0
  226. package/apps/web/upstream/static/emoticons/smile.png +0 -0
  227. package/apps/web/upstream/static/favicon.png +0 -0
  228. package/apps/web/upstream/static/highlight.mp3 +0 -0
  229. package/apps/web/upstream/static/highlight.ogg +0 -0
  230. package/apps/web/upstream/static/locales/.gitignore +2 -0
  231. package/apps/web/upstream/static/plugins/customise.html.example +10 -0
  232. package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
  233. package/apps/web/upstream/static/themes/common/base.css +1064 -0
  234. package/apps/web/upstream/static/themes/dark/theme.css +196 -0
  235. package/apps/web/upstream/static/themes/default/theme.css +92 -0
  236. package/apps/web/upstream/static/themes/elite/theme.css +248 -0
  237. package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
  238. package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
  239. package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
  240. package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
  241. package/apps/web/upstream/static/themes/sky/theme.css +107 -0
  242. package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
  243. package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
  244. package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
  245. package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
  246. package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
  247. package/apps/web/upstream/vue.config.js +150 -0
  248. package/apps/web/upstream/yarn.lock +9767 -0
  249. package/apps/web/vitest.config.ts +13 -0
  250. package/biome.json +2 -0
  251. package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
  252. package/docs/ADR-002-location-of-authority.md +82 -0
  253. package/docs/ADR-003-durable-object-sharding.md +110 -0
  254. package/docs/ADR-004-dynamodb-schema.md +96 -0
  255. package/docs/ADR-005-wss-only-transport-v1.md +83 -0
  256. package/docs/ADR-006-sasl-mechanism-scope.md +96 -0
  257. package/docs/ADR-007-deterministic-ports.md +82 -0
  258. package/docs/ADR-008-monorepo-tooling.md +60 -0
  259. package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
  260. package/docs/ADR-Index.md +15 -0
  261. package/docs/AWS-Adapter-Architecture.md +496 -0
  262. package/docs/AWS-Deployment.md +1275 -0
  263. package/docs/AWS-TCP-Deployment.md +410 -0
  264. package/docs/Cloudflare-Deployment-Guide.md +719 -0
  265. package/docs/Cloudflare-TCP-Deployment.md +510 -0
  266. package/docs/Home.md +16 -0
  267. package/docs/Observability.md +87 -0
  268. package/docs/PlanExtensions.md +812 -0
  269. package/docs/PlanIRCv3Websocket.md +503 -0
  270. package/docs/PlanWebClient.md +451 -0
  271. package/docs/Release-Process.md +443 -0
  272. package/docs/WebClientGuide.md +535 -0
  273. package/package.json +2 -2
  274. package/packages/aws-adapter/package.json +1 -1
  275. package/packages/aws-adapter/src/handlers/default.ts +3 -0
  276. package/packages/aws-adapter/src/handlers/nlb-stream.ts +9 -0
  277. package/packages/aws-adapter/src/serialize.ts +29 -1
  278. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
  279. package/packages/aws-adapter/tests/account-store.test.ts +4 -7
  280. package/packages/aws-adapter/tests/aws-harness.ts +5 -2
  281. package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
  282. package/packages/aws-adapter/tests/aws-runtime.test.ts +79 -0
  283. package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
  284. package/packages/aws-adapter/tests/connect.test.ts +9 -9
  285. package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
  286. package/packages/aws-adapter/tests/global-setup.ts +55 -30
  287. package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
  288. package/packages/aws-adapter/tests/handlers.test.ts +90 -4
  289. package/packages/aws-adapter/tests/message-store.test.ts +6 -5
  290. package/packages/aws-adapter/tests/nlb-stream.test.ts +60 -8
  291. package/packages/cf-adapter/package.json +2 -1
  292. package/packages/cf-adapter/src/connection-do.ts +3 -0
  293. package/packages/cf-adapter/src/serialize.ts +25 -4
  294. package/packages/cf-adapter/tests/cf-harness.ts +12 -1
  295. package/packages/cf-adapter/tests/cf-runtime.test.ts +269 -0
  296. package/packages/cf-adapter/tests/channel-do.test.ts +117 -0
  297. package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
  298. package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
  299. package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
  300. package/packages/cf-adapter/tests/registry-do.test.ts +104 -0
  301. package/packages/cf-adapter/tests/serialize.test.ts +308 -0
  302. package/packages/cf-adapter/vitest.config.ts +1 -1
  303. package/packages/in-memory-runtime/package.json +1 -1
  304. package/packages/irc-core/package.json +1 -1
  305. package/packages/irc-core/reports/mutation/mutation.html +342 -0
  306. package/packages/irc-core/src/caps/capabilities.ts +37 -3
  307. package/packages/irc-core/src/caps/index.ts +1 -0
  308. package/packages/irc-core/src/caps/sts.ts +84 -0
  309. package/packages/irc-core/src/commands/account-notify.ts +53 -0
  310. package/packages/irc-core/src/commands/away.ts +9 -3
  311. package/packages/irc-core/src/commands/cap.ts +23 -5
  312. package/packages/irc-core/src/commands/chathistory.ts +40 -11
  313. package/packages/irc-core/src/commands/index.ts +34 -0
  314. package/packages/irc-core/src/commands/isupport.ts +53 -0
  315. package/packages/irc-core/src/commands/mode.ts +8 -1
  316. package/packages/irc-core/src/commands/monitor.ts +327 -0
  317. package/packages/irc-core/src/commands/multiline.ts +256 -0
  318. package/packages/irc-core/src/commands/pre-away.ts +112 -0
  319. package/packages/irc-core/src/commands/privmsg.ts +19 -12
  320. package/packages/irc-core/src/commands/read-marker.ts +108 -0
  321. package/packages/irc-core/src/commands/registration.ts +8 -0
  322. package/packages/irc-core/src/commands/sasl.ts +24 -1
  323. package/packages/irc-core/src/commands/tagmsg.ts +71 -1
  324. package/packages/irc-core/src/commands/whois.ts +5 -0
  325. package/packages/irc-core/src/config.ts +65 -10
  326. package/packages/irc-core/src/effects.ts +14 -0
  327. package/packages/irc-core/src/ports.ts +389 -0
  328. package/packages/irc-core/src/protocol/index.ts +14 -0
  329. package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
  330. package/packages/irc-core/src/protocol/numerics.ts +15 -0
  331. package/packages/irc-core/src/protocol/outbound.ts +16 -1
  332. package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
  333. package/packages/irc-core/src/state/connection.ts +32 -1
  334. package/packages/irc-core/src/types.ts +112 -0
  335. package/packages/irc-core/stryker.commands.conf.json +1 -2
  336. package/packages/irc-core/tests/away-store.test.ts +73 -0
  337. package/packages/irc-core/tests/batch.test.ts +15 -0
  338. package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
  339. package/packages/irc-core/tests/caps/sts.test.ts +123 -0
  340. package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
  341. package/packages/irc-core/tests/commands/away.test.ts +85 -2
  342. package/packages/irc-core/tests/commands/cap.test.ts +165 -0
  343. package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
  344. package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
  345. package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
  346. package/packages/irc-core/tests/commands/mode.test.ts +72 -0
  347. package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
  348. package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
  349. package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
  350. package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
  351. package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
  352. package/packages/irc-core/tests/commands/registration.test.ts +64 -0
  353. package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
  354. package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
  355. package/packages/irc-core/tests/commands/whois.test.ts +34 -0
  356. package/packages/irc-core/tests/config.test.ts +103 -13
  357. package/packages/irc-core/tests/labeled-response.test.ts +181 -0
  358. package/packages/irc-core/tests/message-tags.test.ts +114 -0
  359. package/packages/irc-core/tests/monitor-store.test.ts +200 -0
  360. package/packages/irc-core/tests/numerics.test.ts +13 -0
  361. package/packages/irc-core/tests/parser.test.ts +109 -0
  362. package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
  363. package/packages/irc-core/tests/standard-replies.test.ts +412 -0
  364. package/packages/irc-core/tests/types.test.ts +27 -0
  365. package/packages/irc-server/package.json +1 -1
  366. package/packages/irc-server/src/actor.ts +450 -3
  367. package/packages/irc-server/src/dispatch.ts +108 -16
  368. package/packages/irc-server/tests/actor.test.ts +1432 -39
  369. package/packages/irc-server/tests/dispatch.test.ts +263 -2
  370. package/packages/irc-test-support/package.json +1 -1
  371. package/packages/irc-test-support/src/index.ts +6 -0
  372. package/packages/irc-test-support/src/scenarios.ts +24 -2
  373. package/packages/irc-test-support/src/test-config.ts +54 -0
  374. package/packages/irc-test-support/tests/test-config.test.ts +51 -0
  375. package/tools/ci-hardening/package.json +1 -1
  376. package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
  377. package/tools/load-test/package.json +33 -0
  378. package/tools/load-test/src/client.ts +351 -0
  379. package/tools/load-test/src/config.ts +313 -0
  380. package/tools/load-test/src/harness.ts +116 -0
  381. package/tools/load-test/src/main.ts +120 -0
  382. package/tools/load-test/src/metrics.ts +168 -0
  383. package/tools/load-test/src/report.ts +106 -0
  384. package/tools/load-test/tests/client.test.ts +212 -0
  385. package/tools/load-test/tests/config.test.ts +152 -0
  386. package/tools/load-test/tests/framing.test.ts +37 -0
  387. package/tools/load-test/tests/harness.test.ts +165 -0
  388. package/tools/load-test/tests/metrics.test.ts +174 -0
  389. package/tools/load-test/tests/report.test.ts +161 -0
  390. package/tools/load-test/tests/smoke.test.ts +67 -0
  391. package/tools/load-test/tsconfig.build.json +12 -0
  392. package/tools/load-test/tsconfig.test.json +10 -0
  393. package/tools/load-test/vitest.config.ts +29 -0
  394. package/tools/tcp-ws-forwarder/package.json +1 -1
@@ -0,0 +1,551 @@
1
+ 'kiwi public';
2
+
3
+ import _ from 'lodash';
4
+ import Logger from '@/libs/Logger';
5
+ import bouncerMiddleware from '@/libs/BouncerMiddleware';
6
+
7
+ let log = Logger.namespace('BouncerProvider.js');
8
+
9
+ export default class BouncerProvider {
10
+ constructor(state) {
11
+ this.state = state;
12
+
13
+ // This is the network that will be used to control the bouncer
14
+ this.controllerNetwork = null;
15
+
16
+ // The detected credentials for the BNC
17
+ this.bnc = {
18
+ enabled: false,
19
+ username: '',
20
+ password: '',
21
+ server: '',
22
+ port: 6667,
23
+ tls: false,
24
+ direct: false,
25
+ path: '',
26
+ registered: false,
27
+ };
28
+
29
+ // If enabled, new IRC connections will be re-routed through the bouncer
30
+ this.rewriteConnections = true;
31
+
32
+ // A snapshot of the current networks. Compared against to detect changed networks
33
+ this.networksSnapshot = Object.create(null);
34
+
35
+ // If we are currently monitoring for network settings changes
36
+ this.monitoringChanges = false;
37
+
38
+ state.$on('irc.motd', this.onNetworkMotd.bind(this));
39
+ state.$on('irc.bouncer state', this.onNetworkState.bind(this));
40
+ state.$on('irc.bouncer networks', this.onBouncerNetworks.bind(this));
41
+ }
42
+
43
+ enable(server, port, tls, direct, path) {
44
+ log(`Using a Bouncer Provider with server ${server}:${port}`);
45
+
46
+ this.bnc.server = server;
47
+ this.bnc.port = port || 6667;
48
+ this.bnc.tls = !!tls;
49
+ this.bnc.direct = !!direct;
50
+ this.bnc.path = path || '';
51
+ this.bnc.enabled = true;
52
+
53
+ // get the bnc controller network
54
+ const bncNetwork = this.state.networks.find((network) => network.is_bnc);
55
+
56
+ // the bnc controller network password is saved in the format <username>:<password>.
57
+ // if there is a bnc controller network with a password, use these credentials
58
+ // for the bnc connection.
59
+ if (bncNetwork?.connection?.password) {
60
+ let [username, password] = this.parseBncCredentials(bncNetwork.connection.password);
61
+ this.bnc.username = username;
62
+ this.bnc.password = password;
63
+ }
64
+
65
+ // Enable BOUNCER on existing connections
66
+ this.state.networks.forEach((network) => {
67
+ network.ircClient.use(bouncerMiddleware());
68
+ });
69
+
70
+ // this.monitorNetworkChanges();
71
+ this.listenToState();
72
+ }
73
+
74
+ // Try to get a connected network that can be used to control the bouncer
75
+ getController() {
76
+ if (this.controllerNetwork && this.controllerNetwork.state === 'connected') {
77
+ return this.controllerNetwork;
78
+ }
79
+
80
+ this.controllerNetwork = null;
81
+ for (let i = 0; i < this.state.networks.length; i++) {
82
+ let network = this.state.networks[i];
83
+ let client = network.ircClient;
84
+ if (network.state === 'connected' && client.network.cap.isEnabled('bouncer')) {
85
+ this.controllerNetwork = network;
86
+ break;
87
+ }
88
+ }
89
+
90
+ return this.controllerNetwork;
91
+ }
92
+
93
+ async onNetworkMotd(event, network) {
94
+ let client = network.ircClient;
95
+
96
+ if (!this.bnc.enabled) {
97
+ return;
98
+ }
99
+
100
+ if (!client.network.cap.isEnabled('bouncer')) {
101
+ log.debug(`BOUNCER cap not available on network ${network.name}, not using for BNC`);
102
+ return;
103
+ }
104
+
105
+ // Set the bncnetid if the network upstream exists and we havn't already set it
106
+ if (client.bnc.hasNetwork() && !network.connection.bncnetid) {
107
+ network.connection.bncnetid = client.bnc.tags().netid;
108
+ }
109
+
110
+ // Use this initial network password for other network connections
111
+ if (!this.bnc.username) {
112
+ let [username, password] = this.parseBncCredentials(network.connection.password);
113
+ this.bnc.username = username;
114
+ this.bnc.password = password;
115
+ }
116
+
117
+ // If this is a BNC network, sync it before anything else so that we get all its info
118
+ // and buffer states as soon as possible
119
+ if (client.bnc.hasNetwork()) {
120
+ await this.syncBncNetwork(network);
121
+ }
122
+
123
+ // If this is the controller network, add all other networks from the bouncer
124
+ if (!network.connection.bncnetid) {
125
+ await this.initAndAddNetworks(network);
126
+ }
127
+ }
128
+
129
+ onNetworkState(event, network) {
130
+ let isController = this.getController() === network;
131
+ // We get connection state changes advertised on all bouncer connections, so only handle
132
+ // the ones that come from the controller network since we only need to deal with it once.
133
+ if (isController && event.state === 'disconnected') {
134
+ // Mimick any network disconnections by closing this connection too
135
+ let effectedNetwork = this.state.getNetworkFromBncNetId(event.networkId);
136
+ if (effectedNetwork) {
137
+ effectedNetwork.ircClient.connection.transport.close();
138
+ }
139
+ }
140
+ }
141
+
142
+ async initAndAddNetworks(network) {
143
+ let client = network.ircClient;
144
+
145
+ this.bnc.registered = true;
146
+
147
+ // hide the empty (non-network) controller network
148
+ if (!network.ircClient.bnc.hasNetwork()) {
149
+ network.hidden = true;
150
+ network.is_bnc = true;
151
+ } else {
152
+ network.hidden = false;
153
+ network.is_bnc = false;
154
+ }
155
+
156
+ // onBouncerNetworks will pick up the event handler for this
157
+ client.bnc.getNetworks();
158
+ }
159
+
160
+ onBouncerNetworks(bncNetworks) {
161
+ log.debug(`Got ${bncNetworks.length} networks from the BNC`, bncNetworks);
162
+ bncNetworks.forEach((bncNet) => this.addNetworkToState(bncNet));
163
+
164
+ // Remove any networks we have locally but no longer exist on the BNC
165
+ this.state.networks.forEach((existingNet) => {
166
+ let isNetworkInBncList = !!bncNetworks.find((n) => (
167
+ n.networkId === existingNet.connection.bncnetid
168
+ ));
169
+
170
+ if (!existingNet.is_bnc && !isNetworkInBncList) {
171
+ log.debug(`Network '${existingNet.name}' (${existingNet.id}) was not in the BNC, removing locally`);
172
+ this.state.removeNetwork(existingNet.id);
173
+ }
174
+ });
175
+
176
+ // start monitoring network changes
177
+ this.monitorNetworkChanges();
178
+
179
+ if (this.state.getActiveBuffer()) {
180
+ return;
181
+ }
182
+ if (!bncNetworks.length) {
183
+ // BNC has no network, lets add one to help the user
184
+ const net = this.state.addNetwork('Network', '', {});
185
+ net.showServerBuffer('settings');
186
+ } else {
187
+ for (let i = 0; i < this.state.networks.length; i++) {
188
+ const net = this.state.networks[i];
189
+ if (!net.is_bnc) {
190
+ // The first network that is not the BNC control network
191
+ net.showServerBuffer('settings');
192
+ break;
193
+ }
194
+ }
195
+ }
196
+ }
197
+
198
+ async syncBncNetwork(bncNetwork) {
199
+ let client = bncNetwork.ircClient;
200
+
201
+ log.debug(`Syncing network ${bncNetwork.name} from the BNC`);
202
+
203
+ let buffers = await client.bnc.getBuffers(bncNetwork.connection.bncnetid);
204
+ buffers.forEach((buffer) => {
205
+ // The list of buffers also include the network name. Make use of it and make sure our
206
+ // network name is up to date while we can. It may have changed elsewhere
207
+ if (bncNetwork.name !== buffer.network) {
208
+ log(`Detected network name change while syncing buffers. ${bncNetwork.name} > ${buffer.network}`);
209
+ bncNetwork.name = buffer.network;
210
+ }
211
+
212
+ let newBuffer = this.state.addBuffer(bncNetwork.id, buffer.name);
213
+ if (!newBuffer) {
214
+ // The BNC might be giving up bad buffer names or something, so just make sure
215
+ // that it's handled if it ever coccurs
216
+ log.error(`Couldn't add BNC buffer, '${buffer.name}'`, buffer);
217
+ return;
218
+ }
219
+
220
+ if (buffer.joined) {
221
+ newBuffer.enabled = true;
222
+ newBuffer.joined = true;
223
+ } else {
224
+ newBuffer.enabled = false;
225
+ newBuffer.joined = false;
226
+ }
227
+ if (buffer.seen) {
228
+ newBuffer.last_read = (new Date(buffer.seen)).getTime();
229
+ }
230
+ if (buffer.notify) {
231
+ newBuffer.setting('alert_on', buffer.notify);
232
+ }
233
+
234
+ newBuffer.topic = buffer.topic || '';
235
+
236
+ if (bncNetwork.state === 'connected' && newBuffer.isChannel() && newBuffer.joined) {
237
+ client.raw('NAMES ' + newBuffer.name);
238
+ client.who(newBuffer.name);
239
+ }
240
+ });
241
+
242
+ // Remove any existing buffers that we no longer have on the bouncer
243
+ bncNetwork.buffers.forEach((clientBuffer) => {
244
+ if (!clientBuffer.isChannel() && !clientBuffer.isQuery()) {
245
+ return;
246
+ }
247
+
248
+ let existingBuffers = buffers.filter((bncBuffer) => (
249
+ bncBuffer.name.toLowerCase() === clientBuffer.name.toLowerCase()
250
+ ));
251
+
252
+ if (existingBuffers.length === 0) {
253
+ this.state.removeBuffer(clientBuffer);
254
+ }
255
+ });
256
+ }
257
+
258
+ async addNetworkToState(network) {
259
+ // Expects network to be in the format of:
260
+ // {
261
+ // "networkId":"xyz",
262
+ // "name":"freenode",
263
+ // "channel":"1",
264
+ // "connected":"1",
265
+ // "host":"irc.freenode.net",
266
+ // "port":"6667",
267
+ // "tls":"0",
268
+ // "nick":"notprawn99829"
269
+ // },
270
+ let net = this.state.getNetworkFromBncNetId(network.networkId);
271
+ if (!net) {
272
+ net = this.state.addNetwork(network.name, network.nick || '', {
273
+ server: network.host,
274
+ port: network.port,
275
+ tls: network.tls,
276
+ password: network.password || '',
277
+ bncnetid: network.networkId,
278
+ username: network.user,
279
+ account_password: network.account_password,
280
+ });
281
+ } else {
282
+ // Make sure our existing network is all up to date
283
+ net.name = network.name;
284
+ net.connection.server = network.host;
285
+ net.connection.port = parseInt(network.port, 10);
286
+ net.connection.tls = network.tls;
287
+ net.connection.nick = network.account || network.nick;
288
+ net.connection.password = network.password || '';
289
+ net.nick = network.currentNick || network.nick || '';
290
+ net.password = network.account_password;
291
+ }
292
+
293
+ net.editable_name = true;
294
+ return net;
295
+ }
296
+
297
+ // Keep a snapshot of what the current networks are. They will be periodically
298
+ // compared with the active networks to see if anything has changed before
299
+ // saving those changes.
300
+ snapshotCurrentNetworks() {
301
+ this.networksSnapshot = Object.create(null);
302
+
303
+ this.state.networks.forEach((network) => {
304
+ if (!network.connection.bncnetid) {
305
+ return;
306
+ }
307
+
308
+ this.networksSnapshot[network.connection.bncnetid] = {
309
+ bncnetid: network.connection.bncnetid,
310
+ network: network.name,
311
+ host: network.connection.server,
312
+ port: network.connection.port,
313
+ tls: network.connection.tls,
314
+ account: network.connection.nick,
315
+ account_password: network.password,
316
+ server_password: network.connection.password,
317
+ nick: network.connection.nick,
318
+ username: network.username,
319
+ };
320
+ });
321
+ }
322
+
323
+ // Compare the current networks with our previously saved snapshot of
324
+ // networks. Save any changes to the bouncer
325
+ saveState() {
326
+ let controller = this.getController();
327
+ if (!controller) {
328
+ log.debug('No controller available to save networks');
329
+ return;
330
+ }
331
+
332
+ this.state.networks.forEach((network) => {
333
+ // don't save an empty controller to the network.
334
+ // we can't use hasNetwork alone because that requires ircClient to be
335
+ // connected, and we save new nets before they are connected
336
+ if (this.getController() === network && !network.ircClient.bnc.hasNetwork()) {
337
+ return;
338
+ }
339
+
340
+ // Don't save any hidden networks. This includes the BNC controller network
341
+ if (network.hidden) {
342
+ return;
343
+ }
344
+
345
+ // Every network needs a valid name
346
+ if (!network.name.match(/^[a-zA-Z_0-9.]+$/)) {
347
+ log.debug(`Network ${network.id} has an invalid name. Not saving to the BNC`);
348
+ return;
349
+ }
350
+
351
+ let bncnetid = network.connection.bncnetid;
352
+ let snapshot = this.networksSnapshot[bncnetid] || {};
353
+ let tags = {};
354
+
355
+ if (network.name !== snapshot.network) {
356
+ tags.network = network.name;
357
+ }
358
+ if (network.connection.server !== snapshot.host) {
359
+ tags.host = network.connection.server;
360
+ }
361
+ if (network.connection.port !== snapshot.port) {
362
+ tags.port = network.connection.port;
363
+ }
364
+ if (network.connection.tls !== snapshot.tls) {
365
+ tags.tls = network.connection.tls;
366
+ }
367
+ if (network.password !== snapshot.account_password) {
368
+ tags.account_password = network.password;
369
+ }
370
+ if (network.connection.nick !== snapshot.account) {
371
+ tags.account = network.connection.nick;
372
+ }
373
+ if (network.connection.password !== snapshot.server_password) {
374
+ tags.password = network.connection.password;
375
+ }
376
+ if (network.connection.nick !== snapshot.nick) {
377
+ tags.nick = network.connection.nick;
378
+ }
379
+ if (network.username !== snapshot.username) {
380
+ tags.user = network.username;
381
+ }
382
+
383
+ // A newly added network would not have a snapshot name (bncnetid) property set yet.
384
+ // Only save the network if we've entered connection info.
385
+ if (!snapshot.bncnetid && tags.host && tags.port && tags.nick) {
386
+ log(`Saving new network ${network.name} to the BNC`);
387
+ // ?? network.connection.bncname = network.name;
388
+ controller.ircClient.bnc.addNetwork(
389
+ network.name,
390
+ tags.host,
391
+ tags.port,
392
+ tags.tls,
393
+ tags.nick,
394
+ tags.user,
395
+ tags.password,
396
+ ).then((networkInfo) => {
397
+ network.connection.bncnetid = networkInfo.networkId;
398
+ network.name = networkInfo.network;
399
+ });
400
+ } else if (snapshot.bncnetid && Object.keys(tags).length > 0) {
401
+ log(`Updating network ${network.name} on the BNC`);
402
+ controller.ircClient.bnc.saveNetwork(bncnetid, tags);
403
+ }
404
+ });
405
+
406
+ this.snapshotCurrentNetworks();
407
+ }
408
+
409
+ monitorNetworkChanges() {
410
+ if (this.monitoringChanges) {
411
+ return;
412
+ }
413
+ this.monitoringChanges = true;
414
+
415
+ this.snapshotCurrentNetworks();
416
+
417
+ let debouncedSaveState = _.debounce(this.saveState.bind(this), 2000);
418
+ this.state.$watch('networks', debouncedSaveState, { deep: true });
419
+ }
420
+
421
+ listenToState() {
422
+ let state = this.state;
423
+
424
+ // Just before we connect to a network, make sure the BNC is saved and connected to
425
+ // it or at least trying to connect.
426
+ // Ie. Quickly creating a network and hitting connect before it's had time to
427
+ // save itself to the bouncer
428
+ state.$on('network.connecting', (event) => {
429
+ // Redirect the connection towards the bouncer with the network specific password
430
+ let network = event.network;
431
+ if (this.bnc.enabled && this.rewriteConnections) {
432
+ let netname = network.name;
433
+
434
+ let ircClient = network.ircClient;
435
+ ircClient.options.host = this.bnc.server;
436
+ ircClient.options.port = this.bnc.port;
437
+ ircClient.options.tls = this.bnc.tls;
438
+
439
+ // Only re-write the server password for non-bnc controller networks
440
+ if (this.bnc.password && !event.network.is_bnc) {
441
+ let password = `${this.bnc.username}/${netname}:${this.bnc.password}`;
442
+ ircClient.options.password = password;
443
+ }
444
+
445
+ // The SASL auth already happens on the BNC, we only use it for UI purposes in kiwi
446
+ ircClient.options.account = {};
447
+
448
+ network.connection.direct = this.bnc.direct;
449
+ ircClient.options.path = this.bnc.path;
450
+ }
451
+ });
452
+ state.$on('network.connecting', (event) => {
453
+ let controller = this.getController();
454
+ if (!controller) {
455
+ log.debug('No controller available to save network states');
456
+ return;
457
+ }
458
+
459
+ this.saveState();
460
+
461
+ let network = event.network;
462
+
463
+ if (network.connection.bncnetid) {
464
+ controller.ircClient.raw('BOUNCER connect ' + network.connection.bncnetid);
465
+ }
466
+ });
467
+ state.$on('irc.motd', (event, network) => {
468
+ network.buffers.forEach((buffer) => {
469
+ if (buffer.isChannel() && buffer.enabled && buffer.joined) {
470
+ network.ircClient.who(buffer.name);
471
+ }
472
+ });
473
+ });
474
+
475
+ state.$on('network.new', (event) => {
476
+ let network = event.network;
477
+
478
+ // Enable BOUNCER on this connection
479
+ network.ircClient.use(bouncerMiddleware());
480
+
481
+ network.editable_name = true;
482
+
483
+ // If network is attached to a bouncer do nothing
484
+ // (use the name provided by the bouncer)
485
+ if (network.connection.bncnetid) {
486
+ return;
487
+ }
488
+
489
+ let existingNet = true;
490
+
491
+ // append a number to the network name. E.g. "Network, Network2,..."
492
+ // while there is a network with that name
493
+ let currentNum = 1;
494
+ let tryNetworkName;
495
+ while (existingNet) {
496
+ if (network.name && currentNum === 1) {
497
+ // don't append the number 1 if there is a custom name
498
+ tryNetworkName = network.name;
499
+ } else {
500
+ tryNetworkName = (network.name || 'Network') + currentNum;
501
+ }
502
+
503
+ existingNet = _.find(state.networks, {
504
+ name: tryNetworkName,
505
+ });
506
+
507
+ if (!existingNet || existingNet === network) {
508
+ network.name = tryNetworkName;
509
+ break;
510
+ }
511
+
512
+ currentNum++;
513
+ }
514
+ });
515
+
516
+ state.$on('network.removed', (event) => {
517
+ let controller = this.getController();
518
+ if (!controller) {
519
+ log.debug('No controller available to save network states');
520
+ return;
521
+ }
522
+
523
+ if (event.network.connection.bncnetid) {
524
+ controller.ircClient.bnc.removeNetwork(event.network.connection.bncnetid);
525
+ }
526
+ });
527
+
528
+ state.$on('buffer.close', (event) => {
529
+ let buffer = event.buffer;
530
+ let network = event.buffer.getNetwork();
531
+ let bncnetid = network.connection.bncnetid;
532
+
533
+ let controller = this.getController();
534
+ if (!controller) {
535
+ log.debug('No controller available to save buffer states');
536
+ return;
537
+ }
538
+
539
+ if (bncnetid) {
540
+ controller.ircClient.bnc.closeBuffer(bncnetid, buffer.name);
541
+ }
542
+ });
543
+ }
544
+
545
+ parseBncCredentials(bncNetworkPassword) {
546
+ let [username, password] = bncNetworkPassword.split(':');
547
+ username = username.split('/')[0];
548
+
549
+ return [username, password];
550
+ }
551
+ }
@@ -0,0 +1,153 @@
1
+ 'kiwi public';
2
+
3
+ /** @module */
4
+
5
+ import Logger from '@/libs/Logger';
6
+ import * as Misc from '@/helpers/Misc';
7
+
8
+ const log = Logger.namespace('chathistory');
9
+
10
+ /**
11
+ * Adds the CHATHISTORY IRCv3 spec to irc-framework
12
+ */
13
+ export default function chathistoryMiddleware() {
14
+ return function middleware(client, rawEvents, parsedEvents) {
15
+ client.requestCap('draft/chathistory');
16
+ addFunctionsToClient(client);
17
+ parsedEvents.use(theMiddleware);
18
+ };
19
+
20
+ function theMiddleware(command, event, client, next) {
21
+ if (command.toLowerCase() === 'batch end chathistory' && client.chathistory) {
22
+ let target = event.params[0];
23
+ client.chathistory.batchCallbacks.resolve(target, event);
24
+ }
25
+
26
+ // This is sent as 'unknown command', its been structured this way so hopefully it
27
+ // will still work if a fail handler is ever created in irc-fw
28
+ if (event?.command?.toLowerCase() === 'fail' && event.params[0].toLowerCase() === 'chathistory') {
29
+ client.chathistory.batchCallbacks.resolve(event.params[3]);
30
+
31
+ if (event.params[1].toLowerCase() === 'invalid_target') {
32
+ // suppress invalid target errors
33
+ return;
34
+ }
35
+ }
36
+
37
+ next();
38
+ }
39
+ }
40
+
41
+ function addFunctionsToClient(client) {
42
+ const history = client.chathistory = {};
43
+
44
+ history.batchCallbacks = {
45
+ callbacks: Object.create(null),
46
+ queue: [],
47
+ queueActive: false,
48
+ add(cb, target, type, ...args) {
49
+ this.callbacks[target.toUpperCase()] = this.callbacks[target.toUpperCase()] || [];
50
+ this.callbacks[target.toUpperCase()].push(cb);
51
+ this.queue.push({
52
+ target,
53
+ type,
54
+ args,
55
+ });
56
+ if (!this.queueActive) {
57
+ this.queueNext();
58
+ }
59
+ },
60
+ resolve(target, value) {
61
+ const targetCallbacks = this.callbacks[target.toUpperCase()] || [];
62
+ const cb = targetCallbacks.shift();
63
+
64
+ if (!targetCallbacks.length) {
65
+ // last callback, cleanup callbacks
66
+ delete this.callbacks[target.toUpperCase()];
67
+ }
68
+
69
+ if (cb) {
70
+ cb(value);
71
+ } else if (history.isSupported()) {
72
+ // inspircd currently does not support chathistory
73
+ // but sends chathistory batches when joining channels
74
+ log.error('chathistory got a resolve but no associated callback');
75
+ }
76
+
77
+ this.queueNext();
78
+ },
79
+ queueNext() {
80
+ this.queueActive = true;
81
+
82
+ const nextRequest = this.queue.shift();
83
+ if (!nextRequest) {
84
+ this.queueActive = false;
85
+ return;
86
+ }
87
+
88
+ client.raw('CHATHISTORY', nextRequest.type, nextRequest.target, ...nextRequest.args);
89
+ },
90
+ };
91
+
92
+ // supports (ISUPPORT) is used by kiwibnc, the spec and unreal's implementation uses CAP
93
+ history.isSupported = () => !!client.network.supports('draft/chathistory') || client.network.cap.isEnabled('draft/chathistory');
94
+
95
+ history.before = (target, dateOrTime) => new Promise((resolve) => {
96
+ if (!history.isSupported()) {
97
+ resolve();
98
+ return;
99
+ }
100
+
101
+ history.batchCallbacks.add(resolve, target, 'BEFORE', messageReference(dateOrTime), '50');
102
+ });
103
+
104
+ history.after = (target, dateOrTime) => new Promise((resolve) => {
105
+ if (!history.isSupported()) {
106
+ resolve();
107
+ return;
108
+ }
109
+
110
+ history.batchCallbacks.add(resolve, target, 'AFTER', messageReference(dateOrTime), '50');
111
+ });
112
+
113
+ history.latest = (target, dateOrTime) => new Promise((resolve) => {
114
+ if (!history.isSupported()) {
115
+ resolve();
116
+ return;
117
+ }
118
+
119
+ history.batchCallbacks.add(resolve, target, 'LATEST', messageReference(dateOrTime), '50');
120
+ });
121
+
122
+ history.around = (target, dateOrTime) => new Promise((resolve) => {
123
+ if (!history.isSupported()) {
124
+ resolve();
125
+ return;
126
+ }
127
+
128
+ history.batchCallbacks.add(resolve, target, 'AROUND', messageReference(dateOrTime), '50');
129
+ });
130
+
131
+ history.between = (target, fromDateOrTime, toDateOrTime) => new Promise((resolve) => {
132
+ if (!history.isSupported()) {
133
+ resolve();
134
+ return;
135
+ }
136
+
137
+ const fromRef = messageReference(fromDateOrTime);
138
+ const toRef = messageReference(toDateOrTime);
139
+ history.batchCallbacks.add(resolve, target, 'BETWEEN', fromRef, toRef, 50);
140
+ });
141
+
142
+ function messageReference(inp) {
143
+ if (typeof inp === 'object') {
144
+ return 'timestamp=' + Misc.dateIso(inp);
145
+ }
146
+
147
+ if (inp === '*') {
148
+ return '*';
149
+ }
150
+
151
+ return 'msgid=' + inp;
152
+ }
153
+ }