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,20 @@
1
+ export class SoundBleep {
2
+ constructor() {
3
+ this.audio = document.createElement('audio');
4
+ let source = document.createElement('source');
5
+
6
+ if (this.audio.canPlayType('audio/mpeg;')) {
7
+ source.type = 'audio/mpeg';
8
+ source.src = 'static/highlight.mp3';
9
+ } else {
10
+ source.type = 'audio/ogg';
11
+ source.src = 'static/highlight.ogg';
12
+ }
13
+
14
+ this.audio.appendChild(source);
15
+ }
16
+
17
+ play() {
18
+ this.audio.play();
19
+ }
20
+ }
@@ -0,0 +1,90 @@
1
+ 'kiwi public';
2
+
3
+ import _ from 'lodash';
4
+
5
+ export default class StatePersistence {
6
+ constructor(storageKey, state, storage, logger) {
7
+ this.storageKey = storageKey;
8
+ this.state = state;
9
+ this.storage = storage;
10
+ this.logger = logger;
11
+ this.isPersisting = false;
12
+ this.includeBuffers = true;
13
+ this.watcher = null;
14
+
15
+ this.state.persistence = this;
16
+ }
17
+
18
+ async loadStateIfExists() {
19
+ if (!this.storageKey) {
20
+ return;
21
+ }
22
+
23
+ // If we have networks from a previous state, launch directly into it
24
+ let storedState = await this.storage.get(this.storageKey);
25
+ if (storedState) {
26
+ if (this.logger) {
27
+ this.logger('Importing state', storedState);
28
+ }
29
+
30
+ this.state.importState(storedState);
31
+ }
32
+ }
33
+
34
+ stopWatchingState() {
35
+ if (this.watcher) {
36
+ this.watcher();
37
+ this.watcher = null;
38
+ }
39
+ }
40
+
41
+ watchStateForChanges() {
42
+ if (!this.storageKey) {
43
+ return;
44
+ }
45
+
46
+ // Throttle saving the state into storage so we don't thrash the disk
47
+ let debouncedSaveState = _.debounce(() => {
48
+ if (this.logger) {
49
+ this.logger('State updated, setting localStorage');
50
+ }
51
+
52
+ this.storage.set(this.storageKey, this.state.exportState(this.includeBuffers));
53
+ }, 1000);
54
+
55
+ this.state.$watch('networks', debouncedSaveState, { deep: true });
56
+ this.state.$watch('user_settings', debouncedSaveState, { deep: true });
57
+
58
+ // We need to touch each property of each buffer for that we want to save our state on.
59
+ // If any of the properties change then the $watch()er will call debouncedSaveState()
60
+ this.watcher = this.state.$watch(() => {
61
+ let watchVals = [];
62
+
63
+ this.state.networks.forEach((network) => {
64
+ let buffersVals = [];
65
+ buffersVals.push(network.buffers.map((b) => b.name).join(','));
66
+
67
+ network.buffers.forEach((buffer) => {
68
+ buffersVals.push([
69
+ buffer.name,
70
+ buffer.settings,
71
+ buffer.joined,
72
+ buffer.enabled,
73
+ buffer.last_read,
74
+ ]);
75
+ });
76
+
77
+ watchVals.push(buffersVals);
78
+ });
79
+
80
+ return JSON.stringify(watchVals);
81
+ }, debouncedSaveState);
82
+
83
+ this.isPersisting = true;
84
+ }
85
+
86
+ async forgetState() {
87
+ this.state.resetState();
88
+ await this.storage.set(this.storageKey, null);
89
+ }
90
+ }
@@ -0,0 +1,128 @@
1
+ 'kiwi public';
2
+
3
+ import _ from 'lodash';
4
+
5
+ let createdInstance = null;
6
+
7
+ export default class ThemeManager {
8
+ constructor(state) {
9
+ this.state = state;
10
+ this.listenForIrcEvents();
11
+ this.varsEl = null;
12
+ }
13
+
14
+ themeVar(varName) {
15
+ if (!this.varsEl) {
16
+ this.varsEl = document.querySelector('.kiwi-wrap');
17
+ }
18
+
19
+ let styles = window.getComputedStyle(this.varsEl);
20
+ let v = styles.getPropertyValue('--kiwi-' + varName);
21
+ return (v || '').trim();
22
+ }
23
+
24
+ availableThemes() {
25
+ return this.state.settings.themes;
26
+ }
27
+
28
+ currentTheme() {
29
+ let state = this.state;
30
+ let currentThemeName = state.setting('theme');
31
+ currentThemeName = currentThemeName.toLowerCase();
32
+
33
+ let theme = _.find(state.settings.themes, (t) => {
34
+ let isMatch = t.name.toLowerCase() === currentThemeName;
35
+ return isMatch;
36
+ });
37
+
38
+ // If no theme was set, use the first one in our theme list
39
+ if (!theme) {
40
+ theme = state.settings.themes[0];
41
+ }
42
+
43
+ return theme;
44
+ }
45
+
46
+ setTheme(theme) {
47
+ let theTheme = null;
48
+
49
+ if (typeof theme === 'string') {
50
+ // Make sure this theme exists
51
+ theTheme = _.find(this.availableThemes(), (t) => {
52
+ let isMatch = t.name.toLowerCase() === theme.toLowerCase();
53
+ return isMatch;
54
+ });
55
+
56
+ if (!theTheme) {
57
+ return;
58
+ }
59
+ } else {
60
+ theTheme = theme;
61
+ }
62
+
63
+ this.state.setting('theme', theTheme.name);
64
+ this.state.$emit('theme.change');
65
+ }
66
+
67
+ reload() {
68
+ let theme = this.currentTheme();
69
+ if (!theme) {
70
+ return;
71
+ }
72
+
73
+ let url = theme.url;
74
+ if (url.indexOf('cb=') > -1) {
75
+ url = url.replace(/cb=[0-9]+/, () => 'cb=' + Date.now());
76
+ } else if (url.indexOf('?') > -1) {
77
+ url += '&cb=' + Date.now();
78
+ } else {
79
+ url += '?cb=' + Date.now();
80
+ }
81
+
82
+ theme.url = url;
83
+ this.state.$emit('theme.change');
84
+ }
85
+
86
+ static themeUrl(theme) {
87
+ let parts = theme.url.split('?');
88
+ let url = parts[0];
89
+ let qs = parts[1] || '';
90
+
91
+ if (url[url.length - 1] !== '/') {
92
+ url += '/';
93
+ }
94
+ return url + 'theme.css' + (qs ? '?' + qs : '');
95
+ }
96
+
97
+ setCustomThemeUrl(url) {
98
+ let theme = _.find(ThemeManager.instance().availableThemes(), {
99
+ name: 'custom',
100
+ });
101
+
102
+ if (theme) {
103
+ theme.url = url;
104
+ }
105
+
106
+ if (theme.name === 'custom') {
107
+ this.state.$emit('theme.change');
108
+ }
109
+ }
110
+
111
+ // When we get a CTCP 'kiwi theme reload' then reload our theme. Handy for theme devs
112
+ listenForIrcEvents() {
113
+ this.state.$on('irc.ctcp request', (event, network) => {
114
+ let ctcpType = (event.type || '').toLowerCase();
115
+ if (ctcpType === 'kiwi' && event.message.indexOf('theme reload') > -1) {
116
+ this.reload();
117
+ }
118
+ });
119
+ }
120
+ }
121
+
122
+ ThemeManager.instance = function instance(state) {
123
+ if (!createdInstance) {
124
+ createdInstance = new ThemeManager(state);
125
+ }
126
+
127
+ return createdInstance;
128
+ };
@@ -0,0 +1,105 @@
1
+ 'kiwi public';
2
+
3
+ /** @module */
4
+
5
+ /**
6
+ * Adds the +typing IRCv3 spec to irc-framework
7
+ */
8
+ export default function typingMiddleware() {
9
+ return function middleware(client, rawEvents, parsedEvents) {
10
+ addFunctionsToClient(client);
11
+ rawEvents.use(theMiddleware);
12
+ };
13
+
14
+ function theMiddleware(command, message, rawLine, client, next) {
15
+ if (
16
+ !(command === 'TAGMSG' && message.tags['+typing']) &&
17
+ !(command === 'PRIVMSG' && message.nick)
18
+ ) {
19
+ next();
20
+ return;
21
+ }
22
+
23
+ // if we are params[0] then the target is the sender (direct message)
24
+ let target = (message.params[0].toLowerCase() === client.user.nick.toLowerCase()) ?
25
+ message.nick :
26
+ message.params[0];
27
+
28
+ // if its a privmsg without typing tag emit done
29
+ let status = message.tags['+typing'] || 'done';
30
+
31
+ client.emit('typing', {
32
+ target: target,
33
+ nick: message.nick,
34
+ ident: message.ident,
35
+ hostname: message.hostname,
36
+ status: status,
37
+ });
38
+
39
+ next();
40
+ }
41
+ }
42
+
43
+ function addFunctionsToClient(client) {
44
+ let typing = client.typing = {};
45
+ let activeTyping = Object.create(null);
46
+
47
+ function isEnabled() {
48
+ return client.network.cap.isEnabled('message-tags');
49
+ }
50
+
51
+ typing.start = function start(target) {
52
+ if (!isEnabled()) {
53
+ return;
54
+ }
55
+
56
+ let lastSentStatus = activeTyping[target.toLowerCase()];
57
+ if (lastSentStatus && lastSentStatus > Date.now() - 3000) {
58
+ return;
59
+ }
60
+
61
+ activeTyping[target.toLowerCase()] = Date.now();
62
+
63
+ let message = new client.Message('TAGMSG', target);
64
+ message.tags['+typing'] = 'active';
65
+ client.raw(message);
66
+ };
67
+
68
+ typing.pause = function pause(target) {
69
+ if (!isEnabled()) {
70
+ return;
71
+ }
72
+
73
+ if (!activeTyping[target.toLowerCase()]) {
74
+ // Did not start typing so cannot pause
75
+ return;
76
+ }
77
+
78
+ let message = new client.Message('TAGMSG', target);
79
+ message.tags['+typing'] = 'paused';
80
+ client.raw(message);
81
+ };
82
+
83
+ typing.stop = function stop(target, sendStop) {
84
+ if (!isEnabled()) {
85
+ return;
86
+ }
87
+
88
+ if (!activeTyping[target.toLowerCase()]) {
89
+ // Did not start typing so cannot stop
90
+ return;
91
+ }
92
+
93
+ delete activeTyping[target.toLowerCase()];
94
+
95
+ if (!sendStop) {
96
+ // Stop was called after a message was sent,
97
+ // only a cleanup of activeTyping is needed
98
+ return;
99
+ }
100
+
101
+ let message = new client.Message('TAGMSG', target);
102
+ message.tags['+typing'] = 'done';
103
+ client.raw(message);
104
+ };
105
+ }
@@ -0,0 +1,63 @@
1
+ export default class WindowTitle {
2
+ constructor(state) {
3
+ this.state = state;
4
+ this.title = state.settings.windowTitle || '';
5
+ this.alert = false;
6
+
7
+ // Update the window title if we have one
8
+ if (state.settings.windowTitle) {
9
+ this.updateTitle();
10
+ }
11
+
12
+ state.$watch('settings.windowTitle', (newVal) => {
13
+ this.updateTitle(newVal);
14
+ });
15
+
16
+ state.$watch('ui.app_has_focus', (newVal) => {
17
+ if (newVal && this.alertTmr) {
18
+ this.stopAlert();
19
+ }
20
+ });
21
+
22
+ state.$on('notification.title', (enable) => {
23
+ if (enable) {
24
+ this.startAlert();
25
+ } else {
26
+ this.stopAlert();
27
+ }
28
+ });
29
+ }
30
+
31
+ startAlert() {
32
+ if (this.alertTmr) {
33
+ return;
34
+ }
35
+
36
+ this.alertTmr = setInterval(() => {
37
+ this.updateTitle();
38
+ }, 1000);
39
+ }
40
+
41
+ stopAlert() {
42
+ if (this.alertTmr) {
43
+ clearInterval(this.alertTmr);
44
+ this.alertTmr = null;
45
+ }
46
+
47
+ this.updateTitle();
48
+ }
49
+
50
+ updateTitle(newTitle) {
51
+ if (typeof newTitle === 'string') {
52
+ this.title = newTitle;
53
+ }
54
+
55
+ if (this.alertTmr && !this.alert) {
56
+ window.document.title = '* ' + this.title;
57
+ this.alert = true;
58
+ } else {
59
+ window.document.title = this.title;
60
+ this.alert = false;
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,74 @@
1
+ 'kiwi public';
2
+
3
+ /**
4
+ * batchedAdd prevents a flood of new inserts into state. Based on JS ticks, inserts /sec are
5
+ * counted by each JS tick that inserts an item. Eg. 10 inserts in 1 tick = 1 count. 10 inserts
6
+ * on different JS ticks = 10 counts.
7
+ * After X counts/sec, batch each second worth of new items at the same time.
8
+ */
9
+ export default function batchedAdd(singleFn, batchedFn, numInsertsSec = 3) {
10
+ let inTick = false;
11
+ let queue = [];
12
+ let numInLastSec = 0;
13
+ let queueLoopTmr = null;
14
+ let loopInterval = 1000;
15
+ let checkSecRateTmr = null;
16
+
17
+ function queueLoop() {
18
+ numInLastSec = 0;
19
+ if (queue.length) {
20
+ // emptying queue before calling batchedFn in case that function triggers
21
+ // code that needs to see that the queue has been processed.
22
+ let q = queue;
23
+ queue = [];
24
+ batchedFn(q);
25
+ queueLoopTmr = setTimeout(queueLoop, loopInterval);
26
+ } else {
27
+ queueLoopTmr = null;
28
+ }
29
+ }
30
+
31
+ function maybeStartLoop() {
32
+ if (!queueLoopTmr) {
33
+ queueLoopTmr = setTimeout(queueLoop, loopInterval);
34
+ }
35
+ }
36
+
37
+ // Reset numInLastSec after loopInterval. This allows enough time for the counter to
38
+ // increase to detect batching. Only needs to run if we are not currently batching and
39
+ // only needs to run once at a time.
40
+ function resetAddCounter() {
41
+ if (!queueLoopTmr && !checkSecRateTmr) {
42
+ checkSecRateTmr = setTimeout(() => {
43
+ checkSecRateTmr = null;
44
+ if (!queueLoopTmr) {
45
+ numInLastSec = 0;
46
+ }
47
+ }, loopInterval);
48
+ }
49
+ }
50
+
51
+ function batchFn(item) {
52
+ if (!inTick) {
53
+ numInLastSec++;
54
+ }
55
+
56
+ // If already queuing or we reached our limit on items/sec, queue the item
57
+ if (queue.length || numInLastSec > numInsertsSec) {
58
+ queue.push(item);
59
+ maybeStartLoop();
60
+ } else {
61
+ inTick = true;
62
+ setTimeout(() => {
63
+ inTick = false;
64
+ });
65
+ singleFn(item);
66
+ resetAddCounter();
67
+ }
68
+ }
69
+ batchFn.queue = function getQueue() {
70
+ return queue;
71
+ };
72
+
73
+ return batchFn;
74
+ }
@@ -0,0 +1,177 @@
1
+ 'kiwi public';
2
+
3
+ import _ from 'lodash';
4
+ import getState from '@/libs/state';
5
+
6
+ export function orderBuffers(buffers) {
7
+ // Since vuejs will sort in-place and update views when .sort is called
8
+ // on an array, clone it first so that we have a plain array to sort
9
+ let list = buffers.map((b) => b);
10
+
11
+ list = _.filter(list, (buffer) => !buffer.isServer());
12
+ list = list.sort((a, b) => {
13
+ let order = 0;
14
+ if (a.isChannel() && b.isQuery()) {
15
+ order = -1;
16
+ } else if (a.isQuery() && b.isChannel()) {
17
+ order = 1;
18
+ } else {
19
+ order = a.name.localeCompare(b.name);
20
+ }
21
+
22
+ return order;
23
+ });
24
+
25
+ return list;
26
+ }
27
+
28
+ export function orderedMessages(buffer, opts = {}) {
29
+ let state = getState();
30
+ let network = buffer.getNetwork();
31
+ let currentNick = network.nick;
32
+ let bufferMessages = buffer.getMessages();
33
+
34
+ // Hack; We need to make vue aware that we depend on buffer.message_count in order to
35
+ // get the messagelist to update its DOM, as the change of message_count alerts
36
+ // us that the messages have changed. This is done so that vue does not have to make
37
+ // every emssage reactive which gets very expensive.
38
+ /* eslint-disable no-unused-vars */
39
+ let ignoredVar = buffer.message_count;
40
+
41
+ let messages = [];
42
+ if (opts.inPlace) {
43
+ messages = bufferMessages;
44
+ } else {
45
+ messages = bufferMessages.slice(0, bufferMessages.length);
46
+ }
47
+
48
+ messages.sort((a, b) => {
49
+ if (a.time > b.time) {
50
+ return 1;
51
+ } else if (b.time > a.time) {
52
+ return -1;
53
+ }
54
+
55
+ return a.instance_num > b.instance_num ?
56
+ 1 :
57
+ -1;
58
+ });
59
+
60
+ if (opts.noFilter) {
61
+ return messages;
62
+ }
63
+
64
+ let messageTypesToShowSetting = {
65
+ // message.type: 'settings_name'
66
+ traffic: 'show_joinparts',
67
+ topic: 'show_topics',
68
+ nick: 'show_nick_changes',
69
+ mode: 'show_mode_changes',
70
+ presence: 'show_presence_changes',
71
+ };
72
+
73
+ let hiddenMessageTypes = Object.keys(messageTypesToShowSetting)
74
+ .filter((type) => !buffer.setting(messageTypesToShowSetting[type]));
75
+
76
+ let typeExtraIncludesSelf = (message) => message.type_extra &&
77
+ message.type_extra.endsWith('_self');
78
+
79
+ let settingShowTopicInHeader = buffer.setting('show_topic_in_header');
80
+
81
+ let list = [];
82
+ for (let i = messages.length - 1; i >= 0; i--) {
83
+ // don't include hidden message types
84
+ if (hiddenMessageTypes.includes(messages[i].type) && !typeExtraIncludesSelf(messages[i])) {
85
+ continue;
86
+ }
87
+
88
+ // Ignored users have the ignore flag set
89
+ if (messages[i].ignore) {
90
+ continue;
91
+ }
92
+
93
+ // Don't show the first connection message. Channels are only interested in
94
+ // the joining message at first. Dis/connection messages are only relevant here
95
+ // if the dis/connection happens between messages (during a conversation)
96
+ if (messages[i].type === 'connection' && i === 0) {
97
+ continue;
98
+ }
99
+
100
+ if (
101
+ messages[i].type === 'topic' &&
102
+ messages[i].type_extra === 'topic_join' &&
103
+ !state.ui.is_narrow &&
104
+ settingShowTopicInHeader
105
+ ) {
106
+ continue;
107
+ }
108
+
109
+ // When we join a channel the topic is usually sent next. But this looks
110
+ // ugly when rendered. So we switch the topic + join messages around so
111
+ // that the topic is first in the message list.
112
+ if (
113
+ messages[i].type === 'topic' &&
114
+ messages[i - 1] &&
115
+ messages[i - 1].type === 'traffic' &&
116
+ messages[i - 1].nick === currentNick
117
+ ) {
118
+ list.push(messages[i - 1]);
119
+ list.push(messages[i]);
120
+ i--;
121
+ } else {
122
+ list.push(messages[i]);
123
+ }
124
+ }
125
+
126
+ return list.reverse();
127
+ }
128
+
129
+ export function getNextBuffer() {
130
+ return getBufferFromDirection(1);
131
+ }
132
+
133
+ export function getPreviousBuffer() {
134
+ return getBufferFromDirection(-1);
135
+ }
136
+
137
+ function getBufferFromDirection(direction) {
138
+ let state = getState();
139
+ let network = state.getActiveNetwork();
140
+ let buffer = state.getActiveBuffer();
141
+
142
+ if (!network || !buffer) {
143
+ return null;
144
+ }
145
+
146
+ let ordered = orderBuffers(network.buffers);
147
+ let index = _.findIndex(ordered, ['name', buffer.name]) + direction;
148
+
149
+ if (index >= ordered.length || index < 0) {
150
+ network = getNetworkFromDirection(direction);
151
+ ordered = orderBuffers(network.buffers);
152
+ buffer = (direction === 1) ? ordered[0] : ordered[ordered.length - 1];
153
+ } else {
154
+ buffer = ordered[index];
155
+ }
156
+ return buffer;
157
+ }
158
+
159
+ function getNetworkFromDirection(direction) {
160
+ let state = getState();
161
+ let network = state.getActiveNetwork();
162
+ for (let i = 0; i < state.networks.length; i++) {
163
+ let index = _.findIndex(state.networks, ['id', network.id]) + direction;
164
+
165
+ if (index >= state.networks.length && state.networks.length >= 0) {
166
+ network = state.networks[0];
167
+ } else if (index < 0) {
168
+ network = state.networks[state.networks.length - 1];
169
+ } else {
170
+ network = state.networks[index];
171
+ }
172
+ if (network.buffers.length > 1) {
173
+ return network;
174
+ }
175
+ }
176
+ return network;
177
+ }
@@ -0,0 +1,18 @@
1
+ // From the Element.closest mdn page.
2
+ if (!Element.prototype.matches) {
3
+ Element.prototype.matches = Element.prototype.msMatchesSelector ||
4
+ Element.prototype.webkitMatchesSelector;
5
+ }
6
+
7
+ if (!Element.prototype.closest) {
8
+ Element.prototype.closest = function closest(s) {
9
+ let el = this;
10
+ do {
11
+ if (el.matches(s)) {
12
+ return el;
13
+ }
14
+ el = el.parentElement || el.parentNode;
15
+ } while (el !== null && el.nodeType === 1);
16
+ return null;
17
+ };
18
+ }