hono 0.0.8 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +225 -40
  2. package/dist/compose.d.ts +1 -0
  3. package/dist/compose.js +27 -0
  4. package/dist/context.d.ts +9 -0
  5. package/dist/context.js +47 -0
  6. package/dist/hono.d.ts +42 -0
  7. package/dist/hono.js +137 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +8 -0
  10. package/dist/middleware/basic-auth/basic-auth.d.ts +6 -0
  11. package/dist/middleware/basic-auth/basic-auth.js +50 -0
  12. package/dist/middleware/defaultFilter.d.ts +2 -0
  13. package/dist/middleware/defaultFilter.js +12 -0
  14. package/dist/middleware/logger/logger.d.ts +6 -0
  15. package/dist/middleware/logger/logger.js +58 -0
  16. package/dist/middleware/poweredBy/poweredBy.d.ts +2 -0
  17. package/dist/middleware/poweredBy/poweredBy.js +11 -0
  18. package/dist/middleware.d.ts +14 -0
  19. package/dist/middleware.js +14 -0
  20. package/dist/node.d.ts +24 -0
  21. package/dist/node.js +104 -0
  22. package/dist/util.d.ts +4 -0
  23. package/dist/util.js +63 -0
  24. package/package.json +46 -5
  25. package/.github/workflows/ci.yml +0 -24
  26. package/src/compose.js +0 -22
  27. package/src/compose.test.js +0 -42
  28. package/src/hono.d.ts +0 -47
  29. package/src/hono.js +0 -142
  30. package/src/hono.test.js +0 -115
  31. package/src/methods.js +0 -30
  32. package/src/middleware/defaultFilter.js +0 -19
  33. package/src/middleware/poweredBy.js +0 -6
  34. package/src/middleware/poweredBy.test.js +0 -17
  35. package/src/middleware.js +0 -7
  36. package/src/middleware.test.js +0 -17
  37. package/src/node.js +0 -97
  38. package/src/node.test.js +0 -135
  39. package/src/router.test.js +0 -88
  40. package/src/util.js +0 -26
  41. package/src/util.test.js +0 -29
package/README.md CHANGED
@@ -6,31 +6,43 @@ Hono [炎] - Tiny web framework for Cloudflare Workers and others.
6
6
  const { Hono } = require('hono')
7
7
  const app = new Hono()
8
8
 
9
- app.get('/', () => new Response('Hono!!'))
9
+ app.get('/', (c) => c.text('Hono!!'))
10
10
 
11
11
  app.fire()
12
12
  ```
13
13
 
14
- ## Feature
14
+ Hono[炎] - _**means flame🔥 in Japanese**_ - is small, fast and simple web flamework for a Service Workers API based serverless such as **Cloudflare Workers** and **Fastly Compute@Edge**. Hono does not depend on any npm packages. However, Hono has a router, context object, and middleware including the builtins. It's easy to make a web application.
15
15
 
16
- - Fast - the router is implemented with Trie-Tree structure.
17
- - Tiny - use only standard API.
18
- - Portable - zero dependencies.
19
- - Flexible - you can make your own middlewares.
20
- - Optimized - for Cloudflare Workers and Fastly Compute@Edge.
16
+ ## Features
17
+
18
+ - **Fast** - the router is implemented with Trie-Tree structure.
19
+ - **Tiny** - zero dependencies, using Web standard API.
20
+ - **Flexible** - you can make your own middleware.
21
+ - **Easy** - simple API, builtin middleware, and written in TypeScript.
22
+ - **Optimized** - for Cloudflare Workers or Fastly Compute@Edge.
21
23
 
22
24
  ## Benchmark
23
25
 
26
+ **Hono is fastest** compared to other routers for Cloudflare Workers.
27
+
24
28
  ```
25
- hono x 813,001 ops/sec ±2.96% (75 runs sampled)
26
- itty-router x 160,415 ops/sec ±3.31% (85 runs sampled)
27
- sunder x 307,438 ops/sec ±4.77% (73 runs sampled)
29
+ hono x 758,264 ops/sec ±5.41% (75 runs sampled)
30
+ itty-router x 158,359 ops/sec ±3.21% (89 runs sampled)
31
+ sunder x 297,581 ops/sec ±4.74% (83 runs sampled)
28
32
  Fastest is hono
