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,1016 @@
1
+ <template>
2
+ <div
3
+ :class="{
4
+ 'kiwi-controlinput--focus': has_focus,
5
+ 'kiwi-controlinput--show-send': shouldShowSendButton,
6
+ 'kiwi-controlinput--show-tools': shouldShowTools,
7
+ 'kiwi-controlinput--show-tools--inline': shouldShowToolsInline,
8
+ 'kiwi-controlinput--selfuser-open': selfuser_open,
9
+ }"
10
+ class="kiwi-controlinput kiwi-theme-bg"
11
+ >
12
+ <div class="kiwi-controlinput-selfuser">
13
+ <transition name="kiwi-selfuser-trans">
14
+ <self-user
15
+ v-if="networkState==='connected'
16
+ && selfuser_open === true"
17
+ :network="network"
18
+ @close="selfuser_open=false"
19
+ />
20
+ </transition>
21
+ </div>
22
+
23
+ <div class="kiwi-controlinput-inner">
24
+ <away-status-indicator
25
+ v-if="network && network.state === 'connected'"
26
+ :network="network"
27
+ :user="network.currentUser()"
28
+ />
29
+ <div v-if="currentNick" class="kiwi-controlinput-user" @click="toggleSelfUser">
30
+ <span class="kiwi-controlinput-user-nick">{{ currentNick }}</span>
31
+ <i
32
+ :class="[selfuser_open ? 'fa-caret-down' : 'fa-caret-up']"
33
+ class="fa"
34
+ aria-hidden="true"
35
+ />
36
+ </div>
37
+ <form
38
+ class="kiwi-controlinput-form"
39
+ @submit.prevent="submitForm"
40
+ @click="maybeHidePlugins"
41
+ >
42
+ <auto-complete
43
+ v-if="autocomplete_open"
44
+ ref="autocomplete"
45
+ :items="autocomplete_items"
46
+ :filter="autocomplete_filter"
47
+ :buffer="buffer"
48
+ @temp="onAutocompleteTemp"
49
+ @selected="onAutocompleteSelected"
50
+ @cancel="onAutocompleteCancel"
51
+ />
52
+ <typing-users-list v-if="buffer.setting('share_typing')" :buffer="buffer" />
53
+ <div class="kiwi-controlinput-input-wrap">
54
+ <irc-input
55
+ ref="input"
56
+ :placeholder="$t('input_placeholder')"
57
+ class="kiwi-controlinput-input"
58
+ wrap="off"
59
+ @input="inputUpdate"
60
+ @keydown="inputKeyDown($event)"
61
+ @keyup="inputKeyUp($event)"
62
+ @click="closeToolsPlugins"
63
+ @focus="focusChanged"
64
+ @blur="focusChanged"
65
+ />
66
+ </div>
67
+ <div
68
+ v-if="shouldShowSendButton"
69
+ class="kiwi-controlinput-send-container kiwi-controlinput-tools"
70
+ >
71
+ <button
72
+ ref="sendButton"
73
+ type="submit"
74
+ class="kiwi-controlinput-button kiwi-controlinput-send fa fa-paper-plane"
75
+ />
76
+ </div>
77
+ </form>
78
+
79
+ <div
80
+ v-if="shouldShowTools"
81
+ ref="plugins"
82
+ class="kiwi-controlinput-tools kiwi-controlinput-tools-wrapper"
83
+ >
84
+ <div
85
+ v-if="!shouldShowToolsInline"
86
+ class="kiwi-controlinput-tools-expand kiwi-controlinput-button"
87
+ :class="{'kiwi-controlinput-tools-expand--closed': !showPlugins}"
88
+ @click="showPlugins=!showPlugins"
89
+ >
90
+ <i class="fa fa-bars" aria-hidden="true" />
91
+ </div>
92
+ <transition name="kiwi-plugin-ui-trans">
93
+ <div
94
+ v-if="showPlugins || shouldShowToolsInline"
95
+ class="kiwi-controlinput-tools-container"
96
+ >
97
+ <div
98
+ v-if="shouldShowColorPicker"
99
+ class="kiwi-controlinput-button"
100
+ @click.prevent="onToolClickTextStyle"
101
+ >
102
+ <i class="fa fa-adjust" aria-hidden="true" />
103
+ </div>
104
+ <div
105
+ v-if="shouldShowEmojiPicker"
106
+ class="kiwi-controlinput-button"
107
+ @click.prevent="onToolClickEmoji"
108
+ >
109
+ <i class="fa fa-smile-o" aria-hidden="true" />
110
+ </div>
111
+ <component
112
+ :is="plugin.component"
113
+ v-for="plugin in pluginUiElements"
114
+ :key="plugin.id"
115
+ :plugin-props="{
116
+ buffer: buffer,
117
+ controlinput: self,
118
+ }"
119
+ v-bind="plugin.props"
120
+ :network="network"
121
+ :buffer="buffer"
122
+ class="kiwi-controlinput-button"
123
+ />
124
+ </div>
125
+ </transition>
126
+ </div>
127
+ </div>
128
+
129
+ <div class="kiwi-controlinput-active-tool">
130
+ <component :is="active_tool" v-bind="active_tool_props" />
131
+ </div>
132
+ </div>
133
+ </template>
134
+
135
+ <script>
136
+ 'kiwi public';
137
+
138
+ import _ from 'lodash';
139
+ import * as TextFormatting from '@/helpers/TextFormatting';
140
+ import * as settingTools from '@/libs/settingTools';
141
+ import autocompleteCommands from '@/res/autocompleteCommands';
142
+ import GlobalApi from '@/libs/GlobalApi';
143
+ import * as EmojiProvider from '@/libs/EmojiProvider';
144
+ import AutoComplete from './AutoComplete';
145
+ import ToolTextStyle from './inputtools/TextStyle';
146
+ import ToolEmoji from './inputtools/Emoji';
147
+ import SelfUser from './SelfUser';
148
+ import AwayStatusIndicator from './AwayStatusIndicator';
149
+ import TypingUsersList from './TypingUsersList';
150
+
151
+ export default {
152
+ components: {
153
+ AutoComplete,
154
+ AwayStatusIndicator,
155
+ SelfUser,
156
+ TypingUsersList,
157
+ },
158
+ props: ['network', 'buffer', 'sidebarState'],
159
+ data() {
160
+ return {
161
+ self: this,
162
+ selfuser_open: false,
163
+ autocomplete_open: false,
164
+ autocomplete_items: [],
165
+ autocomplete_filter: '',
166
+ // Not filtering through the autocomplete list means that the entire word is put
167
+ // in place when cycling through items. Just as with traditional IRC clients when
168
+ // tabbing through nicks.
169
+ // When filtering through the list, we keep typing more of the word we want as the
170
+ // autocomplete list filters its results to show us the relevant items, not replacing
171
+ // the current word until we select an item.
172
+ autocomplete_filtering: true,
173
+ active_tool: null,
174
+ active_tool_props: {},
175
+ pluginUiElements: GlobalApi.singleton().controlInputPlugins,
176
+ showPlugins: false,
177
+ current_input_value: '',
178
+ has_focus: false,
179
+ keep_focus: false,
180
+ };
181
+ },
182
+ computed: {
183
+ currentNick() {
184
+ let activeNetwork = this.$state.getActiveNetwork();
185
+ return activeNetwork ?
186
+ activeNetwork.nick :
187
+ '';
188
+ },
189
+ networkState() {
190
+ let activeNetwork = this.$state.getActiveNetwork();
191
+ return activeNetwork ?
192
+ activeNetwork.state :
193
+ '';
194
+ },
195
+ shouldShowSendButton() {
196
+ return this.$state.ui.is_touch || this.$state.setting('showSendButton');
197
+ },
198
+ shouldShowEmojiPicker() {
199
+ return (
200
+ this.$state.setting('forceShowEmojiPicker') ||
201
+ (this.$state.setting('showEmojiPicker') && !this.$state.ui.is_touch)
202
+ );
203
+ },
204
+ shouldShowColorPicker() {
205
+ return this.$state.setting('showColorPicker');
206
+ },
207
+ shouldShowTools() {
208
+ if (
209
+ this.pluginUiElements.length ||
210
+ this.shouldShowEmojiPicker ||
211
+ this.shouldShowColorPicker
212
+ ) {
213
+ return true;
214
+ }
215
+ return false;
216
+ },
217
+ shouldShowToolsInline() {
218
+ let toolCount = this.pluginUiElements.length;
219
+ if (this.shouldShowEmojiPicker) {
220
+ toolCount++;
221
+ }
222
+ if (this.shouldShowColorPicker) {
223
+ toolCount++;
224
+ }
225
+
226
+ if (toolCount === 1) {
227
+ // No point showing a menu button to replace one item
228
+ return true;
229
+ }
230
+
231
+ // Button size (36px)
232
+ // Total buttons width < 1/5 screen width
233
+ return (36 * toolCount < this.$state.ui.app_width / 5);
234
+ },
235
+ history() {
236
+ if (this.$state.setting('buffers.shared_input')) {
237
+ return this.$state.ui.input_history;
238
+ }
239
+ return this.buffer.input_history;
240
+ },
241
+ history_pos: {
242
+ get() {
243
+ if (this.$state.setting('buffers.shared_input')) {
244
+ return this.$state.ui.input_history_pos;
245
+ }
246
+ return this.buffer.input_history_pos;
247
+ },
248
+ set(newVal) {
249
+ if (this.$state.setting('buffers.shared_input')) {
250
+ this.$state.ui.input_history_pos = newVal;
251
+ } else {
252
+ this.buffer.input_history_pos = newVal;
253
+ }
254
+ },
255
+ },
256
+ },
257
+ watch: {
258
+ history_pos(newVal) {
259
+ let val = this.history[this.history_pos];
260
+ this.$refs.input.setValue(val || '');
261
+ },
262
+ buffer() {
263
+ if (!this.$state.setting('buffers.shared_input')) {
264
+ this.inputRestore();
265
+ }
266
+
267
+ this.autocomplete_open = false;
268
+ },
269
+ },
270
+ created() {
271
+ this.typingTimer = null;
272
+ this.lastTypingTime = 0;
273
+
274
+ this.listen(this.$state, 'document.keydown', (ev) => {
275
+ // No input box currently? Nothing to shift focus to
276
+ if (!this.$refs.input) {
277
+ return;
278
+ }
279
+
280
+ // If we're copying text, don't shift focus
281
+ if (ev.ctrlKey || ev.altKey || ev.metaKey) {
282
+ return;
283
+ }
284
+
285
+ // shift key on its own, don't shift focus we handle this below
286
+ if (ev.keyCode === 16) {
287
+ return;
288
+ }
289
+
290
+ // Firefox 66.0.3 on linux isn't consistently setting ev.ctrlKey === true when only
291
+ // the control key is pressed so add a specific check for this
292
+ // TODO: Remove this check once ff 66.0.3 is no longer around
293
+ if (ev.keyCode === 17) {
294
+ return;
295
+ }
296
+
297
+ // If we are using shift and arrow keys, don't shift focus
298
+ // this allows users to adjust text selection
299
+ let arrowKeyCodes = [37, 38, 39, 40];
300
+ if (ev.shiftKey && arrowKeyCodes.indexOf(ev.keyCode) !== -1) {
301
+ return;
302
+ }
303
+
304
+ // If we're typing into an input box somewhere, ignore
305
+ let elements = ['input', 'select', 'textarea', 'button', 'datalist', 'keygen'];
306
+ let doNotRefocus =
307
+ elements.indexOf(ev.target.tagName.toLowerCase()) > -1 ||
308
+ ev.target.getAttribute('contenteditable');
309
+
310
+ if (doNotRefocus) {
311
+ return;
312
+ }
313
+
314
+ this.$refs.input.focus();
315
+ });
316
+
317
+ this.listen(this.$state, 'input.insertnick', (nick) => {
318
+ if (!this.$refs.input) {
319
+ return;
320
+ }
321
+
322
+ let val = nick;
323
+ if (this.current_input_value === '') {
324
+ val += ': ';
325
+ } else {
326
+ val += ' ';
327
+ }
328
+
329
+ this.$refs.input.insertText(val);
330
+ });
331
+
332
+ this.listen(this.$state, 'input.tool', (toolComponent) => {
333
+ this.toggleInputTool(toolComponent);
334
+ });
335
+ },
336
+ mounted() {
337
+ this.inputRestore();
338
+ },
339
+ methods: {
340
+ inputUpdate(val) {
341
+ this.current_input_value = val;
342
+
343
+ if (this.$state.setting('buffers.shared_input')) {
344
+ this.$state.ui.current_input = val;
345
+ } else {
346
+ this.buffer.current_input = val;
347
+ }
348
+
349
+ this.maybeHidePlugins();
350
+ },
351
+ inputRestore() {
352
+ let currentInput = this.$state.setting('buffers.shared_input') ?
353
+ this.$state.ui.current_input :
354
+ this.buffer.current_input;
355
+
356
+ this.$refs.input.reset(currentInput, this.keep_focus);
357
+ this.$refs.input.selectionToEnd();
358
+ },
359
+ toggleSelfUser() {
360
+ if (this.networkState === 'connected') {
361
+ this.selfuser_open = !this.selfuser_open;
362
+ }
363
+ },
364
+ maybeHidePlugins() {
365
+ // Save some space if we're typing on a small screen
366
+ if (this.$state.ui.app_width < 500) {
367
+ this.showPlugins = false;
368
+ }
369
+ },
370
+ onToolClickTextStyle() {
371
+ this.toggleInputTool(ToolTextStyle);
372
+ },
373
+ onToolClickEmoji() {
374
+ this.toggleInputTool(ToolEmoji);
375
+ },
376
+ closeToolsPlugins() {
377
+ this.showPlugins = false;
378
+ this.closeInputTool();
379
+ },
380
+ closeInputTool() {
381
+ this.active_tool = null;
382
+ },
383
+ toggleInputTool(tool) {
384
+ if (!tool || this.active_tool === tool) {
385
+ this.active_tool = null;
386
+ } else {
387
+ this.active_tool_props = {
388
+ buffer: this.buffer,
389
+ ircinput: this.$refs.input,
390
+ };
391
+ this.active_tool = tool;
392
+ }
393
+ },
394
+ toggleBold() {
395
+ this.$refs.input.toggleBold();
396
+ },
397
+ toggleItalic() {
398
+ this.$refs.input.toggleItalic();
399
+ },
400
+ toggleUnderline() {
401
+ this.$refs.input.toggleUnderline();
402
+ },
403
+ onAutocompleteCancel() {
404
+ this.autocomplete_open = false;
405
+ },
406
+ onAutocompleteTemp(selectedValue, selectedItem) {
407
+ if (!this.autocomplete_filtering) {
408
+ this.$refs.input.setCurrentWord(selectedValue);
409
+ }
410
+ },
411
+ onAutocompleteSelected(selectedValue, selectedItem) {
412
+ let word = selectedValue;
413
+ if (word.length > 0) {
414
+ this.$refs.input.setCurrentWord(word);
415
+ }
416
+ this.autocomplete_open = false;
417
+ },
418
+ inputKeyDown(event) {
419
+ let meta = false;
420
+
421
+ if (navigator.appVersion.indexOf('Mac') !== -1) {
422
+ meta = event.metaKey;
423
+ } else {
424
+ meta = event.ctrlKey;
425
+ }
426
+
427
+ // If autocomplete has handled the event, don't also handle it here
428
+ if (this.autocomplete_open && this.$refs.autocomplete.handleOnKeyDown(event)) {
429
+ return;
430
+ }
431
+
432
+ // When not filtering, select the current autocomplete item so that we can type any
433
+ // character directly after a nick
434
+ if (this.autocomplete_open && !this.autocomplete_filtering) {
435
+ this.$refs.autocomplete.selectCurrentItem();
436
+ }
437
+
438
+ if (event.keyCode === 13 && (
439
+ (event.altKey && !event.shiftKey && !event.metaKey && !event.ctrlKey) ||
440
+ (event.shiftKey && !event.altKey && !event.metaKey && !event.ctrlKey)
441
+ )) {
442
+ // Add new line when shift+enter or alt+enter is pressed
443
+ event.preventDefault();
444
+ this.$refs.input.insertText('\n');
445
+ } else if (event.keyCode === 13) {
446
+ // Send message when enter is pressed
447
+ event.preventDefault();
448
+ this.submitForm();
449
+ } else if (event.keyCode === 32) {
450
+ // Hitting space after just typing an ascii emoji will get it replaced with
451
+ // its image
452
+ if (this.$state.setting('buffers.show_emoticons')) {
453
+ let currentWord = this.$refs.input.getCurrentWord(true);
454
+ let emojis = EmojiProvider.getEmojis(currentWord.word);
455
+ if (emojis.length) {
456
+ event.preventDefault();
457
+ this.$refs.input.setCurrentWord('', false, true);
458
+ this.$refs.input.addImg(
459
+ emojis[0].ascii,
460
+ emojis[0].url,
461
+ emojis[0].imgProps,
462
+ );
463
+ }
464
+ }
465
+ } else if (event.keyCode === 38) {
466
+ // Up
467
+ if (this.$refs.input.getCaretIdx() > 0) {
468
+ // not at the start of input, allow normal input behaviour
469
+ return;
470
+ }
471
+
472
+ event.preventDefault();
473
+ this.historyBack();
474
+ } else if (event.keyCode === 40) {
475
+ // Down
476
+ let end = this.$refs.input.getRawText().replace(/\r?\n/g, '').length;
477
+ if (this.$refs.input.getCaretIdx() < end) {
478
+ // not at the end of input, allow normal input behaviour
479
+ return;
480
+ }
481
+ event.preventDefault();
482
+ this.historyForward();
483
+ this.$nextTick(() => {
484
+ this.$refs.input.selectionToEnd();
485
+ });
486
+ } else if (
487
+ event.keyCode === 9
488
+ && !event.shiftKey
489
+ && !event.altKey
490
+ && !event.metaKey
491
+ && !event.ctrlKey
492
+ ) {
493
+ // Tab and no other keys as tab+other is often a keyboard shortcut
494
+ // Tab key was just pressed, start general auto completion
495
+ let currentWord = this.$refs.input.getCurrentWord();
496
+ let currentToken = currentWord.word.substr(0, currentWord.position);
497
+ let inputText = this.$refs.input.getRawText();
498
+
499
+ let items = [];
500
+ if (inputText.indexOf('/set') === 0) {
501
+ items = this.buildAutoCompleteItems({ settings: true });
502
+ } else {
503
+ items = this.buildAutoCompleteItems({
504
+ users: true,
505
+ buffers: true,
506
+ });
507
+ }
508
+
509
+ this.openAutoComplete(items);
510
+ this.autocomplete_filter = currentToken;
511
+
512
+ // Disable filtering so that tabbing cycles through words more like
513
+ // traditional IRC clients.
514
+ this.autocomplete_filtering = false;
515
+ event.preventDefault();
516
+ } else if (meta && event.keyCode === 75) {
517
+ // meta + k
518
+ this.toggleInputTool(ToolTextStyle);
519
+ event.preventDefault();
520
+ } else if (meta && event.keyCode === 66) {
521
+ // meta + b
522
+ this.toggleBold();
523
+ event.preventDefault();
524
+ } else if (meta && event.keyCode === 73) {
525
+ // meta + i
526
+ this.toggleItalic();
527
+ event.preventDefault();
528
+ } else if (meta && event.keyCode === 85) {
529
+ // meta + u
530
+ this.toggleUnderline();
531
+ event.preventDefault();
532
+ }
533
+ },
534
+ inputKeyUp(event) {
535
+ let inputVal = this.$refs.input.getRawText();
536
+ let currentWord = this.$refs.input.getCurrentWord();
537
+ let currentToken = currentWord.word.substr(0, currentWord.position);
538
+ let autocompleteTokens = this.$state.setting('autocompleteTokens');
539
+
540
+ if (event.keyCode === 27 && this.autocomplete_open) {
541
+ this.autocomplete_open = false;
542
+ } else if (this.autocomplete_open && currentToken === '') {
543
+ this.autocomplete_open = false;
544
+ } else if (this.autocomplete_open) {
545
+ // @ is a shortcut to open the nicklist autocomplete. It's not part
546
+ // of the nick so strip it out before passing currentToken to the
547
+ // filter.
548
+ if (currentToken[0] === '@') {
549
+ currentToken = currentToken.substr(1);
550
+ }
551
+ } else if (currentToken === '@' && autocompleteTokens.includes('@')) {
552
+ // Just typed @ so start the nick auto completion
553
+ this.openAutoComplete(this.buildAutoCompleteItems({ users: true }));
554
+ this.autocomplete_filtering = true;
555
+ } else if (inputVal === '/' && autocompleteTokens.includes('/')) {
556
+ // Just typed / so start the command auto completion
557
+ this.openAutoComplete(this.buildAutoCompleteItems({ commands: true }));
558
+ this.autocomplete_filtering = true;
559
+ } else if (currentToken === '#' && autocompleteTokens.includes('#')) {
560
+ // Just typed # so start the command auto completion
561
+ this.openAutoComplete(this.buildAutoCompleteItems({ buffers: true }));
562
+ this.autocomplete_filtering = true;
563
+ } else if (
564
+ event.keyCode === 9
565
+ && !event.shiftKey
566
+ && !event.altKey
567
+ && !event.metaKey
568
+ && !event.ctrlKey
569
+ ) {
570
+ // Tab and no other keys as tab+other is often a keyboard shortcut
571
+ event.preventDefault();
572
+ } else if (!event.key.match(/^(Shift|Control|Alt|Enter)/)) {
573
+ if (inputVal[0] === '/') {
574
+ // Don't send typing status for commands
575
+ return;
576
+ }
577
+
578
+ if (inputVal.trim()) {
579
+ this.startTyping();
580
+ } else {
581
+ this.stopTyping(true);
582
+ }
583
+ }
584
+
585
+ if (this.autocomplete_open && this.autocomplete_filtering) {
586
+ this.autocomplete_filter = currentToken;
587
+ }
588
+ },
589
+ submitForm() {
590
+ let rawInput = this.$refs.input.getValue();
591
+ if (!rawInput) {
592
+ if (!this.has_focus && this.keep_focus) {
593
+ // Maybe triggered by the send button on empty input,
594
+ // put focus back into the input
595
+ this.$refs.input.focus();
596
+ }
597
+ return;
598
+ }
599
+
600
+ let ircText = this.$refs.input.buildIrcText();
601
+ this.$state.$emit('input.raw', ircText);
602
+
603
+ this.historyAdd(rawInput);
604
+
605
+ this.$refs.input.reset('', this.keep_focus);
606
+
607
+ this.stopTyping(false);
608
+ },
609
+ historyAdd(rawInput) {
610
+ // Add to history, keeping the history trimmed to the last 50 entries
611
+ this.history.push(rawInput);
612
+ this.history.splice(0, this.history.length - 50);
613
+ this.history_pos = this.history.length;
614
+ },
615
+ historyBack() {
616
+ let rawText = this.$refs.input.getRawText();
617
+ let rawInput = this.$refs.input.getValue();
618
+ if (rawText.trim() && this.history_pos === this.history.length) {
619
+ this.historyAdd(rawInput);
620
+ this.history_pos--;
621
+ }
622
+ if (this.history_pos > 0) {
623
+ this.history_pos--;
624
+ }
625
+ },
626
+ historyForward() {
627
+ // Purposely let history_pos go 1 index beyond the history length
628
+ // so that we can detect if we're not currently using a history value
629
+ if (this.history_pos < this.history.length) {
630
+ this.history_pos++;
631
+ }
632
+ },
633
+ focusChanged(event) {
634
+ this.has_focus = event.type === 'focus';
635
+
636
+ // iOS does not populate relatedTarget when the relatedTarget is the send button
637
+ // leaving it impossible to detect if returning focus is required
638
+ if (
639
+ event.type === 'blur' &&
640
+ event.relatedTarget &&
641
+ event.relatedTarget === this.$refs.sendButton
642
+ ) {
643
+ // new target is the send button, keep focus on reset
644
+ return;
645
+ }
646
+ this.keep_focus = event.type === 'focus';
647
+ },
648
+ openAutoComplete(items) {
649
+ if (this.$state.setting('showAutocomplete')) {
650
+ this.autocomplete_items = items;
651
+ this.autocomplete_open = true;
652
+ }
653
+ },
654
+ buildAutoCompleteItems(_opts) {
655
+ let opts = _opts || {};
656
+ let list = [];
657
+
658
+ if (opts.users) {
659
+ let userList = _.values(this.buffer.users).map((user) => {
660
+ let item = {
661
+ text: user.nick,
662
+ type: 'user',
663
+ };
664
+ return item;
665
+ });
666
+
667
+ if (this.buffer.isQuery()) {
668
+ userList.push({
669
+ text: this.buffer.name,
670
+ type: 'user',
671
+ });
672
+ }
673
+
674
+ list = list.concat(userList);
675
+ }
676
+
677
+ if (opts.buffers) {
678
+ let bufferList = [];
679
+ this.network.buffers.forEach((buffer) => {
680
+ if (buffer.isChannel()) {
681
+ bufferList.push({
682
+ text: buffer.name,
683
+ type: 'buffer',
684
+ });
685
+ }
686
+ });
687
+
688
+ list = list.concat(bufferList);
689
+ }
690
+
691
+ if (opts.commands) {
692
+ let commandList = [];
693
+ autocompleteCommands.forEach((command) => {
694
+ // allow descriptions to be translation keys or static strings
695
+ let desc = command.description.indexOf('locale_id_') === 0 ?
696
+ TextFormatting.t(command.description.substr(10)) :
697
+ command.description;
698
+ commandList.push({
699
+ text: '/' + command.command,
700
+ description: desc,
701
+ type: 'command',
702
+ // Each alias needs the / command prefix adding
703
+ alias: (command.alias || []).map((c) => '/' + c),
704
+ });
705
+ });
706
+
707
+ list = list.concat(commandList);
708
+ }
709
+
710
+ if (opts.settings) {
711
+ let out = {};
712
+ let base = [];
713
+ settingTools.buildTree(out, base, this.$state.getSetting('settings'), false);
714
+ settingTools.buildTree(out, base, this.$state.getSetting('user_settings'), true);
715
+
716
+ let settingList = [];
717
+ Object.keys(out).forEach((setting) => {
718
+ settingList.push({
719
+ text: setting,
720
+ type: 'setting',
721
+ });
722
+ });
723
+
724
+ list = list.concat(settingList);
725
+ }
726
+
727
+ return list;
728
+ },
729
+ startTyping() {
730
+ if (!this.network.ircClient.network.cap.isEnabled('message-tags')) {
731
+ return;
732
+ }
733
+ if (!this.buffer || !this.buffer.shouldShareTyping()) {
734
+ return;
735
+ }
736
+
737
+ if (this.typingTimer) {
738
+ clearTimeout(this.typingTimer);
739
+ this.typingTimer = null;
740
+ }
741
+ this.typingTimer = setTimeout(() => this.stopTyping(true), 3000);
742
+
743
+ if (Date.now() < this.lastTypingTime + 3000) {
744
+ return;
745
+ }
746
+
747
+ this.network.ircClient.typing.start(this.buffer.name);
748
+
749
+ this.lastTypingTime = Date.now();
750
+ },
751
+ stopTyping(sendStop) {
752
+ if (!this.network.ircClient.network.cap.isEnabled('message-tags')) {
753
+ return;
754
+ }
755
+ if (!this.buffer || !this.buffer.shouldShareTyping()) {
756
+ return;
757
+ }
758
+
759
+ if (this.typingTimer) {
760
+ clearTimeout(this.typingTimer);
761
+ this.typingTimer = null;
762
+ this.lastTypingTime = 0;
763
+ }
764
+
765
+ this.$refs.input.getRawText().trim() ?
766
+ this.network.ircClient.typing.pause(this.buffer.name) :
767
+ this.network.ircClient.typing.stop(this.buffer.name, sendStop);
768
+ },
769
+ },
770
+ };
771
+ </script>
772
+
773
+ <style lang="less">
774
+
775
+ .kiwi-controlinput {
776
+ z-index: 999;
777
+ position: relative;
778
+ border-top: 1px solid;
779
+ }
780
+
781
+ .kiwi-controlinput,
782
+ .kiwi-controlinput-inner {
783
+ padding: 0;
784
+ box-sizing: border-box;
785
+ transition: width 0.2s;
786
+ transition-delay: 0.2s;
787
+ }
788
+
789
+ .kiwi-controlinput-inner {
790
+ display: flex;
791
+ position: relative;
792
+ height: 100%;
793
+
794
+ .kiwi-awaystatusindicator {
795
+ margin-top: 16px;
796
+ margin-left: 10px;
797
+ margin-right: -2px;
798
+ }
799
+ }
800
+
801
+ .kiwi-controlinput-user {
802
+ height: 100%;
803
+ padding-left: 10px;
804
+ font-weight: bold;
805
+ text-align: center;
806
+ cursor: pointer;
807
+ line-height: 40px;
808
+ transition: width 0.2s;
809
+ transition-delay: 0.1s;
810
+
811
+ > i {
812
+ font-size: 120%;
813
+ margin-left: 8px;
814
+ }
815
+ }
816
+
817
+ .kiwi-controlinput--selfuser-open {
818
+ .kiwi-controlinput-user {
819
+ width: 296px;
820
+ transition: width 0.2s;
821
+ transition-delay: 0.1s;
822
+ }
823
+
824
+ .kiwi-controlinput-selfuser {
825
+ width: 324px;
826
+ max-height: 300px;
827
+ opacity: 1;
828
+ }
829
+ }
830
+
831
+ .kiwi-controlinput-form {
832
+ flex: 1;
833
+ overflow: hidden;
834
+ display: flex;
835
+ box-sizing: border-box;
836
+ }
837
+
838
+ .kiwi-controlinput-input {
839
+ text-align: left;
840
+ height: 100%;
841
+ outline: none;
842
+ border: none;
843
+ }
844
+
845
+ .kiwi-controlinput-input-wrap {
846
+ width: 100%;
847
+ height: 100%;
848
+ box-sizing: border-box;
849
+ overflow: hidden;
850
+ margin: 0 10px;
851
+ }
852
+
853
+ .kiwi-controlinput-active-tool {
854
+ position: absolute;
855
+ bottom: calc(100% + 1px);
856
+ right: 74px;
857
+ left: 0;
858
+ z-index: 1;
859
+ }
860
+
861
+ .kiwi-controlinput-selfuser {
862
+ position: absolute;
863
+ bottom: 0;
864
+ z-index: 10;
865
+ left: 0;
866
+ max-height: 0;
867
+ width: 324px;
868
+ box-sizing: border-box;
869
+ border-radius: 0 6px 0 0;
870
+ opacity: 0;
871
+ border-top: 1px solid;
872
+ border-right: 1px solid;
873
+ overflow: hidden;
874
+ }
875
+
876
+ .kiwi-selfuser-trans-enter,
877
+ .kiwi-selfuser-trans-leave-to {
878
+ opacity: 0;
879
+ height: 0;
880
+ }
881
+
882
+ .kiwi-selfuser-trans-enter-to,
883
+ .kiwi-selfuser-trans-leave {
884
+ opacity: 1;
885
+ }
886
+
887
+ .kiwi-selfuser-trans-enter-active,
888
+ .kiwi-selfuser-trans-leave-active {
889
+ transition: all 0.4s;
890
+ }
891
+
892
+ .kiwi-controlinput-tools {
893
+ border-radius: 8px;
894
+ padding: 1px;
895
+ height: 36px;
896
+ }
897
+
898
+ .kiwi-controlinput-tools-expand > i {
899
+ transition: transform 0.2s;
900
+ }
901
+
902
+ .kiwi-controlinput-tools-expand--closed > i {
903
+ transform: rotateZ(180deg);
904
+ }
905
+
906
+ .kiwi-controlinput-send {
907
+ padding: 1px 6px;
908
+ }
909
+
910
+ .kiwi-controlinput--show-send.kiwi-controlinput--show-tools {
911
+ // The send button and tools are visible, merge their borders
912
+ .kiwi-controlinput-tools-wrapper {
913
+ border-radius: 0 8px 8px 0;
914
+ padding: 1px 1px 1px 0;
915
+ }
916
+
917
+ .kiwi-controlinput-send-container {
918
+ border-radius: 8px 0 0 8px;
919
+ padding: 1px 0 1px 1px;
920
+ }
921
+ }
922
+
923
+ .kiwi-controlinput-tools-container {
924
+ display: flex;
925
+ flex-wrap: wrap-reverse;
926
+ flex-direction: row-reverse;
927
+ padding: 1px;
928
+ border-radius: 8px;
929
+ position: absolute;
930
+ bottom: calc(100% + 1px);
931
+ top: auto;
932
+ right: 0;
933
+ width: 72px;
934
+ }
935
+
936
+ .kiwi-controlinput--show-tools--inline {
937
+ .kiwi-controlinput-tools-container {
938
+ flex-direction: row;
939
+ position: relative;
940
+ width: auto;
941
+ top: 0;
942
+ padding: 0;
943
+ }
944
+
945
+ .kiwi-controlinput-active-tool {
946
+ right: 0;
947
+ }
948
+ }
949
+
950
+ .kiwi-controlinput-button {
951
+ display: inline-block;
952
+ width: 34px;
953
+ height: 34px;
954
+ margin: 1px;
955
+ text-align: center;
956
+ border-radius: 8px;
957
+ box-sizing: border-box;
958
+ cursor: pointer;
959
+
960
+ i {
961
+ font-size: 20px;
962
+ line-height: 32px;
963
+ margin: 0;
964
+ }
965
+ }
966
+
967
+ .kiwi-plugin-ui-trans-enter,
968
+ .kiwi-plugin-ui-trans-leave-to {
969
+ right: -100%;
970
+ }
971
+
972
+ .kiwi-plugin-ui-trans-enter-to,
973
+ .kiwi-plugin-ui-trans-leave {
974
+ right: 0;
975
+ }
976
+
977
+ .kiwi-plugin-ui-trans-enter-active,
978
+ .kiwi-plugin-ui-trans-leave-active {
979
+ transition: right 0.2s;
980
+ }
981
+
982
+ @media screen and (max-width: 500px) {
983
+ .kiwi-controlinput-user-nick {
984
+ display: none;
985
+ }
986
+
987
+ .kiwi-controlinput-user > i {
988
+ margin-left: 0;
989
+ }
990
+ }
991
+
992
+ @media screen and (max-width: 769px) {
993
+ .kiwi-controlinput--selfuser-open .kiwi-controlinput-selfuser {
994
+ width: 100%;
995
+ border-radius: 0;
996
+ border-right: 0;
997
+ }
998
+
999
+ .kiwi-wrap--statebrowser-drawopen .kiwi-controlinput {
1000
+ z-index: 0;
1001
+ }
1002
+
1003
+ // hide the control input on narrow screens when the self user box is open
1004
+ .kiwi-controlinput--selfuser-open .kiwi-controlinput-inner {
1005
+ display: none;
1006
+ }
1007
+
1008
+ .kiwi-controlinput-tools-container {
1009
+ width: 34px;
1010
+ }
1011
+
1012
+ .kiwi-controlinput-active-tool {
1013
+ right: 36px;
1014
+ }
1015
+ }
1016
+ </style>