@logto/tunnel 0.2.0 → 0.2.2

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.
@@ -25,4 +25,4 @@ export declare const checkExperienceInput: (url?: string, staticPath?: string) =
25
25
  * @example isLogtoRequestPath('/consent') // true
26
26
  */
27
27
  export declare const isLogtoRequestPath: (requestPath?: string) => boolean;
28
- export declare const isFileAssetPath: (url: string) => boolean;
28
+ export declare const getMimeType: (requestPath: string) => string;
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
- import { isValidUrl } from '@logto/core-kit';
4
+ import { isFileAssetPath, isValidUrl, parseRange } from '@logto/core-kit';
5
5
  import { conditional, trySafe } from '@silverhand/essentials';
6
6
  import chalk from 'chalk';
7
7
  import { createProxyMiddleware, responseInterceptor } from 'http-proxy-middleware';
@@ -35,18 +35,42 @@ export const createStaticFileProxy = (staticPath) => async (request, response) =
35
35
  if (request.method === 'HEAD' || request.method === 'GET') {
36
36
  const fallBackToIndex = !isFileAssetPath(request.url);
37
37
  const requestPath = path.join(staticPath, fallBackToIndex ? index : request.url);
38
+ const { range = '' } = request.headers;
39
+ const readFile = async (requestPath, start, end) => {
40
+ const fileHandle = await fs.open(requestPath, 'r');
41
+ const { size } = await fileHandle.stat();
42
+ const readStart = start ?? 0;
43
+ const readEnd = end ?? Math.max(size - 1, 0);
44
+ const buffer = Buffer.alloc(readEnd - readStart + 1);
45
+ await fileHandle.read(buffer, 0, buffer.length, readStart);
46
+ await fileHandle.close();
47
+ return { buffer, totalFileSize: size };
48
+ };
49
+ const setRangeHeaders = (response, range, totalFileSize) => {
50
+ if (range) {
51
+ const { start, end } = parseRange(range);
52
+ const readStart = start ?? 0;
53
+ const readEnd = end ?? totalFileSize - 1;
54
+ response.setHeader('Accept-Ranges', 'bytes');
55
+ response.setHeader('Content-Range', `bytes ${readStart}-${readEnd}/${totalFileSize}`);
56
+ }
57
+ };
38
58
  try {
39
- const content = await fs.readFile(requestPath, 'utf8');
59
+ const { start, end } = parseRange(range);
60
+ const { buffer, totalFileSize } = await readFile(requestPath, start, end);
40
61
  response.setHeader('cache-control', fallBackToIndex ? noCache : maxAgeSevenDays);
41
62
  response.setHeader('content-type', getMimeType(request.url));
42
- response.writeHead(200);
43
- response.end(content);
63
+ setRangeHeaders(response, range, totalFileSize);
64
+ response.setHeader('content-length', String(buffer.length));
65
+ response.writeHead(range ? 206 : 200);
66
+ response.end(buffer);
44
67
  }
45
68
  catch (error) {
46
69
  const errorMessage = error instanceof Error ? error.message : String(error);
47
70
  consoleLog.error(chalk.red(errorMessage));
48
71
  response.setHeader('content-type', getMimeType(request.url));
49
- response.writeHead(existsSync(request.url) ? 500 : 404);
72
+ const statusCode = errorMessage === 'Range not satisfiable.' ? 416 : existsSync(request.url) ? 500 : 404;
73
+ response.writeHead(statusCode);
50
74
  response.end();
51
75
  }
52
76
  }
@@ -116,12 +140,7 @@ Specify --help for available options`);
116
140
  * @example isLogtoRequestPath('/consent') // true
117
141
  */
118
142
  export const isLogtoRequestPath = (requestPath) => ['/oidc/', '/api/'].some((path) => requestPath?.startsWith(path)) || requestPath === '/consent';
119
- export const isFileAssetPath = (url) => {
120
- // Check if the request URL contains query params. If yes, ignore the params and check the request path
121
- const pathWithoutQuery = url.split('?')[0];
122
- return Boolean(pathWithoutQuery?.split('/').at(-1)?.includes('.'));
123
- };
124
- const getMimeType = (requestPath) => {
143
+ export const getMimeType = (requestPath) => {
125
144
  const fallBackToIndex = !isFileAssetPath(requestPath);
126
145
  if (fallBackToIndex) {
127
146
  return indexContentType;
@@ -1,14 +1,11 @@
1
1
  import { expect, describe, it } from 'vitest';
2
- import { isFileAssetPath } from './utils.js';
2
+ import { getMimeType } from './utils.js';
3
3
  describe('Tunnel utils', () => {
4
- it('should be able to check if a request path is file asset', () => {
5
- expect(isFileAssetPath('/file.js')).toBe(true);
6
- expect(isFileAssetPath('/file.css')).toBe(true);
7
- expect(isFileAssetPath('/file.png')).toBe(true);
8
- expect(isFileAssetPath('/oidc/.well-known/openid-configuration')).toBe(false);
9
- expect(isFileAssetPath('/oidc/auth')).toBe(false);
10
- expect(isFileAssetPath('/api/interaction/submit')).toBe(false);
11
- expect(isFileAssetPath('/consent')).toBe(false);
12
- expect(isFileAssetPath('/callback/45doq0d004awrjyvdbp92?state=PxsR_Iqtkxw&code=4/0AcvDMrCOMTFXWlKzTcUO24xDify5tQbIMYvaYDS0sj82NzzYlrG4BWXJB4-OxjBI1RPL8g&scope=email%20profile%20openid%20https:/www.googleapis.com/auth/userinfo.profile%20https:/www.googleapis.com/auth/userinfo.email&authuser=0&hd=silverhand.io&prompt=consent')).toBe(false);
4
+ it('should be able to get mime type according to request path', () => {
5
+ expect(getMimeType('/scripts.js')).toEqual('text/javascript');
6
+ expect(getMimeType('/image.png')).toEqual('image/png');
7
+ expect(getMimeType('/style.css')).toEqual('text/css');
8
+ expect(getMimeType('/index.html')).toEqual('text/html');
9
+ expect(getMimeType('/')).toEqual('text/html; charset=utf-8');
13
10
  });
14
11
  });
@@ -1,6 +1,6 @@
1
1
  export const packageJson = {
2
2
  "name": "@logto/tunnel",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "A CLI tool that creates tunnel service to Logto Cloud for local development.",
5
5
  "author": "Silverhand Inc. <contact@silverhand.io>",
6
6
  "homepage": "https://github.com/logto-io/logto#readme",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logto/tunnel",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "A CLI tool that creates tunnel service to Logto Cloud for local development.",
5
5
  "author": "Silverhand Inc. <contact@silverhand.io>",
6
6
  "homepage": "https://github.com/logto-io/logto#readme",
@@ -38,8 +38,8 @@
38
38
  "ora": "^8.0.1",
39
39
  "yargs": "^17.6.0",
40
40
  "zod": "^3.23.8",
41
- "@logto/core-kit": "^2.5.0",
42
- "@logto/shared": "^3.1.1"
41
+ "@logto/shared": "^3.1.2",
42
+ "@logto/core-kit": "^2.5.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@silverhand/eslint-config": "6.0.1",