Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/gateway/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,33 @@ export interface Transport {
// loudly instead of leaving a gateway that 403s all inbound traffic.
const TUNNEL_CONNECT_TIMEOUT_MS = 15_000;

// The tunnel server idle-caps parked intake slots on a timer, and the SDK
// reports each one via bare console.warn — on a healthy gateway that one
// line repeats forever and buries real warnings. A warn call is dropped only
// when its first argument contains all three markers.
const IDLE_CAP_WARNING_MARKERS = ["/_system/intake slot=", "status=408", "reason=intake-idle-cap"];

// Tags the wrapped console.warn so repeat installs (gateway restarts within
// one process) recognize it and no-op instead of stacking wrappers.
const WARN_FILTER_TAG = Symbol.for("inkbox.tunnelWarnFilter");

// Replace console.warn with a filter that drops the expected idle-cap line
// and forwards everything else — 401s, disconnects, non-string args — to the
// original warn unchanged (fail-open). Idempotent.
export function installTunnelWarnFilter(): void {
const original = console.warn as typeof console.warn & { [WARN_FILTER_TAG]?: true };
if (original[WARN_FILTER_TAG]) return;
const filtered = ((...args: unknown[]) => {
const first = args[0];
if (typeof first === "string" && IDLE_CAP_WARNING_MARKERS.every((m) => first.includes(m))) {
return;
}
original(...args);
}) as typeof console.warn & { [WARN_FILTER_TAG]?: true };
filtered[WARN_FILTER_TAG] = true;
console.warn = filtered;
}

// Bring up the inbound transport: either the Inkbox tunnel (forwarding to the
// local webhook server) or a caller-provided public URL. `ownsProcess` is
// false when running inside a host we don't control (in-plugin mode), which
Expand All @@ -43,6 +70,10 @@ export async function openTransport(opts: {
);
}

// Must be in place before connect(): the SDK's runtime starts warning as
// soon as the data plane parks its intake slots.
installTunnelWarnFilter();

const client = await opts.inkbox.getClient();
let onConnected: () => void = () => {};
const connected = new Promise<void>((resolve) => {
Expand Down
72 changes: 70 additions & 2 deletions tests/gateway/transport.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// The tunnel listener returned by connect() is inert until wait() drives its
// data plane — openTransport must start it and gate on "connected".
import { beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

const connectMock = vi.fn();
vi.mock("@inkbox/sdk/tunnels/connect", () => ({
connect: (...args: unknown[]) => connectMock(...args),
}));

import { defaultGatewayConfig } from "../../src/config.js";
import { openTransport } from "../../src/gateway/transport.js";
import { installTunnelWarnFilter, openTransport } from "../../src/gateway/transport.js";

const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn() };

Expand Down Expand Up @@ -142,3 +142,71 @@ describe("openTransport tunnel driving", () => {
expect(connectMock).not.toHaveBeenCalled();
});
});

describe("installTunnelWarnFilter", () => {
const savedWarn = console.warn;
let sink: ReturnType<typeof vi.fn>;

beforeEach(() => {
// A fresh, untagged spy stands in as the "original" console.warn, so the
// filter wraps it and every passthrough is observable.
sink = vi.fn();
console.warn = sink;
installTunnelWarnFilter();
});

afterEach(() => {
console.warn = savedWarn;
});

it("suppresses the expected intake idle-cap warning", () => {
console.warn("/_system/intake slot=2 -> status=408 reason=intake-idle-cap");
expect(sink).not.toHaveBeenCalled();
});

it("keeps 401 warnings visible", () => {
const line = "/_system/intake slot=2 -> status=401 reason=owner-token-invalid";
console.warn(line);
expect(sink).toHaveBeenCalledWith(line);
});

it("keeps status=408 with a different reason visible", () => {
console.warn("/_system/intake slot=2 -> status=408 reason=intake-superseded");
expect(sink).toHaveBeenCalledTimes(1);
});

it("keeps unrelated warnings, extra args included, visible", () => {
const err = new Error("boom");
console.warn("tunnel runtime: h2 session error", err);
expect(sink).toHaveBeenCalledWith("tunnel runtime: h2 session error", err);
});

it("passes a non-string first argument through even if it mentions the markers", () => {
const err = new Error("/_system/intake slot=2 -> status=408 reason=intake-idle-cap");
console.warn(err);
expect(sink).toHaveBeenCalledWith(err);
});

it("is idempotent: a second install keeps the same wrapper and a single layer", () => {
const wrapped = console.warn;
installTunnelWarnFilter();
expect(console.warn).toBe(wrapped);
console.warn("still one layer");
expect(sink).toHaveBeenCalledTimes(1);
});

it("is installed by openTransport before the tunnel connects", async () => {
const listener = makeListener();
const opts = makeOpts(listener);
// Undo the beforeEach install: openTransport must wrap the bare spy itself.
console.warn = sink;

const pending = openTransport(opts);
await new Promise((r) => setTimeout(r, 0));
(connectMock.mock.calls[0][1] as { onStatus: (s: string) => void }).onStatus("connected");
await pending;

console.warn("/_system/intake slot=0 -> status=408 reason=intake-idle-cap");
expect(sink).not.toHaveBeenCalled();
});
});
Loading