cpeak 2.5.0 → 2.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.
- package/README.md +300 -2
- package/dist/index.d.ts +88 -16
- package/dist/index.js +653 -165
- package/dist/index.js.map +1 -1
- package/lib/index.ts +229 -158
- package/lib/internal/compression.ts +180 -0
- package/lib/internal/types.ts +10 -0
- package/lib/types.ts +20 -5
- package/lib/utils/auth.ts +148 -0
- package/lib/utils/cookieParser.ts +179 -0
- package/lib/utils/cors.ts +109 -0
- package/lib/utils/index.ts +15 -1
- package/lib/utils/render.ts +7 -0
- package/lib/utils/serveStatic.ts +16 -5
- package/lib/utils/swagger.ts +31 -0
- package/lib/utils/types.ts +51 -0
- package/package.json +4 -4
- /package/lib/utils/{paseJSON.ts → parseJSON.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -2,71 +2,120 @@
|
|
|
2
2
|
import http from "http";
|
|
3
3
|
import fs3 from "fs/promises";
|
|
4
4
|
import { createReadStream } from "fs";
|
|
5
|
-
import { pipeline } from "stream/promises";
|
|
5
|
+
import { pipeline as pipeline2 } from "stream/promises";
|
|
6
6
|
|
|
7
|
-
// lib/utils/
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (newMimeTypes) {
|
|
27
|
-
Object.assign(MIME_TYPES, newMimeTypes);
|
|
28
|
-
}
|
|
29
|
-
function processFolder(folderPath2, parentFolder) {
|
|
30
|
-
const staticFiles = [];
|
|
31
|
-
const files = fs.readdirSync(folderPath2);
|
|
32
|
-
for (const file of files) {
|
|
33
|
-
const fullPath = path.join(folderPath2, file);
|
|
34
|
-
if (fs.statSync(fullPath).isDirectory()) {
|
|
35
|
-
const subfolderFiles = processFolder(fullPath, parentFolder);
|
|
36
|
-
staticFiles.push(...subfolderFiles);
|
|
37
|
-
} else {
|
|
38
|
-
const relativePath = path.relative(parentFolder, fullPath);
|
|
39
|
-
const fileExtension = path.extname(file).slice(1);
|
|
40
|
-
if (MIME_TYPES[fileExtension]) staticFiles.push("/" + relativePath);
|
|
41
|
-
}
|
|
7
|
+
// lib/utils/compression.ts
|
|
8
|
+
import zlib from "zlib";
|
|
9
|
+
import { Readable } from "stream";
|
|
10
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
11
|
+
import { pipeline } from "stream/promises";
|
|
12
|
+
var COMPRESSIBLE_TYPE = /text|json|javascript|css|xml|svg/i;
|
|
13
|
+
var NO_TRANSFORM = /(?:^|,)\s*no-transform\s*(?:,|$)/i;
|
|
14
|
+
function pickEncoding(header) {
|
|
15
|
+
if (!header) return null;
|
|
16
|
+
const accepted = {};
|
|
17
|
+
let wildcard;
|
|
18
|
+
for (const part of header.split(",")) {
|
|
19
|
+
const [rawName, ...params] = part.trim().split(";");
|
|
20
|
+
const name = rawName.trim().toLowerCase();
|
|
21
|
+
if (!name) continue;
|
|
22
|
+
let q = 1;
|
|
23
|
+
for (const p of params) {
|
|
24
|
+
const m = p.trim().match(/^q=([\d.]+)$/i);
|
|
25
|
+
if (m) q = Number(m[1]);
|
|
42
26
|
}
|
|
43
|
-
|
|
27
|
+
if (Number.isNaN(q)) q = 0;
|
|
28
|
+
if (name === "*") wildcard = q;
|
|
29
|
+
else accepted[name] = q;
|
|
44
30
|
}
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
const fileExtension = path.extname(file).slice(1);
|
|
49
|
-
filesMap2[file] = {
|
|
50
|
-
path: folderPath + file,
|
|
51
|
-
mime: MIME_TYPES[fileExtension]
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
return filesMap2;
|
|
31
|
+
const tryPick = (enc) => {
|
|
32
|
+
const q = enc in accepted ? accepted[enc] : wildcard;
|
|
33
|
+
return q !== void 0 && q > 0;
|
|
55
34
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
35
|
+
if (tryPick("br")) return "br";
|
|
36
|
+
if (tryPick("gzip")) return "gzip";
|
|
37
|
+
if (tryPick("deflate")) return "deflate";
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function appendVary(res, value) {
|
|
41
|
+
const existing = res.getHeader("Vary");
|
|
42
|
+
if (!existing) return res.setHeader("Vary", value);
|
|
43
|
+
const current = String(existing).split(",").map((s) => s.trim()).filter(Boolean);
|
|
44
|
+
if (current.includes("*") || current.some((v) => v.toLowerCase() === value.toLowerCase()))
|
|
45
|
+
return;
|
|
46
|
+
res.setHeader("Vary", [...current, value].join(", "));
|
|
47
|
+
}
|
|
48
|
+
function brotliOptsFor(config) {
|
|
49
|
+
const userBrotli = config.brotli || {};
|
|
50
|
+
return {
|
|
51
|
+
...userBrotli,
|
|
52
|
+
params: {
|
|
53
|
+
[zlib.constants.BROTLI_PARAM_QUALITY]: 4,
|
|
54
|
+
...userBrotli.params || {}
|
|
63
55
|
}
|
|
64
|
-
next();
|
|
65
56
|
};
|
|
66
|
-
}
|
|
57
|
+
}
|
|
58
|
+
function createCompressorStream(encoding, config) {
|
|
59
|
+
if (encoding === "br")
|
|
60
|
+
return zlib.createBrotliCompress(brotliOptsFor(config));
|
|
61
|
+
if (encoding === "gzip") return zlib.createGzip(config.gzip);
|
|
62
|
+
return zlib.createDeflate(config.deflate);
|
|
63
|
+
}
|
|
64
|
+
function negotiate(res, mime, size, config) {
|
|
65
|
+
if (!COMPRESSIBLE_TYPE.test(mime)) return { encoding: null, eligible: false };
|
|
66
|
+
if (res.req?.method === "HEAD") return { encoding: null, eligible: false };
|
|
67
|
+
const cc = res.getHeader("Cache-Control");
|
|
68
|
+
if (cc && NO_TRANSFORM.test(String(cc)))
|
|
69
|
+
return { encoding: null, eligible: false };
|
|
70
|
+
const existing = res.getHeader("Content-Encoding");
|
|
71
|
+
if (existing && existing !== "identity")
|
|
72
|
+
return { encoding: null, eligible: false };
|
|
73
|
+
if (size < config.threshold) return { encoding: null, eligible: true };
|
|
74
|
+
const encoding = pickEncoding(
|
|
75
|
+
String(res.req?.headers["accept-encoding"] || "")
|
|
76
|
+
);
|
|
77
|
+
return { encoding, eligible: true };
|
|
78
|
+
}
|
|
79
|
+
function bodyAsReadable(body) {
|
|
80
|
+
if (Buffer2.isBuffer(body)) return Readable.from([body]);
|
|
81
|
+
if (typeof body === "string") return Readable.from([Buffer2.from(body)]);
|
|
82
|
+
return body;
|
|
83
|
+
}
|
|
84
|
+
function resolveCompressionOptions(input) {
|
|
85
|
+
const options = input === true ? {} : input;
|
|
86
|
+
return {
|
|
87
|
+
threshold: options.threshold ?? 1024,
|
|
88
|
+
brotli: options.brotli ?? {},
|
|
89
|
+
gzip: options.gzip ?? {},
|
|
90
|
+
deflate: options.deflate ?? {}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
async function compressAndSend(res, mime, body, config, size) {
|
|
94
|
+
res.setHeader("Content-Type", mime);
|
|
95
|
+
const knownSize = Buffer2.isBuffer(body) ? body.length : typeof body === "string" ? Buffer2.byteLength(body) : size ?? Infinity;
|
|
96
|
+
const { encoding, eligible } = negotiate(res, mime, knownSize, config);
|
|
97
|
+
if (!encoding) {
|
|
98
|
+
if (eligible) appendVary(res, "Accept-Encoding");
|
|
99
|
+
if (Buffer2.isBuffer(body) || typeof body === "string") {
|
|
100
|
+
res.setHeader("Content-Length", String(knownSize));
|
|
101
|
+
res.end(body);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (size !== void 0) res.setHeader("Content-Length", String(size));
|
|
105
|
+
await pipeline(body, res);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
res.setHeader("Content-Encoding", encoding);
|
|
109
|
+
appendVary(res, "Accept-Encoding");
|
|
110
|
+
await pipeline(
|
|
111
|
+
bodyAsReadable(body),
|
|
112
|
+
createCompressorStream(encoding, config),
|
|
113
|
+
res
|
|
114
|
+
);
|
|
115
|
+
}
|
|
67
116
|
|
|
68
|
-
// lib/utils/
|
|
69
|
-
import { Buffer } from "buffer";
|
|
117
|
+
// lib/utils/parseJSON.ts
|
|
118
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
70
119
|
function isJSON(contentType) {
|
|
71
120
|
if (!contentType) return false;
|
|
72
121
|
if (contentType === "application/json") return true;
|
|
@@ -99,7 +148,7 @@ var parseJSON = (options = {}) => {
|
|
|
99
148
|
};
|
|
100
149
|
const onEnd = () => {
|
|
101
150
|
try {
|
|
102
|
-
const rawBody = chunks.length === 1 ? chunks[0].toString("utf-8") :
|
|
151
|
+
const rawBody = chunks.length === 1 ? chunks[0].toString("utf-8") : Buffer3.concat(chunks).toString("utf-8");
|
|
103
152
|
req.body = rawBody ? JSON.parse(rawBody) : {};
|
|
104
153
|
next();
|
|
105
154
|
} catch (err) {
|
|
@@ -119,6 +168,73 @@ var parseJSON = (options = {}) => {
|
|
|
119
168
|
};
|
|
120
169
|
};
|
|
121
170
|
|
|
171
|
+
// lib/utils/serveStatic.ts
|
|
172
|
+
import fs from "fs";
|
|
173
|
+
import path from "path";
|
|
174
|
+
var MIME_TYPES = {
|
|
175
|
+
html: "text/html",
|
|
176
|
+
css: "text/css",
|
|
177
|
+
js: "application/javascript",
|
|
178
|
+
jpg: "image/jpeg",
|
|
179
|
+
jpeg: "image/jpeg",
|
|
180
|
+
png: "image/png",
|
|
181
|
+
svg: "image/svg+xml",
|
|
182
|
+
txt: "text/plain",
|
|
183
|
+
eot: "application/vnd.ms-fontobject",
|
|
184
|
+
otf: "font/otf",
|
|
185
|
+
ttf: "font/ttf",
|
|
186
|
+
woff: "font/woff",
|
|
187
|
+
woff2: "font/woff2",
|
|
188
|
+
gif: "image/gif",
|
|
189
|
+
ico: "image/x-icon",
|
|
190
|
+
json: "application/json",
|
|
191
|
+
webmanifest: "application/manifest+json"
|
|
192
|
+
};
|
|
193
|
+
var serveStatic = (folderPath, newMimeTypes, options) => {
|
|
194
|
+
if (newMimeTypes) {
|
|
195
|
+
Object.assign(MIME_TYPES, newMimeTypes);
|
|
196
|
+
}
|
|
197
|
+
const prefix = options?.prefix ?? "";
|
|
198
|
+
function processFolder(folderPath2, parentFolder) {
|
|
199
|
+
const staticFiles = [];
|
|
200
|
+
const files = fs.readdirSync(folderPath2);
|
|
201
|
+
for (const file of files) {
|
|
202
|
+
const fullPath = path.join(folderPath2, file);
|
|
203
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
204
|
+
const subfolderFiles = processFolder(fullPath, parentFolder);
|
|
205
|
+
staticFiles.push(...subfolderFiles);
|
|
206
|
+
} else {
|
|
207
|
+
const relativePath = path.relative(parentFolder, fullPath);
|
|
208
|
+
const fileExtension = path.extname(file).slice(1);
|
|
209
|
+
if (MIME_TYPES[fileExtension]) staticFiles.push("/" + relativePath);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return staticFiles;
|
|
213
|
+
}
|
|
214
|
+
const filesArrayToFilesMap = (filesArray) => {
|
|
215
|
+
const filesMap2 = {};
|
|
216
|
+
for (const file of filesArray) {
|
|
217
|
+
const fileExtension = path.extname(file).slice(1);
|
|
218
|
+
filesMap2[prefix + file] = {
|
|
219
|
+
path: folderPath + file,
|
|
220
|
+
mime: MIME_TYPES[fileExtension]
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return filesMap2;
|
|
224
|
+
};
|
|
225
|
+
const filesMap = filesArrayToFilesMap(processFolder(folderPath, folderPath));
|
|
226
|
+
return function(req, res, next) {
|
|
227
|
+
const url = req.url;
|
|
228
|
+
if (typeof url !== "string") return next();
|
|
229
|
+
const pathname = url.split("?")[0];
|
|
230
|
+
if (Object.prototype.hasOwnProperty.call(filesMap, pathname)) {
|
|
231
|
+
const fileRoute = filesMap[pathname];
|
|
232
|
+
return res.sendFile(fileRoute.path, fileRoute.mime);
|
|
233
|
+
}
|
|
234
|
+
next();
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
|
|
122
238
|
// lib/utils/render.ts
|
|
123
239
|
import fs2 from "fs/promises";
|
|
124
240
|
function renderTemplate(templateStr, data) {
|
|
@@ -154,6 +270,11 @@ var render = () => {
|
|
|
154
270
|
}
|
|
155
271
|
let fileStr = await fs2.readFile(path2, "utf-8");
|
|
156
272
|
const finalStr = renderTemplate(fileStr, data);
|
|
273
|
+
const config = res.socket?.server?._cpeakCompression;
|
|
274
|
+
if (config) {
|
|
275
|
+
await compressAndSend(res, mime, finalStr, config);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
157
278
|
res.setHeader("Content-Type", mime);
|
|
158
279
|
res.end(finalStr);
|
|
159
280
|
};
|
|
@@ -161,6 +282,326 @@ var render = () => {
|
|
|
161
282
|
};
|
|
162
283
|
};
|
|
163
284
|
|
|
285
|
+
// lib/utils/swagger.ts
|
|
286
|
+
var swagger = (spec, prefix = "/api-docs") => {
|
|
287
|
+
const initializerJs = `window.onload = function() {
|
|
288
|
+
SwaggerUIBundle({
|
|
289
|
+
url: "${prefix}/spec.json",
|
|
290
|
+
dom_id: '#swagger-ui',
|
|
291
|
+
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
|
|
292
|
+
layout: "StandaloneLayout"
|
|
293
|
+
});
|
|
294
|
+
};`;
|
|
295
|
+
return (req, res, next) => {
|
|
296
|
+
if (req.url === prefix || req.url === `${prefix}/`) {
|
|
297
|
+
res.writeHead(302, { Location: `${prefix}/index.html` });
|
|
298
|
+
res.end();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (req.url === `${prefix}/spec.json`) {
|
|
302
|
+
return res.json(spec);
|
|
303
|
+
}
|
|
304
|
+
if (req.url === `${prefix}/swagger-initializer.js`) {
|
|
305
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
306
|
+
res.end(initializerJs);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
next();
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// lib/utils/auth.ts
|
|
314
|
+
import { randomBytes, pbkdf2, createHmac, timingSafeEqual } from "crypto";
|
|
315
|
+
import { promisify } from "util";
|
|
316
|
+
var pbkdf2Async = promisify(pbkdf2);
|
|
317
|
+
var DEFAULTS = {
|
|
318
|
+
iterations: 21e4,
|
|
319
|
+
keylen: 64,
|
|
320
|
+
digest: "sha512",
|
|
321
|
+
saltSize: 32,
|
|
322
|
+
hmacAlgorithm: "sha256",
|
|
323
|
+
tokenIdSize: 20,
|
|
324
|
+
tokenExpiry: 7 * 24 * 60 * 60 * 1e3
|
|
325
|
+
// 7 days in ms
|
|
326
|
+
};
|
|
327
|
+
async function hashPassword(password, options) {
|
|
328
|
+
const iterations = options?.iterations ?? DEFAULTS.iterations;
|
|
329
|
+
const keylen = options?.keylen ?? DEFAULTS.keylen;
|
|
330
|
+
const digest = options?.digest ?? DEFAULTS.digest;
|
|
331
|
+
const saltSize = options?.saltSize ?? DEFAULTS.saltSize;
|
|
332
|
+
const salt = randomBytes(saltSize);
|
|
333
|
+
const hash = await pbkdf2Async(password, salt, iterations, keylen, digest);
|
|
334
|
+
return `pbkdf2:${iterations}:${keylen}:${digest}:${salt.toString("hex")}:${hash.toString("hex")}`;
|
|
335
|
+
}
|
|
336
|
+
async function verifyPassword(password, stored) {
|
|
337
|
+
const withoutPrefix = stored.slice(stored.indexOf(":") + 1);
|
|
338
|
+
const parts = withoutPrefix.split(":");
|
|
339
|
+
if (parts.length !== 5) return false;
|
|
340
|
+
const [itersStr, keylenStr, digest, saltHex, hashHex] = parts;
|
|
341
|
+
const iterations = parseInt(itersStr, 10);
|
|
342
|
+
const keylen = parseInt(keylenStr, 10);
|
|
343
|
+
if (!digest || !saltHex || !hashHex || isNaN(iterations) || isNaN(keylen))
|
|
344
|
+
return false;
|
|
345
|
+
const salt = Buffer.from(saltHex, "hex");
|
|
346
|
+
const hash = await pbkdf2Async(password, salt, iterations, keylen, digest);
|
|
347
|
+
const storedHash = Buffer.from(hashHex, "hex");
|
|
348
|
+
if (storedHash.length !== hash.length) return false;
|
|
349
|
+
return timingSafeEqual(hash, storedHash);
|
|
350
|
+
}
|
|
351
|
+
function signToken(tokenId, secret, algorithm) {
|
|
352
|
+
const sig = createHmac(algorithm, secret).update(tokenId).digest("hex");
|
|
353
|
+
return `${tokenId}.${sig}`;
|
|
354
|
+
}
|
|
355
|
+
function extractTokenId(token, secret, algorithm) {
|
|
356
|
+
const dot = token.indexOf(".");
|
|
357
|
+
if (dot === -1) return null;
|
|
358
|
+
const tokenId = token.slice(0, dot);
|
|
359
|
+
const sig = token.slice(dot + 1);
|
|
360
|
+
const expected = createHmac(algorithm, secret).update(tokenId).digest("hex");
|
|
361
|
+
const expectedBuf = Buffer.from(expected, "hex");
|
|
362
|
+
const actualBuf = Buffer.from(sig, "hex");
|
|
363
|
+
if (expectedBuf.length !== actualBuf.length) return null;
|
|
364
|
+
if (!timingSafeEqual(expectedBuf, actualBuf)) return null;
|
|
365
|
+
return tokenId;
|
|
366
|
+
}
|
|
367
|
+
function auth(options) {
|
|
368
|
+
if (!options.secret || options.secret.length < 32) {
|
|
369
|
+
throw frameworkError(
|
|
370
|
+
"Secret must be at least 32 characters. HMAC security is only as strong as the key.",
|
|
371
|
+
auth,
|
|
372
|
+
"CPEAK_ERR_WEAK_SECRET" /* WEAK_SECRET */
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
const {
|
|
376
|
+
secret,
|
|
377
|
+
saveToken,
|
|
378
|
+
findToken,
|
|
379
|
+
revokeToken,
|
|
380
|
+
tokenExpiry = DEFAULTS.tokenExpiry,
|
|
381
|
+
hmacAlgorithm = DEFAULTS.hmacAlgorithm,
|
|
382
|
+
tokenIdSize = DEFAULTS.tokenIdSize
|
|
383
|
+
} = options;
|
|
384
|
+
const pbkdfOpts = {
|
|
385
|
+
iterations: options.iterations,
|
|
386
|
+
keylen: options.keylen,
|
|
387
|
+
digest: options.digest,
|
|
388
|
+
saltSize: options.saltSize
|
|
389
|
+
};
|
|
390
|
+
const _hashPassword = ({ password }) => hashPassword(password, pbkdfOpts);
|
|
391
|
+
const login = async ({
|
|
392
|
+
password,
|
|
393
|
+
hashedPassword,
|
|
394
|
+
userId
|
|
395
|
+
}) => {
|
|
396
|
+
const isMatch = await verifyPassword(password, hashedPassword);
|
|
397
|
+
if (!isMatch) return null;
|
|
398
|
+
const tokenId = randomBytes(tokenIdSize).toString("hex");
|
|
399
|
+
const token = signToken(tokenId, secret, hmacAlgorithm);
|
|
400
|
+
await saveToken(tokenId, userId, new Date(Date.now() + tokenExpiry));
|
|
401
|
+
return token;
|
|
402
|
+
};
|
|
403
|
+
const verifyToken = async (token) => {
|
|
404
|
+
if (!token) return null;
|
|
405
|
+
const tokenId = extractTokenId(token, secret, hmacAlgorithm);
|
|
406
|
+
if (!tokenId) return null;
|
|
407
|
+
const record = await findToken(tokenId);
|
|
408
|
+
if (!record) return null;
|
|
409
|
+
if (new Date(record.expiresAt) < /* @__PURE__ */ new Date()) return null;
|
|
410
|
+
return { userId: record.userId };
|
|
411
|
+
};
|
|
412
|
+
const logout = revokeToken ? async (token) => {
|
|
413
|
+
const tokenId = extractTokenId(token, secret, hmacAlgorithm);
|
|
414
|
+
if (!tokenId) return false;
|
|
415
|
+
await revokeToken(tokenId);
|
|
416
|
+
return true;
|
|
417
|
+
} : void 0;
|
|
418
|
+
return (req, _res, next) => {
|
|
419
|
+
req.hashPassword = _hashPassword;
|
|
420
|
+
req.login = login;
|
|
421
|
+
req.verifyToken = verifyToken;
|
|
422
|
+
if (logout) req.logout = logout;
|
|
423
|
+
next();
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// lib/utils/cookieParser.ts
|
|
428
|
+
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
429
|
+
function sign(value, secret) {
|
|
430
|
+
const sig = createHmac2("sha256", secret).update(value).digest("base64url");
|
|
431
|
+
return `s:${value}.${sig}`;
|
|
432
|
+
}
|
|
433
|
+
function unsign(signed, secret) {
|
|
434
|
+
if (!signed.startsWith("s:")) return false;
|
|
435
|
+
const withoutPrefix = signed.slice(2);
|
|
436
|
+
const lastDot = withoutPrefix.lastIndexOf(".");
|
|
437
|
+
if (lastDot === -1) return false;
|
|
438
|
+
const value = withoutPrefix.slice(0, lastDot);
|
|
439
|
+
const sig = withoutPrefix.slice(lastDot + 1);
|
|
440
|
+
const expected = createHmac2("sha256", secret).update(value).digest("base64url");
|
|
441
|
+
const expectedBuf = Buffer.from(expected);
|
|
442
|
+
const actualBuf = Buffer.from(sig);
|
|
443
|
+
if (expectedBuf.length !== actualBuf.length) return false;
|
|
444
|
+
if (!timingSafeEqual2(expectedBuf, actualBuf)) return false;
|
|
445
|
+
return value;
|
|
446
|
+
}
|
|
447
|
+
function parseRawCookies(header) {
|
|
448
|
+
const cookies = /* @__PURE__ */ Object.create(null);
|
|
449
|
+
if (!header) return cookies;
|
|
450
|
+
const pairs = header.split(";");
|
|
451
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
452
|
+
const pair = pairs[i];
|
|
453
|
+
const equalSignIndex = pair.indexOf("=");
|
|
454
|
+
if (equalSignIndex === -1) continue;
|
|
455
|
+
const key = pair.slice(0, equalSignIndex).trim();
|
|
456
|
+
if (!key || cookies[key] !== void 0) continue;
|
|
457
|
+
let val = pair.slice(equalSignIndex + 1).trim();
|
|
458
|
+
if (val.length > 1 && val[0] === '"' && val[val.length - 1] === '"') {
|
|
459
|
+
val = val.slice(1, -1);
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
cookies[key] = val.indexOf("%") !== -1 ? decodeURIComponent(val) : val;
|
|
463
|
+
} catch (e) {
|
|
464
|
+
cookies[key] = val;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return cookies;
|
|
468
|
+
}
|
|
469
|
+
function buildSetCookieHeader(name, value, options) {
|
|
470
|
+
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
471
|
+
const path2 = options.path ?? "/";
|
|
472
|
+
parts.push(`Path=${path2}`);
|
|
473
|
+
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
474
|
+
if (options.maxAge !== void 0)
|
|
475
|
+
parts.push(`Max-Age=${Math.floor(options.maxAge / 1e3)}`);
|
|
476
|
+
if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
|
|
477
|
+
if (options.httpOnly) parts.push("HttpOnly");
|
|
478
|
+
if (options.secure) parts.push("Secure");
|
|
479
|
+
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
480
|
+
return parts.join("; ");
|
|
481
|
+
}
|
|
482
|
+
function appendSetCookie(res, header) {
|
|
483
|
+
const existing = res.getHeader("Set-Cookie");
|
|
484
|
+
if (!existing) {
|
|
485
|
+
res.setHeader("Set-Cookie", [header]);
|
|
486
|
+
} else if (Array.isArray(existing)) {
|
|
487
|
+
res.setHeader("Set-Cookie", [...existing, header]);
|
|
488
|
+
} else {
|
|
489
|
+
res.setHeader("Set-Cookie", [String(existing), header]);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function cookieParser(options = {}) {
|
|
493
|
+
const { secret } = options;
|
|
494
|
+
if (secret !== void 0 && secret.length < 32) {
|
|
495
|
+
throw frameworkError(
|
|
496
|
+
"Secret must be at least 32 characters. HMAC security is only as strong as the key.",
|
|
497
|
+
cookieParser,
|
|
498
|
+
"CPEAK_ERR_WEAK_SECRET" /* WEAK_SECRET */
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
return (req, res, next) => {
|
|
502
|
+
const rawHeader = req.headers["cookie"] || "";
|
|
503
|
+
const raw = parseRawCookies(rawHeader);
|
|
504
|
+
const cookies = /* @__PURE__ */ Object.create(null);
|
|
505
|
+
const signedCookies = /* @__PURE__ */ Object.create(null);
|
|
506
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
507
|
+
if (val.startsWith("s:") && secret) {
|
|
508
|
+
signedCookies[key] = unsign(val, secret);
|
|
509
|
+
} else {
|
|
510
|
+
cookies[key] = val;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
req.cookies = cookies;
|
|
514
|
+
req.signedCookies = signedCookies;
|
|
515
|
+
res.cookie = (name, value, options2 = {}) => {
|
|
516
|
+
let finalValue = value;
|
|
517
|
+
if (options2.signed) {
|
|
518
|
+
if (!secret)
|
|
519
|
+
throw new Error(
|
|
520
|
+
"cookieParser: secret is required to use signed cookies"
|
|
521
|
+
);
|
|
522
|
+
finalValue = sign(value, secret);
|
|
523
|
+
}
|
|
524
|
+
appendSetCookie(res, buildSetCookieHeader(name, finalValue, options2));
|
|
525
|
+
return res;
|
|
526
|
+
};
|
|
527
|
+
res.clearCookie = (name, options2 = {}) => {
|
|
528
|
+
appendSetCookie(
|
|
529
|
+
res,
|
|
530
|
+
buildSetCookieHeader(name, "", {
|
|
531
|
+
...options2,
|
|
532
|
+
maxAge: 0,
|
|
533
|
+
expires: /* @__PURE__ */ new Date(0)
|
|
534
|
+
})
|
|
535
|
+
);
|
|
536
|
+
return res;
|
|
537
|
+
};
|
|
538
|
+
next();
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// lib/utils/cors.ts
|
|
543
|
+
function appendVary2(res, value) {
|
|
544
|
+
const existing = res.getHeader("Vary");
|
|
545
|
+
if (!existing) return res.setHeader("Vary", value);
|
|
546
|
+
const current = String(existing).split(",").map((s) => s.trim()).filter(Boolean);
|
|
547
|
+
if (current.includes("*") || current.includes(value)) return;
|
|
548
|
+
res.setHeader("Vary", [...current, value].join(", "));
|
|
549
|
+
}
|
|
550
|
+
async function isAllowed(origin, rule) {
|
|
551
|
+
if (rule === true || rule === "*") return true;
|
|
552
|
+
if (rule === false || !origin) return false;
|
|
553
|
+
if (typeof rule === "string") return rule === origin;
|
|
554
|
+
if (Array.isArray(rule)) return rule.includes(origin);
|
|
555
|
+
if (rule instanceof RegExp) return rule.test(origin);
|
|
556
|
+
if (typeof rule === "function") return await rule(origin);
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
var cors = (options = {}) => {
|
|
560
|
+
const {
|
|
561
|
+
origin = "*",
|
|
562
|
+
methods = "GET,HEAD,PUT,PATCH,POST,DELETE",
|
|
563
|
+
allowedHeaders,
|
|
564
|
+
exposedHeaders,
|
|
565
|
+
credentials = false,
|
|
566
|
+
maxAge = 86400,
|
|
567
|
+
preflightContinue = false,
|
|
568
|
+
optionsSuccessStatus = 204
|
|
569
|
+
} = options;
|
|
570
|
+
const methodsStr = Array.isArray(methods) ? methods.join(",") : methods;
|
|
571
|
+
const allowedHeadersStr = Array.isArray(allowedHeaders) ? allowedHeaders.join(",") : allowedHeaders;
|
|
572
|
+
const exposedHeadersStr = Array.isArray(exposedHeaders) ? exposedHeaders.join(",") : exposedHeaders;
|
|
573
|
+
return async (req, res, next) => {
|
|
574
|
+
const requestOrigin = req.headers.origin;
|
|
575
|
+
if (!requestOrigin) return next();
|
|
576
|
+
const allowed = await isAllowed(requestOrigin, origin);
|
|
577
|
+
if (!allowed) return next();
|
|
578
|
+
const allowOriginValue = origin === "*" && !credentials ? "*" : requestOrigin;
|
|
579
|
+
res.setHeader("Access-Control-Allow-Origin", allowOriginValue);
|
|
580
|
+
if (allowOriginValue !== "*") appendVary2(res, "Origin");
|
|
581
|
+
if (credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
582
|
+
if (exposedHeadersStr)
|
|
583
|
+
res.setHeader("Access-Control-Expose-Headers", exposedHeadersStr);
|
|
584
|
+
const isPreflight = req.method === "OPTIONS" && req.headers["access-control-request-method"] !== void 0;
|
|
585
|
+
if (!isPreflight) return next();
|
|
586
|
+
res.setHeader("Access-Control-Allow-Methods", methodsStr);
|
|
587
|
+
if (allowedHeadersStr) {
|
|
588
|
+
res.setHeader("Access-Control-Allow-Headers", allowedHeadersStr);
|
|
589
|
+
} else if (origin === "*") {
|
|
590
|
+
const requested = req.headers["access-control-request-headers"];
|
|
591
|
+
if (requested) res.setHeader("Access-Control-Allow-Headers", requested);
|
|
592
|
+
} else {
|
|
593
|
+
res.setHeader(
|
|
594
|
+
"Access-Control-Allow-Headers",
|
|
595
|
+
"Content-Type, Authorization"
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
res.setHeader("Access-Control-Max-Age", String(maxAge));
|
|
599
|
+
if (preflightContinue) return next();
|
|
600
|
+
res.statusCode = optionsSuccessStatus;
|
|
601
|
+
res.end();
|
|
602
|
+
};
|
|
603
|
+
};
|
|
604
|
+
|
|
164
605
|
// lib/index.ts
|
|
165
606
|
function frameworkError(message, skipFn, code, status) {
|
|
166
607
|
const err = new Error(message);
|
|
@@ -177,27 +618,32 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
|
177
618
|
ErrorCode2["SEND_FILE_FAIL"] = "CPEAK_ERR_SEND_FILE_FAIL";
|
|
178
619
|
ErrorCode2["INVALID_JSON"] = "CPEAK_ERR_INVALID_JSON";
|
|
179
620
|
ErrorCode2["PAYLOAD_TOO_LARGE"] = "CPEAK_ERR_PAYLOAD_TOO_LARGE";
|
|
621
|
+
ErrorCode2["WEAK_SECRET"] = "CPEAK_ERR_WEAK_SECRET";
|
|
622
|
+
ErrorCode2["COMPRESSION_NOT_ENABLED"] = "CPEAK_ERR_COMPRESSION_NOT_ENABLED";
|
|
180
623
|
return ErrorCode2;
|
|
181
624
|
})(ErrorCode || {});
|
|
625
|
+
function compressionConfigFor(res) {
|
|
626
|
+
return res.socket?.server?._cpeakCompression;
|
|
627
|
+
}
|
|
182
628
|
var CpeakIncomingMessage = class extends http.IncomingMessage {
|
|
183
629
|
// We define body and params here for better V8 optimization (not changing the shape of the object at runtime)
|
|
184
630
|
body = void 0;
|
|
185
631
|
params = {};
|
|
186
|
-
|
|
632
|
+
#query;
|
|
187
633
|
// Parse the URL parameters (like /users?key1=value1&key2=value2)
|
|
188
634
|
// We will call this query to be more familiar with other node.js frameworks.
|
|
189
635
|
// This is a getter method (accessed like a property)
|
|
190
636
|
get query() {
|
|
191
|
-
if (this
|
|
637
|
+
if (this.#query) return this.#query;
|
|
192
638
|
const url = this.url || "";
|
|
193
639
|
const qIndex = url.indexOf("?");
|
|
194
640
|
if (qIndex === -1) {
|
|
195
|
-
this
|
|
641
|
+
this.#query = {};
|
|
196
642
|
} else {
|
|
197
643
|
const searchParams = new URLSearchParams(url.substring(qIndex + 1));
|
|
198
|
-
this
|
|
644
|
+
this.#query = Object.fromEntries(searchParams.entries());
|
|
199
645
|
}
|
|
200
|
-
return this
|
|
646
|
+
return this.#query;
|
|
201
647
|
}
|
|
202
648
|
};
|
|
203
649
|
var CpeakServerResponse = class extends http.ServerResponse {
|
|
@@ -219,9 +665,14 @@ var CpeakServerResponse = class extends http.ServerResponse {
|
|
|
219
665
|
"CPEAK_ERR_NOT_A_FILE" /* NOT_A_FILE */
|
|
220
666
|
);
|
|
221
667
|
}
|
|
668
|
+
const config = compressionConfigFor(this);
|
|
669
|
+
if (config) {
|
|
670
|
+
await compressAndSend(this, mime, createReadStream(path2), config, stat.size);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
222
673
|
this.setHeader("Content-Type", mime);
|
|
223
674
|
this.setHeader("Content-Length", String(stat.size));
|
|
224
|
-
await
|
|
675
|
+
await pipeline2(createReadStream(path2), this);
|
|
225
676
|
} catch (err) {
|
|
226
677
|
if (err?.code === "ENOENT") {
|
|
227
678
|
throw frameworkError(
|
|
@@ -242,142 +693,170 @@ var CpeakServerResponse = class extends http.ServerResponse {
|
|
|
242
693
|
this.statusCode = code;
|
|
243
694
|
return this;
|
|
244
695
|
}
|
|
696
|
+
// Set the Content-Disposition header to prompt the user to download a file
|
|
697
|
+
attachment(filename) {
|
|
698
|
+
const contentDisposition = filename ? `attachment; filename="${filename}"` : "attachment";
|
|
699
|
+
this.setHeader("Content-Disposition", contentDisposition);
|
|
700
|
+
return this;
|
|
701
|
+
}
|
|
245
702
|
// Redirects to a new URL
|
|
246
703
|
redirect(location) {
|
|
247
704
|
this.writeHead(302, { Location: location });
|
|
248
705
|
this.end();
|
|
249
|
-
return this;
|
|
250
706
|
}
|
|
251
|
-
// Send a json data back to the client
|
|
707
|
+
// Send a json data back to the client. Sync hot path when compression is
|
|
708
|
+
// off — no Promise allocation, no microtask. Branches into compressAndSend
|
|
709
|
+
// (async) when compression was enabled at cpeak() construction.
|
|
252
710
|
json(data) {
|
|
711
|
+
const body = JSON.stringify(data);
|
|
712
|
+
const config = compressionConfigFor(this);
|
|
713
|
+
if (config) {
|
|
714
|
+
return compressAndSend(this, "application/json", body, config);
|
|
715
|
+
}
|
|
253
716
|
this.setHeader("Content-Type", "application/json");
|
|
254
|
-
this.end(
|
|
717
|
+
this.end(body);
|
|
718
|
+
}
|
|
719
|
+
// Explicit compression entry point. Throws if compression wasn't configured —
|
|
720
|
+
// the developer asked to compress but the framework was never told to.
|
|
721
|
+
compress(mime, body, size) {
|
|
722
|
+
const config = compressionConfigFor(this);
|
|
723
|
+
if (!config) {
|
|
724
|
+
throw frameworkError(
|
|
725
|
+
"compression is not enabled. Pass `compression` to cpeak({ compression: true | { ... } }) to use res.compress.",
|
|
726
|
+
this.compress,
|
|
727
|
+
"CPEAK_ERR_COMPRESSION_NOT_ENABLED" /* COMPRESSION_NOT_ENABLED */
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
return compressAndSend(this, mime, body, config, size);
|
|
255
731
|
}
|
|
256
732
|
};
|
|
257
733
|
var Cpeak = class {
|
|
258
|
-
server;
|
|
259
|
-
routes;
|
|
260
|
-
middleware;
|
|
261
|
-
|
|
262
|
-
constructor() {
|
|
263
|
-
this
|
|
734
|
+
#server;
|
|
735
|
+
#routes;
|
|
736
|
+
#middleware;
|
|
737
|
+
#handleErr;
|
|
738
|
+
constructor(options = {}) {
|
|
739
|
+
this.#server = http.createServer({
|
|
264
740
|
IncomingMessage: CpeakIncomingMessage,
|
|
265
741
|
ServerResponse: CpeakServerResponse
|
|
266
742
|
});
|
|
267
|
-
this
|
|
268
|
-
this
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
743
|
+
this.#routes = {};
|
|
744
|
+
this.#middleware = [];
|
|
745
|
+
if (options.compression) {
|
|
746
|
+
this.#server._cpeakCompression = resolveCompressionOptions(
|
|
747
|
+
options.compression
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
this.#server.on(
|
|
751
|
+
"request",
|
|
752
|
+
async (req, res) => {
|
|
753
|
+
const qIndex = req.url?.indexOf("?");
|
|
754
|
+
const urlWithoutQueries = qIndex === -1 ? req.url || "" : req.url?.substring(0, qIndex);
|
|
755
|
+
const dispatchError = (error) => {
|
|
756
|
+
if (res.headersSent) {
|
|
757
|
+
req.socket?.destroy();
|
|
758
|
+
return;
|
|
282
759
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
760
|
+
res.setHeader("Connection", "close");
|
|
761
|
+
this.#handleErr?.(error, req, res);
|
|
762
|
+
};
|
|
763
|
+
const runHandler = async (req2, res2, middleware, cb, index) => {
|
|
764
|
+
if (index === middleware.length) {
|
|
765
|
+
try {
|
|
766
|
+
await cb(req2, res2, dispatchError);
|
|
767
|
+
} catch (error) {
|
|
768
|
+
dispatchError(error);
|
|
769
|
+
}
|
|
770
|
+
} else {
|
|
771
|
+
try {
|
|
772
|
+
await middleware[index](
|
|
773
|
+
req2,
|
|
774
|
+
res2,
|
|
775
|
+
// The next function
|
|
776
|
+
async (error) => {
|
|
777
|
+
if (error) {
|
|
778
|
+
return dispatchError(error);
|
|
779
|
+
}
|
|
780
|
+
await runHandler(req2, res2, middleware, cb, index + 1);
|
|
781
|
+
},
|
|
782
|
+
// Error handler for a route middleware
|
|
783
|
+
dispatchError
|
|
784
|
+
);
|
|
785
|
+
} catch (error) {
|
|
786
|
+
dispatchError(error);
|
|
787
|
+
}
|
|
305
788
|
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
789
|
+
};
|
|
790
|
+
const runMiddleware = async (req2, res2, middleware, index) => {
|
|
791
|
+
if (index === middleware.length) {
|
|
792
|
+
const routes = this.#routes[req2.method?.toLowerCase() || ""];
|
|
793
|
+
if (routes && typeof routes[Symbol.iterator] === "function")
|
|
794
|
+
for (const route of routes) {
|
|
795
|
+
const match = urlWithoutQueries?.match(route.regex);
|
|
796
|
+
if (match) {
|
|
797
|
+
const pathVariables = this.#extractPathVariables(
|
|
798
|
+
route.path,
|
|
799
|
+
match
|
|
800
|
+
);
|
|
801
|
+
req2.params = pathVariables;
|
|
802
|
+
return await runHandler(
|
|
803
|
+
req2,
|
|
804
|
+
res2,
|
|
805
|
+
route.middleware,
|
|
806
|
+
route.cb,
|
|
807
|
+
0
|
|
808
|
+
);
|
|
809
|
+
}
|
|
327
810
|
}
|
|
811
|
+
return res2.status(404).json({ error: `Cannot ${req2.method} ${urlWithoutQueries}` });
|
|
812
|
+
} else {
|
|
813
|
+
try {
|
|
814
|
+
await middleware[index](req2, res2, async (err) => {
|
|
815
|
+
if (err) {
|
|
816
|
+
return dispatchError(err);
|
|
817
|
+
}
|
|
818
|
+
await runMiddleware(req2, res2, middleware, index + 1);
|
|
819
|
+
});
|
|
820
|
+
} catch (error) {
|
|
821
|
+
dispatchError(error);
|
|
328
822
|
}
|
|
329
|
-
return res2.status(404).json({ error: `Cannot ${req2.method} ${urlWithoutQueries}` });
|
|
330
|
-
} else {
|
|
331
|
-
try {
|
|
332
|
-
await middleware[index](req2, res2, async (err) => {
|
|
333
|
-
if (err) {
|
|
334
|
-
res2.setHeader("Connection", "close");
|
|
335
|
-
return this._handleErr?.(err, req2, res2);
|
|
336
|
-
}
|
|
337
|
-
await runMiddleware(req2, res2, middleware, index + 1);
|
|
338
|
-
});
|
|
339
|
-
} catch (error) {
|
|
340
|
-
res2.setHeader("Connection", "close");
|
|
341
|
-
this._handleErr?.(error, req2, res2);
|
|
342
823
|
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
824
|
+
};
|
|
825
|
+
await runMiddleware(req, res, this.#middleware, 0);
|
|
826
|
+
}
|
|
827
|
+
);
|
|
347
828
|
}
|
|
348
829
|
route(method, path2, ...args) {
|
|
349
|
-
if (!this
|
|
830
|
+
if (!this.#routes[method]) this.#routes[method] = [];
|
|
350
831
|
const cb = args.pop();
|
|
351
832
|
if (!cb || typeof cb !== "function") {
|
|
352
833
|
throw new Error("Route definition must include a handler");
|
|
353
834
|
}
|
|
354
835
|
const middleware = args.flat();
|
|
355
836
|
const regex = this.#pathToRegex(path2);
|
|
356
|
-
this
|
|
837
|
+
this.#routes[method].push({ path: path2, regex, middleware, cb });
|
|
357
838
|
}
|
|
358
839
|
beforeEach(cb) {
|
|
359
|
-
this
|
|
840
|
+
this.#middleware.push(cb);
|
|
360
841
|
}
|
|
361
842
|
handleErr(cb) {
|
|
362
|
-
this
|
|
843
|
+
this.#handleErr = cb;
|
|
363
844
|
}
|
|
364
845
|
listen(port, cb) {
|
|
365
|
-
return this
|
|
846
|
+
return this.#server.listen(port, cb);
|
|
847
|
+
}
|
|
848
|
+
address() {
|
|
849
|
+
return this.#server.address();
|
|
366
850
|
}
|
|
367
851
|
close(cb) {
|
|
368
|
-
this
|
|
852
|
+
this.#server.close(cb);
|
|
369
853
|
}
|
|
370
854
|
// ------------------------------
|
|
371
855
|
// PRIVATE METHODS:
|
|
372
856
|
// ------------------------------
|
|
373
857
|
#pathToRegex(path2) {
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
paramNames.push(match.slice(1));
|
|
377
|
-
return "([^/]+)";
|
|
378
|
-
}) + "$";
|
|
379
|
-
const regex = new RegExp(regexString);
|
|
380
|
-
return regex;
|
|
858
|
+
const regexString = "^" + path2.replace(/:\w+/g, "([^/]+)").replace(/\*/g, ".*") + "$";
|
|
859
|
+
return new RegExp(regexString);
|
|
381
860
|
}
|
|
382
861
|
#extractPathVariables(path2, match) {
|
|
383
862
|
const paramNames = (path2.match(/:\w+/g) || []).map(
|
|
@@ -390,15 +869,24 @@ var Cpeak = class {
|
|
|
390
869
|
return params;
|
|
391
870
|
}
|
|
392
871
|
};
|
|
393
|
-
function cpeak() {
|
|
394
|
-
return new Cpeak();
|
|
872
|
+
function cpeak(options) {
|
|
873
|
+
return new Cpeak(options);
|
|
395
874
|
}
|
|
396
875
|
export {
|
|
876
|
+
Cpeak,
|
|
877
|
+
CpeakIncomingMessage,
|
|
878
|
+
CpeakServerResponse,
|
|
397
879
|
ErrorCode,
|
|
880
|
+
auth,
|
|
881
|
+
cookieParser,
|
|
882
|
+
cors,
|
|
398
883
|
cpeak as default,
|
|
399
884
|
frameworkError,
|
|
885
|
+
hashPassword,
|
|
400
886
|
parseJSON,
|
|
401
887
|
render,
|
|
402
|
-
serveStatic
|
|
888
|
+
serveStatic,
|
|
889
|
+
swagger,
|
|
890
|
+
verifyPassword
|
|
403
891
|
};
|
|
404
892
|
//# sourceMappingURL=index.js.map
|