@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.
- package/.thelounge_home +1 -0
- package/LICENSE +22 -0
- package/README.md +95 -0
- package/client/index.html.tpl +69 -0
- package/dist/defaults/config.js +465 -0
- package/dist/package.json +174 -0
- package/dist/server/client.js +678 -0
- package/dist/server/clientManager.js +220 -0
- package/dist/server/command-line/index.js +85 -0
- package/dist/server/command-line/install.js +123 -0
- package/dist/server/command-line/outdated.js +30 -0
- package/dist/server/command-line/start.js +34 -0
- package/dist/server/command-line/storage.js +103 -0
- package/dist/server/command-line/uninstall.js +40 -0
- package/dist/server/command-line/upgrade.js +64 -0
- package/dist/server/command-line/users/add.js +67 -0
- package/dist/server/command-line/users/edit.js +39 -0
- package/dist/server/command-line/users/index.js +17 -0
- package/dist/server/command-line/users/list.js +53 -0
- package/dist/server/command-line/users/remove.js +37 -0
- package/dist/server/command-line/users/reset.js +64 -0
- package/dist/server/command-line/utils.js +177 -0
- package/dist/server/config.js +138 -0
- package/dist/server/helper.js +161 -0
- package/dist/server/identification.js +139 -0
- package/dist/server/index.js +3 -0
- package/dist/server/log.js +35 -0
- package/dist/server/models/chan.js +275 -0
- package/dist/server/models/msg.js +92 -0
- package/dist/server/models/network.js +546 -0
- package/dist/server/models/prefix.js +31 -0
- package/dist/server/models/user.js +42 -0
- package/dist/server/plugins/auth/ldap.js +188 -0
- package/dist/server/plugins/auth/local.js +41 -0
- package/dist/server/plugins/auth.js +70 -0
- package/dist/server/plugins/changelog.js +103 -0
- package/dist/server/plugins/clientCertificate.js +115 -0
- package/dist/server/plugins/dev-server.js +33 -0
- package/dist/server/plugins/inputs/action.js +54 -0
- package/dist/server/plugins/inputs/away.js +20 -0
- package/dist/server/plugins/inputs/ban.js +45 -0
- package/dist/server/plugins/inputs/blow.js +44 -0
- package/dist/server/plugins/inputs/connect.js +41 -0
- package/dist/server/plugins/inputs/ctcp.js +29 -0
- package/dist/server/plugins/inputs/disconnect.js +15 -0
- package/dist/server/plugins/inputs/ignore.js +74 -0
- package/dist/server/plugins/inputs/ignorelist.js +50 -0
- package/dist/server/plugins/inputs/index.js +105 -0
- package/dist/server/plugins/inputs/invite.js +31 -0
- package/dist/server/plugins/inputs/kick.js +26 -0
- package/dist/server/plugins/inputs/kill.js +13 -0
- package/dist/server/plugins/inputs/list.js +12 -0
- package/dist/server/plugins/inputs/mode.js +55 -0
- package/dist/server/plugins/inputs/msg.js +106 -0
- package/dist/server/plugins/inputs/mute.js +56 -0
- package/dist/server/plugins/inputs/nick.js +55 -0
- package/dist/server/plugins/inputs/notice.js +42 -0
- package/dist/server/plugins/inputs/part.js +46 -0
- package/dist/server/plugins/inputs/quit.js +27 -0
- package/dist/server/plugins/inputs/raw.js +13 -0
- package/dist/server/plugins/inputs/rejoin.js +25 -0
- package/dist/server/plugins/inputs/topic.js +24 -0
- package/dist/server/plugins/inputs/whois.js +19 -0
- package/dist/server/plugins/irc-events/away.js +59 -0
- package/dist/server/plugins/irc-events/cap.js +62 -0
- package/dist/server/plugins/irc-events/chghost.js +29 -0
- package/dist/server/plugins/irc-events/connection.js +152 -0
- package/dist/server/plugins/irc-events/ctcp.js +72 -0
- package/dist/server/plugins/irc-events/error.js +80 -0
- package/dist/server/plugins/irc-events/help.js +21 -0
- package/dist/server/plugins/irc-events/info.js +21 -0
- package/dist/server/plugins/irc-events/invite.js +27 -0
- package/dist/server/plugins/irc-events/join.js +53 -0
- package/dist/server/plugins/irc-events/kick.js +39 -0
- package/dist/server/plugins/irc-events/link.js +442 -0
- package/dist/server/plugins/irc-events/list.js +47 -0
- package/dist/server/plugins/irc-events/message.js +187 -0
- package/dist/server/plugins/irc-events/mode.js +124 -0
- package/dist/server/plugins/irc-events/modelist.js +67 -0
- package/dist/server/plugins/irc-events/motd.js +29 -0
- package/dist/server/plugins/irc-events/names.js +21 -0
- package/dist/server/plugins/irc-events/nick.js +45 -0
- package/dist/server/plugins/irc-events/part.js +35 -0
- package/dist/server/plugins/irc-events/quit.js +32 -0
- package/dist/server/plugins/irc-events/sasl.js +26 -0
- package/dist/server/plugins/irc-events/topic.js +42 -0
- package/dist/server/plugins/irc-events/unhandled.js +31 -0
- package/dist/server/plugins/irc-events/welcome.js +22 -0
- package/dist/server/plugins/irc-events/whois.js +57 -0
- package/dist/server/plugins/messageStorage/sqlite.js +454 -0
- package/dist/server/plugins/messageStorage/text.js +124 -0
- package/dist/server/plugins/packages/index.js +200 -0
- package/dist/server/plugins/packages/publicClient.js +66 -0
- package/dist/server/plugins/packages/themes.js +61 -0
- package/dist/server/plugins/storage.js +88 -0
- package/dist/server/plugins/sts.js +85 -0
- package/dist/server/plugins/uploader.js +267 -0
- package/dist/server/plugins/webpush.js +99 -0
- package/dist/server/server.js +857 -0
- package/dist/server/storageCleaner.js +131 -0
- package/dist/server/utils/fish.js +432 -0
- package/dist/shared/irc.js +19 -0
- package/dist/shared/linkify.js +81 -0
- package/dist/shared/types/chan.js +22 -0
- package/dist/shared/types/changelog.js +2 -0
- package/dist/shared/types/config.js +2 -0
- package/dist/shared/types/mention.js +2 -0
- package/dist/shared/types/msg.js +34 -0
- package/dist/shared/types/network.js +2 -0
- package/dist/shared/types/storage.js +2 -0
- package/dist/shared/types/user.js +2 -0
- package/dist/webpack.config.js +224 -0
- package/index.js +38 -0
- package/package.json +174 -0
- package/public/audio/pop.wav +0 -0
- package/public/css/style.css +12 -0
- package/public/css/style.css.map +1 -0
- package/public/favicon.ico +0 -0
- package/public/fonts/fa-solid-900.woff +0 -0
- package/public/fonts/fa-solid-900.woff2 +0 -0
- package/public/img/favicon-alerted.ico +0 -0
- package/public/img/icon-alerted-black-transparent-bg-72x72px.png +0 -0
- package/public/img/icon-alerted-grey-bg-192x192px.png +0 -0
- package/public/img/icon-black-transparent-bg.svg +1 -0
- package/public/img/logo-grey-bg-120x120px.png +0 -0
- package/public/img/logo-grey-bg-152x152px.png +0 -0
- package/public/img/logo-grey-bg-167x167px.png +0 -0
- package/public/img/logo-grey-bg-180x180px.png +0 -0
- package/public/img/logo-grey-bg-192x192px.png +0 -0
- package/public/img/logo-grey-bg-512x512px.png +0 -0
- package/public/img/logo-grey-bg.svg +1 -0
- package/public/img/logo-horizontal-transparent-bg-inverted.svg +1 -0
- package/public/img/logo-horizontal-transparent-bg.svg +1 -0
- package/public/img/logo-transparent-bg-inverted.svg +1 -0
- package/public/img/logo-transparent-bg.svg +1 -0
- package/public/img/logo-vertical-transparent-bg-inverted.svg +1 -0
- package/public/img/logo-vertical-transparent-bg.svg +1 -0
- package/public/js/bundle.js +2 -0
- package/public/js/bundle.js.map +1 -0
- package/public/js/bundle.vendor.js +3 -0
- package/public/js/bundle.vendor.js.LICENSE.txt +18 -0
- package/public/js/bundle.vendor.js.map +1 -0
- package/public/js/loading-error-handlers.js +1 -0
- package/public/robots.txt +2 -0
- package/public/service-worker.js +1 -0
- package/public/thelounge.webmanifest +53 -0
- package/public/themes/default.css +35 -0
- 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;
|