From 9534f30fbfe30f035c6f4b59175ff3cc68f06e22 Mon Sep 17 00:00:00 2001 From: Dan Radenkovic Date: Thu, 2 Oct 2025 15:00:27 +0200 Subject: [PATCH] normalize api url --- .changeset/api-url-version-suffix.md | 20 +++ .../nylas-connect/src/connect-client.test.ts | 126 ++++++++++++++++++ packages/nylas-connect/src/connect-client.ts | 18 ++- 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 .changeset/api-url-version-suffix.md diff --git a/.changeset/api-url-version-suffix.md b/.changeset/api-url-version-suffix.md new file mode 100644 index 0000000..c610207 --- /dev/null +++ b/.changeset/api-url-version-suffix.md @@ -0,0 +1,20 @@ +--- +"@nylas/connect": minor +--- + +Add automatic API URL version suffix handling + +The NylasConnect client now automatically appends `/v3` to API URLs that don't already have a version suffix. This ensures all API calls use versioned endpoints while preserving any explicitly set versions. + +**Features:** +- Automatically appends `/v3` to API URLs without version suffixes +- Preserves existing version suffixes (e.g., `/v1`, `/v2`, `/v10`) +- Handles trailing slashes correctly +- Works with custom API URLs and regional endpoints + +**Examples:** +- `https://api.us.nylas.com` → `https://api.us.nylas.com/v3` +- `https://api.us.nylas.com/v2` → `https://api.us.nylas.com/v2` (unchanged) +- `https://custom.api.com` → `https://custom.api.com/v3` + +This change is backward compatible and doesn't affect existing functionality. diff --git a/packages/nylas-connect/src/connect-client.test.ts b/packages/nylas-connect/src/connect-client.test.ts index 38fe906..3335b6a 100644 --- a/packages/nylas-connect/src/connect-client.test.ts +++ b/packages/nylas-connect/src/connect-client.test.ts @@ -759,3 +759,129 @@ describe("NylasConnect (sessions, validation, and events)", () => { }); }); }); + +describe("NylasConnect (API URL normalization)", () => { + const clientId = "client_123"; + const redirectUri = "https://app.example/callback"; + + beforeEach(() => { + localStorage.clear(); + vi.restoreAllMocks(); + }); + + it("should append /v3 to default API URL when no version is present", async () => { + const auth = new NylasConnect({ + clientId, + redirectUri, + apiUrl: "https://api.us.nylas.com", + }); + + const { url } = await auth.getAuthUrl(); + expect(url).toContain("https://api.us.nylas.com/v3/connect/auth"); + }); + + it("should append /v3 to custom API URL when no version is present", async () => { + const auth = new NylasConnect({ + clientId, + redirectUri, + apiUrl: "https://custom.api.com", + }); + + const { url } = await auth.getAuthUrl(); + expect(url).toContain("https://custom.api.com/v3/connect/auth"); + }); + + it("should preserve existing version suffix (v2)", async () => { + const auth = new NylasConnect({ + clientId, + redirectUri, + apiUrl: "https://api.us.nylas.com/v2", + }); + + const { url } = await auth.getAuthUrl(); + expect(url).toContain("https://api.us.nylas.com/v2/connect/auth"); + expect(url).not.toContain("/v3"); + }); + + it("should preserve existing version suffix (v1)", async () => { + const auth = new NylasConnect({ + clientId, + redirectUri, + apiUrl: "https://api.us.nylas.com/v1", + }); + + const { url } = await auth.getAuthUrl(); + expect(url).toContain("https://api.us.nylas.com/v1/connect/auth"); + expect(url).not.toContain("/v3"); + }); + + it("should handle trailing slashes correctly", async () => { + const auth = new NylasConnect({ + clientId, + redirectUri, + apiUrl: "https://api.us.nylas.com/", + }); + + const { url } = await auth.getAuthUrl(); + expect(url).toContain("https://api.us.nylas.com/v3/connect/auth"); + expect(url).not.toContain("//v3"); // Should not have double slashes + }); + + it("should handle multiple trailing slashes correctly", async () => { + const auth = new NylasConnect({ + clientId, + redirectUri, + apiUrl: "https://api.us.nylas.com///", + }); + + const { url } = await auth.getAuthUrl(); + expect(url).toContain("https://api.us.nylas.com/v3/connect/auth"); + expect(url).not.toContain("//v3"); // Should not have double slashes + }); + + it("should preserve version with trailing slashes", async () => { + const auth = new NylasConnect({ + clientId, + redirectUri, + apiUrl: "https://api.us.nylas.com/v2/", + }); + + const { url } = await auth.getAuthUrl(); + expect(url).toContain("https://api.us.nylas.com/v2/connect/auth"); + expect(url).not.toContain("/v3"); + }); + + it("should append /v3 to default URL when no apiUrl is provided", async () => { + const auth = new NylasConnect({ + clientId, + redirectUri, + // No apiUrl provided - should use default + }); + + const { url } = await auth.getAuthUrl(); + expect(url).toContain("https://api.us.nylas.com/v3/connect/auth"); + }); + + it("should handle EU API URL correctly", async () => { + const auth = new NylasConnect({ + clientId, + redirectUri, + apiUrl: "https://api.eu.nylas.com", + }); + + const { url } = await auth.getAuthUrl(); + expect(url).toContain("https://api.eu.nylas.com/v3/connect/auth"); + }); + + it("should work with higher version numbers", async () => { + const auth = new NylasConnect({ + clientId, + redirectUri, + apiUrl: "https://api.us.nylas.com/v10", + }); + + const { url } = await auth.getAuthUrl(); + expect(url).toContain("https://api.us.nylas.com/v10/connect/auth"); + expect(url).not.toContain("/v3"); + }); +}); diff --git a/packages/nylas-connect/src/connect-client.ts b/packages/nylas-connect/src/connect-client.ts index 01d0476..abc474a 100644 --- a/packages/nylas-connect/src/connect-client.ts +++ b/packages/nylas-connect/src/connect-client.ts @@ -91,11 +91,27 @@ export class NylasConnect { }); } + /** + * Normalize API URL to ensure it has a version suffix + */ + private normalizeApiUrl(apiUrl: string): string { + // Remove trailing slashes + const cleanUrl = apiUrl.replace(/\/+$/, ""); + // Check if URL already has a version suffix (e.g., /v3, /v2, etc.) + const versionPattern = /\/v\d+$/; + if (versionPattern.test(cleanUrl)) { + return cleanUrl; + } + // Append /v3 if no version suffix is present + return `${cleanUrl}/v3`; + } + /** * Resolve configuration with environment variables and smart defaults */ private resolveConfig(config: ConnectConfig): ConnectConfig { const environment = this.detectEnvironment(config.environment); + const baseApiUrl = config.apiUrl || "https://api.us.nylas.com"; return { clientId: config.clientId || this.getEnvVar("NYLAS_CLIENT_ID"), @@ -103,7 +119,7 @@ export class NylasConnect { config.redirectUri || this.getEnvVar("NYLAS_REDIRECT_URI") || this.detectRedirectUri(), - apiUrl: config.apiUrl || "https://api.us.nylas.com", + apiUrl: this.normalizeApiUrl(baseApiUrl), environment, defaultScopes: config.defaultScopes, debug: config.debug ?? environment === "development",