@cloudflare/sandbox 0.0.0-fddccfd → 0.0.0-ff2fa91
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/CHANGELOG.md +102 -15
- package/Dockerfile +84 -31
- package/README.md +9 -2
- package/dist/index.d.ts +1889 -9
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3144 -64
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/clients/base-client.ts +39 -24
- package/src/clients/command-client.ts +8 -8
- package/src/clients/file-client.ts +51 -20
- package/src/clients/git-client.ts +3 -4
- package/src/clients/index.ts +12 -15
- package/src/clients/interpreter-client.ts +51 -47
- package/src/clients/port-client.ts +10 -10
- package/src/clients/process-client.ts +11 -8
- package/src/clients/sandbox-client.ts +2 -4
- package/src/clients/types.ts +6 -2
- package/src/clients/utility-client.ts +34 -5
- package/src/errors/adapter.ts +90 -32
- package/src/errors/classes.ts +189 -64
- package/src/errors/index.ts +9 -5
- package/src/file-stream.ts +11 -6
- package/src/index.ts +22 -15
- package/src/interpreter.ts +50 -41
- package/src/request-handler.ts +34 -21
- package/src/sandbox.ts +443 -144
- package/src/security.ts +21 -6
- package/src/sse-parser.ts +4 -3
- package/src/version.ts +6 -0
- package/tests/base-client.test.ts +116 -80
- package/tests/command-client.test.ts +149 -112
- package/tests/file-client.test.ts +373 -185
- package/tests/file-stream.test.ts +24 -20
- package/tests/get-sandbox.test.ts +149 -0
- package/tests/git-client.test.ts +188 -101
- package/tests/port-client.test.ts +100 -108
- package/tests/process-client.test.ts +204 -179
- package/tests/request-handler.test.ts +292 -0
- package/tests/sandbox.test.ts +303 -62
- package/tests/sse-parser.test.ts +17 -16
- package/tests/utility-client.test.ts +129 -56
- package/tests/version.test.ts +16 -0
- package/tsdown.config.ts +12 -0
- package/vitest.config.ts +6 -6
- package/dist/chunk-2P3MDMNJ.js +0 -2367
- package/dist/chunk-2P3MDMNJ.js.map +0 -1
- package/dist/chunk-BFVUNTP4.js +0 -104
- package/dist/chunk-BFVUNTP4.js.map +0 -1
- package/dist/chunk-EKSWCBCA.js +0 -86
- package/dist/chunk-EKSWCBCA.js.map +0 -1
- package/dist/chunk-JXZMAU2C.js +0 -559
- package/dist/chunk-JXZMAU2C.js.map +0 -1
- package/dist/chunk-Z532A7QC.js +0 -78
- package/dist/chunk-Z532A7QC.js.map +0 -1
- package/dist/file-stream.d.ts +0 -43
- package/dist/file-stream.js +0 -9
- package/dist/file-stream.js.map +0 -1
- package/dist/interpreter.d.ts +0 -33
- package/dist/interpreter.js +0 -8
- package/dist/interpreter.js.map +0 -1
- package/dist/request-handler.d.ts +0 -18
- package/dist/request-handler.js +0 -12
- package/dist/request-handler.js.map +0 -1
- package/dist/sandbox-CZTMzV2R.d.ts +0 -587
- package/dist/sandbox.d.ts +0 -4
- package/dist/sandbox.js +0 -12
- package/dist/sandbox.js.map +0 -1
- package/dist/security.d.ts +0 -31
- package/dist/security.js +0 -13
- package/dist/security.js.map +0 -1
- package/dist/sse-parser.d.ts +0 -28
- package/dist/sse-parser.js +0 -11
- package/dist/sse-parser.js.map +0 -1
package/src/errors/index.ts
CHANGED
|
@@ -39,11 +39,13 @@
|
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
// Re-export context types for advanced usage
|
|
42
|
-
export type {
|
|
42
|
+
export type {
|
|
43
43
|
CodeExecutionContext,
|
|
44
44
|
CommandErrorContext,
|
|
45
45
|
CommandNotFoundContext,
|
|
46
|
-
ContextNotFoundContext,
|
|
46
|
+
ContextNotFoundContext,
|
|
47
|
+
ErrorCodeType,
|
|
48
|
+
ErrorResponse,
|
|
47
49
|
FileExistsContext,
|
|
48
50
|
FileNotFoundContext,
|
|
49
51
|
FileSystemContext,
|
|
@@ -53,13 +55,15 @@ export type {
|
|
|
53
55
|
GitRepositoryNotFoundContext,
|
|
54
56
|
InternalErrorContext,
|
|
55
57
|
InterpreterNotReadyContext,
|
|
56
|
-
InvalidPortContext,
|
|
58
|
+
InvalidPortContext,
|
|
59
|
+
OperationType,
|
|
57
60
|
PortAlreadyExposedContext,
|
|
58
61
|
PortErrorContext,
|
|
59
62
|
PortNotExposedContext,
|
|
60
63
|
ProcessErrorContext,
|
|
61
64
|
ProcessNotFoundContext,
|
|
62
|
-
ValidationFailedContext
|
|
65
|
+
ValidationFailedContext
|
|
66
|
+
} from '@repo/shared/errors';
|
|
63
67
|
// Re-export shared types and constants
|
|
64
68
|
export { ErrorCode, Operation } from '@repo/shared/errors';
|
|
65
69
|
|
|
@@ -101,5 +105,5 @@ export {
|
|
|
101
105
|
SandboxError,
|
|
102
106
|
ServiceNotRespondingError,
|
|
103
107
|
// Validation Errors
|
|
104
|
-
ValidationFailedError
|
|
108
|
+
ValidationFailedError
|
|
105
109
|
} from './classes';
|
package/src/file-stream.ts
CHANGED
|
@@ -3,7 +3,9 @@ import type { FileChunk, FileMetadata, FileStreamEvent } from '@repo/shared';
|
|
|
3
3
|
/**
|
|
4
4
|
* Parse SSE (Server-Sent Events) lines from a stream
|
|
5
5
|
*/
|
|
6
|
-
async function* parseSSE(
|
|
6
|
+
async function* parseSSE(
|
|
7
|
+
stream: ReadableStream<Uint8Array>
|
|
8
|
+
): AsyncGenerator<FileStreamEvent> {
|
|
7
9
|
const reader = stream.getReader();
|
|
8
10
|
const decoder = new TextDecoder();
|
|
9
11
|
let buffer = '';
|
|
@@ -59,7 +61,9 @@ async function* parseSSE(stream: ReadableStream<Uint8Array>): AsyncGenerator<Fil
|
|
|
59
61
|
* }
|
|
60
62
|
* ```
|
|
61
63
|
*/
|
|
62
|
-
export async function* streamFile(
|
|
64
|
+
export async function* streamFile(
|
|
65
|
+
stream: ReadableStream<Uint8Array>
|
|
66
|
+
): AsyncGenerator<FileChunk, FileMetadata> {
|
|
63
67
|
let metadata: FileMetadata | null = null;
|
|
64
68
|
|
|
65
69
|
for await (const event of parseSSE(stream)) {
|
|
@@ -69,7 +73,7 @@ export async function* streamFile(stream: ReadableStream<Uint8Array>): AsyncGene
|
|
|
69
73
|
mimeType: event.mimeType,
|
|
70
74
|
size: event.size,
|
|
71
75
|
isBinary: event.isBinary,
|
|
72
|
-
encoding: event.encoding
|
|
76
|
+
encoding: event.encoding
|
|
73
77
|
};
|
|
74
78
|
break;
|
|
75
79
|
|
|
@@ -144,8 +148,9 @@ export async function collectFile(stream: ReadableStream<Uint8Array>): Promise<{
|
|
|
144
148
|
// Combine chunks based on type
|
|
145
149
|
if (metadata.isBinary) {
|
|
146
150
|
// Binary file - combine Uint8Arrays
|
|
147
|
-
const totalLength = chunks.reduce(
|
|
148
|
-
sum + (chunk instanceof Uint8Array ? chunk.length : 0),
|
|
151
|
+
const totalLength = chunks.reduce(
|
|
152
|
+
(sum, chunk) => sum + (chunk instanceof Uint8Array ? chunk.length : 0),
|
|
153
|
+
0
|
|
149
154
|
);
|
|
150
155
|
const combined = new Uint8Array(totalLength);
|
|
151
156
|
let offset = 0;
|
|
@@ -158,7 +163,7 @@ export async function collectFile(stream: ReadableStream<Uint8Array>): Promise<{
|
|
|
158
163
|
return { content: combined, metadata };
|
|
159
164
|
} else {
|
|
160
165
|
// Text file - combine strings
|
|
161
|
-
const combined = chunks.filter(c => typeof c === 'string').join('');
|
|
166
|
+
const combined = chunks.filter((c) => typeof c === 'string').join('');
|
|
162
167
|
return { content: combined, metadata };
|
|
163
168
|
}
|
|
164
169
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Export the main Sandbox class and utilities
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
// Export the new client architecture
|
|
5
4
|
export {
|
|
6
5
|
CommandClient,
|
|
@@ -10,8 +9,8 @@ export {
|
|
|
10
9
|
ProcessClient,
|
|
11
10
|
SandboxClient,
|
|
12
11
|
UtilityClient
|
|
13
|
-
} from
|
|
14
|
-
export { getSandbox, Sandbox } from
|
|
12
|
+
} from './clients';
|
|
13
|
+
export { getSandbox, Sandbox } from './sandbox';
|
|
15
14
|
|
|
16
15
|
// Legacy types are now imported from the new client architecture
|
|
17
16
|
|
|
@@ -20,21 +19,20 @@ export type {
|
|
|
20
19
|
BaseExecOptions,
|
|
21
20
|
ExecEvent,
|
|
22
21
|
ExecOptions,
|
|
23
|
-
ExecResult,
|
|
22
|
+
ExecResult,
|
|
23
|
+
FileChunk,
|
|
24
|
+
FileMetadata,
|
|
25
|
+
FileStreamEvent,
|
|
24
26
|
ISandbox,
|
|
25
27
|
LogEvent,
|
|
26
28
|
Process,
|
|
27
29
|
ProcessOptions,
|
|
28
30
|
ProcessStatus,
|
|
29
|
-
StreamOptions
|
|
30
|
-
} from
|
|
31
|
+
StreamOptions
|
|
32
|
+
} from '@repo/shared';
|
|
31
33
|
export * from '@repo/shared';
|
|
32
34
|
// Export type guards for runtime validation
|
|
33
|
-
export {
|
|
34
|
-
isExecResult,
|
|
35
|
-
isProcess,
|
|
36
|
-
isProcessStatus
|
|
37
|
-
} from "@repo/shared";
|
|
35
|
+
export { isExecResult, isProcess, isProcessStatus } from '@repo/shared';
|
|
38
36
|
// Export all client types from new architecture
|
|
39
37
|
export type {
|
|
40
38
|
BaseApiResponse,
|
|
@@ -79,15 +77,24 @@ export type {
|
|
|
79
77
|
StartProcessRequest,
|
|
80
78
|
UnexposePortRequest,
|
|
81
79
|
WriteFileRequest
|
|
82
|
-
} from
|
|
83
|
-
export type {
|
|
80
|
+
} from './clients';
|
|
81
|
+
export type {
|
|
82
|
+
ExecutionCallbacks,
|
|
83
|
+
InterpreterClient
|
|
84
|
+
} from './clients/interpreter-client.js';
|
|
84
85
|
// Export file streaming utilities for binary file support
|
|
85
86
|
export { collectFile, streamFile } from './file-stream';
|
|
86
87
|
// Export interpreter functionality
|
|
87
88
|
export { CodeInterpreter } from './interpreter.js';
|
|
88
89
|
// Re-export request handler utilities
|
|
89
90
|
export {
|
|
90
|
-
proxyToSandbox,
|
|
91
|
+
proxyToSandbox,
|
|
92
|
+
type RouteInfo,
|
|
93
|
+
type SandboxEnv
|
|
91
94
|
} from './request-handler';
|
|
92
95
|
// Export SSE parser for converting ReadableStream to AsyncIterable
|
|
93
|
-
export {
|
|
96
|
+
export {
|
|
97
|
+
asyncIterableToSSEStream,
|
|
98
|
+
parseSSEStream,
|
|
99
|
+
responseToAsyncIterable
|
|
100
|
+
} from './sse-parser';
|
package/src/interpreter.ts
CHANGED
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
type OutputMessage,
|
|
7
7
|
type Result,
|
|
8
8
|
ResultImpl,
|
|
9
|
-
type RunCodeOptions
|
|
10
|
-
} from
|
|
11
|
-
import type { InterpreterClient } from
|
|
12
|
-
import type { Sandbox } from
|
|
13
|
-
import { validateLanguage } from
|
|
9
|
+
type RunCodeOptions
|
|
10
|
+
} from '@repo/shared';
|
|
11
|
+
import type { InterpreterClient } from './clients/interpreter-client.js';
|
|
12
|
+
import type { Sandbox } from './sandbox.js';
|
|
13
|
+
import { validateLanguage } from './security.js';
|
|
14
14
|
|
|
15
15
|
export class CodeInterpreter {
|
|
16
16
|
private interpreterClient: InterpreterClient;
|
|
@@ -18,7 +18,8 @@ export class CodeInterpreter {
|
|
|
18
18
|
|
|
19
19
|
constructor(sandbox: Sandbox) {
|
|
20
20
|
// In init-testing architecture, client is a SandboxClient with an interpreter property
|
|
21
|
-
this.interpreterClient = (sandbox.client as any)
|
|
21
|
+
this.interpreterClient = (sandbox.client as any)
|
|
22
|
+
.interpreter as InterpreterClient;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -46,7 +47,7 @@ export class CodeInterpreter {
|
|
|
46
47
|
let context = options.context;
|
|
47
48
|
if (!context) {
|
|
48
49
|
// Try to find or create a default context for the language
|
|
49
|
-
const language = options.language ||
|
|
50
|
+
const language = options.language || 'python';
|
|
50
51
|
context = await this.getOrCreateDefaultContext(language);
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -54,24 +55,29 @@ export class CodeInterpreter {
|
|
|
54
55
|
const execution = new Execution(code, context);
|
|
55
56
|
|
|
56
57
|
// Stream execution
|
|
57
|
-
await this.interpreterClient.runCodeStream(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
58
|
+
await this.interpreterClient.runCodeStream(
|
|
59
|
+
context.id,
|
|
60
|
+
code,
|
|
61
|
+
options.language,
|
|
62
|
+
{
|
|
63
|
+
onStdout: (output: OutputMessage) => {
|
|
64
|
+
execution.logs.stdout.push(output.text);
|
|
65
|
+
if (options.onStdout) return options.onStdout(output);
|
|
66
|
+
},
|
|
67
|
+
onStderr: (output: OutputMessage) => {
|
|
68
|
+
execution.logs.stderr.push(output.text);
|
|
69
|
+
if (options.onStderr) return options.onStderr(output);
|
|
70
|
+
},
|
|
71
|
+
onResult: async (result: Result) => {
|
|
72
|
+
execution.results.push(new ResultImpl(result) as any);
|
|
73
|
+
if (options.onResult) return options.onResult(result);
|
|
74
|
+
},
|
|
75
|
+
onError: (error: ExecutionError) => {
|
|
76
|
+
execution.error = error;
|
|
77
|
+
if (options.onError) return options.onError(error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
);
|
|
75
81
|
|
|
76
82
|
return execution;
|
|
77
83
|
}
|
|
@@ -86,36 +92,39 @@ export class CodeInterpreter {
|
|
|
86
92
|
// Get or create context
|
|
87
93
|
let context = options.context;
|
|
88
94
|
if (!context) {
|
|
89
|
-
const language = options.language ||
|
|
95
|
+
const language = options.language || 'python';
|
|
90
96
|
context = await this.getOrCreateDefaultContext(language);
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
// Create streaming response
|
|
94
100
|
// Note: doFetch is protected but we need direct access for raw stream response
|
|
95
|
-
const response = await (this.interpreterClient as any).doFetch(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
const response = await (this.interpreterClient as any).doFetch(
|
|
102
|
+
'/api/execute/code',
|
|
103
|
+
{
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: {
|
|
106
|
+
'Content-Type': 'application/json',
|
|
107
|
+
Accept: 'text/event-stream'
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify({
|
|
110
|
+
context_id: context.id,
|
|
111
|
+
code,
|
|
112
|
+
language: options.language
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
);
|
|
107
116
|
|
|
108
117
|
if (!response.ok) {
|
|
109
118
|
const errorData = (await response
|
|
110
119
|
.json()
|
|
111
|
-
.catch(() => ({ error:
|
|
120
|
+
.catch(() => ({ error: 'Unknown error' }))) as { error?: string };
|
|
112
121
|
throw new Error(
|
|
113
122
|
errorData.error || `Failed to execute code: ${response.status}`
|
|
114
123
|
);
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
if (!response.body) {
|
|
118
|
-
throw new Error(
|
|
127
|
+
throw new Error('No response body for streaming execution');
|
|
119
128
|
}
|
|
120
129
|
|
|
121
130
|
return response.body;
|
|
@@ -144,7 +153,7 @@ export class CodeInterpreter {
|
|
|
144
153
|
}
|
|
145
154
|
|
|
146
155
|
private async getOrCreateDefaultContext(
|
|
147
|
-
language:
|
|
156
|
+
language: 'python' | 'javascript' | 'typescript'
|
|
148
157
|
): Promise<CodeContext> {
|
|
149
158
|
// Check if we have a cached context for this language
|
|
150
159
|
for (const context of this.contexts.values()) {
|
package/src/request-handler.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
validatePort
|
|
6
|
-
} from "./security";
|
|
1
|
+
import { switchPort } from '@cloudflare/containers';
|
|
2
|
+
import { createLogger, type LogContext, TraceContext } from '@repo/shared';
|
|
3
|
+
import { getSandbox, type Sandbox } from './sandbox';
|
|
4
|
+
import { sanitizeSandboxId, validatePort } from './security';
|
|
7
5
|
|
|
8
6
|
export interface SandboxEnv {
|
|
9
7
|
Sandbox: DurableObjectNamespace<Sandbox>;
|
|
@@ -21,7 +19,8 @@ export async function proxyToSandbox<E extends SandboxEnv>(
|
|
|
21
19
|
env: E
|
|
22
20
|
): Promise<Response | null> {
|
|
23
21
|
// Create logger context for this request
|
|
24
|
-
const traceId =
|
|
22
|
+
const traceId =
|
|
23
|
+
TraceContext.fromHeaders(request.headers) || TraceContext.generate();
|
|
25
24
|
const logger = createLogger({
|
|
26
25
|
component: 'sandbox-do',
|
|
27
26
|
traceId,
|
|
@@ -70,6 +69,14 @@ export async function proxyToSandbox<E extends SandboxEnv>(
|
|
|
70
69
|
}
|
|
71
70
|
}
|
|
72
71
|
|
|
72
|
+
// Detect WebSocket upgrade request
|
|
73
|
+
const upgradeHeader = request.headers.get('Upgrade');
|
|
74
|
+
if (upgradeHeader?.toLowerCase() === 'websocket') {
|
|
75
|
+
// WebSocket path: Must use fetch() not containerFetch()
|
|
76
|
+
// This bypasses JSRPC serialization boundary which cannot handle WebSocket upgrades
|
|
77
|
+
return await sandbox.fetch(switchPort(request, port));
|
|
78
|
+
}
|
|
79
|
+
|
|
73
80
|
// Build proxy request with proper headers
|
|
74
81
|
let proxyUrl: string;
|
|
75
82
|
|
|
@@ -89,23 +96,29 @@ export async function proxyToSandbox<E extends SandboxEnv>(
|
|
|
89
96
|
'X-Original-URL': request.url,
|
|
90
97
|
'X-Forwarded-Host': url.hostname,
|
|
91
98
|
'X-Forwarded-Proto': url.protocol.replace(':', ''),
|
|
92
|
-
'X-Sandbox-Name': sandboxId
|
|
99
|
+
'X-Sandbox-Name': sandboxId // Pass the friendly name
|
|
93
100
|
},
|
|
94
101
|
body: request.body,
|
|
95
102
|
// @ts-expect-error - duplex required for body streaming in modern runtimes
|
|
96
|
-
duplex: 'half'
|
|
103
|
+
duplex: 'half'
|
|
97
104
|
});
|
|
98
105
|
|
|
99
|
-
return sandbox.containerFetch(proxyRequest, port);
|
|
106
|
+
return await sandbox.containerFetch(proxyRequest, port);
|
|
100
107
|
} catch (error) {
|
|
101
|
-
logger.error(
|
|
108
|
+
logger.error(
|
|
109
|
+
'Proxy routing error',
|
|
110
|
+
error instanceof Error ? error : new Error(String(error))
|
|
111
|
+
);
|
|
102
112
|
return new Response('Proxy routing error', { status: 500 });
|
|
103
113
|
}
|
|
104
114
|
}
|
|
105
115
|
|
|
106
116
|
function extractSandboxRoute(url: URL): RouteInfo | null {
|
|
107
117
|
// Parse subdomain pattern: port-sandboxId-token.domain (tokens mandatory)
|
|
108
|
-
|
|
118
|
+
// Token is always exactly 16 chars (generated by generatePortToken)
|
|
119
|
+
const subdomainMatch = url.hostname.match(
|
|
120
|
+
/^(\d{4,5})-([^.-][^.]*?[^.-]|[^.-])-([a-z0-9_-]{16})\.(.+)$/
|
|
121
|
+
);
|
|
109
122
|
|
|
110
123
|
if (!subdomainMatch) {
|
|
111
124
|
return null;
|
|
@@ -136,8 +149,8 @@ function extractSandboxRoute(url: URL): RouteInfo | null {
|
|
|
136
149
|
return {
|
|
137
150
|
port,
|
|
138
151
|
sandboxId: sanitizedSandboxId,
|
|
139
|
-
path: url.pathname ||
|
|
140
|
-
token
|
|
152
|
+
path: url.pathname || '/',
|
|
153
|
+
token
|
|
141
154
|
};
|
|
142
155
|
}
|
|
143
156
|
|
|
@@ -153,18 +166,18 @@ export function isLocalhostPattern(hostname: string): boolean {
|
|
|
153
166
|
return hostname === '[::1]';
|
|
154
167
|
}
|
|
155
168
|
}
|
|
156
|
-
|
|
169
|
+
|
|
157
170
|
// Handle bare IPv6 without brackets
|
|
158
171
|
if (hostname === '::1') {
|
|
159
172
|
return true;
|
|
160
173
|
}
|
|
161
|
-
|
|
174
|
+
|
|
162
175
|
// For IPv4 and regular hostnames, split on colon to remove port
|
|
163
|
-
const hostPart = hostname.split(
|
|
164
|
-
|
|
176
|
+
const hostPart = hostname.split(':')[0];
|
|
177
|
+
|
|
165
178
|
return (
|
|
166
|
-
hostPart ===
|
|
167
|
-
hostPart ===
|
|
168
|
-
hostPart ===
|
|
179
|
+
hostPart === 'localhost' ||
|
|
180
|
+
hostPart === '127.0.0.1' ||
|
|
181
|
+
hostPart === '0.0.0.0'
|
|
169
182
|
);
|
|
170
183
|
}
|