diff --git a/README.md b/README.md index 18a77c1..78d5f81 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,6 @@ const res = await client.proxy.request({ body: { user: "%USER_ID%" }, // Optional per-call overrides: // environment: "other-env-id", - // usePersonal: false, }); ``` diff --git a/src/interceptor.ts b/src/interceptor.ts index 19c5ee4..a8a9fa5 100644 --- a/src/interceptor.ts +++ b/src/interceptor.ts @@ -215,12 +215,10 @@ export class HttpInterceptor { method, headers: mergedHeaders, body: finalBody, - config: { - workspace: rule.workspace ?? this.#defaults.workspace, - project: rule.project ?? this.#defaults.project, - "environment-id": rule.environment ?? this.#defaults.environment, - "is-personal": rule.usePersonal ?? this.#defaults.usePersonalValues, - }, + workspace: rule.workspace ?? this.#defaults.workspace, + project: rule.project ?? this.#defaults.project, + "environment-id": rule.environment ?? this.#defaults.environment, + "is-personal": rule.usePersonal ?? this.#defaults.usePersonalValues, }; } diff --git a/src/proxy.ts b/src/proxy.ts index f3d4028..0bb717f 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -35,12 +35,10 @@ export interface ProxyWireBody { method: ProxyMethod; headers?: Record; body?: JsonValue; - config: { - workspace: string; - project: string; - "environment-id": string; - "is-personal": boolean; - }; + workspace: string; + project: string; + "environment-id": string; + "is-personal": boolean; } /** @@ -82,15 +80,16 @@ export async function sendProxyWire( const wireBody: Record = { url: body.url, method: body.method, - config: body.config, + "is-personal": body["is-personal"], }; if (body.headers !== undefined) wireBody.headers = body.headers; if (body.body !== undefined) wireBody.body = body.body; + const proxyRequestUrl = buildProxyRequestUrl(ctx.proxyUrl, body.workspace, body.project, body["environment-id"]); ctx.logger.debug(`Proxy request: ${body.method} ${body.url}`); const start = Date.now(); - const response = await fetch(ctx.proxyUrl, { + const response = await fetch(proxyRequestUrl, { method: "POST", headers: { Authorization: `Bearer ${token}`, @@ -179,7 +178,7 @@ export class EnkryptifyProxy implements IEnkryptifyProxy { method, headers, body, - config: this.#buildConfig(), + ...this.#buildScope(), }, init?.signal ?? null, ); @@ -202,7 +201,7 @@ export class EnkryptifyProxy implements IEnkryptifyProxy { ); } - const config = this.#buildConfig({ + const scope = this.#buildScope({ workspace: options.workspace, project: options.project, environment: options.environment, @@ -216,18 +215,18 @@ export class EnkryptifyProxy implements IEnkryptifyProxy { method, headers: options.headers, body: options.body, - config, + ...scope, }, null, ); } - #buildConfig(overrides?: { + #buildScope(overrides?: { workspace?: string; project?: string; environment?: string; usePersonal?: boolean; - }): ProxyWireBody["config"] { + }): Pick { return { workspace: overrides?.workspace ?? this.#workspace, project: overrides?.project ?? this.#project, @@ -309,3 +308,8 @@ function bodyTypeError(typeName: string): EnkryptifyError { "Docs: https://docs.enkryptify.com/sdk/proxy", ); } + +function buildProxyRequestUrl(baseUrl: string, workspace: string, project: string, environmentId: string): string { + const normalizedBaseUrl = baseUrl.replace(/\/+$/, ""); + return `${normalizedBaseUrl}/${encodeURIComponent(workspace)}/${encodeURIComponent(project)}/${encodeURIComponent(environmentId)}`; +} diff --git a/tests/interceptor.test.ts b/tests/interceptor.test.ts index c1d05ff..1d86432 100644 --- a/tests/interceptor.test.ts +++ b/tests/interceptor.test.ts @@ -78,7 +78,7 @@ async function findProxyCall(fetchMock: ReturnType): Promise; @@ -359,7 +359,7 @@ describe("interceptor — rule matching", () => { }); describe("interceptor — ProxyWireBody shape", () => { - it("includes config block with client defaults", async () => { + it("puts client default context in the proxy URL path", async () => { fetchMock.mockResolvedValue(new Response("{}", { status: 200 })); activeClient = new Enkryptify( @@ -378,12 +378,9 @@ describe("interceptor — ProxyWireBody shape", () => { await fetch("https://api.example.com/v1"); const wire = await findProxyCall(fetchMock); - expect(wire?.config).toEqual({ - workspace: "ws-x", - project: "prj-y", - "environment-id": "env-z", - "is-personal": false, - }); + expect(wire?.["is-personal"]).toBe(false); + const proxyCall = await findCallByUrlPrefix(fetchMock, "https://proxy.test.com/"); + expect(proxyCall?.url).toBe("https://proxy.test.com/ws-x/prj-y/env-z"); }); it("rule-level workspace/project/environment/usePersonal override defaults", async () => { @@ -410,12 +407,9 @@ describe("interceptor — ProxyWireBody shape", () => { await fetch("https://api.example.com/v1"); const wire = await findProxyCall(fetchMock); - expect(wire?.config).toEqual({ - workspace: "override-ws", - project: "override-prj", - "environment-id": "override-env", - "is-personal": false, - }); + expect(wire?.["is-personal"]).toBe(false); + const proxyCall = await findCallByUrlPrefix(fetchMock, "https://proxy.test.com/"); + expect(proxyCall?.url).toBe("https://proxy.test.com/override-ws/override-prj/override-env"); }); it("sends Authorization: Bearer on the proxy call", async () => { diff --git a/tests/proxy.test.ts b/tests/proxy.test.ts index be377f0..919df96 100644 --- a/tests/proxy.test.ts +++ b/tests/proxy.test.ts @@ -47,7 +47,7 @@ describe("client.proxy.fetch — body translation", () => { expect(fetchMock).toHaveBeenCalledTimes(1); const url = fetchMock.mock.calls[0]?.[0] as string; - expect(url).toBe("https://proxy.test.com"); + expect(url).toBe("https://proxy.test.com/ws-1/prj-1/env-1"); const opts = fetchMock.mock.calls[0]?.[1] as RequestInit; expect(opts.method).toBe("POST"); @@ -55,12 +55,7 @@ describe("client.proxy.fetch — body translation", () => { expect(body).toMatchObject({ url: "https://upstream/x?k=%K%", method: "GET", - config: { - workspace: "ws-1", - project: "prj-1", - "environment-id": "env-1", - "is-personal": true, - }, + "is-personal": true, }); expect(body.body).toBeUndefined(); expect(body.headers).toBeUndefined(); @@ -211,7 +206,7 @@ describe("client.proxy.fetch — body translation", () => { }); describe("client.proxy.request — low-level API", () => { - it("sends exact config in kebab-case", async () => { + it("sends wire body and routes context in URL path", async () => { fetchMock.mockResolvedValue(new Response("{}", { status: 200 })); const client = new Enkryptify(makeConfig()); @@ -226,13 +221,9 @@ describe("client.proxy.request — low-level API", () => { url: "https://upstream/x", method: "POST", body: { foo: "%BAR%" }, - config: { - workspace: "ws-1", - project: "prj-1", - "environment-id": "env-1", - "is-personal": true, - }, + "is-personal": true, }); + expect(fetchMock.mock.calls[0]?.[0]).toBe("https://proxy.test.com/ws-1/prj-1/env-1"); }); it("applies per-call environment override", async () => { @@ -245,8 +236,7 @@ describe("client.proxy.request — low-level API", () => { environment: "other-env", }); - const body = getCallBody(fetchMock.mock.calls[0] as unknown[]); - expect((body.config as Record)["environment-id"]).toBe("other-env"); + expect(fetchMock.mock.calls[0]?.[0]).toBe("https://proxy.test.com/ws-1/prj-1/other-env"); }); it("applies per-call workspace/project/usePersonal overrides", async () => { @@ -262,12 +252,8 @@ describe("client.proxy.request — low-level API", () => { }); const body = getCallBody(fetchMock.mock.calls[0] as unknown[]); - expect(body.config).toEqual({ - workspace: "other-ws", - project: "other-prj", - "environment-id": "env-1", - "is-personal": false, - }); + expect(body["is-personal"]).toBe(false); + expect(fetchMock.mock.calls[0]?.[0]).toBe("https://proxy.test.com/other-ws/other-prj/env-1"); }); it("rejects GET with body", async () => { @@ -449,7 +435,7 @@ describe("client.proxy — URL resolution", () => { const client = new Enkryptify(makeConfig({ proxy: { url: "https://config.test.com" } })); await client.proxy.fetch("https://upstream/x"); - expect(fetchMock.mock.calls[0]?.[0]).toBe("https://config.test.com"); + expect(fetchMock.mock.calls[0]?.[0]).toBe("https://config.test.com/ws-1/prj-1/env-1"); }); it("falls back to ENKRYPTIFY_PROXY_URL env var", async () => { @@ -459,7 +445,7 @@ describe("client.proxy — URL resolution", () => { const client = new Enkryptify(makeConfig({ proxy: undefined })); await client.proxy.fetch("https://upstream/x"); - expect(fetchMock.mock.calls[0]?.[0]).toBe("https://env.test.com"); + expect(fetchMock.mock.calls[0]?.[0]).toBe("https://env.test.com/ws-1/prj-1/env-1"); }); it("falls back to default POC URL when nothing else is set", async () => { @@ -469,7 +455,7 @@ describe("client.proxy — URL resolution", () => { const client = new Enkryptify(makeConfig({ proxy: undefined })); await client.proxy.fetch("https://upstream/x"); - expect(fetchMock.mock.calls[0]?.[0]).toBe("https://proxy.enkryptify.com"); + expect(fetchMock.mock.calls[0]?.[0]).toBe("https://proxy.enkryptify.com/ws-1/prj-1/env-1"); }); });