@lordbex/thelounge 4.4.3-blowfish

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 (148) hide show
  1. package/.thelounge_home +1 -0
  2. package/LICENSE +22 -0
  3. package/README.md +95 -0
  4. package/client/index.html.tpl +69 -0
  5. package/dist/defaults/config.js +465 -0
  6. package/dist/package.json +174 -0
  7. package/dist/server/client.js +678 -0
  8. package/dist/server/clientManager.js +220 -0
  9. package/dist/server/command-line/index.js +85 -0
  10. package/dist/server/command-line/install.js +123 -0
  11. package/dist/server/command-line/outdated.js +30 -0
  12. package/dist/server/command-line/start.js +34 -0
  13. package/dist/server/command-line/storage.js +103 -0
  14. package/dist/server/command-line/uninstall.js +40 -0
  15. package/dist/server/command-line/upgrade.js +64 -0
  16. package/dist/server/command-line/users/add.js +67 -0
  17. package/dist/server/command-line/users/edit.js +39 -0
  18. package/dist/server/command-line/users/index.js +17 -0
  19. package/dist/server/command-line/users/list.js +53 -0
  20. package/dist/server/command-line/users/remove.js +37 -0
  21. package/dist/server/command-line/users/reset.js +64 -0
  22. package/dist/server/command-line/utils.js +177 -0
  23. package/dist/server/config.js +138 -0
  24. package/dist/server/helper.js +161 -0
  25. package/dist/server/identification.js +139 -0
  26. package/dist/server/index.js +3 -0
  27. package/dist/server/log.js +35 -0
  28. package/dist/server/models/chan.js +275 -0
  29. package/dist/server/models/msg.js +92 -0
  30. package/dist/server/models/network.js +546 -0
  31. package/dist/server/models/prefix.js +31 -0
  32. package/dist/server/models/user.js +42 -0
  33. package/dist/server/plugins/auth/ldap.js +188 -0
  34. package/dist/server/plugins/auth/local.js +41 -0
  35. package/dist/server/plugins/auth.js +70 -0
  36. package/dist/server/plugins/changelog.js +103 -0
  37. package/dist/server/plugins/clientCertificate.js +115 -0
  38. package/dist/server/plugins/dev-server.js +33 -0
  39. package/dist/server/plugins/inputs/action.js +54 -0
  40. package/dist/server/plugins/inputs/away.js +20 -0
  41. package/dist/server/plugins/inputs/ban.js +45 -0
  42. package/dist/server/plugins/inputs/blow.js +44 -0
  43. package/dist/server/plugins/inputs/connect.js +41 -0
  44. package/dist/server/plugins/inputs/ctcp.js +29 -0
  45. package/dist/server/plugins/inputs/disconnect.js +15 -0
  46. package/dist/server/plugins/inputs/ignore.js +74 -0
  47. package/dist/server/plugins/inputs/ignorelist.js +50 -0
  48. package/dist/server/plugins/inputs/index.js +105 -0
  49. package/dist/server/plugins/inputs/invite.js +31 -0
  50. package/dist/server/plugins/inputs/kick.js +26 -0
  51. package/dist/server/plugins/inputs/kill.js +13 -0
  52. package/dist/server/plugins/inputs/list.js +12 -0
  53. package/dist/server/plugins/inputs/mode.js +55 -0
  54. package/dist/server/plugins/inputs/msg.js +106 -0
  55. package/dist/server/plugins/inputs/mute.js +56 -0
  56. package/dist/server/plugins/inputs/nick.js +55 -0
  57. package/dist/server/plugins/inputs/notice.js +42 -0
  58. package/dist/server/plugins/inputs/part.js +46 -0
  59. package/dist/server/plugins/inputs/quit.js +27 -0
  60. package/dist/server/plugins/inputs/raw.js +13 -0
  61. package/dist/server/plugins/inputs/rejoin.js +25 -0
  62. package/dist/server/plugins/inputs/topic.js +24 -0
  63. package/dist/server/plugins/inputs/whois.js +19 -0
  64. package/dist/server/plugins/irc-events/away.js +59 -0
  65. package/dist/server/plugins/irc-events/cap.js +62 -0
  66. package/dist/server/plugins/irc-events/chghost.js +29 -0
  67. package/dist/server/plugins/irc-events/connection.js +152 -0
  68. package/dist/server/plugins/irc-events/ctcp.js +72 -0
  69. package/dist/server/plugins/irc-events/error.js +80 -0
  70. package/dist/server/plugins/irc-events/help.js +21 -0
  71. package/dist/server/plugins/irc-events/info.js +21 -0
  72. package/dist/server/plugins/irc-events/invite.js +27 -0
  73. package/dist/server/plugins/irc-events/join.js +53 -0
  74. package/dist/server/plugins/irc-events/kick.js +39 -0
  75. package/dist/server/plugins/irc-events/link.js +442 -0
  76. package/dist/server/plugins/irc-events/list.js +47 -0
  77. package/dist/server/plugins/irc-events/message.js +187 -0
  78. package/dist/server/plugins/irc-events/mode.js +124 -0
  79. package/dist/server/plugins/irc-events/modelist.js +67 -0
  80. package/dist/server/plugins/irc-events/motd.js +29 -0
  81. package/dist/server/plugins/irc-events/names.js +21 -0
  82. package/dist/server/plugins/irc-events/nick.js +45 -0
  83. package/dist/server/plugins/irc-events/part.js +35 -0
  84. package/dist/server/plugins/irc-events/quit.js +32 -0
  85. package/dist/server/plugins/irc-events/sasl.js +26 -0
  86. package/dist/server/plugins/irc-events/topic.js +42 -0
  87. package/dist/server/plugins/irc-events/unhandled.js +31 -0
  88. package/dist/server/plugins/irc-events/welcome.js +22 -0
  89. package/dist/server/plugins/irc-events/whois.js +57 -0
  90. package/dist/server/plugins/messageStorage/sqlite.js +454 -0
  91. package/dist/server/plugins/messageStorage/text.js +124 -0
  92. package/dist/server/plugins/packages/index.js +200 -0
  93. package/dist/server/plugins/packages/publicClient.js +66 -0
  94. package/dist/server/plugins/packages/themes.js +61 -0
  95. package/dist/server/plugins/storage.js +88 -0
  96. package/dist/server/plugins/sts.js +85 -0
  97. package/dist/server/plugins/uploader.js +267 -0
  98. package/dist/server/plugins/webpush.js +99 -0
  99. package/dist/server/server.js +857 -0
  100. package/dist/server/storageCleaner.js +131 -0
  101. package/dist/server/utils/fish.js +432 -0
  102. package/dist/shared/irc.js +19 -0
  103. package/dist/shared/linkify.js +81 -0
  104. package/dist/shared/types/chan.js +22 -0
  105. package/dist/shared/types/changelog.js +2 -0
  106. package/dist/shared/types/config.js +2 -0
  107. package/dist/shared/types/mention.js +2 -0
  108. package/dist/shared/types/msg.js +34 -0
  109. package/dist/shared/types/network.js +2 -0
  110. package/dist/shared/types/storage.js +2 -0
  111. package/dist/shared/types/user.js +2 -0
  112. package/dist/webpack.config.js +224 -0
  113. package/index.js +38 -0
  114. package/package.json +174 -0
  115. package/public/audio/pop.wav +0 -0
  116. package/public/css/style.css +12 -0
  117. package/public/css/style.css.map +1 -0
  118. package/public/favicon.ico +0 -0
  119. package/public/fonts/fa-solid-900.woff +0 -0
  120. package/public/fonts/fa-solid-900.woff2 +0 -0
  121. package/public/img/favicon-alerted.ico +0 -0
  122. package/public/img/icon-alerted-black-transparent-bg-72x72px.png +0 -0
  123. package/public/img/icon-alerted-grey-bg-192x192px.png +0 -0
  124. package/public/img/icon-black-transparent-bg.svg +1 -0
  125. package/public/img/logo-grey-bg-120x120px.png +0 -0
  126. package/public/img/logo-grey-bg-152x152px.png +0 -0
  127. package/public/img/logo-grey-bg-167x167px.png +0 -0
  128. package/public/img/logo-grey-bg-180x180px.png +0 -0
  129. package/public/img/logo-grey-bg-192x192px.png +0 -0
  130. package/public/img/logo-grey-bg-512x512px.png +0 -0
  131. package/public/img/logo-grey-bg.svg +1 -0
  132. package/public/img/logo-horizontal-transparent-bg-inverted.svg +1 -0
  133. package/public/img/logo-horizontal-transparent-bg.svg +1 -0
  134. package/public/img/logo-transparent-bg-inverted.svg +1 -0
  135. package/public/img/logo-transparent-bg.svg +1 -0
  136. package/public/img/logo-vertical-transparent-bg-inverted.svg +1 -0
  137. package/public/img/logo-vertical-transparent-bg.svg +1 -0
  138. package/public/js/bundle.js +2 -0
  139. package/public/js/bundle.js.map +1 -0
  140. package/public/js/bundle.vendor.js +3 -0
  141. package/public/js/bundle.vendor.js.LICENSE.txt +18 -0
  142. package/public/js/bundle.vendor.js.map +1 -0
  143. package/public/js/loading-error-handlers.js +1 -0
  144. package/public/robots.txt +2 -0
  145. package/public/service-worker.js +1 -0
  146. package/public/thelounge.webmanifest +53 -0
  147. package/public/themes/default.css +35 -0
  148. package/public/themes/morning.css +183 -0
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const config_1 = __importDefault(require("../config"));
7
+ const busboy_1 = __importDefault(require("@fastify/busboy"));
8
+ const uuid_1 = require("uuid");
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const file_type_1 = __importDefault(require("file-type"));
12
+ const read_chunk_1 = __importDefault(require("read-chunk"));
13
+ const crypto_1 = __importDefault(require("crypto"));
14
+ const is_utf8_1 = __importDefault(require("is-utf8"));
15
+ const log_1 = __importDefault(require("../log"));
16
+ const content_disposition_1 = __importDefault(require("content-disposition"));
17
+ // Map of allowed mime types to their respecive default filenames
18
+ // that will be rendered in browser without forcing them to be downloaded
19
+ const inlineContentDispositionTypes = {
20
+ "application/ogg": "media.ogx",
21
+ "audio/midi": "audio.midi",
22
+ "audio/mpeg": "audio.mp3",
23
+ "audio/ogg": "audio.ogg",
24
+ "audio/vnd.wave": "audio.wav",
25
+ "audio/x-flac": "audio.flac",
26
+ "audio/x-m4a": "audio.m4a",
27
+ "image/bmp": "image.bmp",
28
+ "image/gif": "image.gif",
29
+ "image/jpeg": "image.jpg",
30
+ "image/png": "image.png",
31
+ "image/webp": "image.webp",
32
+ "image/avif": "image.avif",
33
+ "image/jxl": "image.jxl",
34
+ "text/plain": "text.txt",
35
+ "video/mp4": "video.mp4",
36
+ "video/ogg": "video.ogv",
37
+ "video/webm": "video.webm",
38
+ };
39
+ const uploadTokens = new Map();
40
+ class Uploader {
41
+ constructor(socket) {
42
+ socket.on("upload:auth", () => {
43
+ const token = (0, uuid_1.v4)();
44
+ socket.emit("upload:auth", token);
45
+ // Invalidate the token in one minute
46
+ const timeout = Uploader.createTokenTimeout(token);
47
+ uploadTokens.set(token, timeout);
48
+ });
49
+ socket.on("upload:ping", (token) => {
50
+ if (typeof token !== "string") {
51
+ return;
52
+ }
53
+ let timeout = uploadTokens.get(token);
54
+ if (!timeout) {
55
+ return;
56
+ }
57
+ clearTimeout(timeout);
58
+ timeout = Uploader.createTokenTimeout(token);
59
+ uploadTokens.set(token, timeout);
60
+ });
61
+ }
62
+ static createTokenTimeout(token) {
63
+ return setTimeout(() => uploadTokens.delete(token), 60 * 1000);
64
+ }
65
+ // TODO: type
66
+ static router(express) {
67
+ express.get("/uploads/:name/:slug*?", Uploader.routeGetFile);
68
+ express.post("/uploads/new/:token", Uploader.routeUploadFile);
69
+ }
70
+ static async routeGetFile(req, res) {
71
+ const name = req.params.name;
72
+ const nameRegex = /^[0-9a-f]{16}$/;
73
+ if (!nameRegex.test(name)) {
74
+ return res.status(404).send("Not found");
75
+ }
76
+ const folder = name.substring(0, 2);
77
+ const uploadPath = config_1.default.getFileUploadPath();
78
+ const filePath = path_1.default.join(uploadPath, folder, name);
79
+ let detectedMimeType = await Uploader.getFileType(filePath);
80
+ // doesn't exist
81
+ if (detectedMimeType === null) {
82
+ return res.status(404).send("Not found");
83
+ }
84
+ // Force a download in the browser if it's not an allowed type (binary or otherwise unknown)
85
+ let slug = req.params.slug;
86
+ const isInline = detectedMimeType in inlineContentDispositionTypes;
87
+ let disposition = isInline ? "inline" : "attachment";
88
+ if (!slug && isInline) {
89
+ slug = inlineContentDispositionTypes[detectedMimeType];
90
+ }
91
+ if (slug) {
92
+ disposition = (0, content_disposition_1.default)(slug.trim(), {
93
+ fallback: false,
94
+ type: disposition,
95
+ });
96
+ }
97
+ // Send a more common mime type for audio files
98
+ // so that browsers can play them correctly
99
+ if (detectedMimeType === "audio/vnd.wave") {
100
+ detectedMimeType = "audio/wav";
101
+ }
102
+ else if (detectedMimeType === "audio/x-flac") {
103
+ detectedMimeType = "audio/flac";
104
+ }
105
+ else if (detectedMimeType === "audio/x-m4a") {
106
+ detectedMimeType = "audio/mp4";
107
+ }
108
+ else if (detectedMimeType === "video/quicktime") {
109
+ detectedMimeType = "video/mp4";
110
+ }
111
+ res.setHeader("Content-Disposition", disposition);
112
+ res.setHeader("Cache-Control", "max-age=86400");
113
+ res.contentType(detectedMimeType);
114
+ return res.sendFile(filePath);
115
+ }
116
+ static routeUploadFile(req, res) {
117
+ let busboyInstance;
118
+ let uploadUrl;
119
+ let randomName;
120
+ let destDir;
121
+ let destPath;
122
+ let streamWriter;
123
+ const doneCallback = () => {
124
+ // detach the stream and drain any remaining data
125
+ if (busboyInstance) {
126
+ req.unpipe(busboyInstance);
127
+ req.on("readable", req.read.bind(req));
128
+ busboyInstance.removeAllListeners();
129
+ busboyInstance = null;
130
+ }
131
+ // close the output file stream
132
+ if (streamWriter) {
133
+ streamWriter.end();
134
+ streamWriter = null;
135
+ }
136
+ };
137
+ const abortWithError = (err) => {
138
+ doneCallback();
139
+ // if we ended up erroring out, delete the output file from disk
140
+ if (destPath && fs_1.default.existsSync(destPath)) {
141
+ fs_1.default.unlinkSync(destPath);
142
+ destPath = null;
143
+ }
144
+ return res.status(400).json({ error: err.message });
145
+ };
146
+ // if the authentication token is incorrect, bail out
147
+ if (uploadTokens.delete(req.params.token) !== true) {
148
+ return abortWithError(Error("Invalid upload token"));
149
+ }
150
+ // if the request does not contain any body data, bail out
151
+ if (req.headers["content-length"] && parseInt(req.headers["content-length"]) < 1) {
152
+ return abortWithError(Error("Length Required"));
153
+ }
154
+ // Only allow multipart, as busboy can throw an error on unsupported types
155
+ if (!(req.headers["content-type"] &&
156
+ req.headers["content-type"].startsWith("multipart/form-data"))) {
157
+ return abortWithError(Error("Unsupported Content Type"));
158
+ }
159
+ // create a new busboy processor, it is wrapped in try/catch
160
+ // because it can throw on malformed headers
161
+ try {
162
+ busboyInstance = new busboy_1.default({
163
+ headers: req.headers,
164
+ limits: {
165
+ files: 1, // only allow one file per upload
166
+ fileSize: Uploader.getMaxFileSize(),
167
+ },
168
+ });
169
+ }
170
+ catch (err) {
171
+ return abortWithError(err);
172
+ }
173
+ // Any error or limit from busboy will abort the upload with an error
174
+ busboyInstance.on("error", abortWithError);
175
+ busboyInstance.on("partsLimit", () => abortWithError(Error("Parts limit reached")));
176
+ busboyInstance.on("filesLimit", () => abortWithError(Error("Files limit reached")));
177
+ busboyInstance.on("fieldsLimit", () => abortWithError(Error("Fields limit reached")));
178
+ // generate a random output filename for the file
179
+ // we use do/while loop to prevent the rare case of generating a file name
180
+ // that already exists on disk
181
+ do {
182
+ randomName = crypto_1.default.randomBytes(8).toString("hex");
183
+ destDir = path_1.default.join(config_1.default.getFileUploadPath(), randomName.substring(0, 2));
184
+ destPath = path_1.default.join(destDir, randomName);
185
+ } while (fs_1.default.existsSync(destPath));
186
+ // we split the filename into subdirectories (by taking 2 letters from the beginning)
187
+ // this helps avoid file system and certain tooling limitations when there are
188
+ // too many files on one folder
189
+ try {
190
+ fs_1.default.mkdirSync(destDir, { recursive: true });
191
+ }
192
+ catch (err) {
193
+ log_1.default.error(`Error ensuring ${destDir} exists for uploads: ${err.message}`);
194
+ return abortWithError(err);
195
+ }
196
+ // Open a file stream for writing
197
+ streamWriter = fs_1.default.createWriteStream(destPath);
198
+ streamWriter.on("error", abortWithError);
199
+ busboyInstance.on("file", (fieldname, fileStream, filename) => {
200
+ uploadUrl = `${randomName}/${encodeURIComponent(filename)}`;
201
+ if (config_1.default.values.fileUpload.baseUrl) {
202
+ uploadUrl = new URL(uploadUrl, config_1.default.values.fileUpload.baseUrl).toString();
203
+ }
204
+ else {
205
+ uploadUrl = `uploads/${uploadUrl}`;
206
+ }
207
+ // if the busboy data stream errors out or goes over the file size limit
208
+ // abort the processing with an error
209
+ // @ts-expect-error Argument of type '(err: any) => Response<any, Record<string, any>>' is not assignable to parameter of type '{ (err: any): Response<any, Record<string, any>>; (): void; }'.ts(2345)
210
+ fileStream.on("error", abortWithError);
211
+ fileStream.on("limit", () => {
212
+ fileStream.unpipe(streamWriter);
213
+ fileStream.on("readable", fileStream.read.bind(fileStream));
214
+ return abortWithError(Error("File size limit reached"));
215
+ });
216
+ // Attempt to write the stream to file
217
+ fileStream.pipe(streamWriter);
218
+ });
219
+ busboyInstance.on("finish", () => {
220
+ doneCallback();
221
+ if (!uploadUrl) {
222
+ return res.status(400).json({ error: "Missing file" });
223
+ }
224
+ // upload was done, send the generated file url to the client
225
+ res.status(200).json({
226
+ url: uploadUrl,
227
+ });
228
+ });
229
+ // pipe request body to busboy for processing
230
+ return req.pipe(busboyInstance);
231
+ }
232
+ static getMaxFileSize() {
233
+ const configOption = config_1.default.values.fileUpload.maxFileSize;
234
+ // Busboy uses Infinity to allow unlimited file size
235
+ if (configOption < 1) {
236
+ return Infinity;
237
+ }
238
+ // maxFileSize is in bytes, but config option is passed in as KB
239
+ return configOption * 1024;
240
+ }
241
+ // Returns null if an error occurred (e.g. file not found)
242
+ // Returns a string with the type otherwise
243
+ static async getFileType(filePath) {
244
+ try {
245
+ const buffer = await (0, read_chunk_1.default)(filePath, 0, 5120);
246
+ // returns {ext, mime} if found, null if not.
247
+ const file = await file_type_1.default.fromBuffer(buffer);
248
+ // if a file type was detected correctly, return it
249
+ if (file) {
250
+ return file.mime;
251
+ }
252
+ // if the buffer is a valid UTF-8 buffer, use text/plain
253
+ if ((0, is_utf8_1.default)(buffer)) {
254
+ return "text/plain";
255
+ }
256
+ // otherwise assume it's random binary data
257
+ return "application/octet-stream";
258
+ }
259
+ catch (e) {
260
+ if (e.code !== "ENOENT") {
261
+ log_1.default.warn(`Failed to read ${filePath}: ${e.message}`);
262
+ }
263
+ }
264
+ return null;
265
+ }
266
+ }
267
+ exports.default = Uploader;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const lodash_1 = __importDefault(require("lodash"));
30
+ const log_1 = __importDefault(require("../log"));
31
+ const fs_1 = __importDefault(require("fs"));
32
+ const path_1 = __importDefault(require("path"));
33
+ const web_push_1 = __importDefault(require("web-push"));
34
+ const config_1 = __importDefault(require("../config"));
35
+ const os = __importStar(require("os"));
36
+ class WebPush {
37
+ vapidKeys;
38
+ constructor() {
39
+ const vapidPath = path_1.default.join(config_1.default.getHomePath(), "vapid.json");
40
+ let vapidStat = undefined;
41
+ try {
42
+ vapidStat = fs_1.default.statSync(vapidPath);
43
+ }
44
+ catch {
45
+ // ignored on purpose, node v14.17.0 will give us {throwIfNoEntry: false}
46
+ }
47
+ if (vapidStat) {
48
+ const isWorldReadable = (vapidStat.mode & 0o004) !== 0;
49
+ if (isWorldReadable) {
50
+ log_1.default.warn(vapidPath, "is world readable.", "The file contains secrets. Please fix the permissions.");
51
+ if (os.platform() !== "win32") {
52
+ log_1.default.warn(`run \`chmod o= "${vapidPath}"\` to correct it.`);
53
+ }
54
+ }
55
+ const data = fs_1.default.readFileSync(vapidPath, "utf-8");
56
+ const parsedData = JSON.parse(data);
57
+ if (typeof parsedData.publicKey === "string" &&
58
+ typeof parsedData.privateKey === "string") {
59
+ this.vapidKeys = {
60
+ publicKey: parsedData.publicKey,
61
+ privateKey: parsedData.privateKey,
62
+ };
63
+ }
64
+ }
65
+ if (!this.vapidKeys) {
66
+ this.vapidKeys = web_push_1.default.generateVAPIDKeys();
67
+ fs_1.default.writeFileSync(vapidPath, JSON.stringify(this.vapidKeys, null, "\t"), {
68
+ mode: 0o600,
69
+ });
70
+ log_1.default.info("New VAPID key pair has been generated for use with push subscription.");
71
+ }
72
+ web_push_1.default.setVapidDetails("https://github.com/thelounge/thelounge", this.vapidKeys.publicKey, this.vapidKeys.privateKey);
73
+ }
74
+ push(client, payload, onlyToOffline) {
75
+ lodash_1.default.forOwn(client.config.sessions, ({ pushSubscription }, token) => {
76
+ if (pushSubscription) {
77
+ if (onlyToOffline && lodash_1.default.find(client.attachedClients, { token }) !== undefined) {
78
+ return;
79
+ }
80
+ this.pushSingle(client, pushSubscription, payload);
81
+ }
82
+ });
83
+ }
84
+ pushSingle(client, subscription, payload) {
85
+ web_push_1.default.sendNotification(subscription, JSON.stringify(payload)).catch((error) => {
86
+ if (error.statusCode >= 400 && error.statusCode < 500) {
87
+ log_1.default.warn(`WebPush subscription for ${client.name} returned an error (${String(error.statusCode)}), removing subscription`);
88
+ lodash_1.default.forOwn(client.config.sessions, ({ pushSubscription }, token) => {
89
+ if (pushSubscription && pushSubscription.endpoint === subscription.endpoint) {
90
+ client.unregisterPushSubscription(token);
91
+ }
92
+ });
93
+ return;
94
+ }
95
+ log_1.default.error(`WebPush Error (${String(error)})`);
96
+ });
97
+ }
98
+ }
99
+ exports.default = WebPush;