koa-files 2.3.8 → 3.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 +81 -26
- package/cjs/Service.cjs +280 -0
- package/cjs/index.cjs +14 -8
- package/cjs/utils/fs.cjs +29 -0
- package/cjs/utils/hash.cjs +46 -0
- package/cjs/utils/http.cjs +227 -0
- package/cjs/utils/path.cjs +46 -0
- package/esm/Service.js +271 -0
- package/esm/index.js +8 -8
- package/esm/utils/fs.js +27 -0
- package/esm/utils/hash.js +44 -0
- package/esm/utils/http.js +216 -0
- package/esm/utils/path.js +42 -0
- package/package.json +10 -9
- package/types/Service.d.ts +81 -0
- package/types/index.d.ts +4 -4
- package/types/utils/fs.d.ts +25 -0
- package/types/utils/hash.d.ts +9 -0
- package/types/utils/http.d.ts +39 -0
- package/types/utils/path.d.ts +22 -0
- package/cjs/Files.cjs +0 -389
- package/cjs/utils.cjs +0 -150
- package/esm/Files.js +0 -378
- package/esm/utils.js +0 -134
- package/types/Files.d.ts +0 -98
- package/types/utils.d.ts +0 -62
package/README.md
CHANGED
|
@@ -16,48 +16,93 @@ $ npm install koa-files
|
|
|
16
16
|
|
|
17
17
|
## API
|
|
18
18
|
|
|
19
|
-
```
|
|
20
|
-
import
|
|
21
|
-
import
|
|
19
|
+
```ts
|
|
20
|
+
import { Middleware } from 'koa';
|
|
21
|
+
import { createReadStream, stat, Stats } from 'fs';
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
interface IgnoreFunction {
|
|
24
|
+
(path: string): boolean;
|
|
25
|
+
}
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
interface Headers {
|
|
28
|
+
[key: string]: string | string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface HeaderFunction {
|
|
32
|
+
(path: string, stats: Stats): Headers | void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface FileSystem {
|
|
36
|
+
readonly stat: typeof stat;
|
|
37
|
+
readonly createReadStream: typeof createReadStream;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface Options {
|
|
41
|
+
etag?: boolean;
|
|
42
|
+
fs?: FileSystem;
|
|
43
|
+
defer?: boolean;
|
|
44
|
+
acceptRanges?: boolean;
|
|
45
|
+
lastModified?: boolean;
|
|
46
|
+
ignore?: IgnoreFunction;
|
|
47
|
+
headers?: Headers | HeaderFunction;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default function server(root: string, options?: Options): Middleware;
|
|
27
51
|
```
|
|
28
52
|
|
|
29
|
-
|
|
30
|
-
|
|
53
|
+
### root
|
|
54
|
+
|
|
55
|
+
- Root directory string.
|
|
56
|
+
- Nothing above this root directory can be served.
|
|
31
57
|
|
|
32
58
|
### Options
|
|
33
59
|
|
|
34
|
-
|
|
60
|
+
##### `fs`
|
|
61
|
+
|
|
62
|
+
- Defaults to `node:fs`.
|
|
63
|
+
- The file system to used.
|
|
35
64
|
|
|
36
|
-
|
|
65
|
+
##### `headers`
|
|
37
66
|
|
|
38
|
-
|
|
67
|
+
- Defaults to `undefined`.
|
|
68
|
+
- Set headers to be sent.
|
|
69
|
+
- See docs: [Headers in MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers).
|
|
39
70
|
|
|
40
|
-
|
|
71
|
+
##### `acceptRanges`
|
|
41
72
|
|
|
42
|
-
|
|
73
|
+
- Defaults to `true`.
|
|
74
|
+
- Enable or disable accepting ranged requests.
|
|
75
|
+
- Disabling this will not send Accept-Ranges and ignore the contents of the Range request header.
|
|
76
|
+
- Can be overridden by the `headers`.
|
|
43
77
|
|
|
44
|
-
|
|
78
|
+
##### `etag`
|
|
45
79
|
|
|
46
|
-
|
|
80
|
+
- Defaults to `true`.
|
|
81
|
+
- Enable or disable etag generation.
|
|
82
|
+
- Use weak etag internally.
|
|
83
|
+
- Can be overridden by the `headers`.
|
|
47
84
|
|
|
48
|
-
|
|
85
|
+
##### `lastModified`
|
|
49
86
|
|
|
50
|
-
|
|
87
|
+
- Defaults to `true`.
|
|
88
|
+
- Enable or disable Last-Modified header.
|
|
89
|
+
- Use the file system's last modified value.
|
|
90
|
+
- Can be overridden by the `headers`.
|
|
51
91
|
|
|
52
|
-
|
|
92
|
+
##### `ignore`
|
|
53
93
|
|
|
54
|
-
|
|
94
|
+
- Defaults to `undefined`.
|
|
95
|
+
- Function that determines if a file should be ignored.
|
|
55
96
|
|
|
56
|
-
|
|
97
|
+
##### `defer`
|
|
98
|
+
|
|
99
|
+
- Defaults to `false`.
|
|
100
|
+
- If true, serves after `await next()`.
|
|
101
|
+
- Allowing any downstream middleware to respond first.
|
|
57
102
|
|
|
58
103
|
## Example
|
|
59
104
|
|
|
60
|
-
```
|
|
105
|
+
```ts
|
|
61
106
|
/**
|
|
62
107
|
* @module server
|
|
63
108
|
* @license MIT
|
|
@@ -70,6 +115,15 @@ import files from 'koa-files';
|
|
|
70
115
|
const app = new Koa();
|
|
71
116
|
const port = process.env.PORT || 80;
|
|
72
117
|
|
|
118
|
+
// Static files server
|
|
119
|
+
app.use(
|
|
120
|
+
files('tests', {
|
|
121
|
+
headers: {
|
|
122
|
+
'Cache-Control': 'public, max-age=31557600'
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
|
|
73
127
|
/**
|
|
74
128
|
* @function httpError
|
|
75
129
|
* @param {NodeJS.ErrnoException} error
|
|
@@ -79,14 +133,15 @@ function httpError(error) {
|
|
|
79
133
|
return /^(EOF|EPIPE|ECANCELED|ECONNRESET|ECONNABORTED)$/i.test(error.code);
|
|
80
134
|
}
|
|
81
135
|
|
|
82
|
-
// Static files server
|
|
83
|
-
app.use(files('tests', { cacheControl: 'public, max-age=31557600' }));
|
|
84
|
-
|
|
85
136
|
// Listen error event
|
|
86
|
-
app.on('error', error =>
|
|
137
|
+
app.on('error', error => {
|
|
138
|
+
!httpError(error) && console.error(error);
|
|
139
|
+
});
|
|
87
140
|
|
|
88
141
|
// Start server
|
|
89
|
-
app.listen(port, () =>
|
|
142
|
+
app.listen(port, () => {
|
|
143
|
+
console.log(`> server running at: 127.0.0.1:${port}`);
|
|
144
|
+
});
|
|
90
145
|
```
|
|
91
146
|
|
|
92
147
|
## Features
|
package/cjs/Service.cjs
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @package koa-files
|
|
3
|
+
* @license MIT
|
|
4
|
+
* @version 3.0.1
|
|
5
|
+
* @author nuintun <nuintun@qq.com>
|
|
6
|
+
* @description A static files serving middleware for koa.
|
|
7
|
+
* @see https://github.com/nuintun/koa-files#readme
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const createETag = require('etag');
|
|
13
|
+
const destroy = require('destroy');
|
|
14
|
+
const stream = require('stream');
|
|
15
|
+
const fs = require('./utils/fs.cjs');
|
|
16
|
+
const path$1 = require('path');
|
|
17
|
+
const path = require('./utils/path.cjs');
|
|
18
|
+
const http = require('./utils/http.cjs');
|
|
19
|
+
|
|
20
|
+
function _interopDefault(e) {
|
|
21
|
+
return e && e.__esModule ? e : { default: e };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const createETag__default = /*#__PURE__*/ _interopDefault(createETag);
|
|
25
|
+
const destroy__default = /*#__PURE__*/ _interopDefault(destroy);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @module Service
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* @function isFunction
|
|
32
|
+
* @description Check if value is function.
|
|
33
|
+
* @param value The value to check.
|
|
34
|
+
*/
|
|
35
|
+
function isFunction(value) {
|
|
36
|
+
return typeof value === 'function';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* @class Service
|
|
40
|
+
*/
|
|
41
|
+
class Service {
|
|
42
|
+
/**
|
|
43
|
+
* @constructor
|
|
44
|
+
* @description Create file service.
|
|
45
|
+
* @param root File service root.
|
|
46
|
+
* @param options File service options.
|
|
47
|
+
*/
|
|
48
|
+
constructor(root, options) {
|
|
49
|
+
this.options = options;
|
|
50
|
+
this.root = path.unixify(path$1.resolve(root));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* @private
|
|
54
|
+
* @method isIgnore
|
|
55
|
+
* @description Check if path is ignore.
|
|
56
|
+
* @param path File path.
|
|
57
|
+
*/
|
|
58
|
+
isIgnore(path) {
|
|
59
|
+
const { ignore } = this.options;
|
|
60
|
+
return (isFunction(ignore) ? ignore(path) : false) === true;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* @private
|
|
64
|
+
* @method setupHeaders
|
|
65
|
+
* @description Setup headers
|
|
66
|
+
* @param context Koa context
|
|
67
|
+
* @param path File path
|
|
68
|
+
* @param stats File stats
|
|
69
|
+
*/
|
|
70
|
+
setupHeaders(context, path, stats) {
|
|
71
|
+
const { options } = this;
|
|
72
|
+
const { headers, etag } = options;
|
|
73
|
+
// Set status.
|
|
74
|
+
context.status = 200;
|
|
75
|
+
// Set Content-Type.
|
|
76
|
+
context.type = path$1.extname(path);
|
|
77
|
+
// Accept-Ranges.
|
|
78
|
+
if (options.acceptRanges === false) {
|
|
79
|
+
// Set Accept-Ranges to none tell client not support.
|
|
80
|
+
context.set('Accept-Ranges', 'none');
|
|
81
|
+
} else {
|
|
82
|
+
// Set Accept-Ranges.
|
|
83
|
+
context.set('Accept-Ranges', 'bytes');
|
|
84
|
+
}
|
|
85
|
+
// ETag.
|
|
86
|
+
if (etag === false) {
|
|
87
|
+
// Remove ETag.
|
|
88
|
+
context.remove('ETag');
|
|
89
|
+
} else {
|
|
90
|
+
context.set('ETag', createETag__default.default(stats));
|
|
91
|
+
}
|
|
92
|
+
// Last-Modified.
|
|
93
|
+
if (options.lastModified === false) {
|
|
94
|
+
// Remove Last-Modified.
|
|
95
|
+
context.remove('Last-Modified');
|
|
96
|
+
} else {
|
|
97
|
+
// Set mtime utc string.
|
|
98
|
+
context.set('Last-Modified', stats.mtime.toUTCString());
|
|
99
|
+
}
|
|
100
|
+
// Set headers.
|
|
101
|
+
if (headers) {
|
|
102
|
+
if (isFunction(headers)) {
|
|
103
|
+
const fields = headers(path, stats);
|
|
104
|
+
if (fields) {
|
|
105
|
+
context.set(fields);
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
context.set(headers);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* @private
|
|
114
|
+
* @method write
|
|
115
|
+
* @description Write file to stream.
|
|
116
|
+
* @param stream Destination stream.
|
|
117
|
+
* @param path The file path to read.
|
|
118
|
+
* @param range The range to read.
|
|
119
|
+
* @param end Is destory destination stream after read complete.
|
|
120
|
+
*/
|
|
121
|
+
write(stream, path, range, end) {
|
|
122
|
+
const { fs } = this.options;
|
|
123
|
+
return new Promise(resolve => {
|
|
124
|
+
// Range prefix and suffix.
|
|
125
|
+
const { prefix, suffix } = range;
|
|
126
|
+
// Create file stream reader.
|
|
127
|
+
const reader = fs.createReadStream(path, range);
|
|
128
|
+
// File read stream open.
|
|
129
|
+
if (prefix) {
|
|
130
|
+
reader.once('open', () => {
|
|
131
|
+
// Write prefix boundary.
|
|
132
|
+
stream.write(prefix);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// File read stream end.
|
|
136
|
+
if (suffix) {
|
|
137
|
+
reader.once('end', () => {
|
|
138
|
+
// Push suffix boundary.
|
|
139
|
+
stream.write(suffix);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// File read stream error.
|
|
143
|
+
reader.once('error', () => {
|
|
144
|
+
// End stream.
|
|
145
|
+
stream.end();
|
|
146
|
+
// Unpipe.
|
|
147
|
+
reader.unpipe();
|
|
148
|
+
// Destroy.
|
|
149
|
+
destroy__default.default(reader);
|
|
150
|
+
// Resolve.
|
|
151
|
+
resolve(false);
|
|
152
|
+
});
|
|
153
|
+
// File read stream close.
|
|
154
|
+
reader.once('close', () => {
|
|
155
|
+
// Unpipe.
|
|
156
|
+
reader.unpipe();
|
|
157
|
+
// Destroy.
|
|
158
|
+
destroy__default.default(reader);
|
|
159
|
+
// Resolve.
|
|
160
|
+
resolve(true);
|
|
161
|
+
});
|
|
162
|
+
// Write data to buffer.
|
|
163
|
+
reader.pipe(stream, { end });
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* @private
|
|
168
|
+
* @method send
|
|
169
|
+
* @description Send file.
|
|
170
|
+
* @param context Koa context.
|
|
171
|
+
* @param path File path.
|
|
172
|
+
* @param ranges Read ranges.
|
|
173
|
+
*/
|
|
174
|
+
async send(context, path, ranges) {
|
|
175
|
+
// Set stream body, highWaterMark 64kb.
|
|
176
|
+
const stream$1 = new stream.PassThrough({
|
|
177
|
+
highWaterMark: 65536
|
|
178
|
+
});
|
|
179
|
+
// Set response body.
|
|
180
|
+
context.body = stream$1;
|
|
181
|
+
// Ranges length.
|
|
182
|
+
let { length } = ranges;
|
|
183
|
+
// Write ranges to stream.
|
|
184
|
+
for (const range of ranges) {
|
|
185
|
+
// Write range.
|
|
186
|
+
const passed = await this.write(stream$1, path, range, --length === 0);
|
|
187
|
+
// If not passed, break.
|
|
188
|
+
if (!passed) {
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* @public
|
|
195
|
+
* @method response
|
|
196
|
+
* @description Response to koa context.
|
|
197
|
+
* @param context Koa context.
|
|
198
|
+
*/
|
|
199
|
+
async response(context) {
|
|
200
|
+
const { root } = this;
|
|
201
|
+
// Only support GET and HEAD (405).
|
|
202
|
+
if (context.method !== 'GET' && context.method !== 'HEAD') {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
// Get path of file
|
|
206
|
+
const pathname = http.decodeURI(context.path);
|
|
207
|
+
const path$2 = pathname === -1 ? pathname : path.unixify(path$1.join(root, pathname));
|
|
208
|
+
// Path -1 or null byte(s)
|
|
209
|
+
if (path$2 === -1 || path$2.includes('\0')) {
|
|
210
|
+
return context.throw(400);
|
|
211
|
+
}
|
|
212
|
+
// Malicious path (403).
|
|
213
|
+
if (path.isOutRoot(path$2, root)) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
// Is ignore path or file (403).
|
|
217
|
+
if (this.isIgnore(path$2)) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
// File stats.
|
|
221
|
+
const stats = await fs.stat(this.options.fs, path$2);
|
|
222
|
+
// Check file stats.
|
|
223
|
+
if (
|
|
224
|
+
// File not exist (404 | 500).
|
|
225
|
+
stats == null ||
|
|
226
|
+
// Is directory (403).
|
|
227
|
+
stats.isDirectory() ||
|
|
228
|
+
// Not a directory but has trailing slash (404).
|
|
229
|
+
path.hasTrailingSlash(path$2)
|
|
230
|
+
) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
// Setup headers.
|
|
234
|
+
this.setupHeaders(context, path$2, stats);
|
|
235
|
+
// Conditional get support.
|
|
236
|
+
if (http.isConditionalGET(context)) {
|
|
237
|
+
// Request precondition failure.
|
|
238
|
+
if (http.isPreconditionFailure(context)) {
|
|
239
|
+
return context.throw(412);
|
|
240
|
+
}
|
|
241
|
+
// Request fresh (304).
|
|
242
|
+
if (context.fresh) {
|
|
243
|
+
// Set status.
|
|
244
|
+
context.status = 304;
|
|
245
|
+
// Set body null.
|
|
246
|
+
context.body = null;
|
|
247
|
+
// File found.
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Head request.
|
|
252
|
+
if (context.method === 'HEAD') {
|
|
253
|
+
// Set Content-Length.
|
|
254
|
+
context.length = stats.size;
|
|
255
|
+
// Set body null
|
|
256
|
+
context.body = null;
|
|
257
|
+
// File found.
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
// Parsed ranges.
|
|
261
|
+
const ranges = http.parseRanges(context, stats);
|
|
262
|
+
// 416
|
|
263
|
+
if (ranges === -1) {
|
|
264
|
+
// Set Content-Range.
|
|
265
|
+
context.set('Content-Range', `bytes */${stats.size}`);
|
|
266
|
+
// Unsatisfiable 416.
|
|
267
|
+
return context.throw(416);
|
|
268
|
+
}
|
|
269
|
+
// 400.
|
|
270
|
+
if (ranges === -2) {
|
|
271
|
+
return context.throw(400);
|
|
272
|
+
}
|
|
273
|
+
// Send file.
|
|
274
|
+
this.send(context, path$2, ranges);
|
|
275
|
+
// File found.
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
module.exports = Service;
|
package/cjs/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @package koa-files
|
|
3
3
|
* @license MIT
|
|
4
|
-
* @version
|
|
4
|
+
* @version 3.0.1
|
|
5
5
|
* @author nuintun <nuintun@qq.com>
|
|
6
6
|
* @description A static files serving middleware for koa.
|
|
7
7
|
* @see https://github.com/nuintun/koa-files#readme
|
|
@@ -9,12 +9,17 @@
|
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const Service = require('./Service.cjs');
|
|
14
|
+
|
|
15
|
+
function _interopDefault(e) {
|
|
16
|
+
return e && e.__esModule ? e : { default: e };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const fs__default = /*#__PURE__*/ _interopDefault(fs);
|
|
13
20
|
|
|
14
21
|
/**
|
|
15
22
|
* @module index
|
|
16
|
-
* @license MIT
|
|
17
|
-
* @author nuintun
|
|
18
23
|
*/
|
|
19
24
|
/**
|
|
20
25
|
* @function server
|
|
@@ -22,15 +27,16 @@ const Files = require('./Files.cjs');
|
|
|
22
27
|
* @param {Options} options
|
|
23
28
|
*/
|
|
24
29
|
function server(root, options) {
|
|
25
|
-
const
|
|
26
|
-
|
|
30
|
+
const config = Object.assign({ fs: fs__default.default }, options);
|
|
31
|
+
const service = new Service(root, config);
|
|
32
|
+
if (config.defer) {
|
|
27
33
|
return async (context, next) => {
|
|
28
34
|
await next();
|
|
29
|
-
await
|
|
35
|
+
await service.response(context);
|
|
30
36
|
};
|
|
31
37
|
}
|
|
32
38
|
return async (context, next) => {
|
|
33
|
-
if (!(await
|
|
39
|
+
if (!(await service.response(context))) {
|
|
34
40
|
await next();
|
|
35
41
|
}
|
|
36
42
|
};
|
package/cjs/utils/fs.cjs
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @package koa-files
|
|
3
|
+
* @license MIT
|
|
4
|
+
* @version 3.0.1
|
|
5
|
+
* @author nuintun <nuintun@qq.com>
|
|
6
|
+
* @description A static files serving middleware for koa.
|
|
7
|
+
* @see https://github.com/nuintun/koa-files#readme
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @module fs
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* @function stat
|
|
17
|
+
* @description Get file stats.
|
|
18
|
+
* @param fs The file system to used.
|
|
19
|
+
* @param path The file path.
|
|
20
|
+
*/
|
|
21
|
+
function stat(fs, path) {
|
|
22
|
+
return new Promise(resolve => {
|
|
23
|
+
fs.stat(path, (error, stats) => {
|
|
24
|
+
resolve(error ? null : stats);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
exports.stat = stat;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @package koa-files
|
|
3
|
+
* @license MIT
|
|
4
|
+
* @version 3.0.1
|
|
5
|
+
* @author nuintun <nuintun@qq.com>
|
|
6
|
+
* @description A static files serving middleware for koa.
|
|
7
|
+
* @see https://github.com/nuintun/koa-files#readme
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @module hash
|
|
16
|
+
*/
|
|
17
|
+
// prettier-ignore
|
|
18
|
+
const CHARS = [
|
|
19
|
+
// 0-9
|
|
20
|
+
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
21
|
+
// A-M
|
|
22
|
+
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
|
23
|
+
// N-Z
|
|
24
|
+
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
|
25
|
+
// a-m
|
|
26
|
+
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
|
27
|
+
// n-z
|
|
28
|
+
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* @function generate
|
|
32
|
+
* @description Generate a hash.
|
|
33
|
+
* @param length The length of hash.
|
|
34
|
+
*/
|
|
35
|
+
function generate(length = 32) {
|
|
36
|
+
let hash = '';
|
|
37
|
+
const randomValues = crypto.getRandomValues(new Uint8Array(length));
|
|
38
|
+
// Create hash.
|
|
39
|
+
for (const value of randomValues) {
|
|
40
|
+
hash += CHARS[value % 62];
|
|
41
|
+
}
|
|
42
|
+
// Return hash.
|
|
43
|
+
return hash;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
exports.generate = generate;
|