mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-31 06:04:11 +01:00
fix: avoid wildcard websocket hosts
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
3463456bee
commit
996c0e95e0
7 files changed
+104
-8
No files matched your search
@@ -6,6 +6,7 @@ import { instanceSettingsApi } from "../../api/instanceSettings";
|
||||
import { heartbeatsApi } from "../../api/heartbeats";
|
||||
import { buildTranscript, getUIAdapter, onAdapterChange, type RunLogChunk, type TranscriptEntry } from "../../adapters";
|
||||
import { queryKeys } from "../../lib/queryKeys";
|
||||
import { buildSameOriginWebSocketUrl } from "../../lib/websocket-url";
|
||||
|
||||
const LOG_POLL_INTERVAL_MS = 2000;
|
||||
const LOG_READ_LIMIT_BYTES = 256_000;
|
||||
@@ -279,8 +280,9 @@ export function useLiveRunTranscripts({
|
||||
|
||||
const connect = () => {
|
||||
if (closed) return;
|
||||
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
|
||||
const url = `${protocol}://${window.location.host}/api/companies/${encodeURIComponent(companyId)}/events/ws`;
|
||||
const url = buildSameOriginWebSocketUrl(
|
||||
`/api/companies/${encodeURIComponent(companyId)}/events/ws`,
|
||||
);
|
||||
socket = new WebSocket(url);
|
||||
|
||||
socket.onmessage = (message) => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { clearIssueExecutionRun, removeLiveRunById } from "../lib/optimistic-iss
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { toCompanyRelativePath } from "../lib/company-routes";
|
||||
import { useLocation } from "../lib/router";
|
||||
import { buildSameOriginWebSocketUrl } from "../lib/websocket-url";
|
||||
|
||||
const TOAST_COOLDOWN_WINDOW_MS = 10_000;
|
||||
const TOAST_COOLDOWN_MAX = 3;
|
||||
@@ -979,8 +980,9 @@ export function LiveUpdatesProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
const connect = () => {
|
||||
if (closed) return;
|
||||
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
|
||||
const url = `${protocol}://${window.location.host}/api/companies/${encodeURIComponent(liveCompanyId)}/events/ws`;
|
||||
const url = buildSameOriginWebSocketUrl(
|
||||
`/api/companies/${encodeURIComponent(liveCompanyId)}/events/ws`,
|
||||
);
|
||||
const nextSocket = new WebSocket(url);
|
||||
socket = nextSocket;
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { browserReachableHost, buildSameOriginWebSocketUrl } from "./websocket-url";
|
||||
|
||||
describe("browserReachableHost", () => {
|
||||
it("keeps concrete browser hosts unchanged", () => {
|
||||
expect(browserReachableHost({
|
||||
protocol: "http:",
|
||||
hostname: "paperclip-dev",
|
||||
host: "paperclip-dev:46259",
|
||||
port: "46259",
|
||||
})).toBe("paperclip-dev:46259");
|
||||
});
|
||||
|
||||
it("rewrites wildcard IPv4 bind hosts to localhost", () => {
|
||||
expect(browserReachableHost({
|
||||
protocol: "http:",
|
||||
hostname: "0.0.0.0",
|
||||
host: "0.0.0.0:46259",
|
||||
port: "46259",
|
||||
})).toBe("localhost:46259");
|
||||
});
|
||||
|
||||
it("rewrites wildcard IPv6 bind hosts to localhost", () => {
|
||||
expect(browserReachableHost({
|
||||
protocol: "http:",
|
||||
hostname: "::",
|
||||
host: "[::]:46259",
|
||||
port: "46259",
|
||||
})).toBe("localhost:46259");
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildSameOriginWebSocketUrl", () => {
|
||||
it("uses wss for https pages", () => {
|
||||
expect(buildSameOriginWebSocketUrl("/api/events/ws", {
|
||||
protocol: "https:",
|
||||
hostname: "example.com",
|
||||
host: "example.com",
|
||||
port: "",
|
||||
})).toBe("wss://example.com/api/events/ws");
|
||||
});
|
||||
|
||||
it("does not emit 0.0.0.0 websocket URLs", () => {
|
||||
expect(buildSameOriginWebSocketUrl("api/events/ws", {
|
||||
protocol: "http:",
|
||||
hostname: "0.0.0.0",
|
||||
host: "0.0.0.0:46259",
|
||||
port: "46259",
|
||||
})).toBe("ws://localhost:46259/api/events/ws");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
type BrowserLocationLike = Pick<Location, "host" | "hostname" | "port" | "protocol">;
|
||||
|
||||
function isWildcardHost(hostname: string): boolean {
|
||||
const normalized = hostname.trim().toLowerCase();
|
||||
return normalized === "0.0.0.0" || normalized === "::" || normalized === "[::]";
|
||||
}
|
||||
|
||||
export function browserReachableHost(location: BrowserLocationLike = window.location): string {
|
||||
if (!isWildcardHost(location.hostname)) return location.host;
|
||||
return location.port ? `localhost:${location.port}` : "localhost";
|
||||
}
|
||||
|
||||
export function buildSameOriginWebSocketUrl(
|
||||
path: string,
|
||||
location: BrowserLocationLike = window.location,
|
||||
): string {
|
||||
const protocol = location.protocol === "https:" ? "wss" : "ws";
|
||||
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
||||
return `${protocol}://${browserReachableHost(location)}${normalizedPath}`;
|
||||
}
|
||||
@@ -45,6 +45,7 @@ import { ScrollToBottom } from "../components/ScrollToBottom";
|
||||
import { SourceResolvedFoldCallout } from "../components/SourceResolvedFoldCallout";
|
||||
import { SourceResolvedFoldBadge } from "../components/SourceResolvedFoldBadge";
|
||||
import { readSourceResolvedWatchdogFold } from "../lib/source-resolved-watchdog-fold";
|
||||
import { buildSameOriginWebSocketUrl } from "../lib/websocket-url";
|
||||
import { formatCents, formatDate, relativeTime, formatTokens, visibleRunCostUsd } from "../lib/utils";
|
||||
import { cn } from "../lib/utils";
|
||||
import { describeRunRetryState } from "../lib/runRetryState";
|
||||
@@ -3865,8 +3866,9 @@ function LogViewer({ run, adapterType }: { run: HeartbeatRun; adapterType: strin
|
||||
|
||||
const connect = () => {
|
||||
if (closed) return;
|
||||
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
|
||||
const url = `${protocol}://${window.location.host}/api/companies/${encodeURIComponent(run.companyId)}/events/ws`;
|
||||
const url = buildSameOriginWebSocketUrl(
|
||||
`/api/companies/${encodeURIComponent(run.companyId)}/events/ws`,
|
||||
);
|
||||
socket = new WebSocket(url);
|
||||
|
||||
socket.onopen = () => {
|
||||
|
||||
Reference in new issue
Block a user