pinggy 0.3.5 → 0.3.7

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 (68) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-65R2GMKQ.js +2101 -0
  3. package/dist/index.cjs +1798 -1349
  4. package/dist/index.d.cts +616 -0
  5. package/dist/index.d.ts +616 -0
  6. package/dist/index.js +24 -2
  7. package/dist/{main-K44C44NW.js → main-2QDG7PWL.js} +166 -1705
  8. package/package.json +2 -3
  9. package/.github/workflows/npm-publish-github-packages.yml +0 -34
  10. package/.github/workflows/publish-binaries.yml +0 -223
  11. package/Makefile +0 -4
  12. package/caxa_build.js +0 -24
  13. package/dist/chunk-T5ESYDJY.js +0 -121
  14. package/ent.plist +0 -14
  15. package/jest.config.js +0 -19
  16. package/src/_tests_/build_config.test.ts +0 -91
  17. package/src/cli/buildConfig.ts +0 -535
  18. package/src/cli/defaults.ts +0 -20
  19. package/src/cli/extendedOptions.ts +0 -153
  20. package/src/cli/help.ts +0 -43
  21. package/src/cli/options.ts +0 -50
  22. package/src/cli/starCli.ts +0 -229
  23. package/src/index.ts +0 -31
  24. package/src/logger.ts +0 -138
  25. package/src/main.ts +0 -87
  26. package/src/remote_management/handler.ts +0 -244
  27. package/src/remote_management/remoteManagement.ts +0 -226
  28. package/src/remote_management/remote_schema.ts +0 -176
  29. package/src/remote_management/websocket_handlers.ts +0 -180
  30. package/src/tui/blessed/TunnelTui.ts +0 -340
  31. package/src/tui/blessed/components/DisplayUpdaters.ts +0 -189
  32. package/src/tui/blessed/components/KeyBindings.ts +0 -236
  33. package/src/tui/blessed/components/Modals.ts +0 -302
  34. package/src/tui/blessed/components/UIComponents.ts +0 -306
  35. package/src/tui/blessed/components/index.ts +0 -4
  36. package/src/tui/blessed/config.ts +0 -53
  37. package/src/tui/blessed/headerFetcher.ts +0 -42
  38. package/src/tui/blessed/index.ts +0 -2
  39. package/src/tui/blessed/qrCodeGenerator.ts +0 -20
  40. package/src/tui/blessed/webDebuggerConnection.ts +0 -128
  41. package/src/tui/ink/asciArt.ts +0 -7
  42. package/src/tui/ink/hooks/useQrCodes.ts +0 -27
  43. package/src/tui/ink/hooks/useReqResHeaders.ts +0 -27
  44. package/src/tui/ink/hooks/useTerminalSize.ts +0 -26
  45. package/src/tui/ink/hooks/useTerminalStats.ts +0 -24
  46. package/src/tui/ink/hooks/useWebDebugger.ts +0 -98
  47. package/src/tui/ink/index.tsx +0 -243
  48. package/src/tui/ink/layout/Borders.tsx +0 -15
  49. package/src/tui/ink/layout/Container.tsx +0 -15
  50. package/src/tui/ink/sections/DebuggerDetailModal.tsx +0 -53
  51. package/src/tui/ink/sections/KeyBindings.tsx +0 -58
  52. package/src/tui/ink/sections/QrCodeSection.tsx +0 -28
  53. package/src/tui/ink/sections/StatsSection.tsx +0 -20
  54. package/src/tui/ink/sections/URLsSection.tsx +0 -53
  55. package/src/tui/ink/utils/utils.ts +0 -35
  56. package/src/tui/spinner/spinner.ts +0 -64
  57. package/src/tunnel_manager/TunnelManager.ts +0 -1212
  58. package/src/types.ts +0 -255
  59. package/src/utils/FileServer.ts +0 -112
  60. package/src/utils/detect_vc_redist_on_windows.ts +0 -111
  61. package/src/utils/getFreePort.ts +0 -41
  62. package/src/utils/htmlTemplates.ts +0 -146
  63. package/src/utils/parseArgs.ts +0 -79
  64. package/src/utils/printer.ts +0 -81
  65. package/src/utils/util.ts +0 -18
  66. package/src/workers/file_serve_worker.ts +0 -33
  67. package/tsconfig.json +0 -17
  68. package/tsup.config.ts +0 -12
