serverless-ircd 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/.github/workflows/ci.yml +9 -4
  2. package/.gitmodules +4 -1
  3. package/CHANGELOG.md +480 -22
  4. package/README.md +244 -57
  5. package/apps/aws-stack/package.json +1 -1
  6. package/apps/aws-stack/src/aws-stack.ts +186 -18
  7. package/apps/aws-stack/tests/stack.test.ts +400 -56
  8. package/apps/cf-tcp-container/package.json +1 -1
  9. package/apps/cf-worker/package.json +1 -1
  10. package/apps/cf-worker/src/origin-allowlist.ts +99 -0
  11. package/apps/cf-worker/src/worker.ts +89 -9
  12. package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
  13. package/apps/cf-worker/tests/fixtures/web-dist/webclient/index.html +18 -0
  14. package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
  15. package/apps/cf-worker/tests/smoke.test.ts +28 -2
  16. package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
  17. package/apps/cf-worker/wrangler.test.toml +23 -0
  18. package/apps/cf-worker/wrangler.toml +56 -0
  19. package/apps/local-cli/package.json +1 -1
  20. package/apps/local-cli/src/config-loader.ts +8 -0
  21. package/apps/local-cli/src/main.ts +16 -0
  22. package/apps/local-cli/src/server.ts +1 -0
  23. package/apps/local-cli/tests/config-loader.test.ts +14 -0
  24. package/apps/web/landing/index.html +215 -0
  25. package/apps/web/package.json +28 -0
  26. package/apps/web/scripts/build.mjs +132 -0
  27. package/apps/web/src/build-env.ts +43 -0
  28. package/apps/web/src/config-schema.ts +138 -0
  29. package/apps/web/static/config.json +28 -0
  30. package/apps/web/static/config.prod.json +28 -0
  31. package/apps/web/static/config.staging.json +28 -0
  32. package/apps/web/tests/build-env.test.ts +63 -0
  33. package/apps/web/tests/build-smoke.test.ts +103 -0
  34. package/apps/web/tests/config-schema.test.ts +432 -0
  35. package/apps/web/tests/workspace.test.ts +12 -0
  36. package/apps/web/tsconfig.json +10 -0
  37. package/apps/web/upstream/.browserslistrc +3 -0
  38. package/apps/web/upstream/.editorconfig +9 -0
  39. package/apps/web/upstream/.eslintignore +3 -0
  40. package/apps/web/upstream/.eslintrc.js +90 -0
  41. package/apps/web/upstream/.github/FUNDING.yml +12 -0
  42. package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  43. package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  44. package/apps/web/upstream/.github/workflows/push.yml +26 -0
  45. package/apps/web/upstream/.github/workflows/release.yml +33 -0
  46. package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
  47. package/apps/web/upstream/.prettierrc.js +9 -0
  48. package/apps/web/upstream/.stylelintignore +5 -0
  49. package/apps/web/upstream/.stylelintrc.js +21 -0
  50. package/apps/web/upstream/LICENSE +201 -0
  51. package/apps/web/upstream/README.md +76 -0
  52. package/apps/web/upstream/babel.config.js +25 -0
  53. package/apps/web/upstream/crowdin.yml +3 -0
  54. package/apps/web/upstream/index.html +35 -0
  55. package/apps/web/upstream/jest.config.js +7 -0
  56. package/apps/web/upstream/jsdoc.json +22 -0
  57. package/apps/web/upstream/package.json +80 -0
  58. package/apps/web/upstream/scripts/pre-commit +43 -0
  59. package/apps/web/upstream/src/components/App.vue +516 -0
  60. package/apps/web/upstream/src/components/AppSettings.vue +587 -0
  61. package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
  62. package/apps/web/upstream/src/components/Avatar.vue +105 -0
  63. package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
  64. package/apps/web/upstream/src/components/BufferKey.vue +121 -0
  65. package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
  66. package/apps/web/upstream/src/components/Captcha.vue +85 -0
  67. package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
  68. package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
  69. package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
  70. package/apps/web/upstream/src/components/ChannelList.vue +345 -0
  71. package/apps/web/upstream/src/components/Container.vue +401 -0
  72. package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
  73. package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
  74. package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
  75. package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
  76. package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
  77. package/apps/web/upstream/src/components/MessageList.vue +1028 -0
  78. package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
  79. package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
  80. package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
  81. package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
  82. package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
  83. package/apps/web/upstream/src/components/Nicklist.vue +309 -0
  84. package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
  85. package/apps/web/upstream/src/components/NotConnected.vue +271 -0
  86. package/apps/web/upstream/src/components/SelfUser.vue +283 -0
  87. package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
  88. package/apps/web/upstream/src/components/ServerView.vue +126 -0
  89. package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
  90. package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
  91. package/apps/web/upstream/src/components/Sidebar.vue +429 -0
  92. package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
  93. package/apps/web/upstream/src/components/SidebarState.vue +145 -0
  94. package/apps/web/upstream/src/components/StartupError.vue +35 -0
  95. package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
  96. package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
  97. package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
  98. package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
  99. package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
  100. package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
  101. package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
  102. package/apps/web/upstream/src/components/UserBox.vue +728 -0
  103. package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
  104. package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
  105. package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
  106. package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
  107. package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
  108. package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
  109. package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
  110. package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
  111. package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
  112. package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
  113. package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
  114. package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
  115. package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
  116. package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
  117. package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
  118. package/apps/web/upstream/src/helpers/Colours.js +128 -0
  119. package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
  120. package/apps/web/upstream/src/helpers/Md5.js +193 -0
  121. package/apps/web/upstream/src/helpers/Misc.js +401 -0
  122. package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
  123. package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
  124. package/apps/web/upstream/src/libs/AudioManager.js +60 -0
  125. package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
  126. package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
  127. package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
  128. package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
  129. package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
  130. package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
  131. package/apps/web/upstream/src/libs/IPC.js +51 -0
  132. package/apps/web/upstream/src/libs/InputHandler.js +921 -0
  133. package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
  134. package/apps/web/upstream/src/libs/Logger.js +71 -0
  135. package/apps/web/upstream/src/libs/Message.js +169 -0
  136. package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
  137. package/apps/web/upstream/src/libs/MessageParser.js +249 -0
  138. package/apps/web/upstream/src/libs/Notifications.js +78 -0
  139. package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
  140. package/apps/web/upstream/src/libs/ServerSession.js +102 -0
  141. package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
  142. package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
  143. package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
  144. package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
  145. package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
  146. package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
  147. package/apps/web/upstream/src/libs/bufferTools.js +177 -0
  148. package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
  149. package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
  150. package/apps/web/upstream/src/libs/settingTools.js +31 -0
  151. package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
  152. package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
  153. package/apps/web/upstream/src/libs/state/UserState.js +89 -0
  154. package/apps/web/upstream/src/libs/state/common.js +26 -0
  155. package/apps/web/upstream/src/libs/state.js +961 -0
  156. package/apps/web/upstream/src/libs/storage/Local.js +51 -0
  157. package/apps/web/upstream/src/main.js +566 -0
  158. package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
  159. package/apps/web/upstream/src/res/configTemplates.js +373 -0
  160. package/apps/web/upstream/src/res/globalStyle.css +277 -0
  161. package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
  162. package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
  163. package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
  164. package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
  165. package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
  166. package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
  167. package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
  168. package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
  169. package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
  170. package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
  171. package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
  172. package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
  173. package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
  174. package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
  175. package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
  176. package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
  177. package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
  178. package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
  179. package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
  180. package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
  181. package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
  182. package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
  183. package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
  184. package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
  185. package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
  186. package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
  187. package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
  188. package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
  189. package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
  190. package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
  191. package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
  192. package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
  193. package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
  194. package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
  195. package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
  196. package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
  197. package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
  198. package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
  199. package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
  200. package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
  201. package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
  202. package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
  203. package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
  204. package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
  205. package/apps/web/upstream/src/res/localesList.json +27 -0
  206. package/apps/web/upstream/src/res/logo.png +0 -0
  207. package/apps/web/upstream/src/thirdparty/about.html +43 -0
  208. package/apps/web/upstream/src/thirdparty/index.js +5 -0
  209. package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
  210. package/apps/web/upstream/static/config.json +28 -0
  211. package/apps/web/upstream/static/emoticons/smile.png +0 -0
  212. package/apps/web/upstream/static/favicon.png +0 -0
  213. package/apps/web/upstream/static/highlight.mp3 +0 -0
  214. package/apps/web/upstream/static/highlight.ogg +0 -0
  215. package/apps/web/upstream/static/locales/.gitignore +2 -0
  216. package/apps/web/upstream/static/plugins/customise.html.example +10 -0
  217. package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
  218. package/apps/web/upstream/static/themes/common/base.css +1064 -0
  219. package/apps/web/upstream/static/themes/dark/theme.css +196 -0
  220. package/apps/web/upstream/static/themes/default/theme.css +92 -0
  221. package/apps/web/upstream/static/themes/elite/theme.css +248 -0
  222. package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
  223. package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
  224. package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
  225. package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
  226. package/apps/web/upstream/static/themes/sky/theme.css +107 -0
  227. package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
  228. package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
  229. package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
  230. package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
  231. package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
  232. package/apps/web/upstream/vue.config.js +150 -0
  233. package/apps/web/upstream/yarn.lock +9767 -0
  234. package/apps/web/vitest.config.ts +13 -0
  235. package/biome.json +1 -0
  236. package/docs/AWS-Deployment.md +21 -8
  237. package/docs/Cloudflare-Deployment-Guide.md +22 -6
  238. package/docs/Home.md +1 -0
  239. package/docs/PlanExtensions.md +113 -3
  240. package/docs/Release-Process.md +23 -13
  241. package/docs/Services.md +546 -0
  242. package/docs/WebClientGuide.md +536 -0
  243. package/package.json +2 -2
  244. package/packages/aws-adapter/package.json +1 -1
  245. package/packages/aws-adapter/src/aws-runtime.ts +5 -0
  246. package/packages/aws-adapter/src/cdk-table-defs.ts +6 -0
  247. package/packages/aws-adapter/src/config-loader.ts +11 -0
  248. package/packages/aws-adapter/src/connection-counter.ts +89 -0
  249. package/packages/aws-adapter/src/dynamo-services-store.ts +649 -0
  250. package/packages/aws-adapter/src/handlers/connect.ts +55 -51
  251. package/packages/aws-adapter/src/handlers/default.ts +36 -4
  252. package/packages/aws-adapter/src/handlers/index.ts +15 -0
  253. package/packages/aws-adapter/src/handlers/nlb-stream.ts +15 -0
  254. package/packages/aws-adapter/src/handlers/sweeper.ts +5 -1
  255. package/packages/aws-adapter/src/index.ts +4 -0
  256. package/packages/aws-adapter/src/stats.ts +6 -1
  257. package/packages/aws-adapter/src/tables.ts +34 -4
  258. package/packages/aws-adapter/tests/aws-harness.ts +3 -0
  259. package/packages/aws-adapter/tests/config-loader.test.ts +8 -0
  260. package/packages/aws-adapter/tests/connect.test.ts +158 -32
  261. package/packages/aws-adapter/tests/connection-counter.test.ts +127 -0
  262. package/packages/aws-adapter/tests/dynamo-services-store-dynamo.test.ts +183 -0
  263. package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +568 -0
  264. package/packages/aws-adapter/tests/handlers.test.ts +105 -3
  265. package/packages/aws-adapter/tests/tables.test.ts +6 -1
  266. package/packages/cf-adapter/package.json +1 -1
  267. package/packages/cf-adapter/src/config-loader.ts +11 -0
  268. package/packages/cf-adapter/src/connection-do.ts +112 -2
  269. package/packages/cf-adapter/src/d1-services-store.ts +703 -0
  270. package/packages/cf-adapter/src/env.ts +8 -0
  271. package/packages/cf-adapter/src/index.ts +5 -0
  272. package/packages/cf-adapter/tests/cf-harness.ts +12 -1
  273. package/packages/cf-adapter/tests/config-loader.test.ts +19 -0
  274. package/packages/cf-adapter/tests/connection-do-nickserv-d1.test.ts +128 -0
  275. package/packages/cf-adapter/tests/connection-do.test.ts +150 -2
  276. package/packages/cf-adapter/tests/d1-services-store.test.ts +582 -0
  277. package/packages/cf-adapter/tests/serialize.test.ts +1 -0
  278. package/packages/in-memory-runtime/package.json +1 -1
  279. package/packages/irc-core/package.json +1 -1
  280. package/packages/irc-core/reports/mutation/mutation.html +342 -0
  281. package/packages/irc-core/scripts/generate-build-info.mjs +26 -5
  282. package/packages/irc-core/src/commands/account-auth.ts +172 -0
  283. package/packages/irc-core/src/commands/chanserv.ts +882 -0
  284. package/packages/irc-core/src/commands/hostserv.ts +487 -0
  285. package/packages/irc-core/src/commands/index.ts +12 -0
  286. package/packages/irc-core/src/commands/join.ts +164 -8
  287. package/packages/irc-core/src/commands/markread.ts +202 -0
  288. package/packages/irc-core/src/commands/memoserv.ts +319 -0
  289. package/packages/irc-core/src/commands/mode.ts +96 -4
  290. package/packages/irc-core/src/commands/nickserv.ts +390 -0
  291. package/packages/irc-core/src/commands/oper.ts +18 -1
  292. package/packages/irc-core/src/commands/operserv.ts +346 -0
  293. package/packages/irc-core/src/commands/pre-away.ts +3 -1
  294. package/packages/irc-core/src/commands/privmsg.ts +42 -0
  295. package/packages/irc-core/src/commands/read-marker.ts +8 -8
  296. package/packages/irc-core/src/commands/registration.ts +61 -6
  297. package/packages/irc-core/src/commands/sasl.ts +18 -49
  298. package/packages/irc-core/src/commands/tagmsg.ts +41 -6
  299. package/packages/irc-core/src/commands/topic.ts +37 -0
  300. package/packages/irc-core/src/config.ts +36 -5
  301. package/packages/irc-core/src/effects.ts +56 -1
  302. package/packages/irc-core/src/ports.ts +1653 -84
  303. package/packages/irc-core/src/protocol/numerics.ts +8 -0
  304. package/packages/irc-core/src/state/channel.ts +21 -1
  305. package/packages/irc-core/src/state/connection.ts +25 -1
  306. package/packages/irc-core/src/types.ts +48 -12
  307. package/packages/irc-core/tests/batch.test.ts +15 -0
  308. package/packages/irc-core/tests/commands/chanserv.test.ts +1668 -0
  309. package/packages/irc-core/tests/commands/chathistory.test.ts +6 -0
  310. package/packages/irc-core/tests/commands/hostserv.test.ts +935 -0
  311. package/packages/irc-core/tests/commands/join.test.ts +393 -1
  312. package/packages/irc-core/tests/commands/markread.test.ts +361 -0
  313. package/packages/irc-core/tests/commands/memoserv.test.ts +654 -0
  314. package/packages/irc-core/tests/commands/mode.test.ts +381 -2
  315. package/packages/irc-core/tests/commands/nickserv.test.ts +807 -0
  316. package/packages/irc-core/tests/commands/oper.test.ts +13 -0
  317. package/packages/irc-core/tests/commands/operserv.test.ts +656 -0
  318. package/packages/irc-core/tests/commands/privmsg.test.ts +147 -0
  319. package/packages/irc-core/tests/commands/read-marker.test.ts +28 -28
  320. package/packages/irc-core/tests/commands/registration.test.ts +788 -14
  321. package/packages/irc-core/tests/commands/sasl.test.ts +185 -12
  322. package/packages/irc-core/tests/commands/server-info.test.ts +9 -5
  323. package/packages/irc-core/tests/commands/tagmsg.test.ts +73 -33
  324. package/packages/irc-core/tests/commands/topic.test.ts +94 -2
  325. package/packages/irc-core/tests/commands/unified-account.test.ts +416 -0
  326. package/packages/irc-core/tests/config.test.ts +49 -5
  327. package/packages/irc-core/tests/effects.test.ts +19 -0
  328. package/packages/irc-core/tests/message-store.test.ts +63 -0
  329. package/packages/irc-core/tests/numerics.test.ts +13 -0
  330. package/packages/irc-core/tests/parser.test.ts +109 -0
  331. package/packages/irc-core/tests/persistent-services-store.test.ts +582 -0
  332. package/packages/irc-core/tests/services-store.test.ts +1289 -0
  333. package/packages/irc-core/tests/state/channel.test.ts +3 -0
  334. package/packages/irc-server/package.json +1 -1
  335. package/packages/irc-server/src/actor.ts +71 -16
  336. package/packages/irc-server/src/dispatch.ts +94 -7
  337. package/packages/irc-server/src/routing.ts +19 -0
  338. package/packages/irc-server/tests/actor.test.ts +623 -12
  339. package/packages/irc-server/tests/dispatch.test.ts +270 -2
  340. package/packages/irc-server/tests/routing.test.ts +6 -0
  341. package/packages/irc-test-support/package.json +1 -1
  342. package/packages/irc-test-support/src/in-memory-harness.ts +29 -3
  343. package/packages/irc-test-support/src/index.ts +1 -0
  344. package/packages/irc-test-support/src/scenarios.ts +24 -2
  345. package/packages/irc-test-support/tests/in-memory-harness.test.ts +32 -0
  346. package/tools/ci-hardening/package.json +1 -1
  347. package/tools/load-test/package.json +1 -1
  348. package/tools/tcp-ws-forwarder/package.json +1 -1
  349. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +2 -2
  350. package/packages/irc-core/tests/read-marker-store.test.ts +0 -108
