serverless-ircd 0.6.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 (247) hide show
  1. package/.github/workflows/ci.yml +6 -1
  2. package/.gitmodules +3 -0
  3. package/CHANGELOG.md +214 -0
  4. package/README.md +110 -23
  5. package/apps/aws-stack/package.json +1 -1
  6. package/apps/cf-tcp-container/package.json +1 -1
  7. package/apps/cf-worker/package.json +1 -1
  8. package/apps/cf-worker/src/origin-allowlist.ts +99 -0
  9. package/apps/cf-worker/src/worker.ts +89 -9
  10. package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +18 -0
  11. package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
  12. package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
  13. package/apps/cf-worker/tests/smoke.test.ts +28 -2
  14. package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
  15. package/apps/cf-worker/wrangler.test.toml +23 -0
  16. package/apps/cf-worker/wrangler.toml +54 -0
  17. package/apps/local-cli/package.json +1 -1
  18. package/apps/web/landing/index.html +217 -0
  19. package/apps/web/package.json +28 -0
  20. package/apps/web/scripts/build.mjs +125 -0
  21. package/apps/web/src/build-env.ts +43 -0
  22. package/apps/web/src/config-schema.ts +138 -0
  23. package/apps/web/static/config.json +28 -0
  24. package/apps/web/static/config.prod.json +28 -0
  25. package/apps/web/static/config.staging.json +28 -0
  26. package/apps/web/tests/build-env.test.ts +63 -0
  27. package/apps/web/tests/build-smoke.test.ts +103 -0
  28. package/apps/web/tests/config-schema.test.ts +432 -0
  29. package/apps/web/tests/workspace.test.ts +12 -0
  30. package/apps/web/tsconfig.json +10 -0
  31. package/apps/web/upstream/.browserslistrc +3 -0
  32. package/apps/web/upstream/.editorconfig +9 -0
  33. package/apps/web/upstream/.eslintignore +3 -0
  34. package/apps/web/upstream/.eslintrc.js +90 -0
  35. package/apps/web/upstream/.github/FUNDING.yml +12 -0
  36. package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  37. package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  38. package/apps/web/upstream/.github/workflows/push.yml +26 -0
  39. package/apps/web/upstream/.github/workflows/release.yml +33 -0
  40. package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
  41. package/apps/web/upstream/.prettierrc.js +9 -0
  42. package/apps/web/upstream/.stylelintignore +5 -0
  43. package/apps/web/upstream/.stylelintrc.js +21 -0
  44. package/apps/web/upstream/LICENSE +201 -0
  45. package/apps/web/upstream/README.md +76 -0
  46. package/apps/web/upstream/babel.config.js +25 -0
  47. package/apps/web/upstream/crowdin.yml +3 -0
  48. package/apps/web/upstream/index.html +35 -0
  49. package/apps/web/upstream/jest.config.js +7 -0
  50. package/apps/web/upstream/jsdoc.json +22 -0
  51. package/apps/web/upstream/package.json +80 -0
  52. package/apps/web/upstream/scripts/pre-commit +43 -0
  53. package/apps/web/upstream/src/components/App.vue +516 -0
  54. package/apps/web/upstream/src/components/AppSettings.vue +587 -0
  55. package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
  56. package/apps/web/upstream/src/components/Avatar.vue +105 -0
  57. package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
  58. package/apps/web/upstream/src/components/BufferKey.vue +121 -0
  59. package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
  60. package/apps/web/upstream/src/components/Captcha.vue +85 -0
  61. package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
  62. package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
  63. package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
  64. package/apps/web/upstream/src/components/ChannelList.vue +345 -0
  65. package/apps/web/upstream/src/components/Container.vue +401 -0
  66. package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
  67. package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
  68. package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
  69. package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
  70. package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
  71. package/apps/web/upstream/src/components/MessageList.vue +1028 -0
  72. package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
  73. package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
  74. package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
  75. package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
  76. package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
  77. package/apps/web/upstream/src/components/Nicklist.vue +309 -0
  78. package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
  79. package/apps/web/upstream/src/components/NotConnected.vue +271 -0
  80. package/apps/web/upstream/src/components/SelfUser.vue +283 -0
  81. package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
  82. package/apps/web/upstream/src/components/ServerView.vue +126 -0
  83. package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
  84. package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
  85. package/apps/web/upstream/src/components/Sidebar.vue +429 -0
  86. package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
  87. package/apps/web/upstream/src/components/SidebarState.vue +145 -0
  88. package/apps/web/upstream/src/components/StartupError.vue +35 -0
  89. package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
  90. package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
  91. package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
  92. package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
  93. package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
  94. package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
  95. package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
  96. package/apps/web/upstream/src/components/UserBox.vue +728 -0
  97. package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
  98. package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
  99. package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
  100. package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
  101. package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
  102. package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
  103. package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
  104. package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
  105. package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
  106. package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
  107. package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
  108. package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
  109. package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
  110. package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
  111. package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
  112. package/apps/web/upstream/src/helpers/Colours.js +128 -0
  113. package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
  114. package/apps/web/upstream/src/helpers/Md5.js +193 -0
  115. package/apps/web/upstream/src/helpers/Misc.js +401 -0
  116. package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
  117. package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
  118. package/apps/web/upstream/src/libs/AudioManager.js +60 -0
  119. package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
  120. package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
  121. package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
  122. package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
  123. package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
  124. package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
  125. package/apps/web/upstream/src/libs/IPC.js +51 -0
  126. package/apps/web/upstream/src/libs/InputHandler.js +921 -0
  127. package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
  128. package/apps/web/upstream/src/libs/Logger.js +71 -0
  129. package/apps/web/upstream/src/libs/Message.js +169 -0
  130. package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
  131. package/apps/web/upstream/src/libs/MessageParser.js +249 -0
  132. package/apps/web/upstream/src/libs/Notifications.js +78 -0
  133. package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
  134. package/apps/web/upstream/src/libs/ServerSession.js +102 -0
  135. package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
  136. package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
  137. package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
  138. package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
  139. package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
  140. package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
  141. package/apps/web/upstream/src/libs/bufferTools.js +177 -0
  142. package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
  143. package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
  144. package/apps/web/upstream/src/libs/settingTools.js +31 -0
  145. package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
  146. package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
  147. package/apps/web/upstream/src/libs/state/UserState.js +89 -0
  148. package/apps/web/upstream/src/libs/state/common.js +26 -0
  149. package/apps/web/upstream/src/libs/state.js +961 -0
  150. package/apps/web/upstream/src/libs/storage/Local.js +51 -0
  151. package/apps/web/upstream/src/main.js +566 -0
  152. package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
  153. package/apps/web/upstream/src/res/configTemplates.js +373 -0
  154. package/apps/web/upstream/src/res/globalStyle.css +277 -0
  155. package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
  156. package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
  157. package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
  158. package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
  159. package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
  160. package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
  161. package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
  162. package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
  163. package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
  164. package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
  165. package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
  166. package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
  167. package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
  168. package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
  169. package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
  170. package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
  171. package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
  172. package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
  173. package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
  174. package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
  175. package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
  176. package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
  177. package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
  178. package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
  179. package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
  180. package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
  181. package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
  182. package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
  183. package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
  184. package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
  185. package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
  186. package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
  187. package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
  188. package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
  189. package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
  190. package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
  191. package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
  192. package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
  193. package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
  194. package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
  195. package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
  196. package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
  197. package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
  198. package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
  199. package/apps/web/upstream/src/res/localesList.json +27 -0
  200. package/apps/web/upstream/src/res/logo.png +0 -0
  201. package/apps/web/upstream/src/thirdparty/about.html +43 -0
  202. package/apps/web/upstream/src/thirdparty/index.js +5 -0
  203. package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
  204. package/apps/web/upstream/static/config.json +28 -0
  205. package/apps/web/upstream/static/emoticons/smile.png +0 -0
  206. package/apps/web/upstream/static/favicon.png +0 -0
  207. package/apps/web/upstream/static/highlight.mp3 +0 -0
  208. package/apps/web/upstream/static/highlight.ogg +0 -0
  209. package/apps/web/upstream/static/locales/.gitignore +2 -0
  210. package/apps/web/upstream/static/plugins/customise.html.example +10 -0
  211. package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
  212. package/apps/web/upstream/static/themes/common/base.css +1064 -0
  213. package/apps/web/upstream/static/themes/dark/theme.css +196 -0
  214. package/apps/web/upstream/static/themes/default/theme.css +92 -0
  215. package/apps/web/upstream/static/themes/elite/theme.css +248 -0
  216. package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
  217. package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
  218. package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
  219. package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
  220. package/apps/web/upstream/static/themes/sky/theme.css +107 -0
  221. package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
  222. package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
  223. package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
  224. package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
  225. package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
  226. package/apps/web/upstream/vue.config.js +150 -0
  227. package/apps/web/upstream/yarn.lock +9767 -0
  228. package/apps/web/vitest.config.ts +13 -0
  229. package/biome.json +1 -0
  230. package/docs/Home.md +1 -0
  231. package/docs/WebClientGuide.md +535 -0
  232. package/package.json +2 -2
  233. package/packages/aws-adapter/package.json +1 -1
  234. package/packages/cf-adapter/package.json +1 -1
  235. package/packages/cf-adapter/tests/cf-harness.ts +12 -1
  236. package/packages/in-memory-runtime/package.json +1 -1
  237. package/packages/irc-core/package.json +1 -1
  238. package/packages/irc-core/reports/mutation/mutation.html +342 -0
  239. package/packages/irc-core/tests/batch.test.ts +15 -0
  240. package/packages/irc-core/tests/numerics.test.ts +13 -0
  241. package/packages/irc-core/tests/parser.test.ts +109 -0
  242. package/packages/irc-server/package.json +1 -1
  243. package/packages/irc-test-support/package.json +1 -1
  244. package/packages/irc-test-support/src/scenarios.ts +24 -2
  245. package/tools/ci-hardening/package.json +1 -1
  246. package/tools/load-test/package.json +1 -1
  247. package/tools/tcp-ws-forwarder/package.json +1 -1
