@vex-chat/spire 1.0.0 → 1.0.1
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/README.md +37 -38
- package/dist/ClientManager.d.ts +1 -3
- package/dist/ClientManager.js +6 -48
- package/dist/ClientManager.js.map +1 -1
- package/dist/Database.d.ts +25 -5
- package/dist/Database.js +54 -16
- package/dist/Database.js.map +1 -1
- package/dist/Spire.d.ts +0 -3
- package/dist/Spire.js +35 -73
- package/dist/Spire.js.map +1 -1
- package/dist/__tests__/Database.spec.js +0 -14
- package/dist/__tests__/Database.spec.js.map +1 -1
- package/dist/db/schema.d.ts +1 -0
- package/dist/migrations/2026-04-14_argon2id-password-hashing.d.ts +3 -0
- package/dist/migrations/2026-04-14_argon2id-password-hashing.js +12 -0
- package/dist/migrations/2026-04-14_argon2id-password-hashing.js.map +1 -0
- package/dist/run.js +0 -1
- package/dist/run.js.map +1 -1
- package/dist/server/avatar.d.ts +1 -3
- package/dist/server/avatar.js +7 -11
- package/dist/server/avatar.js.map +1 -1
- package/dist/server/errors.d.ts +3 -6
- package/dist/server/errors.js +2 -28
- package/dist/server/errors.js.map +1 -1
- package/dist/server/file.d.ts +1 -2
- package/dist/server/file.js +5 -7
- package/dist/server/file.js.map +1 -1
- package/dist/server/index.d.ts +1 -2
- package/dist/server/index.js +22 -33
- package/dist/server/index.js.map +1 -1
- package/dist/server/invite.d.ts +1 -2
- package/dist/server/invite.js +1 -1
- package/dist/server/invite.js.map +1 -1
- package/dist/server/rateLimit.d.ts +3 -3
- package/dist/server/rateLimit.js +6 -6
- package/dist/server/rateLimit.js.map +1 -1
- package/dist/server/user.d.ts +1 -2
- package/dist/server/user.js +3 -5
- package/dist/server/user.js.map +1 -1
- package/dist/utils/jwtSecret.d.ts +3 -3
- package/dist/utils/jwtSecret.js +5 -6
- package/dist/utils/jwtSecret.js.map +1 -1
- package/dist/utils/loadEnv.js +1 -1
- package/dist/utils/loadEnv.js.map +1 -1
- package/package.json +6 -4
- package/src/ClientManager.ts +6 -58
- package/src/Database.ts +83 -21
- package/src/Spire.ts +37 -108
- package/src/__tests__/Database.spec.ts +0 -17
- package/src/db/schema.ts +1 -0
- package/src/migrations/2026-04-14_argon2id-password-hashing.ts +18 -0
- package/src/run.ts +0 -1
- package/src/server/avatar.ts +7 -14
- package/src/server/errors.ts +3 -32
- package/src/server/file.ts +5 -10
- package/src/server/index.ts +23 -37
- package/src/server/invite.ts +0 -2
- package/src/server/rateLimit.ts +6 -6
- package/src/server/user.ts +2 -6
- package/src/utils/jwtSecret.ts +5 -6
- package/src/utils/loadEnv.ts +1 -1
- package/dist/utils/createLogger.d.ts +0 -5
- package/dist/utils/createLogger.js +0 -35
- package/dist/utils/createLogger.js.map +0 -1
- package/src/utils/createLogger.ts +0 -47
package/src/server/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Database } from "../Database.ts";
|
|
2
2
|
import type { Emoji } from "@vex-chat/types";
|
|
3
|
-
import type winston from "winston";
|
|
4
3
|
|
|
5
4
|
import * as fs from "node:fs";
|
|
6
5
|
import * as fsp from "node:fs/promises";
|
|
@@ -15,7 +14,6 @@ import cors from "cors";
|
|
|
15
14
|
import { fileTypeFromBuffer, fileTypeFromFile } from "file-type";
|
|
16
15
|
import helmet from "helmet";
|
|
17
16
|
import jwt from "jsonwebtoken";
|
|
18
|
-
import morgan from "morgan";
|
|
19
17
|
import multer from "multer";
|
|
20
18
|
import parseDuration from "parse-duration";
|
|
21
19
|
import { stringify as uuidStringify } from "uuid";
|
|
@@ -156,8 +154,6 @@ export const msgpackParser: express.RequestHandler = (req, res, next) => {
|
|
|
156
154
|
next();
|
|
157
155
|
};
|
|
158
156
|
|
|
159
|
-
const isProduction = process.env["NODE_ENV"] === "production";
|
|
160
|
-
|
|
161
157
|
const directories = ["files", "avatars"];
|
|
162
158
|
for (const dir of directories) {
|
|
163
159
|
if (!fs.existsSync(dir)) {
|
|
@@ -168,7 +164,6 @@ for (const dir of directories) {
|
|
|
168
164
|
export const initApp = (
|
|
169
165
|
api: express.Application,
|
|
170
166
|
db: Database,
|
|
171
|
-
log: winston.Logger,
|
|
172
167
|
tokenValidator: (key: string, scope: TokenScopes) => boolean,
|
|
173
168
|
signKeys: KeyPair,
|
|
174
169
|
notify: (
|
|
@@ -180,10 +175,10 @@ export const initApp = (
|
|
|
180
175
|
) => void,
|
|
181
176
|
) => {
|
|
182
177
|
// INIT ROUTERS
|
|
183
|
-
const userRouter = getUserRouter(db,
|
|
184
|
-
const fileRouter = getFileRouter(db
|
|
185
|
-
const avatarRouter = getAvatarRouter(
|
|
186
|
-
const inviteRouter = getInviteRouter(db,
|
|
178
|
+
const userRouter = getUserRouter(db, tokenValidator);
|
|
179
|
+
const fileRouter = getFileRouter(db);
|
|
180
|
+
const avatarRouter = getAvatarRouter();
|
|
181
|
+
const inviteRouter = getInviteRouter(db, tokenValidator, notify);
|
|
187
182
|
|
|
188
183
|
// MIDDLEWARE
|
|
189
184
|
// Global per-IP rate limit is the FIRST middleware so a flooded
|
|
@@ -197,18 +192,18 @@ export const initApp = (
|
|
|
197
192
|
type: "application/msgpack",
|
|
198
193
|
}),
|
|
199
194
|
);
|
|
200
|
-
|
|
201
|
-
api.use(helmet());
|
|
202
|
-
}
|
|
195
|
+
api.use(helmet());
|
|
203
196
|
api.use(msgpackParser);
|
|
204
197
|
api.use(checkAuth);
|
|
205
198
|
api.use(checkDevice);
|
|
206
199
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
200
|
+
const allowedOrigins = process.env["CORS_ORIGINS"]?.split(",") ?? [];
|
|
201
|
+
api.use(
|
|
202
|
+
cors({
|
|
203
|
+
credentials: true,
|
|
204
|
+
origin: allowedOrigins.length > 0 ? allowedOrigins : false,
|
|
205
|
+
}),
|
|
206
|
+
);
|
|
212
207
|
|
|
213
208
|
api.get("/server/:id", protect, async (req, res) => {
|
|
214
209
|
const server = await db.retrieveServer(getParam(req, "id"));
|
|
@@ -259,7 +254,6 @@ export const initApp = (
|
|
|
259
254
|
POWER_LEVELS.INVITE,
|
|
260
255
|
)
|
|
261
256
|
) {
|
|
262
|
-
log.warn("No permission!");
|
|
263
257
|
res.sendStatus(401);
|
|
264
258
|
return;
|
|
265
259
|
}
|
|
@@ -649,8 +643,8 @@ export const initApp = (
|
|
|
649
643
|
res.set("Cache-control", "public, max-age=31536000");
|
|
650
644
|
|
|
651
645
|
const stream = fs.createReadStream(filePath);
|
|
652
|
-
stream.on("error", (
|
|
653
|
-
|
|
646
|
+
stream.on("error", (_err) => {
|
|
647
|
+
// debugger: emoji stream read error
|
|
654
648
|
res.sendStatus(500);
|
|
655
649
|
});
|
|
656
650
|
stream.pipe(res);
|
|
@@ -703,10 +697,11 @@ export const initApp = (
|
|
|
703
697
|
}
|
|
704
698
|
if (!payload.name) {
|
|
705
699
|
res.sendStatus(400);
|
|
700
|
+
return;
|
|
706
701
|
}
|
|
707
702
|
if (Buffer.byteLength(buf) > 256000) {
|
|
708
|
-
log.warn("File too big.");
|
|
709
703
|
res.sendStatus(413);
|
|
704
|
+
return;
|
|
710
705
|
}
|
|
711
706
|
|
|
712
707
|
const mimeType = await fileTypeFromBuffer(buf);
|
|
@@ -730,10 +725,9 @@ export const initApp = (
|
|
|
730
725
|
try {
|
|
731
726
|
// write the file to disk
|
|
732
727
|
await fsp.writeFile("emoji/" + emoji.emojiID, buf);
|
|
733
|
-
log.info("Wrote new emoji " + emoji.emojiID);
|
|
734
728
|
res.send(msgpack.encode(emoji));
|
|
735
|
-
} catch (
|
|
736
|
-
|
|
729
|
+
} catch (_err: unknown) {
|
|
730
|
+
// debugger: emoji write failed
|
|
737
731
|
res.sendStatus(500);
|
|
738
732
|
}
|
|
739
733
|
});
|
|
@@ -786,17 +780,17 @@ export const initApp = (
|
|
|
786
780
|
|
|
787
781
|
if (!payload.name) {
|
|
788
782
|
res.sendStatus(400);
|
|
783
|
+
return;
|
|
789
784
|
}
|
|
790
785
|
|
|
791
786
|
if (!req.file) {
|
|
792
|
-
log.warn("MISSING FILE");
|
|
793
787
|
res.sendStatus(400);
|
|
794
788
|
return;
|
|
795
789
|
}
|
|
796
790
|
|
|
797
791
|
if (Buffer.byteLength(req.file.buffer) > 256000) {
|
|
798
|
-
log.warn("File too big.");
|
|
799
792
|
res.sendStatus(413);
|
|
793
|
+
return;
|
|
800
794
|
}
|
|
801
795
|
|
|
802
796
|
const mimeType = await fileTypeFromBuffer(req.file.buffer);
|
|
@@ -820,10 +814,9 @@ export const initApp = (
|
|
|
820
814
|
try {
|
|
821
815
|
// write the file to disk
|
|
822
816
|
await fsp.writeFile("emoji/" + emoji.emojiID, req.file.buffer);
|
|
823
|
-
log.info("Wrote new emoji " + emoji.emojiID);
|
|
824
817
|
res.send(msgpack.encode(emoji));
|
|
825
|
-
} catch (
|
|
826
|
-
|
|
818
|
+
} catch (_err: unknown) {
|
|
819
|
+
// debugger: emoji write failed
|
|
827
820
|
res.sendStatus(500);
|
|
828
821
|
}
|
|
829
822
|
},
|
|
@@ -844,12 +837,5 @@ export const initApp = (
|
|
|
844
837
|
// (client-safe status + message) and programmer errors (generic 500
|
|
845
838
|
// with full details logged server-side). See src/server/errors.ts
|
|
846
839
|
// for the CWE mapping.
|
|
847
|
-
api.use(errorHandler(
|
|
848
|
-
};
|
|
849
|
-
|
|
850
|
-
/**
|
|
851
|
-
* @ignore
|
|
852
|
-
*/
|
|
853
|
-
const jestRun = () => {
|
|
854
|
-
return process.env["JEST_WORKER_ID"] !== undefined;
|
|
840
|
+
api.use(errorHandler());
|
|
855
841
|
};
|
package/src/server/invite.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Database } from "../Database.ts";
|
|
2
2
|
import type { TokenScopes } from "@vex-chat/types";
|
|
3
|
-
import type winston from "winston";
|
|
4
3
|
|
|
5
4
|
import express from "express";
|
|
6
5
|
|
|
@@ -12,7 +11,6 @@ import { protect } from "./index.ts";
|
|
|
12
11
|
|
|
13
12
|
export const getInviteRouter = (
|
|
14
13
|
db: Database,
|
|
15
|
-
log: winston.Logger,
|
|
16
14
|
tokenValidator: (key: string, scope: TokenScopes) => boolean,
|
|
17
15
|
notify: (
|
|
18
16
|
userID: string,
|
package/src/server/rateLimit.ts
CHANGED
|
@@ -38,14 +38,14 @@ const keyByIp = (req: Request): string => ipKeyGenerator(req.ip ?? "");
|
|
|
38
38
|
/**
|
|
39
39
|
* Global per-IP limiter. Applied app-wide via `api.use(globalLimiter)`.
|
|
40
40
|
*
|
|
41
|
-
*
|
|
41
|
+
* 3000 requests per 15 minutes per client IP. A human chatting via a
|
|
42
42
|
* browser or the libvex client won't come close; a single-host DoS
|
|
43
43
|
* gets throttled quickly.
|
|
44
44
|
*/
|
|
45
45
|
export const globalLimiter = rateLimit({
|
|
46
46
|
keyGenerator: keyByIp,
|
|
47
47
|
legacyHeaders: false,
|
|
48
|
-
limit:
|
|
48
|
+
limit: 3000,
|
|
49
49
|
standardHeaders: "draft-7",
|
|
50
50
|
windowMs: 15 * 60 * 1000,
|
|
51
51
|
});
|
|
@@ -54,7 +54,7 @@ export const globalLimiter = rateLimit({
|
|
|
54
54
|
* Strict auth endpoint limiter. Applied per-route to /auth, /register,
|
|
55
55
|
* and /auth/device.
|
|
56
56
|
*
|
|
57
|
-
*
|
|
57
|
+
* 50 failed attempts per 15 minutes per IP. Successful logins don't
|
|
58
58
|
* count (`skipSuccessfulRequests`), so a normal user doesn't lock
|
|
59
59
|
* themselves out by fat-fingering a password once. Blocks brute force
|
|
60
60
|
* (CWE-307) without harming UX.
|
|
@@ -62,7 +62,7 @@ export const globalLimiter = rateLimit({
|
|
|
62
62
|
export const authLimiter = rateLimit({
|
|
63
63
|
keyGenerator: keyByIp,
|
|
64
64
|
legacyHeaders: false,
|
|
65
|
-
limit:
|
|
65
|
+
limit: 50,
|
|
66
66
|
skipSuccessfulRequests: true,
|
|
67
67
|
standardHeaders: "draft-7",
|
|
68
68
|
windowMs: 15 * 60 * 1000,
|
|
@@ -74,13 +74,13 @@ export const authLimiter = rateLimit({
|
|
|
74
74
|
* upload attempts per minute so an attacker can't force spire to
|
|
75
75
|
* spend CPU/IO on repeated large-body parses.
|
|
76
76
|
*
|
|
77
|
-
*
|
|
77
|
+
* 200 uploads per minute per IP — generous for a chat client (rapid-
|
|
78
78
|
* fire image attachments) but tight enough to shield the disk.
|
|
79
79
|
*/
|
|
80
80
|
export const uploadLimiter = rateLimit({
|
|
81
81
|
keyGenerator: keyByIp,
|
|
82
82
|
legacyHeaders: false,
|
|
83
|
-
limit:
|
|
83
|
+
limit: 200,
|
|
84
84
|
standardHeaders: "draft-7",
|
|
85
85
|
windowMs: 60 * 1000,
|
|
86
86
|
});
|
package/src/server/user.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Database } from "../Database.ts";
|
|
2
|
-
import type winston from "winston";
|
|
3
2
|
|
|
4
3
|
import express from "express";
|
|
5
4
|
|
|
@@ -17,7 +16,6 @@ import { protect } from "./index.ts";
|
|
|
17
16
|
|
|
18
17
|
export const getUserRouter = (
|
|
19
18
|
db: Database,
|
|
20
|
-
log: winston.Logger,
|
|
21
19
|
tokenValidator: (key: string, scope: TokenScopes) => boolean,
|
|
22
20
|
) => {
|
|
23
21
|
const router = express.Router();
|
|
@@ -98,7 +96,6 @@ export const getUserRouter = (
|
|
|
98
96
|
);
|
|
99
97
|
|
|
100
98
|
if (!token) {
|
|
101
|
-
log.warn("Invalid signature on token.");
|
|
102
99
|
res.sendStatus(400);
|
|
103
100
|
return;
|
|
104
101
|
}
|
|
@@ -110,9 +107,8 @@ export const getUserRouter = (
|
|
|
110
107
|
deviceData,
|
|
111
108
|
);
|
|
112
109
|
res.send(msgpack.encode(device));
|
|
113
|
-
} catch (
|
|
114
|
-
|
|
115
|
-
// failed registration due to signkey being taken
|
|
110
|
+
} catch (_err: unknown) {
|
|
111
|
+
// signkey already taken
|
|
116
112
|
res.sendStatus(470);
|
|
117
113
|
return;
|
|
118
114
|
}
|
package/src/utils/jwtSecret.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Returns the JWT signing secret.
|
|
2
|
+
* Returns the dedicated JWT HMAC signing secret.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* This MUST be a separate key from SPK (the Ed25519 server signing key)
|
|
5
|
+
* so that compromise of one does not affect the other.
|
|
6
6
|
*/
|
|
7
7
|
export function getJwtSecret(): string {
|
|
8
|
-
const secret = process.env["JWT_SECRET"]
|
|
8
|
+
const secret = process.env["JWT_SECRET"];
|
|
9
9
|
if (!secret) {
|
|
10
10
|
throw new Error(
|
|
11
|
-
"
|
|
12
|
-
"Set JWT_SECRET (preferred) or SPK in your environment.",
|
|
11
|
+
"JWT_SECRET is not set. Generate one with: node scripts/gen-spk.js",
|
|
13
12
|
);
|
|
14
13
|
}
|
|
15
14
|
return secret;
|
package/src/utils/loadEnv.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { config } from "dotenv";
|
|
|
3
3
|
/* Populate process.env with vars from .env and verify required vars are present. */
|
|
4
4
|
export function loadEnv(): void {
|
|
5
5
|
config();
|
|
6
|
-
const requiredEnvVars: string[] = ["
|
|
6
|
+
const requiredEnvVars: string[] = ["DB_TYPE", "JWT_SECRET", "SPK"];
|
|
7
7
|
for (const required of requiredEnvVars) {
|
|
8
8
|
if (process.env[required] === undefined) {
|
|
9
9
|
process.stderr.write(
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import winston from "winston";
|
|
2
|
-
/**
|
|
3
|
-
* @ignore
|
|
4
|
-
*/
|
|
5
|
-
export function createLogger(logName, logLevel) {
|
|
6
|
-
const logger = winston.createLogger({
|
|
7
|
-
defaultMeta: { service: "vex-" + logName },
|
|
8
|
-
format: winston.format.combine(winston.format.timestamp({
|
|
9
|
-
format: "YYYY-MM-DD HH:mm:ss",
|
|
10
|
-
}), winston.format.errors({ stack: true }), winston.format.splat(), winston.format.json()),
|
|
11
|
-
level: logLevel || "error",
|
|
12
|
-
transports: [
|
|
13
|
-
//
|
|
14
|
-
// - Write all logs with level `error` and below to `error.log`
|
|
15
|
-
// - Write all logs with level `info` and below to `combined.log`
|
|
16
|
-
//
|
|
17
|
-
new winston.transports.File({
|
|
18
|
-
filename: "vex:" + logName + ".log",
|
|
19
|
-
level: "error",
|
|
20
|
-
}),
|
|
21
|
-
],
|
|
22
|
-
});
|
|
23
|
-
//
|
|
24
|
-
// If we're not in production then log to the `console` with the format:
|
|
25
|
-
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
|
|
26
|
-
//
|
|
27
|
-
if (process.env["NODE_ENV"] !== "production" &&
|
|
28
|
-
process.env["NODE_ENV"] !== "test") {
|
|
29
|
-
logger.add(new winston.transports.Console({
|
|
30
|
-
format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
|
|
31
|
-
}));
|
|
32
|
-
}
|
|
33
|
-
return logger;
|
|
34
|
-
}
|
|
35
|
-
//# sourceMappingURL=createLogger.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createLogger.js","sourceRoot":"","sources":["../../src/utils/createLogger.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,QAAiB;IAC3D,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;QAChC,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,EAAE;QAC1C,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAC1B,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;YACrB,MAAM,EAAE,qBAAqB;SAChC,CAAC,EACF,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EACtC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EACtB,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CACxB;QACD,KAAK,EAAE,QAAQ,IAAI,OAAO;QAC1B,UAAU,EAAE;YACR,EAAE;YACF,+DAA+D;YAC/D,iEAAiE;YACjE,EAAE;YACF,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC;gBACxB,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM;gBACnC,KAAK,EAAE,OAAO;aACjB,CAAC;SACL;KACJ,CAAC,CAAC;IACH,EAAE;IACF,wEAAwE;IACxE,gEAAgE;IAChE,EAAE;IACF,IACI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,YAAY;QACxC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,MAAM,EACpC,CAAC;QACC,MAAM,CAAC,GAAG,CACN,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;YAC3B,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAC1B,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,EACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAC1B;SACJ,CAAC,CACL,CAAC;IACN,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC"}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import winston from "winston";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @ignore
|
|
5
|
-
*/
|
|
6
|
-
export function createLogger(logName: string, logLevel?: string) {
|
|
7
|
-
const logger = winston.createLogger({
|
|
8
|
-
defaultMeta: { service: "vex-" + logName },
|
|
9
|
-
format: winston.format.combine(
|
|
10
|
-
winston.format.timestamp({
|
|
11
|
-
format: "YYYY-MM-DD HH:mm:ss",
|
|
12
|
-
}),
|
|
13
|
-
winston.format.errors({ stack: true }),
|
|
14
|
-
winston.format.splat(),
|
|
15
|
-
winston.format.json(),
|
|
16
|
-
),
|
|
17
|
-
level: logLevel || "error",
|
|
18
|
-
transports: [
|
|
19
|
-
//
|
|
20
|
-
// - Write all logs with level `error` and below to `error.log`
|
|
21
|
-
// - Write all logs with level `info` and below to `combined.log`
|
|
22
|
-
//
|
|
23
|
-
new winston.transports.File({
|
|
24
|
-
filename: "vex:" + logName + ".log",
|
|
25
|
-
level: "error",
|
|
26
|
-
}),
|
|
27
|
-
],
|
|
28
|
-
});
|
|
29
|
-
//
|
|
30
|
-
// If we're not in production then log to the `console` with the format:
|
|
31
|
-
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
|
|
32
|
-
//
|
|
33
|
-
if (
|
|
34
|
-
process.env["NODE_ENV"] !== "production" &&
|
|
35
|
-
process.env["NODE_ENV"] !== "test"
|
|
36
|
-
) {
|
|
37
|
-
logger.add(
|
|
38
|
-
new winston.transports.Console({
|
|
39
|
-
format: winston.format.combine(
|
|
40
|
-
winston.format.colorize(),
|
|
41
|
-
winston.format.simple(),
|
|
42
|
-
),
|
|
43
|
-
}),
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
return logger;
|
|
47
|
-
}
|