package/src/types.ts DELETED
@@ -1,255 +0,0 @@
1
- import { PinggyOptions, TunnelUsageType } from "@pinggy/pinggy";
2
-
3
- // Local representation of additional forwarding
4
- export interface AdditionalForwarding {
5
- localDomain: string;
6
- localPort: number;
7
- remoteDomain?: string;
8
- remotePort?: number;
9
- protocol?: 'http' | 'tcp' | 'udp' | 'tls';
10
- }
11
-
12
-
13
- export interface TunnelStatus {
14
- tunnelid: string,
15
- remoteurls: string[],
16
- tunnelconfig: PinggyOptions,
17
- status: Status,
18
- stats: TunnelUsageType
19
- }
20
-
21
-
22
-
23
- // Enum for TunnelStateType
24
- export enum TunnelStateType {
25
- New = "idle",
26
- Starting = "starting",
27
- Running = "running",
28
- Live = "live",
29
- Closed = "closed",
30
- Exited = "exited"
31
- }
32
-
33
- // Enum for TunnelErrorCodeType
34
- export enum TunnelErrorCodeType {
35
- NonResponsive = "non_responsive",
36
- FailedToConnect = "failed_to_connect",
37
- ErrorInAdditionalForwarding = "additional_forwarding_error",
38
- WebdebuggerError = "webdebugger_error",
39
- NoError = ""
40
- }
41
-
42
- // Enum for TunnelWarningCode
43
- export enum TunnelWarningCode {
44
- InvalidTunnelServePath = "INVALID_TUNNEL_SERVE_PATH",
45
- UnknownWarning = "UNKNOWN_WARNING"
46
- }
47
-
48
- // Interface for Warning
49
- export interface Warning {
50
- code: TunnelWarningCode;
51
- message: string;
52
- }
53
-
54
-
55
-
56
- // Main Status interface
57
- export interface Status {
58
- state: TunnelStateType;
59
- errorcode: TunnelErrorCodeType;
60
- errormsg: string;
61
- createdtimestamp: string;
62
- starttimestamp: string;
63
- endtimestamp: string;
64
- warnings: Warning[];
65
- }
66
-
67
- export type FinalConfig = (PinggyOptions & { configid: string }) & {
68
- tunnelType: string[];
69
- conf?: string;
70
- saveconf?: string;
71
- serve?: string;
72
- remoteManagement?: string;
73
- additionalForwarding?: AdditionalForwarding[];
74
- manage?: string;
75
- version?: boolean;
76
- NoTUI?: boolean;
77
- qrCode?: boolean;
78
- };
79
-
80
- export type ErrorCodeType =
81
- | "INVALID_REQUEST_METHOD"
82
- | "COULD_NOT_READ_BODY"
83
- | "INTERNAL_SERVER_ERROR"
84
- | "INVALID_DATA_FORMAT"
85
- | "ERROR_STARTING_TUNNEL"
86
- | "TUNNEL_WITH_ID_OR_CONFIG_ID_NOT_FOUND"
87
- | "TUNNEL_WITH_ID_OR_CONFIG_ID_ALREADY_RUNNING"
88
- | "WEBSOCKET_UPGRADE_FAILED"
89
- | "REMOTE_MANAGEMENT_ALREADY_RUNNING"
90
- | "REMOTE_MANAGEMENT_NOT_RUNNING"
91
- | "REMOTE_MANAGEMENT_DESERIALIZATION_FAILED";
92
-
93
- export const ErrorCode: Record<string, ErrorCodeType> = {
94
- InvalidRequestMethodError: "INVALID_REQUEST_METHOD",
95
- InvalidRequestBodyError: "COULD_NOT_READ_BODY",
96
- InternalServerError: "INTERNAL_SERVER_ERROR",
97
- InvalidBodyFormatError: "INVALID_DATA_FORMAT",
98
- ErrorStartingTunnel: "ERROR_STARTING_TUNNEL",
99
- TunnelNotFound: "TUNNEL_WITH_ID_OR_CONFIG_ID_NOT_FOUND",
100
- TunnelAlreadyRunningError: "TUNNEL_WITH_ID_OR_CONFIG_ID_ALREADY_RUNNING",
101
- WebsocketUpgradeFailError: "WEBSOCKET_UPGRADE_FAILED",
102
- RemoteManagementAlreadyRunning: "REMOTE_MANAGEMENT_ALREADY_RUNNING",
103
- RemoteManagementNotRunning: "REMOTE_MANAGEMENT_NOT_RUNNING",
104
- RemoteManagementDeserializationFailed: "REMOTE_MANAGEMENT_DESERIALIZATION_FAILED",
105
- } as const;
106
-
107
- export interface ErrorResponse {
108
- code: ErrorCodeType;
109
- message: string;
110
- }
111
-
112
- export function isErrorResponse(obj: unknown): obj is ErrorResponse {
113
- return (
114
- typeof obj === 'object' &&
115
- obj !== null &&
116
- 'code' in obj &&
117
- 'message' in obj &&
118
- typeof (obj as ErrorResponse).message === 'string' &&
119
- Object.values(ErrorCode).includes((obj as ErrorResponse).code)
120
- );
121
- }
122
-
123
- export function newErrorResponse(errorResponse: ErrorResponse): ErrorResponse;
124
- export function newErrorResponse(code: ErrorCodeType, message: string): ErrorResponse;
125
- export function newErrorResponse(codeOrError: ErrorCodeType | ErrorResponse, message?: string): ErrorResponse {
126
- if (typeof codeOrError === 'object') {
127
- return codeOrError;
128
- }
129
- return {
130
- code: codeOrError,
131
- message: message!
132
- };
133
- }
134
-
135
-
136
- export interface ResponseObj {
137
- response: Uint8Array;
138
- requestid: string;
139
- command: string;
140
- error: boolean;
141
- errorresponse: ErrorResponse;
142
- }
143
-
144
- export function NewResponseObject(data: unknown): ResponseObj {
145
- const encoder = new TextEncoder();
146
- const bytes = encoder.encode(JSON.stringify(data));
147
- return {
148
- response: bytes,
149
- requestid: "",
150
- command: "",
151
- error: false,
152
- errorresponse: {} as ErrorResponse,
153
- };
154
- }
155
-
156
- export function NewErrorResponseObject(errorResponse: ErrorResponse): ResponseObj {
157
- return {
158
- response: new Uint8Array(),
159
- requestid: "",
160
- command: "",
161
- error: true,
162
- errorresponse: errorResponse,
163
- };
164
- }
165
-
166
- export function newStatus(
167
- tunnelState: TunnelStateType,
168
- errorCode: TunnelErrorCodeType,
169
- errorMsg: string,
170
- ): Status {
171
- let assignedState = tunnelState;
172
- if (tunnelState === TunnelStateType.Live) {
173
- assignedState = TunnelStateType.Running;
174
- } else if (tunnelState === TunnelStateType.New) {
175
- assignedState = TunnelStateType.New;
176
- } else if (tunnelState === TunnelStateType.Closed) {
177
- assignedState = TunnelStateType.Exited;
178
- }
179
- const now = new Date().toISOString();
180
- return {
181
- state: assignedState,
182
- errorcode: errorCode,
183
- errormsg: errorMsg,
184
- createdtimestamp: now,
185
- starttimestamp: now,
186
- endtimestamp: now,
187
- warnings: []
188
- };
189
- }
190
-
191
- export function newStats(): TunnelUsageType {
192
- return {
193
- numLiveConnections: 0,
194
- numTotalConnections: 0,
195
- numTotalReqBytes: 0,
196
- numTotalResBytes: 0,
197
- numTotalTxBytes: 0,
198
- elapsedTime: 0,
199
- };
200
- }
201
-
202
- export interface Request {
203
- key: number;
204
- method: string;
205
- uri: string;
206
- }
207
-
208
- export interface Response {
209
- key: number;
210
- status: string;
211
- }
212
-
213
- export interface ReqResPair {
214
- request: Request;
215
- response: Response;
216
- reqHeaders: string;
217
- resHeaders: string;
218
- headersLoaded: boolean;
219
- }
220
- export interface StatsAll {
221
- activeConn: number;
222
- numRequests: number;
223
- numResponses: number;
224
- reqBytes: number;
225
- resBytes: number;
226
- totalConn: number;
227
- }
228
- export interface WebDebuggerSocketRequest {
229
- Req: Request;
230
- Res: Response;
231
- }
232
-
233
- export type RemoteManagementStatusType =
234
- | "CONNECTING"
235
- | "DISCONNECTING"
236
- | "RECONNECTING"
237
- | "RUNNING"
238
- | "NOT_RUNNING"
239
- | "ERROR";
240
-
241
- export const RemoteManagementStatus: Record<string, RemoteManagementStatusType> = {
242
- Connecting: "CONNECTING",
243
- Disconnecting: "DISCONNECTING",
244
- Reconnecting: "RECONNECTING",
245
- Running: "RUNNING",
246
- NotRunning: "NOT_RUNNING",
247
- Error: "ERROR",
248
- } as const;
249
-
250
- export interface RemoteManagementState {
251
- status: RemoteManagementStatusType;
252
- errorMessage: string;
253
- }
254
-
255
-
@@ -1,112 +0,0 @@
1
- import { createServer } from "http";
2
- import { readFile, readdir, stat } from "fs/promises";
3
- import { existsSync } from "fs";
4
- import { extname, join, resolve, relative } from "path";
5
- import { URL } from "url";
6
- import mime from "mime";
7
- import { logger } from "../logger.js";
8
- import { directoryListingHtml, invalidPathErrorHtml } from "./htmlTemplates.js";
9
-
10
- export class FileServerError extends Error {
11
- code: string;
12
- constructor(message: string, code: string) {
13
- super(message);
14
- this.name = "FileServerError";
15
- this.code = code;
16
- }
17
- }
18
-
19
- export async function startFileServer(dirPath: string, port = 8080) {
20
- let invalidPathError: FileServerError | null = null;
21
- const root = resolve(dirPath);
22
- logger.debug("Starting file server with root:", root, "on port:", port);
23
-
24
- if (!existsSync(root)) {
25
- logger.debug("Invalid root path for file server:", root);
26
- invalidPathError = new FileServerError(`The path ${dirPath} does not exist. Please check the path and try again.`, "INVALID_TUNNEL_SERVE_PATH");
27
- }
28
-
29
-
30
- const server = createServer(async (req, res) => {
31
- try {
32
- // If invalid root, show an HTML error page
33
- if (invalidPathError) {
34
- const html = invalidPathErrorHtml(invalidPathError);
35
- res.writeHead(200, { "Content-Type": "text/html" });
36
- res.end(html);
37
- return;
38
- }
39
- const reqUrl = new URL(req.url || "/", `http://${req.headers.host}`);
40
- let filePath = join(root, decodeURIComponent(reqUrl.pathname));
41
-
42
- let stats;
43
- try {
44
- stats = await stat(filePath);
45
- } catch {
46
- res.statusCode = 404;
47
- res.end("404 Not Found");
48
- return;
49
- }
50
-
51
- if (stats.isDirectory() && !reqUrl.pathname.endsWith("/")) {
52
- res.writeHead(301, { Location: reqUrl.pathname + "/" });
53
- res.end();
54
- return;
55
- }
56
-
57
- // Directory handling
58
- if (stats.isDirectory()) {
59
- const indexPath = join(filePath, "index.html");
60
- if (existsSync(indexPath)) {
61
- filePath = indexPath;
62
- } else {
63
- // No index.html — show directory listing
64
- const items = await readdir(filePath, { withFileTypes: true });
65
-
66
- const list = items
67
- .map((item) => {
68
- // Get the display name
69
- const name = item.name + (item.isDirectory() ? "/" : "");
70
- // Construct the current base URL
71
- const base = new URL(reqUrl.pathname, `http://${req.headers.host}`);
72
- // Create the full URL
73
- const hrefUrl = new URL(encodeURIComponent(name), base);
74
- return `<li><a href="${hrefUrl.pathname}">${name}</a></li>`;
75
- })
76
- .join("");
77
-
78
- const relativePath = relative(root, filePath) || "/";
79
- const html = directoryListingHtml(relativePath, list);
80
- res.writeHead(200, { "Content-Type": "text/html" });
81
- res.end(html);
82
- return;
83
- }
84
- }
85
-
86
- // Normal file serving
87
- const content = await readFile(filePath);
88
- const type = mime.getType(extname(filePath)) || "application/octet-stream";
89
- res.writeHead(200, { "Content-Type": type });
90
- res.end(content);
91
- } catch (err: any) {
92
- logger.debug("Error in handling request", err)
93
- res.statusCode = 500;
94
- res.end(`Internal Server Error: ${err.message}`);
95
- }
96
- });
97
-
98
- await new Promise<void>((resolve, reject) => {
99
- server.listen(port, () => {
100
- resolve();
101
- });
102
- server.on("error", (err) => {
103
- logger.debug("Error starting file server", err);
104
- reject(err);
105
- });
106
- });
107
-
108
- return {
109
- hasInvalidPath: !!invalidPathError,
110
- error: invalidPathError ? { message: invalidPathError.message, code: invalidPathError.code } : null
111
- }
112
- }
@@ -1,111 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { exec, execSync } from "child_process";
4
- import os from "os";
5
- import CLIPrinter from "./printer.js";
6
- import { promisify } from "util";
7
-
8
- const execAsync = promisify(exec);
9
-
10
- const DLLS = ["vcruntime140.dll", "vcruntime140_1.dll", "msvcp140.dll"];
11
-
12
- const PATHS = ["C:\\Windows\\System32", "C:\\Windows\\SysWOW64"];
13
-
14
- // Registry keys for different VC++ Redistributable versions
15
- const REGISTRY_KEYS = [
16
- "HKLM\\SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\X64",
17
- "HKLM\\SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\X86",
18
- "HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\X64",
19
- "HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\X86",
20
- ];
21
-
22
- /**
23
- * Check if VC++ Redistributable DLLs exist
24
- */
25
- function checkDLLs() {
26
- return DLLS.every((dll) =>
27
- PATHS.some((p) => {
28
- try {
29
- return fs.existsSync(path.join(p, dll));
30
- } catch {
31
- return false;
32
- }
33
- }),
34
- );
35
- }
36
-
37
- /**
38
- * Check Windows Registry for VC++ Redistributable installation
39
- */
40
- function checkRegistry() {
41
- if (os.platform() !== "win32") return false;
42
-
43
- try {
44
- for (const key of REGISTRY_KEYS) {
45
- const cmd = `reg query "${key}" /v Installed 2>nul`;
46
- const result = execSync(cmd, { encoding: "utf8" });
47
-
48
- if (result.includes("0x1")) {
49
- return true;
50
- }
51
- }
52
- } catch {
53
- // Registry check failed, fall back to DLL check
54
- }
55
-
56
- return false;
57
- }
58
-
59
-
60
- /**
61
- * Main detection function - returns status and message for VC++ Redistributable
62
- */
63
- export function checkVCRedist() {
64
- if (os.platform() !== "win32") {
65
- return {
66
- required: false,
67
- installed: true,
68
- message: null,
69
- };
70
- }
71
-
72
- const registryInstalled = checkRegistry();
73
- const dllsPresent = checkDLLs();
74
- const installed = registryInstalled || dllsPresent;
75
-
76
- return {
77
- required: true,
78
- installed,
79
- message: installed
80
- ? null
81
- : "Missing Microsoft Visual C++ Runtime. This application requires the Microsoft Visual C++ Runtime to run on Windows.\n"
82
- };
83
- }
84
-
85
- /**
86
- * Open download page in browser
87
- */
88
- export async function openDownloadPage() {
89
- if (process.platform !== "win32") {
90
- return;
91
- }
92
- const url =
93
- "https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170";
94
-
95
- // Use cmd.exe explicitly for better compatibility
96
- const command = `cmd.exe /c start "" "${url}"`;
97
-
98
- try {
99
- await execAsync(command);
100
- CLIPrinter.info("\nOpening Microsoft download page in your browser...");
101
- CLIPrinter.info(
102
- "Please install the Visual C++ Runtime and restart this application.\n",
103
- );
104
- } catch (err) {
105
- CLIPrinter.info("\nUnable to open your browser automatically.");
106
- CLIPrinter.info(
107
- "Please visit the following page to download the runtime:\n",
108
- );
109
- CLIPrinter.info(url + "\n");
110
- }
111
- }
@@ -1,41 +0,0 @@
1
- import net from "net";
2
-
3
- /**
4
- * Get a free TCP port.
5
- * If `providedPort` is available, returns that.
6
- * Otherwise, returns a randomly assigned free port from the OS.
7
- */
8
- export function getFreePort(webDebugger: string): Promise<number> {
9
- return new Promise((resolve, reject) => {
10
- // Try provided port first
11
- const tryPort = (portToTry: number) => {
12
- const server = net.createServer();
13
-
14
- server.unref();
15
-
16
- server.on("error", (err) => {
17
- // If provided port failed, try random port (0)
18
- if (portToTry !== 0) {
19
- tryPort(0);
20
- } else {
21
- reject(err);
22
- }
23
- });
24
-
25
- server.listen(portToTry, () => {
26
- const address: net.AddressInfo = server.address() as net.AddressInfo;
27
- const port = address ? address.port : 0;
28
- server.close(() => resolve(port));
29
- });
30
- };
31
- let providedPort = 0;
32
- if (webDebugger && webDebugger.includes(":")) {
33
- const portPart = webDebugger.split(":")[1];
34
- const parsed = parseInt(portPart, 10);
35
- if (!isNaN(parsed) && parsed > 0 && parsed < 65536) {
36
- providedPort = parsed;
37
- }
38
- }
39
- tryPort(providedPort);
40
- });
41
- }
@@ -1,146 +0,0 @@
1
- import { FileServerError } from "./FileServer.js"
2
-
3
- export function directoryListingHtml(relativePath: string, list: string): string {
4
- return (
5
-
6
- `
7
- <html>
8
- <head>
9
- <meta charset="utf-8" />
10
- <title>Index of ${relativePath}</title>
11
- <style>
12
- body {
13
- font-family: sans-serif;
14
- margin: 0;
15
- padding: 0;
16
- height: 100vh;
17
- display: flex;
18
- flex-direction: column;
19
- overflow: hidden;
20
- }
21
- header {
22
- padding: 20px;
23
- background: #f9f9f9;
24
- border-bottom: 1px solid #eee;
25
- }
26
- main {
27
- flex: 1;
28
- overflow-y: auto;
29
- padding: 20px;
30
- }
31
- ul {
32
- list-style-type: none;
33
- padding-left: 0;
34
- margin: 0;
35
- }
36
- li {
37
- margin: 4px 0;
38
- }
39
- a {
40
- text-decoration: none;
41
- color: #0366d6;
42
- }
43
- a:hover {
44
- text-decoration: underline;
45
- }
46
- footer {
47
- position: fixed;
48
- bottom: 0;
49
- left: 0;
50
- width: 100%;
51
- background: #f9f9f9;
52
- border-top: 1px solid #eee;
53
- text-align: center;
54
- padding: 10px 0;
55
- font-size: 0.9em;
56
- color: #777;
57
- }
58
- </style>
59
- </head>
60
- <body>
61
- <header><h1>Index of ${relativePath}</h1></header>
62
- <main>
63
- <ul>${list}</ul>
64
- </main>
65
- <footer>
66
- Powered by <a href="https://pinggy.io" target="_blank">Pinggy</a>
67
- </footer>
68
- </body>
69
- </html>
70
- `
71
- )
72
- }
73
-
74
- export function invalidPathErrorHtml(invalidPathError: FileServerError): string {
75
-
76
- return (
77
- `
78
- <html>
79
- <head>
80
- <meta charset="utf-8" />
81
- <title>File server error</title>
82
- <style>
83
- body {
84
- font-family: sans-serif;
85
- margin: 0;
86
- padding: 0;
87
- height: 100vh;
88
- display: flex;
89
- flex-direction: column;
90
- overflow: hidden;
91
- }
92
- header {
93
- padding: 15px;
94
- background: #f9f9f9;
95
- border-bottom: 1px solid #eee;
96
- }
97
- main {
98
- flex: 1;
99
- overflow-y: auto;
100
- padding: 20px;
101
- }
102
- ul {
103
- list-style-type: none;
104
- padding-left: 0;
105
- margin: 0;
106
- }
107
- li {
108
- margin: 4px 0;
109
- }
110
- a {
111
- text-decoration: none;
112
- color: #0366d6;
113
- }
114
- a:hover {
115
- text-decoration: underline;
116
- }
117
- footer {
118
- position: fixed;
119
- bottom: 0;
120
- left: 0;
121
- width: 100%;
122
- background: #f9f9f9;
123
- border-top: 1px solid #eee;
124
- text-align: center;
125
- padding: 10px 0;
126
- font-size: 0.9em;
127
- color: #777;
128
- }
129
- </style>
130
- </head>
131
- <body>
132
- <header><h1>⚠️ File Server Error</h1></header>
133
- <main>
134
- <p>${invalidPathError.message}</p>
135
- <p>Error Code: <code>${invalidPathError.code}</code></p>
136
- </main>
137
- <footer>
138
- Powered by <a href="https://pinggy.io" target="_blank">Pinggy</a>
139
- </footer>
140
- </body>
141
- </html>
142
- `
143
- )
144
-
145
- }
146
-