feat(web): add dispatchFor transport helper for same-origin proxy
This commit is contained in:
36
packages/web/src/lib/dispatch.ts
Normal file
36
packages/web/src/lib/dispatch.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { httpFetch } from "@/lib/http";
|
||||||
|
import { isTauri } from "@/lib/platform";
|
||||||
|
|
||||||
|
/** Same-origin path prefix the web build issues all Honcho calls through. */
|
||||||
|
export const API_PREFIX = "/api";
|
||||||
|
/** Request header naming the real Honcho upstream for the proxy to forward to. */
|
||||||
|
export const UPSTREAM_HEADER = "X-Honcho-Upstream";
|
||||||
|
/** Response header the proxy sets on its OWN refusals (so they aren't read as upstream auth). */
|
||||||
|
export const PROXY_REJECT_HEADER = "X-Honcho-Proxy-Reject";
|
||||||
|
|
||||||
|
export interface Dispatch {
|
||||||
|
baseUrl: string;
|
||||||
|
headers: Record<string, string>;
|
||||||
|
fetch: typeof globalThis.fetch;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeUpstream(url: string): string {
|
||||||
|
return url.trim().replace(/\/+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve how to issue a request for an instance.
|
||||||
|
* - Web: same-origin `/api` + `X-Honcho-Upstream` header (proxy forwards server-side, no CORS).
|
||||||
|
* - Tauri: the absolute instance URL via reqwest (no browser same-origin policy).
|
||||||
|
*/
|
||||||
|
export function dispatchFor(instance: { baseUrl: string; token?: string }): Dispatch {
|
||||||
|
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
||||||
|
if (instance.token) headers.Authorization = `Bearer ${instance.token}`;
|
||||||
|
|
||||||
|
if (isTauri()) {
|
||||||
|
return { baseUrl: instance.baseUrl, headers, fetch: httpFetch };
|
||||||
|
}
|
||||||
|
|
||||||
|
headers[UPSTREAM_HEADER] = normalizeUpstream(instance.baseUrl);
|
||||||
|
return { baseUrl: API_PREFIX, headers, fetch: httpFetch };
|
||||||
|
}
|
||||||
40
packages/web/src/test/dispatch.test.ts
Normal file
40
packages/web/src/test/dispatch.test.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const { mockIsTauri } = vi.hoisted(() => ({ mockIsTauri: vi.fn() }));
|
||||||
|
vi.mock("@/lib/platform", () => ({ isTauri: () => mockIsTauri() }));
|
||||||
|
|
||||||
|
import { API_PREFIX, dispatchFor, PROXY_REJECT_HEADER, UPSTREAM_HEADER } from "@/lib/dispatch";
|
||||||
|
|
||||||
|
afterEach(() => mockIsTauri.mockReset());
|
||||||
|
|
||||||
|
describe("dispatchFor — web mode", () => {
|
||||||
|
it("targets the /api prefix and carries the upstream header", () => {
|
||||||
|
mockIsTauri.mockReturnValue(false);
|
||||||
|
const d = dispatchFor({ baseUrl: "https://honcho.example.net/", token: "" });
|
||||||
|
expect(d.baseUrl).toBe(API_PREFIX);
|
||||||
|
expect(d.headers[UPSTREAM_HEADER]).toBe("https://honcho.example.net");
|
||||||
|
expect(d.headers.Authorization).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adds Authorization only when a token is present", () => {
|
||||||
|
mockIsTauri.mockReturnValue(false);
|
||||||
|
const d = dispatchFor({ baseUrl: "https://honcho.example.net", token: "sk-1" });
|
||||||
|
expect(d.headers.Authorization).toBe("Bearer sk-1");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("dispatchFor — tauri mode", () => {
|
||||||
|
it("targets the absolute URL with no upstream header", () => {
|
||||||
|
mockIsTauri.mockReturnValue(true);
|
||||||
|
const d = dispatchFor({ baseUrl: "https://honcho.example.net", token: "sk-1" });
|
||||||
|
expect(d.baseUrl).toBe("https://honcho.example.net");
|
||||||
|
expect(d.headers[UPSTREAM_HEADER]).toBeUndefined();
|
||||||
|
expect(d.headers.Authorization).toBe("Bearer sk-1");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("proxy reject header constant", () => {
|
||||||
|
it("is the agreed sentinel name", () => {
|
||||||
|
expect(PROXY_REJECT_HEADER).toBe("X-Honcho-Proxy-Reject");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user