peachsim 0.1.1481 → 0.1.1619
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 +56 -13
- package/cli/app-catalog.ts +7 -5
- package/cli/app-config.ts +47 -8
- package/cli/app-fonts.ts +84 -49
- package/cli/app-stubs.ts +433 -0
- package/cli/bin.ts +15 -2
- package/cli/bridge-flow-runner.ts +383 -123
- package/cli/cloud-client.ts +26 -25
- package/cli/cloud-test-tunnel.ts +2 -0
- package/cli/command-registry.ts +2 -1
- package/cli/commands/agent-indicator-mode.ts +39 -0
- package/cli/commands/box/bundle-plane.ts +4 -0
- package/cli/commands/box.ts +16 -23
- package/cli/commands/control.ts +221 -126
- package/cli/commands/daemon.ts +99 -24
- package/cli/commands/debug.ts +31 -3
- package/cli/commands/detect.ts +1 -1
- package/cli/commands/device.ts +6 -8
- package/cli/commands/diagnose.ts +2 -0
- package/cli/commands/do-chain.ts +2 -2
- package/cli/commands/doctor.ts +1030 -0
- package/cli/commands/flow.ts +447 -406
- package/cli/commands/inspect/actions.ts +2 -0
- package/cli/commands/inspect/core.ts +138 -46
- package/cli/commands/inspect/env.ts +10 -0
- package/cli/commands/inspect/list.ts +25 -8
- package/cli/commands/inspect/memory.ts +9 -0
- package/cli/commands/inspect/settling.ts +219 -66
- package/cli/commands/inspect/shared.ts +53 -12
- package/cli/commands/inspect/wait-event.ts +0 -1
- package/cli/commands/inspect/wait.ts +2 -2
- package/cli/commands/inspect.ts +51 -16
- package/cli/commands/install-desktop.ts +3 -2
- package/cli/commands/kotlin.ts +81 -22
- package/cli/commands/login.ts +1 -1
- package/cli/commands/maestro-compare.ts +41 -5
- package/cli/commands/maestro-generate.ts +11 -7
- package/cli/commands/maestro.ts +43 -15
- package/cli/commands/mode.ts +13 -1
- package/cli/commands/no-bridge-hint.ts +26 -3
- package/cli/commands/perf-record.ts +296 -0
- package/cli/commands/perf.ts +6 -1
- package/cli/commands/platform.ts +15 -4
- package/cli/commands/preview.ts +1 -1
- package/cli/commands/record.ts +37 -6
- package/cli/commands/report-issue.ts +2 -1
- package/cli/commands/runtime.ts +9 -11
- package/cli/commands/screenshots.ts +4 -3
- package/cli/commands/serve.ts +30 -2
- package/cli/commands/setup.ts +32 -7
- package/cli/commands/skills.ts +16 -43
- package/cli/commands/swift.ts +22 -3
- package/cli/commands/upgrade.ts +7 -6
- package/cli/commands/upload.ts +118 -69
- package/cli/commands/window.ts +449 -0
- package/cli/current-sim.ts +1 -2
- package/cli/desktop-companion.ts +84 -10
- package/cli/device-option.ts +8 -0
- package/cli/drivers/desktop.ts +3 -1
- package/cli/drivers/playwright-sim-host.ts +30 -11
- package/cli/drivers/playwright.ts +1 -4
- package/cli/flow-export.ts +2 -2
- package/cli/flow-file.ts +19 -18
- package/cli/internal-child.ts +7 -6
- package/cli/maestro-compare.ts +28 -6
- package/cli/maestro-js.ts +2 -1
- package/cli/main.ts +25 -4
- package/cli/material-symbol-fonts.ts +171 -0
- package/cli/outbound-endpoints.ts +132 -54
- package/cli/parse-args.ts +7 -0
- package/cli/preview-capture.ts +12 -0
- package/cli/privacy.ts +1 -1
- package/cli/run-registry.ts +5 -2
- package/cli/runtime-compat.ts +16 -0
- package/cli/telemetry.ts +6 -3
- package/cli/ui.ts +5 -0
- package/cli/ws-bridge.ts +121 -62
- package/detox/attempt-events.cjs +762 -0
- package/detox/attempt-events.d.cts +106 -0
- package/detox/gestures.ts +115 -2
- package/detox/index.ts +762 -426
- package/detox/jest-environment.cjs +86 -0
- package/detox/jest-preset.cjs +3 -1
- package/detox/jest-setup-after-env.cjs +1 -0
- package/detox/matchers.ts +3 -6
- package/detox/motion-change.cjs +259 -0
- package/detox/motion-change.d.cts +73 -0
- package/detox/proof-frame.cjs +83 -9
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +15 -5
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +3 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/bundle-channel.cjs +5950 -1775
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cloud-contract.cjs +6 -1
- package/dist-lib/cloud-contract.mjs +5 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +34 -3
- package/dist-lib/config.mjs +33 -3
- package/dist-lib/detox/index.cjs +3443 -564
- package/dist-lib/detox/jest-environment.cjs +86 -0
- package/dist-lib/detox/jest-preset.cjs +3 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/expo-swift-bundle.cjs +5518 -1446
- package/dist-lib/expo-swift-metadata.cjs +21 -13
- package/dist-lib/expo-swift-metadata.mjs +21 -13
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +21728 -16808
- package/dist-lib/host/fetch-proxy-handler.cjs +177 -102
- package/dist-lib/host/fetch-proxy-overrides.cjs +84 -4
- package/dist-lib/host/fetch-proxy-overrides.mjs +74 -3
- package/dist-lib/host/replacement-module-handler.cjs +16 -15
- package/dist-lib/host/websocket-proxy.cjs +243 -6
- package/dist-lib/index.cjs +6063 -4113
- package/dist-lib/jump-to-source-babel.cjs +73 -35
- package/dist-lib/jump-to-source-native.cjs +118 -10
- package/dist-lib/kotlin-host.cjs +264 -121
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +147 -99
- package/dist-lib/metro-production-bundle.mjs +142 -99
- package/dist-lib/metro.cjs +4249 -360
- package/dist-lib/native-app-identity-project.cjs +318 -131
- package/dist-lib/native-app-identity-project.mjs +317 -131
- package/dist-lib/native-app-identity.cjs +322 -33
- package/dist-lib/native-app-identity.mjs +319 -32
- package/dist-lib/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.mjs +1 -1
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +11 -1
- package/dist-lib/react-native-host-modules.mjs +11 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +127 -42
- package/dist-lib/sdk.cjs +311 -103
- package/dist-lib/sdk.mjs +311 -103
- package/dist-lib/skills.cjs +18344 -11121
- package/dist-lib/swift-compile.cjs +3291 -544
- package/dist-lib/swift-compile.mjs +3227 -478
- package/dist-lib/swift-package.cjs +25 -3
- package/dist-lib/swift-package.mjs +22 -2
- package/dist-lib/swift-submit.cjs +2 -2
- package/dist-lib/swift-submit.mjs +2 -2
- package/dist-lib/swift-toolchain.cjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +5523 -1428
- package/dist-lib/vite.cjs +251 -60
- package/dist-lib/worklet-executable.cjs +18 -4
- package/dist-lib/worklet-executable.mjs +18 -4
- package/install.sh +8 -3
- package/kotlin/AndroidViewSources/android/graphics/Canvas.kt +2 -0
- package/kotlin/AndroidViewSources/android/graphics/Paint.kt +18 -0
- package/kotlin/AndroidViewSources/dev/peach/app/JavaFormat.kt +16 -8
- package/kotlin/AppSources/android/view/Window.kt +4 -1
- package/kotlin/AppSources/android/window/BackEvent.kt +24 -0
- package/kotlin/AppSources/android/window/OnBackAnimationCallback.kt +7 -0
- package/kotlin/AppSources/android/window/OnBackInvokedDispatcher.kt +37 -1
- package/kotlin/AppSources/androidx/activity/BackEventCompat.kt +5 -0
- package/kotlin/AppSources/androidx/activity/OnBackPressedDispatcher.kt +8 -1
- package/kotlin/AppSources/dev/peach/app/PeachApp.kt +14 -0
- package/kotlin/CompilerSources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar +1 -0
- package/kotlin/CompilerSources/PeachJniIncoming.kt +243 -0
- package/kotlin/CompilerSources/PeachJniPlugin.kt +116 -0
- package/kotlin/ComposeSources/PeachComposeHost.kt +58 -32
- package/kotlin/ComposeSources/PeachMagnifier.kt +185 -0
- package/kotlin/ExpoComposeSources/PeachAndroidView.kt +16 -0
- package/kotlin/JniPlatformSources/androidx/annotation/CallSuper.kt +5 -0
- package/kotlin/JniPlatformSources/com/facebook/proguard/annotations/DoNotStrip.kt +5 -0
- package/kotlin/JniPlatformSources/dalvik/annotation/optimization/FastNative.kt +5 -0
- package/kotlin/JniPlatformSources/kotlin/Synchronized.kt +6 -0
- package/kotlin/JniPlatformSources/kotlin/jvm/JvmField.kt +5 -0
- package/kotlin/JniPlatformSources/kotlin/jvm/JvmStatic.kt +5 -0
- package/kotlin/JniSources/com/facebook/jni/HybridData.kt +68 -0
- package/kotlin/JniSources/dev/peach/jni/IncomingBindings.kt +157 -0
- package/kotlin/JniSources/dev/peach/jni/NativeBindings.kt +79 -0
- package/kotlin/JniTransportSources/dev/peach/jni/integration/JniTransport.kt +91 -0
- package/kotlin/README.md +18 -0
- package/kotlin/Sources/java/util/IllegalFormatCodePointException.kt +10 -0
- package/kotlin/Sources/java/util/IllegalFormatException.kt +5 -0
- package/kotlin/fixtures/counter/CounterApp.native.tsx +30 -3
- package/kotlin/fixtures/counter/CounterScreen.d.kt.ts +1 -0
- package/kotlin/fixtures/counter/CounterScreen.kt +2 -1
- package/kotlin/fixtures/counter/package.json +15 -2
- package/kotlin/fixtures/counter/vite.config.ts +11 -1
- package/kotlin/fork/NOTICE +1 -0
- package/kotlin/fork/README.md +5 -4
- package/kotlin/fork/compose-worker-globals.patch +629 -0
- package/kotlin/fork/fork.json +1 -1
- package/kotlin/toolchain.json +3 -3
- package/package.json +1 -1
- package/scripts/dev-server-scanner.ts +166 -81
- package/skills/contrast/SKILL.md +4 -4
- package/skills/peach-debug/SKILL.md +1 -1
- package/skills/peach-perf/SKILL.md +25 -0
- package/skills/peach-setup/SKILL.md +47 -13
- package/skills/peach-test/SKILL.md +2 -3
- package/src/agent-identity.ts +18 -0
- package/src/assetCatalogContract.ts +31 -3
- package/src/auth/shared-session.ts +3 -2
- package/src/bridge-constants.ts +1 -1
- package/src/bridge-contract.ts +5 -0
- package/src/bundle-channel.mjs +38 -7
- package/src/bundleGlobalFooter.ts +70 -20
- package/src/cloud-contract.ts +11 -0
- package/src/config.ts +47 -1
- package/src/configured-bundle-producer.ts +4 -1
- package/src/desktop-host-delivery.ts +4 -3
- package/src/expo-kotlin-host.ts +7 -2
- package/src/guest-bundle.ts +15 -8
- package/src/host/agent-host.ts +2 -4
- package/src/host/bridge-host.ts +260 -119
- package/src/host/fetch-proxy-handler.ts +237 -146
- package/src/host/fetch-proxy-overrides.ts +118 -0
- package/src/host/fetch-proxy-transport.ts +77 -63
- package/src/host/lease-owner.test.ts +18 -1
- package/src/host/lease-owner.ts +4 -5
- package/src/host/loopback-ports.ts +34 -0
- package/src/host/replacement-module-handler.ts +24 -15
- package/src/host/server-scan-handler.ts +87 -0
- package/src/host/staged-host-files.ts +92 -0
- package/src/host/websocket-proxy.ts +271 -5
- package/src/installed-package-versions.ts +66 -0
- package/src/jump-to-source-babel.ts +97 -89
- package/src/jump-to-source-native.ts +109 -11
- package/src/kotlin-host.ts +144 -27
- package/src/kotlin-module.ts +52 -0
- package/src/kotlinDiscoveryContract.ts +27 -0
- package/src/metro-layout-parser.ts +78 -0
- package/src/metro-plugin.ts +129 -39
- package/src/metro-production-bundle.ts +140 -139
- package/src/native-app-identity-project.ts +103 -5
- package/src/native-app-identity.ts +173 -11
- package/src/native-firebase-config.ts +83 -80
- package/src/native-seam-manifest.ts +0 -44
- package/src/nativeAppIconContract.ts +137 -0
- package/src/nativeViewSourceContract.ts +49 -0
- package/src/nativeViewWebContract.ts +41 -0
- package/src/nitro-bundle.ts +43 -0
- package/src/nitro-compile.ts +107 -0
- package/src/nitro-source.ts +310 -0
- package/src/nitroContract.ts +51 -0
- package/src/one-widget-metadata.ts +23 -3
- package/src/oneWidgetContract.ts +36 -3
- package/src/packageVersionsContract.ts +52 -0
- package/src/process-group.ts +306 -0
- package/src/process-liveness.ts +24 -0
- package/src/process-output.ts +21 -2
- package/src/react-devtools-frontend-react.ts +45 -0
- package/src/react-native-host-modules.ts +10 -0
- package/src/serverDiscoveryContract.ts +41 -0
- package/src/skills/builtin/maestro-test.ts +2 -4
- package/src/swift-compile.ts +3195 -650
- package/src/swift-design-time.ts +552 -0
- package/src/swift-distribution-contract.ts +11 -0
- package/src/swift-distribution.lock.json +46 -11
- package/src/swift-host.ts +144 -30
- package/src/swift-icu.ts +10 -2
- package/src/swift-objc-sdk.ts +726 -0
- package/src/swift-objc-standin.ts +441 -0
- package/src/swift-package.ts +25 -0
- package/src/swift-packages.ts +25 -10
- package/src/swift-standin-config.ts +94 -0
- package/src/swift-standin-emit.ts +5 -3
- package/src/swift-standins.ts +225 -81
- package/src/swift-submit.ts +2 -1
- package/src/swiftContract.ts +4 -0
- package/src/target-matcher.ts +67 -0
- package/src/visit-parsed-program.ts +21 -0
- package/src/vite-plugin-one.ts +65 -17
- package/src/vite-plugin-swift.ts +1 -0
- package/src/vite-plugin.ts +168 -70
- package/src/worker-system-chunks.ts +89 -10
- package/src/worklet-executable.ts +21 -3
- package/src/worklets-babel.ts +85 -13
- package/src/xcode-project.ts +81 -134
- package/src/xcode-target-members.ts +107 -0
- package/cli/commands/compat.ts +0 -395
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1619 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -394,6 +394,12 @@ function resolveReplacementModuleFile(fromFile, specifier) {
|
|
|
394
394
|
}
|
|
395
395
|
return found;
|
|
396
396
|
}
|
|
397
|
+
function isInsideAny(roots, file) {
|
|
398
|
+
return roots.some((root) => {
|
|
399
|
+
const relative2 = import_node_path.default.relative(root, file);
|
|
400
|
+
return !relative2.startsWith("..") && !import_node_path.default.isAbsolute(relative2);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
397
403
|
function isReplacementModuleRequestUrl(rawUrl) {
|
|
398
404
|
if (!rawUrl) return false;
|
|
399
405
|
try {
|
|
@@ -452,16 +458,11 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
|
|
|
452
458
|
return true;
|
|
453
459
|
}
|
|
454
460
|
const runtimePackages = new Set(url.searchParams.getAll("runtimePackage"));
|
|
455
|
-
|
|
456
|
-
if (
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if (lexicalTarget.startsWith("..") || import_node_path.default.isAbsolute(lexicalTarget)) {
|
|
461
|
-
res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
|
|
462
|
-
res.end("replacement file must be inside the project root");
|
|
463
|
-
return true;
|
|
464
|
-
}
|
|
461
|
+
const servedRoots = options.servedRoots?.map((root) => import_node_path.default.resolve(root));
|
|
462
|
+
if (servedRoots && !isInsideAny(servedRoots, targetPath)) {
|
|
463
|
+
res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
|
|
464
|
+
res.end("replacement file must be inside a folder the bundler serves");
|
|
465
|
+
return true;
|
|
465
466
|
}
|
|
466
467
|
let resolvedTargetPath;
|
|
467
468
|
try {
|
|
@@ -472,11 +473,11 @@ async function handleReplacementModuleRequest(req, res, options = {}) {
|
|
|
472
473
|
res.end(`replacement file not found: ${message}`);
|
|
473
474
|
return true;
|
|
474
475
|
}
|
|
475
|
-
if (
|
|
476
|
-
const
|
|
477
|
-
if (
|
|
476
|
+
if (servedRoots) {
|
|
477
|
+
const realRoots = (await Promise.all(servedRoots.map((root) => import_promises.default.realpath(root).catch(() => null)))).filter((root) => root !== null);
|
|
478
|
+
if (!isInsideAny(realRoots, resolvedTargetPath)) {
|
|
478
479
|
res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
|
|
479
|
-
res.end("replacement file must be inside the
|
|
480
|
+
res.end("replacement file must be inside a folder the bundler serves");
|
|
480
481
|
return true;
|
|
481
482
|
}
|
|
482
483
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1619 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -22,6 +22,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
22
22
|
// src/host/websocket-proxy.ts
|
|
23
23
|
var websocket_proxy_exports = {};
|
|
24
24
|
__export(websocket_proxy_exports, {
|
|
25
|
+
TCP_RELAY_PATH: () => TCP_RELAY_PATH,
|
|
25
26
|
WEBSOCKET_PROXY_HANDSHAKE_TIMEOUT_MS: () => WEBSOCKET_PROXY_HANDSHAKE_TIMEOUT_MS,
|
|
26
27
|
WEBSOCKET_PROXY_PATH: () => WEBSOCKET_PROXY_PATH,
|
|
27
28
|
handleWebSocketProxyUpgrade: () => handleWebSocketProxyUpgrade,
|
|
@@ -32,6 +33,9 @@ module.exports = __toCommonJS(websocket_proxy_exports);
|
|
|
32
33
|
var import_crypto = require("crypto");
|
|
33
34
|
var import_http = require("http");
|
|
34
35
|
var import_https = require("https");
|
|
36
|
+
var import_net = require("net");
|
|
37
|
+
var import_tls = require("tls");
|
|
38
|
+
var import_ws = require("ws");
|
|
35
39
|
|
|
36
40
|
// src/backend-origin.ts
|
|
37
41
|
function normalizeHostname(hostname) {
|
|
@@ -54,8 +58,40 @@ function isTrustedBridgeRequest(headers) {
|
|
|
54
58
|
}
|
|
55
59
|
}
|
|
56
60
|
|
|
61
|
+
// src/is-record.ts
|
|
62
|
+
function isRecord(value) {
|
|
63
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/host/loopback-ports.ts
|
|
67
|
+
var LOOPBACK_PORTS = /* @__PURE__ */ new Map();
|
|
68
|
+
for (const pair of process.env.PEACH_LOOPBACK_PORTS?.split(",").filter(Boolean) ?? []) {
|
|
69
|
+
const match = /^\s*([1-9][0-9]{0,4}):([1-9][0-9]{0,4})\s*$/.exec(pair);
|
|
70
|
+
const from = Number(match?.[1]);
|
|
71
|
+
const to = Number(match?.[2]);
|
|
72
|
+
if (!match || from > 65535 || to > 65535 || LOOPBACK_PORTS.has(from)) {
|
|
73
|
+
throw new Error(`PEACH_LOOPBACK_PORTS has an invalid entry: ${JSON.stringify(pair)}`);
|
|
74
|
+
}
|
|
75
|
+
LOOPBACK_PORTS.set(from, to);
|
|
76
|
+
}
|
|
77
|
+
function projectLoopbackPort(hostname, port) {
|
|
78
|
+
return isLoopbackHost(hostname) ? LOOPBACK_PORTS.get(port) ?? port : port;
|
|
79
|
+
}
|
|
80
|
+
function projectLoopbackUrl(value) {
|
|
81
|
+
if (LOOPBACK_PORTS.size === 0) return value;
|
|
82
|
+
const url = new URL(value);
|
|
83
|
+
const port = Number(
|
|
84
|
+
url.port || (url.protocol === "https:" || url.protocol === "wss:" ? 443 : 80)
|
|
85
|
+
);
|
|
86
|
+
const projected = projectLoopbackPort(url.hostname, port);
|
|
87
|
+
if (projected === port) return value;
|
|
88
|
+
url.port = String(projected);
|
|
89
|
+
return url.href;
|
|
90
|
+
}
|
|
91
|
+
|
|
57
92
|
// src/host/websocket-proxy.ts
|
|
58
93
|
var WEBSOCKET_PROXY_PATH = "/__websocket-proxy";
|
|
94
|
+
var TCP_RELAY_PATH = "/__tcp-relay";
|
|
59
95
|
var WEBSOCKET_PROXY_HANDSHAKE_TIMEOUT_MS = 1e4;
|
|
60
96
|
var WEBSOCKET_PROTOCOL_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
61
97
|
var WEBSOCKET_KEY = /^[+/0-9A-Za-z]{22}==$/;
|
|
@@ -185,20 +221,220 @@ function createUpstreamHeaders(req, targetUrl, headers, forwardOrigin) {
|
|
|
185
221
|
}
|
|
186
222
|
return upstreamHeaders;
|
|
187
223
|
}
|
|
224
|
+
function requestPathname(rawUrl) {
|
|
225
|
+
if (!rawUrl) return null;
|
|
226
|
+
try {
|
|
227
|
+
return new URL(rawUrl, "http://localhost").pathname;
|
|
228
|
+
} catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
188
232
|
function isWebSocketProxyRequestUrl(rawUrl) {
|
|
189
|
-
|
|
233
|
+
return requestPathname(rawUrl) === WEBSOCKET_PROXY_PATH;
|
|
234
|
+
}
|
|
235
|
+
function rawDataBytes(data) {
|
|
236
|
+
if (Array.isArray(data)) return Buffer.concat(data);
|
|
237
|
+
return Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
238
|
+
}
|
|
239
|
+
function optionalString(value) {
|
|
240
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
241
|
+
}
|
|
242
|
+
function optionalNumber(value) {
|
|
243
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
244
|
+
}
|
|
245
|
+
function readTcpRelayTls(options) {
|
|
246
|
+
return {
|
|
247
|
+
rejectUnauthorized: options.rejectUnauthorized !== false,
|
|
248
|
+
ca: optionalString(options.ca)
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function isTcpPort(value) {
|
|
252
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 65535;
|
|
253
|
+
}
|
|
254
|
+
function readTcpRelayCall(data) {
|
|
255
|
+
let call;
|
|
190
256
|
try {
|
|
191
|
-
|
|
257
|
+
call = JSON.parse(rawDataBytes(data).toString());
|
|
192
258
|
} catch {
|
|
193
|
-
return
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
if (!isRecord(call)) return null;
|
|
262
|
+
switch (call.type) {
|
|
263
|
+
case "connect": {
|
|
264
|
+
const localAddress = optionalString(call.localAddress);
|
|
265
|
+
if (localAddress !== void 0 && (0, import_net.isIP)(localAddress) === 0) return null;
|
|
266
|
+
if (call.localPort != null && !isTcpPort(call.localPort)) return null;
|
|
267
|
+
return typeof call.host === "string" && !call.host.includes("\0") && isTcpPort(call.port) ? {
|
|
268
|
+
type: "connect",
|
|
269
|
+
host: call.host,
|
|
270
|
+
port: call.port,
|
|
271
|
+
localAddress,
|
|
272
|
+
localPort: optionalNumber(call.localPort),
|
|
273
|
+
connectTimeout: optionalNumber(call.connectTimeout),
|
|
274
|
+
tls: isRecord(call.tls) ? readTcpRelayTls(call.tls) : null
|
|
275
|
+
} : null;
|
|
276
|
+
}
|
|
277
|
+
case "startTLS":
|
|
278
|
+
return { type: "startTLS", ...readTcpRelayTls(call) };
|
|
279
|
+
case "end":
|
|
280
|
+
case "pause":
|
|
281
|
+
case "resume":
|
|
282
|
+
return { type: call.type };
|
|
283
|
+
case "setNoDelay":
|
|
284
|
+
return { type: "setNoDelay", noDelay: call.noDelay !== false };
|
|
285
|
+
case "setKeepAlive":
|
|
286
|
+
return {
|
|
287
|
+
type: "setKeepAlive",
|
|
288
|
+
enable: call.enable === true,
|
|
289
|
+
initialDelay: optionalNumber(call.initialDelay) ?? 0
|
|
290
|
+
};
|
|
291
|
+
default:
|
|
292
|
+
return null;
|
|
194
293
|
}
|
|
195
294
|
}
|
|
295
|
+
function pemCertificateBytes(pem) {
|
|
296
|
+
return Buffer.from(
|
|
297
|
+
pem.replace(/-----(BEGIN|END) CERTIFICATE-----/g, "").replace(/\s+/g, ""),
|
|
298
|
+
"base64"
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
function connectionInfo(socket) {
|
|
302
|
+
return {
|
|
303
|
+
type: "connect",
|
|
304
|
+
localAddress: socket.localAddress,
|
|
305
|
+
localPort: socket.localPort,
|
|
306
|
+
remoteAddress: socket.remoteAddress,
|
|
307
|
+
remotePort: socket.remotePort,
|
|
308
|
+
remoteFamily: socket.remoteFamily
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
var tcpRelayServer = new import_ws.WebSocketServer({ noServer: true });
|
|
312
|
+
function handleTcpRelay(ws) {
|
|
313
|
+
let socket = null;
|
|
314
|
+
let host = "";
|
|
315
|
+
let connecting = false;
|
|
316
|
+
let tls = false;
|
|
317
|
+
let held = null;
|
|
318
|
+
let ending = false;
|
|
319
|
+
const send = (event) => {
|
|
320
|
+
if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(event));
|
|
321
|
+
};
|
|
322
|
+
const onData = (chunk) => {
|
|
323
|
+
if (ws.readyState === ws.OPEN) ws.send(chunk);
|
|
324
|
+
};
|
|
325
|
+
const onEnd = () => send({ type: "end" });
|
|
326
|
+
const onError = (error) => send({ type: "error", message: error.message });
|
|
327
|
+
const onClose = () => ws.close();
|
|
328
|
+
const relay = (next) => {
|
|
329
|
+
socket?.off("data", onData).off("end", onEnd).off("error", onError).off("close", onClose);
|
|
330
|
+
socket = next;
|
|
331
|
+
next.on("data", onData).on("end", onEnd).on("error", onError).on("close", onClose);
|
|
332
|
+
};
|
|
333
|
+
const write = (bytes) => {
|
|
334
|
+
if (ending) return;
|
|
335
|
+
if (held) held.push(bytes);
|
|
336
|
+
else socket?.write(bytes, () => send({ type: "written" }));
|
|
337
|
+
};
|
|
338
|
+
const end = (current) => current.end(() => current.destroy());
|
|
339
|
+
const startTls = (options, plain) => {
|
|
340
|
+
if (tls) return;
|
|
341
|
+
tls = true;
|
|
342
|
+
held = [];
|
|
343
|
+
const pinned = options.rejectUnauthorized ? options.ca : void 0;
|
|
344
|
+
const secured = (0, import_tls.connect)({
|
|
345
|
+
socket: plain,
|
|
346
|
+
servername: host,
|
|
347
|
+
rejectUnauthorized: options.rejectUnauthorized && pinned === void 0
|
|
348
|
+
});
|
|
349
|
+
relay(secured);
|
|
350
|
+
secured.once("secureConnect", () => {
|
|
351
|
+
const peer = secured.getPeerCertificate().raw;
|
|
352
|
+
if (pinned !== void 0 && !peer.equals(pemCertificateBytes(pinned))) {
|
|
353
|
+
secured.destroy(new Error("peer certificate does not match the pinned ca"));
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const queued = held ?? [];
|
|
357
|
+
held = null;
|
|
358
|
+
for (const bytes of queued) secured.write(bytes, () => send({ type: "written" }));
|
|
359
|
+
if (connecting) {
|
|
360
|
+
connecting = false;
|
|
361
|
+
send(connectionInfo(secured));
|
|
362
|
+
}
|
|
363
|
+
if (ending) end(secured);
|
|
364
|
+
});
|
|
365
|
+
};
|
|
366
|
+
ws.on("message", (data, isBinary) => {
|
|
367
|
+
if (isBinary) {
|
|
368
|
+
write(rawDataBytes(data));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const call = readTcpRelayCall(data);
|
|
372
|
+
if (!call) {
|
|
373
|
+
onError(new Error("invalid tcp relay call"));
|
|
374
|
+
ws.close(1008, "invalid tcp relay call");
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (call.type === "connect") {
|
|
378
|
+
if (socket) return;
|
|
379
|
+
host = call.host;
|
|
380
|
+
connecting = true;
|
|
381
|
+
const plain = (0, import_net.connect)({
|
|
382
|
+
host,
|
|
383
|
+
port: projectLoopbackPort(host, call.port),
|
|
384
|
+
localAddress: call.localAddress,
|
|
385
|
+
localPort: call.localPort
|
|
386
|
+
});
|
|
387
|
+
relay(plain);
|
|
388
|
+
const timeout = call.connectTimeout === void 0 ? void 0 : setTimeout(
|
|
389
|
+
() => plain.destroy(new Error("Attempt to connect to host timed out")),
|
|
390
|
+
Math.trunc(call.connectTimeout / 1e3) * 1e3
|
|
391
|
+
);
|
|
392
|
+
plain.once("close", () => clearTimeout(timeout));
|
|
393
|
+
plain.once("connect", () => {
|
|
394
|
+
clearTimeout(timeout);
|
|
395
|
+
if (!tls) send(connectionInfo(plain));
|
|
396
|
+
});
|
|
397
|
+
if (call.tls) startTls(call.tls, plain);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const current = socket;
|
|
401
|
+
if (!current) return;
|
|
402
|
+
switch (call.type) {
|
|
403
|
+
case "startTLS":
|
|
404
|
+
startTls(call, current);
|
|
405
|
+
return;
|
|
406
|
+
case "end":
|
|
407
|
+
ending = true;
|
|
408
|
+
if (!held) end(current);
|
|
409
|
+
return;
|
|
410
|
+
case "pause":
|
|
411
|
+
current.pause();
|
|
412
|
+
return;
|
|
413
|
+
case "resume":
|
|
414
|
+
current.resume();
|
|
415
|
+
return;
|
|
416
|
+
case "setNoDelay":
|
|
417
|
+
current.setNoDelay(call.noDelay);
|
|
418
|
+
return;
|
|
419
|
+
case "setKeepAlive":
|
|
420
|
+
current.setKeepAlive(call.enable, call.initialDelay);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
ws.on("close", () => socket?.destroy());
|
|
425
|
+
ws.on("error", () => socket?.destroy());
|
|
426
|
+
}
|
|
196
427
|
function handleWebSocketProxyUpgrade(req, socket, head, forwardOrigin = true) {
|
|
197
|
-
|
|
428
|
+
const pathname = requestPathname(req.url);
|
|
429
|
+
if (pathname !== WEBSOCKET_PROXY_PATH && pathname !== TCP_RELAY_PATH) return false;
|
|
198
430
|
if (!isSameOriginUpgrade(req)) {
|
|
199
431
|
rejectUpgrade(socket, 403, "forbidden websocket proxy origin");
|
|
200
432
|
return true;
|
|
201
433
|
}
|
|
434
|
+
if (pathname === TCP_RELAY_PATH) {
|
|
435
|
+
tcpRelayServer.handleUpgrade(req, socket, head, handleTcpRelay);
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
202
438
|
const upgradeRejection = validateUpgradeRequest(req);
|
|
203
439
|
if (upgradeRejection) {
|
|
204
440
|
rejectUpgrade(
|
|
@@ -231,7 +467,7 @@ function handleWebSocketProxyUpgrade(req, socket, head, forwardOrigin = true) {
|
|
|
231
467
|
const upstreamHeaders = createUpstreamHeaders(req, targetUrl, headers, forwardOrigin);
|
|
232
468
|
let settled = false;
|
|
233
469
|
const request = targetUrl.protocol === "wss:" ? import_https.request : import_http.request;
|
|
234
|
-
const upstreamRequestUrl = new URL(targetUrl);
|
|
470
|
+
const upstreamRequestUrl = new URL(projectLoopbackUrl(targetUrl.href));
|
|
235
471
|
upstreamRequestUrl.protocol = targetUrl.protocol === "wss:" ? "https:" : "http:";
|
|
236
472
|
const upstreamRequest = request(upstreamRequestUrl, {
|
|
237
473
|
method: "GET",
|
|
@@ -320,6 +556,7 @@ function handleWebSocketProxyUpgrade(req, socket, head, forwardOrigin = true) {
|
|
|
320
556
|
}
|
|
321
557
|
// Annotate the CommonJS export names for ESM import in node:
|
|
322
558
|
0 && (module.exports = {
|
|
559
|
+
TCP_RELAY_PATH,
|
|
323
560
|
WEBSOCKET_PROXY_HANDSHAKE_TIMEOUT_MS,
|
|
324
561
|
WEBSOCKET_PROXY_PATH,
|
|
325
562
|
handleWebSocketProxyUpgrade,
|