29
- ✨ Done in 37.03s.
33
+ ✨ Done in 42.84s.
30
34
  ```
31
35
 
36
+ ## Hono in 1 minute
37
+
38
+ Below is a demonstration to create an application of Cloudflare Workers with Hono.
39
+
40
+ ![Demo](https://user-images.githubusercontent.com/10682/148223268-2484a891-57c1-472f-9df3-936a5586f002.gif)
41
+
32
42
  ## Install
33
43
 
44
+ You can install from npm registry:
45
+
34
46
  ```
35
47
  $ yarn add hono
36
48
  ```
@@ -43,10 +55,13 @@ $ npm install hono
43
55
 
44
56
  ## Methods
45
57
 
46
- - app.**HTTP_METHOD**(path, callback)
47
- - app.**all**(path, callback)
58
+ Instance of `Hono` has these methods:
59
+
60
+ - app.**HTTP_METHOD**(path, handler)
61
+ - app.**all**(path, handler)
48
62
  - app.**route**(path)
49
63
  - app.**use**(path, middleware)
64
+ - app.**fire**()
50
65
 
51
66
  ## Routing
52
67
 
@@ -100,6 +115,15 @@ app
100
115
  .put(() => {...})
101
116
  ```
102
117
 
118
+ ## async/await
119
+
120
+ ```js
121
+ app.get('/fetch-url', async () => {
122
+ const response = await fetch('https://example.com/')
123
+ return new Response(`Status is ${response.status}`)
124
+ })
125
+ ```
126
+
103
127
  ## Middleware
104
128
 
105
129
  ### Builtin Middleware
@@ -109,82 +133,243 @@ const { Hono, Middleware } = require('hono')
109
133
 
110
134
  ...
111
135
 
112
- app.use('*', Middleware.poweredBy)
113
-
136
+ app.use(
137
+ '/auth/*',
138
+ Middleware.basicAuth({
139
+ username: 'hono',
140
+ password: 'acoolproject',
141
+ })
142
+ )
114
143
  ```
115
144
 
145
+ Available builtin middleware are listed on [src/middleware](https://github.com/yusukebe/hono/tree/master/src/middleware).
146
+
116
147
  ### Custom Middleware
117
148
 
149
+ You can write your own middleware:
150
+
118
151
  ```js
119
- const logger = (c, next) => {
152
+ // Custom logger
153
+ app.use('*', async (c, next) => {
120
154
  console.log(`[${c.req.method}] ${c.req.url}`)
121
- next()
122
- }
123
-
124
- const addHeader = (c, next) => {
125
- next()
126
- c.res.headers.add('x-message', 'This is middleware!')
127
- }
155
+ await next()
156
+ })
128
157
 
129
- app.use('*', logger)
130
- app.use('/message/*', addHeader)
158
+ // Add a custom header
159
+ app.use('/message/*', async (c, next) => {
160
+ await next()
161
+ await c.res.headers.add('x-message', 'This is middleware!')
162
+ })
131
163
 
132
164
  app.get('/message/hello', () => 'Hello Middleware!')
133
165
  ```
134
166
 
167
+ ### Custom 404 Response
168
+
169
+ You can customize 404 Not Found response:
170
+
171
+ ```js
172
+ app.use('*', async (c, next) => {
173
+ await next()
174
+ if (c.res.status === 404) {
175
+ c.res = new Response('Custom 404 Not Found', { status: 404 })
176
+ }
177
+ })
178
+ ```
179
+
180
+ ### Complex Pattern
181
+
182
+ You can also do this:
183
+
184
+ ```js
185
+ // Output response time
186
+ app.use('*', async (c, next) => {
187
+ await next()
188
+ const responseTime = await c.res.headers.get('X-Response-Time')
189
+ console.log(`X-Response-Time: ${responseTime}`)
190
+ })
191
+
192
+ // Add X-Response-Time header
193
+ app.use('*', async (c, next) => {
194
+ const start = Date.now()
195
+ await next()
196
+ const ms = Date.now() - start
197
+ await c.res.headers.append('X-Response-Time', `${ms}ms`)
198
+ })
199
+ ```
200
+
135
201
  ## Context
136
202
 
137
- ### req
203
+ To handle Request and Reponse easily, you can use Context object:
204
+
205
+ ### c.req
138
206
 
139
207
  ```js