@@ -0,0 +1,703 @@
1
+ /** @module */
2
+
3
+ import Vue from 'vue';
4
+ import _ from 'lodash';
5
+ import { def } from './common';
6
+ import batchedAdd from '../batchedAdd';
7
+ import * as bufferTools from '../bufferTools';
8
+
9
+ let nextBufferId = 0;
10
+
11
+ /** The IRC buffer instance */
12
+ export default class BufferState {
13
+ constructor(name, networkid, state, messageDict) {
14
+ // Enumerable properties that become reactive under Vue
15
+ this.id = nextBufferId++;
16
+ this.networkid = networkid;
17
+ this.name = name;
18
+ this.topics = [];
19
+ this.key = '';
20
+ this.joined = false;
21
+ this.enabled = true;
22
+ this.created_at = null;
23
+ this.users = Object.create(null);
24
+ this.modes = Object.create(null);
25
+ this.flags = {
26
+ unread: 0,
27
+ highlight: false,
28
+ has_opened: false,
29
+ channel_badkey: false,
30
+ chathistory_available: true,
31
+ requested_modes: false,
32
+ requested_banlist: false,
33
+ is_requesting_chathistory: false,
34
+ };
35
+ this.settings = { };
36
+ this.last_read = 0;
37
+ this.active_timeout = null;
38
+ this.message_count = 0;
39
+ this.current_input = '';
40
+ this.input_history = [];
41
+ this.input_history_pos = 0;
42
+ this.show_input = true;
43
+ this.latest_messages = [];
44
+
45
+ // Counter for chathistory requests. While this value is 0, it means that this buffer is
46
+ // still loading messages
47
+ this.chathistory_request_count = 0;
48
+
49
+ Vue.observable(this);
50
+
51
+ // Some non-enumerable properties (vues $watch won't cover these properties)
52
+ def(this, 'state', state, false);
53
+ def(this, 'messageDict', messageDict, false);
54
+
55
+ let messagesObj = {
56
+ networkid: this.networkid,
57
+ buffer: this.name,
58
+ messages: [],
59
+ messageIds: Object.create(null),
60
+ };
61
+ this.messageDict.push(messagesObj);
62
+ def(this, 'messagesObj', messagesObj, false);
63
+
64
+ def(this, 'isMessageTrimming', true, true);
65
+ def(this, 'addMessageBatch', createMessageBatch(this), false);
66
+ def(this, 'addUserBatch', createUserBatch(this), false);
67
+
68
+ // poll who to update away status if away-notify is not enabled
69
+ if (this.isChannel()) {
70
+ maybeStartWhoLoop(this);
71
+ }
72
+
73
+ // When the network re-connects, we reset the chathistory request counter.
74
+ // This will make the `getLoadingState()` stay as 'loading' while the chathistory reloads.
75
+ function onNetworkConnecting(event) {
76
+ if (event.network === this.getNetwork()) {
77
+ this.chathistory_request_count = 0;
78
+ }
79
+ }
80
+
81
+ function onNetworkMotd(event, network) {
82
+ const bufferNetwork = this.getNetwork();
83
+ if (
84
+ network !== bufferNetwork
85
+ || !bufferNetwork.ircClient.chathistory.isSupported()
86
+ || !this.isQuery()
87
+ ) {
88
+ return;
89
+ }
90
+
91
+ if (['all', 'queries'].includes(this.setting('auto_request_history'))) {
92
+ this.requestLatestScrollback();
93
+ }
94
+ }
95
+
96
+ // Clean up the previous event and itself when the buffer is closed.
97
+ function onBufferClose(event) {
98
+ if (event.buffer === this) {
99
+ this.state.$off('network.connecting', onNetworkConnectingBound);
100
+ this.state.$off('buffer.close', onBufferCloseBound);
101
+ this.state.$off('irc.motd', onNetworkMotdBound);
102
+ }
103
+ }
104
+
105
+ const onNetworkConnectingBound = onNetworkConnecting.bind(this);
106
+ const onBufferCloseBound = onBufferClose.bind(this);
107
+ const onNetworkMotdBound = onNetworkMotd.bind(this);
108
+
109
+ state.$on('network.connecting', onNetworkConnectingBound);
110
+ state.$on('buffer.close', onBufferCloseBound);
111
+ state.$on('irc.motd', onNetworkMotdBound);
112
+ }
113
+
114
+ get topic() {
115
+ return this.topics.length === 0 ?
116
+ '' :
117
+ this.topics[this.topics.length - 1];
118
+ }
119
+
120
+ set topic(newVal) {
121
+ this.topics.push(newVal);
122
+ }
123
+
124
+ getNetwork() {
125
+ return this.state.getNetwork(this.networkid);
126
+ }
127
+
128
+ getMessages() {
129
+ let bufMessages = _.find(this.messageDict, {
130
+ networkid: this.networkid,
131
+ buffer: this.name,
132
+ });
133
+
134
+ return bufMessages ?
135
+ bufMessages.messages :
136
+ [];
137
+ }
138
+
139
+ getLastMessage() {
140
+ let messages = this.getMessages();
141
+ if (messages.length === 0) {
142
+ return undefined;
143
+ }
144
+ return messages[messages.length - 1];
145
+ }
146
+
147
+ clearMessages() {
148
+ this.messagesObj.messages.splice(0, this.messagesObj.messages.length);
149
+ this.messagesObj.messageIds = Object.create(null);
150
+ }
151
+
152
+ // Remove a block of messages between a time (server-time) range. Inclusive.
153
+ clearMessageRange(startTime, endTime) {
154
+ this.messagesObj.messages = this.messagesObj.messages.filter((message) => {
155
+ if (message.server_time < startTime || message.server_time > endTime) {
156
+ return true;
157
+ }
158
+
159
+ // This message will be removed
160
+ delete this.messagesObj.messageIds[message.id];
161
+ return false;
162
+ });
163
+
164
+ // Mark that something changed
165
+ this.message_count++;
166
+ }
167
+
168
+ isServer() {
169
+ return this.name === '*';
170
+ }
171
+
172
+ isRaw() {
173
+ return this.name === '*raw';
174
+ }
175
+
176
+ isChannel() {
177
+ let chanPrefixes = ['#', '&'];
178
+ let ircNetwork = this.getNetwork().ircClient.network;
179
+ if (ircNetwork && ircNetwork.options.CHANTYPES) {
180
+ chanPrefixes = ircNetwork.options.CHANTYPES;
181
+ }
182
+
183
+ return chanPrefixes.indexOf(this.name[0]) > -1;
184
+ }
185
+
186
+ isQuery() {
187
+ let chanPrefixes = ['#', '&'];
188
+ let ircNetwork = this.getNetwork().ircClient.network;
189
+ if (ircNetwork && ircNetwork.options.CHANTYPES) {
190
+ chanPrefixes = ircNetwork.options.CHANTYPES;
191
+ }
192
+
193
+ return chanPrefixes.indexOf(this.name[0]) === -1 &&
194
+ !this.isSpecial() &&
195
+ !this.isServer();
196
+ }
197
+
198
+ isSpecial() {
199
+ // Special buffer names (Usually controller queries, like *status or *raw).
200
+ // Server buffer '*' is not included in this classification.
201
+ let name = this.name;
202
+ return name[0] === '*' && name.length > 1;
203
+ }
204
+
205
+ isUserAnOp(nick) {
206
+ let user = this.state.getUser(this.networkid, nick);
207
+ if (!user) {
208
+ return false;
209
+ }
210
+
211
+ let userBufferInfo = user.buffers[this.id];
212
+ if (!userBufferInfo) {
213
+ return false;
214
+ }
215
+
216
+ let modes = userBufferInfo.modes;
217
+ let opModes = ['Y', 'y', 'q', 'a', 'o', 'h'];
218
+ let hasOp = _.find(modes, (mode) => opModes.indexOf(mode.toLowerCase()) > -1);
219
+
220
+ return !!hasOp;
221
+ }
222
+
223
+ /**
224
+ * Get a users prefix symbol on a buffer from its modes
225
+ * @param {Object} user The user object
226
+ */
227
+ userModePrefix(user) {
228
+ // The user may not be on the buffer
229
+ if (!user.buffers[this.id]) {
230
+ return '';
231
+ }
232
+
233
+ let modes = user.buffers[this.id].modes;
234
+ if (modes.length === 0) {
235
+ return '';
236
+ }
237
+
238
+ let network = this.getNetwork();
239
+ let netPrefixes = network.ircClient.network.options.PREFIX;
240
+ // Find the first (highest) netPrefix in the users buffer modes
241
+ let prefix = _.find(netPrefixes, (p) => modes.indexOf(p.mode) > -1);
242
+
243
+ return prefix ?
244
+ prefix.symbol :
245
+ '';
246
+ }
247
+
248
+ /**
249
+ * Get a users mode on a buffer
250
+ * @param user {Object} The user object
251
+ */
252
+ userMode(user) {
253
+ // The user may not be on the buffer
254
+ if (!user.buffers[this.id]) {
255
+ return '';
256
+ }
257
+
258
+ let modes = user.buffers[this.id].modes;
259
+ if (modes.length === 0) {
260
+ return '';
261
+ }
262
+
263
+ // if there is only one mode just return it
264
+ if (modes.length === 1) {
265
+ return modes[0];
266
+ }
267
+
268
+ let network = this.getNetwork();
269
+ let netPrefixes = network.ircClient.network.options.PREFIX;
270
+ // Find the first (highest) netPrefix in the users buffer modes
271
+ let prefix = _.find(netPrefixes, (p) => modes.indexOf(p.mode) > -1);
272
+
273
+ return prefix ?
274
+ prefix.mode :
275
+ '';
276
+ }
277
+
278
+ setting(name, val) {
279
+ if (typeof val !== 'undefined') {
280
+ this.state.$set(this.settings, name, val);
281
+ return val;
282
+ }
283
+
284
+ // Check the buffer specific settings before reverting to global settings
285
+ let result = typeof this.settings[name] !== 'undefined' ?
286
+ this.settings[name] :
287
+ this.state.setting('buffers.' + name);
288
+
289
+ return result;
290
+ }
291
+
292
+ rename(newName) {
293
+ let network = this.getNetwork();
294
+ let oldName = this.name;
295
+ let setActive = this.state.getActiveBuffer() === this;
296
+
297
+ this.name = newName;
298
+ if (setActive) {
299
+ this.state.setActiveBuffer(network.id, newName);
300
+ }
301
+
302
+ // update the buffer name on our messages
303
+ let bufferMessages = _.find(this.messageDict, { networkid: network.id, buffer: oldName });
304
+ bufferMessages.buffer = newName;
305
+ }
306
+
307
+ flag(name, val) {
308
+ if (typeof val !== 'undefined') {
309
+ this.state.$set(this.flags, name, val);
310
+ return val;
311
+ }
312
+
313
+ return this.flags[name];
314
+ }
315
+
316
+ requestScrollback(_direction) {
317
+ let direction = _direction || 'backward';
318
+ let time = '';
319
+ let chathistoryFuncName = 'before';
320
+
321
+ // Going backwards takes the earliest message we already have and requests messages
322
+ // before it. Going forward takes the last message we have and requests messages after
323
+ // it.
324
+
325
+ if (direction === 'backward') {
326
+ let lastMessage = this.getMessages().reduce((earliest, current) => {
327
+ let ignoreTypes = ['traffic', 'topic', 'connection', 'presence'];
328
+ let validType = ignoreTypes.indexOf(earliest.type) === -1;
329
+ if (validType && earliest.time && earliest.time < current.time) {
330
+ return earliest;
331
+ }
332
+ return current;
333
+ }, this.getMessages()[0]);
334
+
335
+ chathistoryFuncName = 'before';
336
+ time = lastMessage ?
337
+ new Date(lastMessage.server_time) :
338
+ new Date();
339
+ } else if (direction === 'forward') {
340
+ let firstMessage = this.getMessages().reduce((latest, current) => {
341
+ let ignoreTypes = ['traffic', 'topic', 'connection', 'presence'];
342
+ let validType = ignoreTypes.indexOf(latest.type) === -1;
343
+ if (validType && latest.time && latest.time > current.time) {
344
+ return latest;
345
+ }
346
+ return current;
347
+ }, this.getMessages()[0]);
348
+
349
+ chathistoryFuncName = 'after';
350
+ time = firstMessage ?
351
+ new Date(firstMessage.server_time) :
352
+ new Date();
353
+ } else {
354
+ throw new Error('Invalid direction for requestScrollback(): ' + _direction);
355
+ }
356
+
357
+ getBufferHistory(this, chathistoryFuncName, time);
358
+ }
359
+
360
+ requestLatestScrollback() {
361
+ getBufferHistory(this, 'latest', '*');
362
+ }
363
+
364
+ markAsRead(delayed) {
365
+ if (this.active_timeout) {
366
+ clearTimeout(this.active_timeout);
367
+ this.active_timeout = null;
368
+ }
369
+ if (delayed) {
370
+ this.active_timeout = setTimeout(
371
+ this.markAsRead.bind(this),
372
+ 10000,
373
+ false
374
+ );
375
+ } else {
376
+ this.last_read = Date.now();
377
+ this.flag('highlight', false);
378
+
379
+ // If running under a bouncer, set it on the server-side too
380
+ let network = this.getNetwork();
381
+ let allowedUpdate = !network ? false : this.isChannel() || this.isQuery();
382
+ if (allowedUpdate && network.connection.bncnetid) {
383
+ network.ircClient.bnc.bufferSeen(network.connection.bncnetid, this.name);
384
+ }
385
+ }
386
+ }
387
+
388
+ incrementFlag(flagName) {
389
+ this.flags[flagName] = (this.flags[flagName] || 0) + 1;
390
+ }
391
+
392
+ addUser(user) {
393
+ this.addUserBatch(user);
394
+ }
395
+
396
+ hasNick(nick) {
397
+ let nickUpper = nick.toUpperCase();
398
+ return (
399
+ nickUpper in this.users
400
+ || this.addUserBatch.queue().some((u) => u.nick.toUpperCase() === nickUpper)
401
+ || (this.isQuery() && this.name.toUpperCase() === nickUpper)
402
+ );
403
+ }
404
+
405
+ hasMode(mode) {
406
+ return Object.keys(this.modes).indexOf(mode) > -1;
407
+ }
408
+
409
+ shouldShareTyping() {
410
+ let network = this.getNetwork();
411
+ if (!this.setting('share_typing')) {
412
+ // Feature disabled
413
+ return false;
414
+ }
415
+ if (!this.isChannel() && !this.isQuery()) {
416
+ // Qnly share tying with channels and queries
417
+ return false;
418
+ }
419
+ if (this.isChannel() && !this.joined) {
420
+ // Channel is in an unjoined state
421
+ return false;
422
+ }
423
+ if (this.hasMode('m') && !this.userMode(network.currentUser())) {
424
+ // Channel is moderated (+m) and we do not have a user mode +v or above
425
+ return false;
426
+ }
427
+ return true;
428
+ }
429
+
430
+ removeUser(nick) {
431
+ let userObj = this.state.getUser(this.networkid, nick);
432
+
433
+ // A user could be queued to be added, so make sure it's not there as it
434
+ // would just be added again. Eg. user joins/parts during a flood
435
+ _.pull(this.addUserBatch.queue(), userObj);
436
+
437
+ this.state.$delete(this.users, nick.toUpperCase());
438
+
439
+ if (userObj) {
440
+ delete userObj.buffers[this.id];
441
+ }
442
+ }
443
+
444
+ clearUsers() {
445
+ // Users could be queued to be added, so make sure to clear them as they
446
+ // would just be added again. Eg. user joins/parts during a flood
447
+ this.addUserBatch && this.addUserBatch.queue().splice(0);
448
+
449
+ _.each(this.users, (userObj, nick) => {
450
+ delete userObj.buffers[this.id];
451
+ });
452
+
453
+ this.state.$set(this, 'users', {});
454
+ }
455
+
456
+ addMessage(message) {
457
+ this.addMessageBatch(message);
458
+ }
459
+
460
+ updateLatestMessages(message) {
461
+ if (!['privmsg', 'notice'].includes(message.type)) {
462
+ return;
463
+ }
464
+
465
+ const isNewer = (msg) => this.latest_messages[0].time <= msg.time &&
466
+ this.latest_messages[0].instance_num < msg.instance_num;
467
+
468
+ if (!this.latest_messages[0] || isNewer(message)) {
469
+ this.latest_messages.unshift(message);
470
+ }
471
+
472
+ if (this.latest_messages.length > 5) {
473
+ // restrict array to 5 elements
474
+ this.latest_messages.length = 5;
475
+ }
476
+ }
477
+
478
+ getLatestMessage() {
479
+ return this.latest_messages[0];
480
+ }
481
+
482
+ say(message, opts = {}) {
483
+ let network = this.getNetwork();
484
+ let newMessage = {
485
+ time: Date.now(),
486
+ nick: network.nick,
487
+ message: message,
488
+ tags: opts.tags || {},
489
+ type: opts.type || 'privmsg',
490
+ };
491
+
492
+ this.state.addMessage(this, newMessage);
493
+
494
+ let fnNames = {
495
+ privmsg: 'say',
496
+ action: 'action',
497
+ notice: 'notice',
498
+ tagmsg: 'tagmsg',
499
+ };
500
+ let fnName = fnNames[opts.type] || 'say';
501
+ network.ircClient[fnName](this.name, message, opts.tags);
502
+ }
503
+
504
+ join() {
505
+ if (!this.isChannel()) {
506
+ return;
507
+ }
508
+
509
+ let network = this.getNetwork();
510
+ network.ircClient.join(this.name, this.key || '');
511
+ }
512
+
513
+ part(reason) {
514
+ if (!this.isChannel()) {
515
+ return;
516
+ }
517
+
518
+ let network = this.getNetwork();
519
+ network.ircClient.part(this.name, reason || '');
520
+ }
521
+
522
+ scrollToMessage(id) {
523
+ this.state.$emit('messagelist.scrollto', { id: id });
524
+ }
525
+
526
+ getLoadingState() {
527
+ const networkState = this.getNetwork().state;
528
+ const historySupport = !!this.getNetwork().ircClient.chathistory.isSupported();
529
+ const messagesInBatchQueue = this.addMessageBatch.queue().length;
530
+ // Hack; We need to make vue aware that we depend on message_count in order to
531
+ // update the loading state.
532
+ // eslint-disable-next-line no-unused-vars
533
+ const messageCount = this.message_count;
534
+
535
+ if (networkState === 'disconnected') {
536
+ return 'disconnected';
537
+ } else if (networkState === 'connecting') {
538
+ return 'connecting';
539
+ } else if (
540
+ networkState === 'connected' &&
541
+ this.enabled &&
542
+ (
543
+ historySupport &&
544
+ (this.flags.is_requesting_chathistory ||
545
+ // If chathistory is supported then a request will always be made when first
546
+ // joining a channel. If request_count===0 then we're still waiting for it
547
+ // to happen.
548
+ this.chathistory_request_count === 0 ||
549
+ // keep in loading state while the batch is being processed
550
+ messagesInBatchQueue > 0
551
+ )
552
+ )
553
+ ) {
554
+ return 'loading';
555
+ }
556
+ return 'done';
557
+ }
558
+
559
+ isReady() {
560
+ return this.getLoadingState() === 'done';
561
+ }
562
+ }
563
+
564
+ /**
565
+ * Batch up floods of addUsers for a huge performance gain.
566
+ * Generally happens when reconnecting to a BNC
567
+ */
568
+ function createUserBatch(bufferState) {
569
+ let addSingleUser = (u) => {
570
+ bufferState.state.$set(bufferState.users, u.nick.toUpperCase(), u);
571
+ };
572
+ let addMultipleUsers = (users) => {
573
+ let o = Object.assign(Object.create(null), bufferState.users);
574
+ users.forEach((u) => {
575
+ o[u.nick.toUpperCase()] = u;
576
+ });
577
+ bufferState.users = o;
578
+ };
579
+
580
+ return batchedAdd(addSingleUser, addMultipleUsers, 2);
581
+ }
582
+
583
+ /**
584
+ * batch up floods of new messages for a huge performance gain
585
+ */
586
+ function createMessageBatch(bufferState) {
587
+ let addSingleMessage = (newMessage) => {
588
+ if (bufferState.messagesObj.messageIds[newMessage.id]) {
589
+ return;
590
+ }
591
+ bufferState.updateLatestMessages(newMessage);
592
+ bufferState.messagesObj.messages.push(newMessage);
593
+ bufferState.messagesObj.messageIds[newMessage.id] = newMessage;
594
+ if (bufferState.isMessageTrimming) {
595
+ trimMessages();
596
+ }
597
+ bufferTools.orderedMessages(bufferState, { inPlace: true, noFilter: true });
598
+ bufferState.message_count++;
599
+ };
600
+ let addMultipleMessages = (newMessages) => {
601
+ let toAdd = newMessages.filter((msg) => !bufferState.messagesObj.messageIds[msg.id]);
602
+ if (toAdd.length > 0) {
603
+ bufferState.messagesObj.messages = bufferState.messagesObj.messages.concat(toAdd);
604
+ toAdd.forEach((msg) => {
605
+ bufferState.updateLatestMessages(msg);
606
+ bufferState.messagesObj.messageIds[msg.id] = msg;
607
+ });
608
+ if (bufferState.isMessageTrimming) {
609
+ trimMessages();
610
+ }
611
+ bufferTools.orderedMessages(bufferState, { inPlace: true, noFilter: true });
612
+ }
613
+ // Trigger Vue's reactivity on the buffer whether messages were added or not, just in case
614
+ // anything was depending on the batch queue which has now been emptied.
615
+ bufferState.message_count++;
616
+ };
617
+ let trimMessages = () => {
618
+ let scrollbackSize = bufferState.setting('scrollback_size');
619
+ let length = bufferState.messagesObj.messages.length;
620
+ if (bufferState.messagesObj.messages.length > scrollbackSize) {
621
+ let removed = bufferState.messagesObj.messages.splice(0, length - scrollbackSize);
622
+ removed.forEach((msg) => delete bufferState.messagesObj.messageIds[msg.id]);
623
+ }
624
+ };
625
+
626
+ return batchedAdd(addSingleMessage, addMultipleMessages, 4);
627
+ }
628
+
629
+ // Update our user list status every 30seconds to get each users current away status
630
+ function maybeStartWhoLoop(bufferState) {
631
+ let network = bufferState.state.getNetwork(bufferState.networkid);
632
+
633
+ if (network.state === 'connected') {
634
+ // network is connected start the loop if its needed
635
+ nextLoop();
636
+ } else {
637
+ // Network is not coonnected. Wait until it is
638
+ let on001 = (command, event, eventNetwork) => {
639
+ if (eventNetwork === network) {
640
+ bufferState.state.$off('irc.raw.001', on001);
641
+ nextLoop();
642
+ }
643
+ };
644
+ bufferState.state.$on('irc.raw.001', on001);
645
+ }
646
+
647
+ function nextLoop() {
648
+ setTimeout(updateWhoStatusLoop, 30000);
649
+ }
650
+
651
+ function updateWhoStatusLoop() {
652
+ network = bufferState.state.getNetwork(bufferState.networkid);
653
+
654
+ // Make sure the network still exists
655
+ if (!network) {
656
+ return;
657
+ }
658
+
659
+ // Make sure the buffer still exists
660
+ if (!network.bufferByName(bufferState.name)) {
661
+ return;
662
+ }
663
+
664
+ let whoLoop = bufferState.setting('who_loop');
665
+ let isJoined = bufferState.joined;
666
+ let hasAwayNotify = network.ircClient.network.cap.isEnabled('away-notify');
667
+ let networkConnected = network.state === 'connected';
668
+
669
+ if (whoLoop && networkConnected && isJoined && !hasAwayNotify) {
670
+ network.ircClient.who(bufferState.name, () => {
671
+ nextLoop();
672
+ });
673
+ } else {
674
+ nextLoop();
675
+ }
676
+ }
677
+ }
678
+
679
+ function getBufferHistory(buffer, chathistoryFuncName, time) {
680
+ let ircClient = buffer.getNetwork().ircClient;
681
+ buffer.flag('is_requesting_chathistory', true);
682
+ buffer.chathistory_request_count += 1;
683
+ let existingMessageIds = Object.assign({}, buffer.messagesObj.messageIds);
684
+ ircClient.chathistory[chathistoryFuncName](buffer.name, time).then((event) => {
685
+ if (!event) {
686
+ buffer.flag('chathistory_available', false);
687
+ return;
688
+ }
689
+
690
+ // The BNC server may reply with messages that are already in the buffer.
691
+ // If we get no new messages that we didn't already have, assume that we have
692
+ // all the available history
693
+ let hasNewMessages = event.commands.some(
694
+ (msg) => msg.tags.msgid && !existingMessageIds[msg.tags.msgid]
695
+ );
696
+
697
+ // If there are new messages, then there could be more in the backlog.
698
+ // If there are no new messages, then the chat history is empty.
699
+ buffer.flag('chathistory_available', hasNewMessages);
700
+ }).finally(() => {
701
+ buffer.flag('is_requesting_chathistory', false);
702
+ });
703
+ }