@@ -0,0 +1,961 @@
1
+ 'kiwi public';
2
+
3
+ import Vue from 'vue';
4
+ import _ from 'lodash';
5
+
6
+ import * as Misc from '@/helpers/Misc';
7
+ import * as TextFormatting from '@/helpers/TextFormatting';
8
+ import { configTemplates } from '@/res/configTemplates';
9
+ import NetworkState from './state/NetworkState';
10
+ import BufferState from './state/BufferState';
11
+ import UserState from './state/UserState';
12
+ import Message from './Message';
13
+
14
+ function createNewState() {
15
+ const stateObj = {
16
+ // May be set by a StatePersistence instance
17
+ persistence: null,
18
+
19
+ // Settings may be overridden via config.json
20
+ settings: configTemplates.default,
21
+ user_settings: {
22
+ },
23
+ connection: {
24
+ // disconnected / connecting / connected
25
+ status: 'connected',
26
+ sessionId: '',
27
+ },
28
+ ui: {
29
+ active_network: 0,
30
+ active_buffer: '',
31
+ last_active_buffers: [],
32
+ app_has_focus: true,
33
+ app_width: 0,
34
+ app_height: 0,
35
+ is_touch: false,
36
+ is_narrow: false,
37
+ favicon_counter: 0,
38
+ current_input: '',
39
+ input_history: [],
40
+ input_history_pos: 0,
41
+ show_advanced_tab: false,
42
+ },
43
+ networks: [],
44
+ };
45
+
46
+ const userDict = new Vue({
47
+ data() {
48
+ return {
49
+ networks: {},
50
+ };
51
+ },
52
+ /*
53
+ (network id): {
54
+ (lowercase nick): UserState,
55
+ (lowercase nick): UserState,
56
+ },
57
+ */
58
+ });
59
+
60
+ const bufferDict = new Vue({
61
+ data() {
62
+ return {
63
+ networks: {},
64
+ };
65
+ },
66
+ /*
67
+ (network id): [
68
+ BufferState,
69
+ BufferState,
70
+ ]
71
+ */
72
+ });
73
+
74
+ // Messages are seperate from the above state object to keep them from being reactive. Saves CPU
75
+ const messages = [
76
+ /* {
77
+ networkid: 1,
78
+ buffer: '#kiwiirc',
79
+ messages: [
80
+ Message,
81
+ Message
82
+ ],
83
+ }, */
84
+ ];
85
+
86
+ const availableStartups = Object.create(null);
87
+
88
+ const state = new Vue({
89
+ data: stateObj,
90
+ methods: {
91
+ // Export enough state so that it can be imported in future to resume
92
+ exportState(includeBuffers) {
93
+ let toExport = {};
94
+
95
+ if (includeBuffers) {
96
+ toExport.networks = state.networks.map((network) => {
97
+ let networkObj = {
98
+ id: network.id,
99
+ name: network.name,
100
+ connection: {
101
+ nick: network.connection.nick,
102
+ server: network.connection.server,
103
+ port: network.connection.port,
104
+ tls: network.connection.tls,
105
+ path: network.connection.path,
106
+ password: network.connection.password,
107
+ direct: network.connection.direct,
108
+ encoding: network.connection.encoding,
109
+ bncnetid: network.connection.bncnetid,
110
+ },
111
+ auto_commands: network.auto_commands,
112
+ settings: _.cloneDeep(network.settings),
113
+ username: network.username,
114
+ gecos: network.gecos,
115
+ password: network.password,
116
+ hidden: network.hidden,
117
+ is_bnc: network.is_bnc,
118
+ buffers: [],
119
+ };
120
+
121
+ networkObj.buffers = network.buffers.map((buffer) => {
122
+ let bufferObj = {
123
+ name: buffer.name,
124
+ key: buffer.key,
125
+ enabled: !!buffer.enabled,
126
+ settings: _.cloneDeep(buffer.settings),
127
+ latest_messages: [],
128
+ };
129
+
130
+ buffer.latest_messages.forEach((msg) => {
131
+ bufferObj.latest_messages.push(msg.serialise());
132
+ });
133
+
134
+ return bufferObj;
135
+ });
136
+
137
+ return networkObj;
138
+ });
139
+ }
140
+
141
+ toExport.user_settings = state.user_settings;
142
+
143
+ return JSON.stringify(toExport);
144
+ },
145
+
146
+ // Import a previously exported state to continue that state
147
+ importState(stateStr) {
148
+ let importObj = JSON.parse(stateStr);
149
+ if (importObj && importObj.networks) {
150
+ this.resetState();
151
+ importObj.networks.forEach((importNetwork) => {
152
+ let network = new NetworkState(
153
+ importNetwork.id,
154
+ state,
155
+ userDict,
156
+ bufferDict
157
+ );
158
+ network.name = importNetwork.name;
159
+ network.connection = { ...network.connection, ...importNetwork.connection };
160
+ network.auto_commands = importNetwork.auto_commands || '';
161
+ network.settings = importNetwork.settings;
162
+ // First check importNetwork.nick as this was used in older versions
163
+ // TODO: Eventually remove this importNetwork.nick check
164
+ network.nick = importNetwork.nick || importNetwork.connection.nick;
165
+ if (!network.connection.nick && importNetwork.nick) {
166
+ network.connection.nick = importNetwork.nick;
167
+ }
168
+ network.username = importNetwork.username;
169
+ network.gecos = importNetwork.gecos;
170
+ network.password = importNetwork.password;
171
+ network.hidden = importNetwork.hidden;
172
+ network.is_bnc = importNetwork.is_bnc;
173
+
174
+ this.networks.push(network);
175
+
176
+ importNetwork.buffers.forEach((impBuffer) => {
177
+ let buffer = new BufferState(
178
+ impBuffer.name,
179
+ network.id,
180
+ state,
181
+ messages
182
+ );
183
+ buffer.key = impBuffer.key;
184
+ buffer.enabled = !!impBuffer.enabled;
185
+ buffer.settings = impBuffer.settings;
186
+
187
+ let latestMessages = impBuffer.latest_messages || [];
188
+ latestMessages.forEach((msg) => {
189
+ buffer.latest_messages.push(new Message(msg));
190
+ });
191
+
192
+ network.buffers.push(buffer);
193
+ });
194
+ });
195
+ }
196
+
197
+ if (importObj && importObj.user_settings) {
198
+ this.user_settings = importObj.user_settings;
199
+ }
200
+ },
201
+
202
+ resetState() {
203
+ this.$set(this.$data, 'user_settings', {});
204
+ this.$set(this.$data, 'networks', []);
205
+ messages.splice(0);
206
+ },
207
+
208
+ setting(name, val) {
209
+ if (typeof val !== 'undefined') {
210
+ if (val === this.getSetting('settings.' + name)) {
211
+ // Remove setting from user_settings if its the default
212
+ return this.setSetting('user_settings.' + name, null);
213
+ }
214
+ // Setting any setting always goes into the user own settings space
215
+ return this.setSetting('user_settings.' + name, val);
216
+ }
217
+
218
+ // Check the user specific settings before reverting to global settings
219
+ let userSetting = this.getSetting('user_settings.' + name);
220
+ let result = typeof userSetting !== 'undefined' ?
221
+ userSetting :
222
+ this.getSetting('settings.' + name);
223
+
224
+ return result;
225
+ },
226
+
227
+ // Accept 'dotted.notation' to read a state property of any depth
228
+ getSetting(name) {
229
+ let parts = name.split('.');
230
+ let val = this.$data;
231
+
232
+ for (let i = 0; i < parts.length; i++) {
233
+ val = val[parts[i]];
234
+ if (typeof val === 'undefined') {
235
+ break;
236
+ }
237
+ }
238
+
239
+ return val;
240
+ },
241
+
242
+ // Accept 'dotted.notation' to set a state property of any depth
243
+ setSetting(name, newVal) {
244
+ let parts = name.split('.');
245
+ let val = this.$data;
246
+
247
+ for (let i = 0; i < parts.length; i++) {
248
+ let propName = parts[i];
249
+ let nextVal = val[propName];
250
+
251
+ if (i < parts.length - 1 && typeof nextVal === 'undefined') {
252
+ nextVal = this.$set(val, propName, {});
253
+ } else if (i === parts.length - 1) {
254
+ if (newVal === null) {
255
+ this.$delete(val, propName);
256
+ } else {
257
+ this.$set(val, propName, newVal);
258
+ }
259
+ }
260
+
261
+ val = nextVal;
262
+ }
263
+
264
+ return val;
265
+ },
266
+
267
+ getActiveNetwork() {
268
+ return this.getNetwork(this.ui.active_network);
269
+ },
270
+
271
+ getNetwork(networkid) {
272
+ let network = _.find(this.networks, {
273
+ id: networkid,
274
+ });
275
+
276
+ return network;
277
+ },
278
+
279
+ getNetworkFromAddress(netAddr) {
280
+ return _.find(this.networks, (net) => {
281
+ let isMatch = netAddr.toLowerCase() === net.connection.server.toLowerCase();
282
+ return isMatch;
283
+ });
284
+ },
285
+
286
+ getNetworkFromBncNetId(bncnetid) {
287
+ return _.find(this.networks, (net) => bncnetid === net.connection.bncnetid);
288
+ },
289
+
290
+ addNetwork(name, nick, serverInfo) {
291
+ // Find the current largest ID and increment it by 1
292
+ function networkidReduce(currentMax, network) {
293
+ return network.id > currentMax ?
294
+ network.id :
295
+ currentMax;
296
+ }
297
+ let networkid = serverInfo.channelId ?
298
+ parseInt(serverInfo.channelId, 10) :
299
+ _.reduce(this.networks, networkidReduce, 0) + 1;
300
+
301
+ let network = new NetworkState(networkid, state, userDict, bufferDict);
302
+ network.name = name;
303
+ network.username = serverInfo.username;
304
+ network.gecos = serverInfo.gecos;
305
+ network.password = serverInfo.account_password || '';
306
+ network.connection.password = serverInfo.password;
307
+ network.connection.nick = nick;
308
+ network.connection.server = serverInfo.server || '';
309
+ network.connection.port = serverInfo.port || 6667;
310
+ network.connection.tls = serverInfo.tls || false;
311
+ network.connection.path = serverInfo.path || '';
312
+ network.connection.direct = !!serverInfo.direct;
313
+ network.connection.path = serverInfo.path || '';
314
+ network.connection.encoding = serverInfo.encoding || 'utf8';
315
+ network.connection.bncnetid = serverInfo.bncnetid || '';
316
+
317
+ if (serverInfo.services) {
318
+ network.services = serverInfo.services;
319
+ }
320
+
321
+ this.networks.push(network);
322
+
323
+ // Add the server server buffer
324
+ this.addBuffer(network.id, '*').joined = true;
325
+
326
+ let eventObj = { network };
327
+ state.$emit('network.new', eventObj);
328
+
329
+ return network;
330
+ },
331
+
332
+ removeNetwork(networkid) {
333
+ let network = this.getNetwork(networkid);
334
+ if (!network) {
335
+ return;
336
+ }
337
+
338
+ if (network.state === 'connected') {
339
+ network.ircClient.quit();
340
+ }
341
+
342
+ while (network.buffers.length > 0) {
343
+ this.removeBuffer(network.buffers[0]);
344
+ }
345
+
346
+ let findNewNetwork = false;
347
+ if (network === this.getActiveNetwork()) {
348
+ findNewNetwork = true;
349
+ }
350
+
351
+ let idx = this.networks.indexOf(network);
352
+ this.networks.splice(idx, 1);
353
+
354
+ if (findNewNetwork) {
355
+ this.openLastActiveBuffer();
356
+ }
357
+
358
+ let eventObj = { network };
359
+ state.$emit('network.removed', eventObj);
360
+ },
361
+
362
+ getActiveBuffer() {
363
+ return this.getBufferByName(this.ui.active_network, this.ui.active_buffer);
364
+ },
365
+
366
+ setActiveBuffer(networkid, bufferName) {
367
+ if (!networkid) {
368
+ this.ui.active_network = 0;
369
+ this.ui.active_buffer = '';
370
+ } else {
371
+ if (this.settings.useBufferHistory && this.ui.active_network) {
372
+ // Keep track of last 20 viewed buffers. When closing buffers we can go back
373
+ // to one of the previous ones
374
+ this.ui.last_active_buffers.push({
375
+ networkid: this.ui.active_network,
376
+ bufferName: this.ui.active_buffer,
377
+ });
378
+
379
+ let lastActive = this.ui.last_active_buffers;
380
+ this.ui.last_active_buffers = lastActive.splice(lastActive.length - 20);
381
+ }
382
+
383
+ this.ui.active_network = networkid;
384
+ this.ui.active_buffer = bufferName;
385
+
386
+ // Clear any unread messages counters for this buffer
387
+ let buffer = this.getBufferByName(networkid, bufferName);
388
+ if (buffer && buffer.flags.unread) {
389
+ buffer.flags.unread = 0;
390
+ }
391
+
392
+ // Update the buffers last read time
393
+ if (buffer) {
394
+ buffer.markAsRead(true);
395
+ }
396
+ }
397
+ },
398
+
399
+ openLastActiveBuffer: function openLastActiveBuffer() {
400
+ // if not using buffer history, just unset the active buffer
401
+ if (!this.settings.useBufferHistory) {
402
+ this.setActiveBuffer();
403
+ return;
404
+ }
405
+
406
+ let targetNetwork;
407
+ let targetBuffer;
408
+ let lastActive = this.ui.last_active_buffers;
409
+
410
+ // Find the last buffer in our history that still exists
411
+ for (let i = lastActive.length - 1; i >= 0; i--) {
412
+ let network = this.getNetwork(lastActive[i].networkid);
413
+ if (!network) {
414
+ continue;
415
+ }
416
+
417
+ let buffer = network.bufferByName(lastActive[i].bufferName);
418
+ if (!buffer) {
419
+ continue;
420
+ }
421
+
422
+ targetNetwork = network;
423
+ targetBuffer = buffer;
424
+
425
+ // Trim the buffer history to this point
426
+ lastActive.splice(i);
427
+ break;
428
+ }
429
+
430
+ // Try to find a suitable buffer
431
+ if (!targetBuffer) {
432
+ let network = this.getActiveNetwork() || this.networks[0];
433
+ if (network) {
434
+ targetNetwork = network;
435
+ let buffer = network.buffers[1];
436
+ if (buffer) {
437
+ targetBuffer = buffer;
438
+ } else {
439
+ targetBuffer = network.serverBuffer();
440
+ }
441
+ }
442
+ }
443
+
444
+ if (targetBuffer) {
445
+ this.setActiveBuffer(targetNetwork.id, targetBuffer.name);
446
+ } else {
447
+ this.setActiveBuffer();
448
+ }
449
+ },
450
+
451
+ updateBufferLastRead(networkid, bufferName) {
452
+ let buffer = this.getBufferByName(networkid, bufferName);
453
+ if (buffer) {
454
+ buffer.last_read = Date.now();
455
+ buffer.active_timeout = null;
456
+ }
457
+ },
458
+
459
+ getOrAddBufferByName(networkid, bufferName) {
460
+ let network = this.getNetwork(networkid);
461
+ if (!network) {
462
+ return null;
463
+ }
464
+
465
+ let toMatch = bufferName.toLowerCase();
466
+ let buffer = _.find(network.buffers, (b) => b.name.toLowerCase() === toMatch);
467
+
468
+ if (!buffer) {
469
+ buffer = this.addBuffer(networkid, bufferName);
470
+ }
471
+
472
+ return buffer;
473
+ },
474
+
475
+ getBufferByName(networkid, bufferName) {
476
+ if (!bufferName) {
477
+ return null;
478
+ }
479
+
480
+ let network = this.getNetwork(networkid);
481
+ if (!network) {
482
+ return null;
483
+ }
484
+
485
+ let toMatch = bufferName.toLowerCase();
486
+ let buffer = _.find(network.buffers, (b) => b.name.toLowerCase() === toMatch);
487
+
488
+ return buffer;
489
+ },
490
+
491
+ addBuffer(networkid, bufferName) {
492
+ if (!bufferName) {
493
+ return false;
494
+ }
495
+
496
+ // If we already have this buffer, don't re-add it
497
+ let buffer = this.getBufferByName(networkid, bufferName);
498
+ if (buffer) {
499
+ return buffer;
500
+ }
501
+
502
+ // Make sure we at least we have this network
503
+ let network = this.getNetwork(networkid);
504
+ if (!network) {
505
+ return false;
506
+ }
507
+
508
+ buffer = new BufferState(bufferName, network.id, state, messages);
509
+ network.buffers.push(buffer);
510
+
511
+ let eventObj = { buffer };
512
+ state.$emit('buffer.new', eventObj);
513
+
514
+ return buffer;
515
+ },
516
+
517
+ removeBuffer(buffer) {
518
+ let isActiveBuffer = (this.getActiveBuffer() === buffer);
519
+
520
+ let network = this.getNetwork(buffer.networkid);
521
+ if (!network) {
522
+ return;
523
+ }
524
+
525
+ let eventObj = { buffer };
526
+ state.$emit('buffer.close', eventObj);
527
+
528
+ let bufferIdx = network.buffers.indexOf(buffer);
529
+ if (bufferIdx > -1) {
530
+ network.buffers.splice(bufferIdx, 1);
531
+ }
532
+
533
+ let messageIdx = _.findIndex(messages, {
534
+ networkid: network.id,
535
+ buffer: buffer.name,
536
+ });
537
+ if (messageIdx > -1) {
538
+ messages.splice(messageIdx, 1);
539
+ }
540
+
541
+ if (buffer.isChannel() && buffer.joined) {
542
+ network.ircClient.part(buffer.name);
543
+ }
544
+
545
+ // Remove the user from network state if no remaining common channels
546
+ if (buffer.isQuery()) {
547
+ let remainingBuffers = state.getBuffersWithUser(network.id, buffer.name);
548
+ if (remainingBuffers.length === 0) {
549
+ state.removeUser(network.d, {
550
+ nick: buffer.name,
551
+ });
552
+ }
553
+ }
554
+
555
+ if (isActiveBuffer) {
556
+ this.openLastActiveBuffer();
557
+ }
558
+
559
+ // Remove this buffer from any users
560
+ /* eslint-disable guard-for-in */
561
+ Object.keys(buffer.users).forEach((nick) => {
562
+ let user = buffer.users[nick];
563
+ delete user.buffers[buffer.id];
564
+ });
565
+ },
566
+
567
+ addMessage(buffer, message) {
568
+ // Some messages try to be added after a network has been removed, meaning no buffer
569
+ // will be available
570
+ if (!buffer || !buffer.getNetwork()) {
571
+ return;
572
+ }
573
+
574
+ let user = this.getUser(buffer.networkid, message.nick);
575
+ let bufferMessage = new Message(message, user);
576
+ if (user && user.ignore) {
577
+ bufferMessage.ignore = true;
578
+ }
579
+
580
+ buffer.addMessage(bufferMessage);
581
+
582
+ // Increment the unread counter if this buffer is not active
583
+ let includeAsActivity = false;
584
+ let typesForActivty = ['privmsg', 'action', 'notice', 'wallops'];
585
+ if (buffer.setting('traffic_as_activity') && message.type === 'traffic') {
586
+ typesForActivty.push('traffic');
587
+ }
588
+
589
+ if (typesForActivty.indexOf(message.type) > -1) {
590
+ includeAsActivity = true;
591
+ }
592
+
593
+ if (buffer.setting('server_as_activity') && buffer.isServer()) {
594
+ includeAsActivity = true;
595
+ }
596
+
597
+ let isActiveBuffer = (
598
+ buffer.networkid === this.ui.active_network &&
599
+ buffer.name === this.ui.active_buffer
600
+ );
601
+
602
+ let network = buffer.getNetwork();
603
+ let isNewMessage = message.time >= buffer.last_read;
604
+ let isHighlight = !network || buffer.isRaw() ?
605
+ false :
606
+ Misc.mentionsNick(bufferMessage.message, network.ircClient.user.nick);
607
+
608
+ if (message.type && message.type === 'invite') {
609
+ // Invites should be treated as highlights
610
+ isHighlight = true;
611
+ }
612
+
613
+ // Check for extra custom highlight words
614
+ let extraHighlights = (state.setting('highlights') || '').toLowerCase().split(' ');
615
+ if (!isHighlight && !buffer.isRaw() && extraHighlights.length > 0) {
616
+ extraHighlights.forEach((word) => {
617
+ if (!word) {
618
+ return;
619
+ }
620
+
621
+ if (bufferMessage.message.toLowerCase().indexOf(word) > -1) {
622
+ isHighlight = true;
623
+ }
624
+ });
625
+ }
626
+
627
+ if (!buffer.isRaw() && state.setting('teamHighlights')) {
628
+ let m = bufferMessage.message;
629
+ let patterns = {
630
+ everyone: /(^|\s)@everybody($|\s|[,.;])/,
631
+ channel: /(^|\s)@channel($|\s|[,.;])/,
632
+ here: /(^|\s)@here($|\s|[,.;])/,
633
+ };
634
+ if (m.match(patterns.everyone) || m.match(patterns.channel)) {
635
+ isHighlight = true;
636
+ }
637
+
638
+ if (m.match(patterns.here) && network && !network.away) {
639
+ isHighlight = true;
640
+ }
641
+ }
642
+
643
+ bufferMessage.isHighlight = isHighlight;
644
+
645
+ if (isNewMessage && isActiveBuffer && state.ui.app_has_focus) {
646
+ buffer.last_read = message.time;
647
+ }
648
+
649
+ // Handle buffer flags
650
+ if (isNewMessage && includeAsActivity && !isActiveBuffer && !bufferMessage.ignore) {
651
+ buffer.incrementFlag('unread');
652
+ if (isHighlight) {
653
+ buffer.flag('highlight', true);
654
+ }
655
+ }
656
+
657
+ // Handle any notifications
658
+ let settingAlertOn = buffer.setting('alert_on');
659
+ let isSelf = !network ? false : message.nick === network.nick;
660
+ if (
661
+ isNewMessage &&
662
+ settingAlertOn !== 'never' &&
663
+ message.type !== 'nick' &&
664
+ message.type !== 'mode' &&
665
+ message.type !== 'traffic' &&
666
+ !buffer.isSpecial() &&
667
+ !bufferMessage.ignore &&
668
+ !isSelf
669
+ ) {
670
+ let notifyTitle = '';
671
+ let notifyMessage = message.nick ?
672
+ message.nick + ': ' :
673
+ '';
674
+ notifyMessage += message.message;
675
+
676
+ if (isHighlight) {
677
+ notifyTitle = TextFormatting.t('notification_mentioned', { channel: buffer.name });
678
+ } else if (settingAlertOn === 'message' && !isHighlight) {
679
+ notifyTitle = buffer.name;
680
+ }
681
+
682
+ if (notifyTitle) {
683
+ this.$emit('notification.show', notifyMessage, {
684
+ title: notifyTitle,
685
+ onclick: () => {
686
+ state.setActiveBuffer(buffer.networkid, buffer.name);
687
+
688
+ // Newer webkit browser use parent.focus() while older webkit uses
689
+ // window.focus()
690
+ /* eslint-disable no-restricted-globals */
691
+ if (parent && parent.focus) {
692
+ parent.focus();
693
+ }
694
+ if (window.focus) {
695
+ window.focus();
696
+ }
697
+ },
698
+ });
699
+ }
700
+ }
701
+
702
+ if (
703
+ isActiveBuffer &&
704
+ !state.ui.app_has_focus &&
705
+ message.type !== 'traffic' &&
706
+ (
707
+ (buffer.setting('flash_title') === 'message') ||
708
+ (buffer.setting('flash_title') === 'highlight' && isHighlight)
709
+ )
710
+ ) {
711
+ this.$emit('notification.title', true);
712
+ }
713
+
714
+ this.$emit('message.new', { message: bufferMessage, buffer });
715
+ },
716
+
717
+ addMessageNoRepeat(buffer, message) {
718
+ // Add a message to the buffer only if it does not match the previous message
719
+
720
+ if (!buffer || !buffer.getNetwork()) {
721
+ // Some messages try to be added after a network has been removed,
722
+ // meaning no buffer will be available
723
+ return;
724
+ }
725
+
726
+ let lastMessage = buffer.getLastMessage();
727
+ if (lastMessage && lastMessage.message === message.message) {
728
+ return;
729
+ }
730
+
731
+ this.addMessage(buffer, message);
732
+ },
733
+
734
+ getUser(networkid, nick, usersArr_) {
735
+ let user = null;
736
+ let users = usersArr_;
737
+
738
+ if (!users) {
739
+ let network = this.getNetwork(networkid);
740
+ if (network) {
741
+ users = network.users;
742
+ }
743
+ }
744
+
745
+ if (users) {
746
+ user = users[nick.toUpperCase()];
747
+ }
748
+
749
+ return user;
750
+ },
751
+
752
+ // Modify a networks user array without hitting vues reactive system until fn()
753
+ // has completed. Good for making large changes in bulk
754
+ usersTransaction(networkid, fn) {
755
+ let network = this.getNetwork(networkid);
756
+ if (!network) {
757
+ return;
758
+ }
759
+
760
+ let users = Object.assign(Object.create(null), network.users);
761
+ fn(users);
762
+ network.users = users;
763
+ },
764
+
765
+ addUser(networkid, user, usersArr_) {
766
+ let network = null;
767
+
768
+ // Accept either a network ID or a direct network object
769
+ if (typeof networkid === 'number') {
770
+ network = this.getNetwork(networkid);
771
+ } else {
772
+ network = networkid;
773
+ }
774
+
775
+ if (!network) {
776
+ return null;
777
+ }
778
+
779
+ let usersArr = usersArr_ || network.users;
780
+ let userObj = null;
781
+
782
+ if (!usersArr[user.nick.toUpperCase()]) {
783
+ userObj = usersArr[user.nick.toUpperCase()] = new UserState(user);
784
+ } else {
785
+ // Update the existing user object with any new info we have
786
+ userObj = state.getUser(network.id, user.nick, usersArr);
787
+ _.each(user, (val, prop) => {
788
+ if (typeof val !== 'undefined') {
789
+ userObj[prop] = val;
790
+ }
791
+ });
792
+ }
793
+
794
+ return userObj;
795
+ },
796
+
797
+ removeUser(networkid, user) {
798
+ let network = this.getNetwork(networkid);
799
+ if (!network) {
800
+ return;
801
+ }
802
+
803
+ let buffers = state.getBuffersWithUser(networkid, user.nick);
804
+ buffers.forEach((buffer) => {
805
+ state.removeUserFromBuffer(buffer, user.nick);
806
+ });
807
+
808
+ this.$delete(network.users, user.nick.toUpperCase());
809
+ },
810
+
811
+ addMultipleUsersToBuffer(buffer, newUsers) {
812
+ let network = this.getNetwork(buffer.networkid);
813
+ let bufUsers = Object.assign(Object.create(null), buffer.users);
814
+
815
+ state.usersTransaction(network.id, (users) => {
816
+ newUsers.forEach((newUser) => {
817
+ let user = newUser.user;
818
+ let modes = newUser.modes;
819
+ let userObj = state.getUser(network.id, user.nick, users);
820
+
821
+ if (!userObj) {
822
+ userObj = this.addUser(network, user, users);
823
+ }
824
+ bufUsers[userObj.nick.toUpperCase()] = userObj;
825
+
826
+ // Add the buffer to the users buffer list
827
+ if (!userObj.buffers[buffer.id]) {
828
+ state.$set(userObj.buffers, buffer.id, {
829
+ modes: modes || [],
830
+ buffer: buffer,
831
+ });
832
+ } else {
833
+ userObj.buffers[buffer.id].modes = modes || [];
834
+ }
835
+ });
836
+ });
837
+
838
+ buffer.users = bufUsers;
839
+ },
840
+
841
+ addUserToBuffer(buffer, user, modes) {
842
+ let network = this.getNetwork(buffer.networkid);
843
+ let userObj = state.getUser(network.id, user.nick);
844
+
845
+ if (!userObj) {
846
+ userObj = this.addUser(network, user);
847
+ } else {
848
+ // Verify the user object is correct
849
+ _.each(user, (val, prop) => {
850
+ if (userObj[prop] !== val) {
851
+ userObj[prop] = val;
852
+ }
853
+ });
854
+ }
855
+
856
+ buffer.addUser(userObj);
857
+
858
+ // Add the buffer to the users buffer list
859
+ if (!userObj.buffers[buffer.id]) {
860
+ state.$set(userObj.buffers, buffer.id, {
861
+ modes: modes || [],
862
+ buffer: buffer,
863
+ });
864
+ } else {
865
+ state.$set(userObj.buffers[buffer.id], 'modes', modes || []);
866
+ }
867
+ },
868
+
869
+ removeUserFromBuffer(buffer, nick) {
870
+ buffer.removeUser(nick);
871
+ },
872
+
873
+ getBuffersWithUser(networkid, nick) {
874
+ let network = this.getNetwork(networkid);
875
+ if (!network) {
876
+ return [];
877
+ }
878
+
879
+ let buffers = [];
880
+ network.buffers.forEach((buffer) => {
881
+ if (buffer.hasNick(nick)) {
882
+ buffers.push(buffer);
883
+ } else if (nick === network.nick && buffer.isQuery()) {
884
+ buffers.push(buffer);
885
+ }
886
+ });
887
+
888
+ return buffers;
889
+ },
890
+
891
+ changeUserNick(networkid, oldNick, newNick) {
892
+ let network = this.getNetwork(networkid);
893
+ if (!network) {
894
+ return;
895
+ }
896
+
897
+ let user = state.getUser(network.id, oldNick);
898
+ if (!user) {
899
+ return;
900
+ }
901
+
902
+ let normalisedNew = newNick.toUpperCase();
903
+ let normalisedOld = oldNick.toUpperCase();
904
+
905
+ user.key = normalisedNew;
906
+ user.nick = newNick;
907
+
908
+ // If the nick has completely changed (ie. not just a case change) then update all
909
+ // associated buffers user lists
910
+ if (normalisedOld !== normalisedNew) {
911
+ state.$set(network.users, normalisedNew, network.users[normalisedOld]);
912
+ state.$delete(network.users, normalisedOld);
913
+
914
+ Object.keys(user.buffers).forEach((bufferId) => {
915
+ let buffer = user.buffers[bufferId].buffer;
916
+ if (!buffer.addUserBatch.queue().includes(user)) {
917
+ // The user is not in the queue to be added to the buffer
918
+ state.$set(buffer.users, normalisedNew, buffer.users[normalisedOld]);
919
+ state.$delete(buffer.users, normalisedOld);
920
+ }
921
+ });
922
+ }
923
+
924
+ let buffer = this.getBufferByName(network.id, oldNick);
925
+ if (buffer) {
926
+ buffer.rename(newNick);
927
+ }
928
+ },
929
+
930
+ getStartups() {
931
+ return availableStartups;
932
+ },
933
+ },
934
+ });
935
+
936
+ return state;
937
+ }
938
+
939
+ let instance = null;
940
+ function singleton() {
941
+ instance = instance || createNewState();
942
+ return instance;
943
+ }
944
+
945
+ singleton.create = function createState() {
946
+ return createNewState();
947
+ };
948
+
949
+ singleton.recreate = function recreateState() {
950
+ if (instance) {
951
+ instance.resetState();
952
+ instance.$destroy();
953
+ // eslint-disable-next-line
954
+ instance._watchers.splice(0, instance._watchers.length);
955
+ }
956
+
957
+ instance = createNewState();
958
+ return instance;
959
+ };
960
+
961
+ export default singleton;