208
+
209
+ // Get Request object
140
210
  app.get('/hello', (c) => {
141
- const userAgent = c.req.headers('User-Agent')
211
+ const userAgent = c.req.headers.get('User-Agent')
212
+ ...
213
+ })
214
+
215
+ // Query params
216
+ app.get('/search', (c) => {
217
+ const query = c.req.query('q')
218
+ ...
219
+ })
220
+
221
+ // Captured params
222
+ app.get('/entry/:id', (c) => {
223
+ const id = c.req.params('id')
142
224
  ...
143
225
  })
144
226
  ```
145
227
 
146
- ### res
228
+ ### c.res
147
229
 
148
230
  ```js
231
+ // Response object
149
232
  app.use('/', (c, next) => {
150
233
  next()
151
234
  c.res.headers.append('X-Debug', 'Debug message')
152
235
  })
153
236
  ```
154
237
 
155
- ## Request
238
+ ### c.text()
156
239
 
157
- ### query
240
+ Render text as `Content-Type:text/plain`:
158
241
 
159
242
  ```js
160
- app.get('/search', (c) => {
161
- const query = c.req.query('q')
162
- ...
243
+ app.get('/say', (c) => {
244
+ return c.text('Hello!')
163
245
  })
164
246
  ```
165
247
 
166
- ### params
248
+ ### c.json()
249
+
250
+ Render JSON as `Content-Type:application/json`:
167
251
 
168
252
  ```js
169
- app.get('/entry/:id', (c) => {
170
- const id = c.req.params('id')
171
- ...
253
+ app.get('/api', (c) => {
254
+ return c.json({ message: 'Hello!' })
172
255
  })
173
256
  ```
174
257
 
258
+ ### c.html()
259
+
260
+ Render HTML as `Content-Type:text/html`:
261
+
262
+ ```js
263
+ app.get('/api', (c) => {
264
+ return c.html('<h1>Hello! Hono!</h1>')
265
+ })
266
+ ```
267
+
268
+ ## fire
269
+
270
+ `app.fire()` do:
271
+
272
+ ```js
273
+ addEventListener('fetch', (event) => {
274
+ event.respondWith(this.handleEvent(event))
275
+ })
276
+ ```
277
+
278
+ ## Cloudflare Workers with Hono
279
+
280
+ Using `wrangler` or `miniflare`, you can develop the application locally and publish it with few commands.
281
+
282
+ Let's write your first code for Cloudflare Workers with Hono.
283
+
284
+ ### 1. Install Wrangler
285
+
286
+ Install Cloudflare Command Line "[Wrangler](https://github.com/cloudflare/wrangler)"
287
+
288
+ ```sh
289
+ $ npm i @cloudflare/wrangler -g
290
+ ```
291
+
292
+ ### 2. `npm init`
293
+
294
+ Make npm skeleton directory.
295
+
296
+ ```sh
297
+ $ mkdir hono-example
298
+ $ ch hono-example
299
+ $ npm init -y
300
+ ```
301
+
302
+ ### 3. `wrangler init`
303
+
304
+ Init as a wrangler project.
305
+
306
+ ```sh
307
+ $ wrangler init
308
+ ```
309
+
310
+ ### 4. `npm install hono`
311
+
312
+ Install `hono` from npm registry.
313
+
314
+ ```
315
+ $ npm i hono
316
+ ```
317
+
318
+ ### 5. Write your app
319
+
320
+ Only 4 lines!!
321
+
322
+ ```js
323
+ const { Hono } = require('hono')
324
+ const app = new Hono()
325
+
326
+ app.get('/', (c) => c.text('Hello! Hono!'))
327
+
328
+ app.fire()
329
+ ```
330
+
331
+ ### 6. Run
332
+
333
+ Run the development server locally. Then, access like `http://127.0.0.1:8787/` in your Web browser.
334
+
335
+ ```sh
336
+ $ wrangler dev
337
+ ```
338
+
339
+ ### Publish
340
+
341
+ Deploy to Cloudflare. That's all!
342
+
343
+ ```sh
344
+ $ wrangler publish
345
+ ```
346
+
175
347
  ## Related projects
176
348
 
177
- - koa <https://github.com/koajs/koa>
349
+ Implementation of the router is inspired by [goblin](https://github.com/bmf-san/goblin). API design is inspired by [express](https://github.com/expressjs/express) and [koa](https://github.com/koajs/koa). [itty-router](https://github.com/kwhitley/itty-router) and [Sunder](https://github.com/SunderJS/sunder) are the other routers or frameworks for Cloudflare Workers.
350
+
178
351
  - express <https://github.com/expressjs/express>
179
- - oak <https://github.com/oakserver/oak>
352
+ - koa <https://github.com/koajs/koa>
180
353
  - itty-router <https://github.com/kwhitley/itty-router>
181
354
  - Sunder <https://github.com/SunderJS/sunder>
182
355
  - goblin <https://github.com/bmf-san/goblin>
183
356
 
357
+ ## Contributing
358
+
359
+ Contributions Welcome! You can contribute by the following way:
360
+
361
+ - Write or fix documents
362
+ - Write code of middleware
363
+ - Fix bugs
364
+ - Refactor the code
365
+ - etc.
366
+
367
+ If you can, let's make Hono together!
368
+
184
369
  ## Author
185
370
 
186
371
  Yusuke Wada <https://github.com/yusukebe>
187
372
 
188
373
  ## License
189
374
 
190
- MIT
375
+ Distributed under the MIT License. See [LICENSE](LICENSE) for more information.
@@ -0,0 +1 @@
1
+ export declare const compose: (middleware: any) => (context: any, next?: Function) => any;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compose = void 0;
4
+ // Based on the code in the MIT licensed `koa-compose` package.
5
+ const compose = (middleware) => {
6
+ return function (context, next) {
7
+ let index = -1;
8
+ return dispatch(0);
9
+ function dispatch(i) {
10
+ if (i <= index)
11
+ return Promise.reject(new Error('next() called multiple times'));
12
+ index = i;
13
+ let fn = middleware[i];
14
+ if (i === middleware.length)
15
+ fn = next;
16
+ if (!fn)
17
+ return Promise.resolve();
18
+ try {
19
+ return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
20
+ }
21
+ catch (err) {
22
+ return Promise.reject(err);
23
+ }
24
+ }
25
+ };
26
+ };
27
+ exports.compose = compose;
@@ -0,0 +1,9 @@
1
+ export declare class Context {
2
+ req: Request;
3
+ res: Response;
4
+ constructor(req: Request, res: Response);
5
+ newResponse(body?: BodyInit | null | undefined, init?: ResponseInit | undefined): Response;
6
+ text(body: string): Response;
7
+ json(object: object, replacer?: (string | number)[], space?: string | number): Response;
8
+ html(body: string): Response;
9
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Context = void 0;
4
+ class Context {
5
+ constructor(req, res) {
6
+ this.req = req;
7
+ this.res = res;
8
+ }
9
+ newResponse(body, init) {
10
+ return new Response(body, init);
11
+ }
12
+ text(body) {
13
+ if (typeof body !== 'string') {
14
+ throw new TypeError('text method arg must be a string!');
15
+ }
16
+ return this.newResponse(body, {
17
+ status: 200,
18
+ headers: {
19
+ 'Content-Type': 'text/plain',
20
+ },
21
+ });
22
+ }
23
+ json(object, replacer, space) {
24
+ if (typeof object !== 'object') {
25
+ throw new TypeError('json method arg must be a object!');
26
+ }
27
+ const body = JSON.stringify(object, replacer, space);
28
+ return this.newResponse(body, {
29
+ status: 200,
30
+ headers: {
31
+ 'Content-Type': 'application/json; charset=UTF-8',
32
+ },
33
+ });
34
+ }
35
+ html(body) {
36
+ if (typeof body !== 'string') {
37
+ throw new TypeError('html method arg must be a string!');
38
+ }
39
+ return this.newResponse(body, {
40
+ status: 200,
41
+ headers: {
42
+ 'Content-Type': 'text/html; charset=UTF-8',
43
+ },
44
+ });
45
+ }
46
+ }
47
+ exports.Context = Context;
package/dist/hono.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+ import type { Result } from './node';
3
+ import { Node } from './node';
4
+ import { Middleware } from './middleware';
5
+ import { Context } from './context';
6
+ export { Middleware };
7
+ declare global {
8
+ interface Request {
9
+ params: (key: string) => any;
10
+ query: (key: string) => string | null;
11
+ }
12
+ }
13
+ declare type Handler = (c: Context, next?: Function) => Response | Promise<Response>;
14
+ declare type MiddlwareHandler = (c: Context, next: Function) => Promise<void>;
15
+ export declare class Router<T> {
16
+ node: Node<T>;
17
+ constructor();
18
+ add(method: string, path: string, handler: T): void;
19
+ match(method: string, path: string): Result<T> | null;
20
+ }
21
+ export declare class Hono {
22
+ router: Router<Handler[]>;
23
+ middlewareRouters: Router<MiddlwareHandler>[];
24
+ tempPath: string;
25
+ constructor();
26
+ get(arg: string | Handler, ...args: Handler[]): Hono;
27
+ post(arg: string | Handler, ...args: Handler[]): Hono;
28
+ put(arg: string | Handler, ...args: Handler[]): Hono;
29
+ head(arg: string | Handler, ...args: Handler[]): Hono;
30
+ delete(arg: string | Handler, ...args: Handler[]): Hono;
31
+ options(arg: string | Handler, ...args: Handler[]): Hono;
32
+ patch(arg: string | Handler, ...args: Handler[]): Hono;
33
+ all(arg: string | Handler, ...args: Handler[]): Hono;
34
+ route(path: string): Hono;
35
+ use(path: string, middleware: MiddlwareHandler): void;
36
+ addRoute(method: string, arg: string | Handler, ...args: Handler[]): Hono;
37
+ matchRoute(method: string, path: string): Promise<Result<Handler[]>>;
38
+ dispatch(request: Request, response?: Response): Promise<Response>;
39
+ handleEvent(event: FetchEvent): Promise<Response>;
40
+ fire(): void;
41
+ notFound(): Response;
42
+ }
package/dist/hono.js ADDED
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Hono = exports.Router = exports.Middleware = void 0;
4
+ const node_1 = require("./node");
5
+ const compose_1 = require("./compose");
6
+ const util_1 = require("./util");
7
+ const middleware_1 = require("./middleware");
8
+ Object.defineProperty(exports, "Middleware", { enumerable: true, get: function () { return middleware_1.Middleware; } });
9
+ const context_1 = require("./context");
10
+ const METHOD_NAME_OF_ALL = 'ALL';
11
+ class Router {
12
+ constructor() {
13
+ this.node = new node_1.Node();
14
+ }
15
+ add(method, path, handler) {
16
+ this.node.insert(method, path, handler);
17
+ }
18
+ match(method, path) {
19
+ return this.node.search(method, path);
20
+ }
21
+ }
22
+ exports.Router = Router;
23
+ class Hono {
24
+ constructor() {
25
+ this.router = new Router();
26
+ this.middlewareRouters = [];
27
+ this.tempPath = '/';
28
+ }
29
+ /* HTTP METHODS */
30
+ get(arg, ...args) {
31
+ return this.addRoute('get', arg, ...args);
32
+ }
33
+ post(arg, ...args) {
34
+ return this.addRoute('post', arg, ...args);
35
+ }
36
+ put(arg, ...args) {
37
+ return this.addRoute('put', arg, ...args);
38
+ }
39
+ head(arg, ...args) {
40
+ return this.addRoute('head', arg, ...args);
41
+ }
42
+ delete(arg, ...args) {
43
+ return this.addRoute('delete', arg, ...args);
44
+ }
45
+ options(arg, ...args) {
46
+ return this.addRoute('options', arg, ...args);
47
+ }
48
+ patch(arg, ...args) {
49
+ return this.addRoute('patch', arg, ...args);
50
+ }
51
+ /*
52
+ trace
53
+ copy
54
+ lock
55
+ purge
56
+ unlock
57
+ report
58
+ checkout
59
+ merge
60
+ notify
61
+ subscribe
62
+ unsubscribe
63
+ search
64
+ connect
65
+ */
66
+ all(arg, ...args) {
67
+ return this.addRoute('all', arg, ...args);
68
+ }
69
+ route(path) {
70
+ this.tempPath = path;
71
+ return this;
72
+ }
73
+ use(path, middleware) {
74
+ if (middleware.constructor.name !== 'AsyncFunction') {
75
+ throw new TypeError('middleware must be a async function!');
76
+ }
77
+ const router = new Router();
78
+ router.add(METHOD_NAME_OF_ALL, path, middleware);
79
+ this.middlewareRouters.push(router);
80
+ }
81
+ // addRoute('get', '/', handler)
82
+ addRoute(method, arg, ...args) {
83
+ method = method.toUpperCase();
84
+ if (typeof arg === 'string') {
85
+ this.tempPath = arg;
86
+ this.router.add(method, arg, args);
87
+ }
88
+ else {
89
+ args.unshift(arg);
90
+ this.router.add(method, this.tempPath, args);
91
+ }
92
+ return this;
93
+ }
94
+ async matchRoute(method, path) {
95
+ return this.router.match(method, path);
96
+ }
97
+ async dispatch(request, response) {
98
+ const [method, path] = [request.method, (0, util_1.getPathFromURL)(request.url)];
99
+ const result = await this.matchRoute(method, path);
100
+ request.params = (key) => {
101
+ if (result) {
102
+ return result.params[key];
103
+ }
104
+ return '';
105
+ };
106
+ const handler = result ? result.handler[0] : this.notFound; // XXX
107
+ const middleware = [];
108
+ for (const mr of this.middlewareRouters) {
109
+ const mwResult = mr.match(METHOD_NAME_OF_ALL, path);
110
+ if (mwResult) {
111
+ middleware.push(mwResult.handler);
112
+ }
113
+ }
114
+ const wrappedHandler = async (context, next) => {
115
+ context.res = await handler(context);
116
+ await next();
117
+ };
118
+ middleware.push(middleware_1.Middleware.defaultFilter);
119
+ middleware.push(wrappedHandler);
120
+ const composed = (0, compose_1.compose)(middleware);
121
+ const c = new context_1.Context(request, response);
122
+ await composed(c);
123
+ return c.res;
124
+ }
125
+ async handleEvent(event) {
126
+ return this.dispatch(event.request);
127
+ }
128
+ fire() {
129
+ addEventListener('fetch', (event) => {
130
+ event.respondWith(this.handleEvent(event));
131
+ });
132
+ }
133
+ notFound() {
134
+ return new Response('Not Found', { status: 404 });
135
+ }
136
+ }
137
+ exports.Hono = Hono;
@@ -0,0 +1,2 @@
1
+ export { Hono, Middleware } from './hono';
2
+ export { Context } from './context';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Context = exports.Middleware = exports.Hono = void 0;
4
+ var hono_1 = require("./hono");
5
+ Object.defineProperty(exports, "Hono", { enumerable: true, get: function () { return hono_1.Hono; } });
6
+ Object.defineProperty(exports, "Middleware", { enumerable: true, get: function () { return hono_1.Middleware; } });
7
+ var context_1 = require("./context");
8
+ Object.defineProperty(exports, "Context", { enumerable: true, get: function () { return context_1.Context; } });
@@ -0,0 +1,6 @@
1
+ import type { Context } from '../../context';
2
+ export declare const basicAuth: (options: {
3
+ username: string;
4
+ password: string;
5
+ realm?: string;
6
+ }) => (ctx: Context, next: Function) => Promise<any>;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.basicAuth = void 0;
4
+ const util_1 = require("../../util");
5
+ const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/;
6
+ const USER_PASS_REGEXP = /^([^:]*):(.*)$/;
7
+ const auth = (req) => {
8
+ if (!req) {
9
+ throw new TypeError('argument req is required');
10
+ }
11
+ if (typeof req !== 'object') {
12
+ throw new TypeError('argument req is required to be an object');
13
+ }
14
+ if (!req.headers || typeof req.headers !== 'object') {
15
+ throw new TypeError('argument req is required to have headers property');
16
+ }
17
+ const match = CREDENTIALS_REGEXP.exec(req.headers.get('Authorization'));
18
+ if (!match) {
19
+ return undefined;
20
+ }
21
+ const userPass = USER_PASS_REGEXP.exec(decodeBase64(match[1]));
22
+ if (!userPass) {
23
+ return undefined;
24
+ }
25
+ return { username: userPass[1], password: userPass[2] };
26
+ };
27
+ function decodeBase64(str) {
28
+ return Buffer.from(str, 'base64').toString();
29
+ }
30
+ const basicAuth = (options) => {
31
+ if (!options.realm) {
32
+ options.realm = 'Secure Area';
33
+ }
34
+ return async (ctx, next) => {
35
+ const user = auth(ctx.req);
36
+ const usernameEqual = user && await (0, util_1.timingSafeEqual)(options.username, user.username);
37
+ const passwordEqual = user && await (0, util_1.timingSafeEqual)(options.password, user.password);
38
+ if (!user || !usernameEqual || !passwordEqual) {
39
+ ctx.res = new Response('Unauthorized', {
40
+ status: 401,
41
+ headers: {
42
+ 'WWW-Authenticate': 'Basic realm="' + options.realm.replace(/"/g, '\\"') + '"',
43
+ },
44
+ });
45
+ return;
46
+ }
47
+ return next();
48
+ };
49
+ };
50
+ exports.basicAuth = basicAuth;