Skip to content

Commit 9534f30

Browse files
committed
normalize api url
1 parent e3eff2e commit 9534f30

3 files changed

Lines changed: 163 additions & 1 deletion

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
"@nylas/connect": minor
3+
---
4+
5+
Add automatic API URL version suffix handling
6+
7+
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.
8+
9+
**Features:**
10+
- Automatically appends `/v3` to API URLs without version suffixes
11+
- Preserves existing version suffixes (e.g., `/v1`, `/v2`, `/v10`)
12+
- Handles trailing slashes correctly
13+
- Works with custom API URLs and regional endpoints
14+
15+
**Examples:**
16+
- `https://api.us.nylas.com``https://api.us.nylas.com/v3`
17+
- `https://api.us.nylas.com/v2``https://api.us.nylas.com/v2` (unchanged)
18+
- `https://custom.api.com``https://custom.api.com/v3`
19+
20+
This change is backward compatible and doesn't affect existing functionality.

packages/nylas-connect/src/connect-client.test.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,3 +759,129 @@ describe("NylasConnect (sessions, validation, and events)", () => {
759759
});
760760
});
761761
});
762+
763+
describe("NylasConnect (API URL normalization)", () => {
764+
const clientId = "client_123";
765+
const redirectUri = "https://app.example/callback";
766+
767+
beforeEach(() => {
768+
localStorage.clear();
769+
vi.restoreAllMocks();
770+
});
771+
772+
it("should append /v3 to default API URL when no version is present", async () => {
773+
const auth = new NylasConnect({
774+
clientId,
775+
redirectUri,
776+
apiUrl: "https://api.us.nylas.com",
777+
});
778+
779+
const { url } = await auth.getAuthUrl();
780+
expect(url).toContain("https://api.us.nylas.com/v3/connect/auth");
781+
});
782+
783+
it("should append /v3 to custom API URL when no version is present", async () => {
784+
const auth = new NylasConnect({
785+
clientId,
786+
redirectUri,
787+
apiUrl: "https://custom.api.com",
788+
});
789+
790+
const { url } = await auth.getAuthUrl();
791+
expect(url).toContain("https://custom.api.com/v3/connect/auth");
792+
});
793+
794+
it("should preserve existing version suffix (v2)", async () => {
795+
const auth = new NylasConnect({
796+
clientId,
797+
redirectUri,
798+
apiUrl: "https://api.us.nylas.com/v2",
799+
});
800+
801+
const { url } = await auth.getAuthUrl();
802+
expect(url).toContain("https://api.us.nylas.com/v2/connect/auth");
803+
expect(url).not.toContain("/v3");
804+
});
805+
806+
it("should preserve existing version suffix (v1)", async () => {
807+
const auth = new NylasConnect({
808+
clientId,
809+
redirectUri,
810+
apiUrl: "https://api.us.nylas.com/v1",
811+
});
812+
813+
const { url } = await auth.getAuthUrl();
814+
expect(url).toContain("https://api.us.nylas.com/v1/connect/auth");
815+
expect(url).not.toContain("/v3");
816+
});
817+
818+
it("should handle trailing slashes correctly", async () => {
819+
const auth = new NylasConnect({
820+
clientId,
821+
redirectUri,
822+
apiUrl: "https://api.us.nylas.com/",
823+
});
824+
825+
const { url } = await auth.getAuthUrl();
826+
expect(url).toContain("https://api.us.nylas.com/v3/connect/auth");
827+
expect(url).not.toContain("//v3"); // Should not have double slashes
828+
});
829+
830+
it("should handle multiple trailing slashes correctly", async () => {
831+
const auth = new NylasConnect({
832+
clientId,
833+
redirectUri,
834+
apiUrl: "https://api.us.nylas.com///",
835+
});
836+
837+
const { url } = await auth.getAuthUrl();
838+
expect(url).toContain("https://api.us.nylas.com/v3/connect/auth");
839+
expect(url).not.toContain("//v3"); // Should not have double slashes
840+
});
841+
842+
it("should preserve version with trailing slashes", async () => {
843+
const auth = new NylasConnect({
844+
clientId,
845+
redirectUri,
846+
apiUrl: "https://api.us.nylas.com/v2/",
847+
});
848+
849+
const { url } = await auth.getAuthUrl();
850+
expect(url).toContain("https://api.us.nylas.com/v2/connect/auth");
851+
expect(url).not.toContain("/v3");
852+
});
853+
854+
it("should append /v3 to default URL when no apiUrl is provided", async () => {
855+
const auth = new NylasConnect({
856+
clientId,
857+
redirectUri,
858+
// No apiUrl provided - should use default
859+
});
860+
861+
const { url } = await auth.getAuthUrl();
862+
expect(url).toContain("https://api.us.nylas.com/v3/connect/auth");
863+
});
864+
865+
it("should handle EU API URL correctly", async () => {
866+
const auth = new NylasConnect({
867+
clientId,
868+
redirectUri,
869+
apiUrl: "https://api.eu.nylas.com",
870+
});
871+
872+
const { url } = await auth.getAuthUrl();
873+
expect(url).toContain("https://api.eu.nylas.com/v3/connect/auth");
874+
});
875+
876+
it("should work with higher version numbers", async () => {
877+
const auth = new NylasConnect({
878+
clientId,
879+
redirectUri,
880+
apiUrl: "https://api.us.nylas.com/v10",
881+
});
882+
883+
const { url } = await auth.getAuthUrl();
884+
expect(url).toContain("https://api.us.nylas.com/v10/connect/auth");
885+
expect(url).not.toContain("/v3");
886+
});
887+
});

packages/nylas-connect/src/connect-client.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,35 @@ export class NylasConnect {
9191
});
9292
}
9393

94+
/**
95+
* Normalize API URL to ensure it has a version suffix
96+
*/
97+
private normalizeApiUrl(apiUrl: string): string {
98+
// Remove trailing slashes
99+
const cleanUrl = apiUrl.replace(/\/+$/, "");
100+
// Check if URL already has a version suffix (e.g., /v3, /v2, etc.)
101+
const versionPattern = /\/v\d+$/;
102+
if (versionPattern.test(cleanUrl)) {
103+
return cleanUrl;
104+
}
105+
// Append /v3 if no version suffix is present
106+
return `${cleanUrl}/v3`;
107+
}
108+
94109
/**
95110
* Resolve configuration with environment variables and smart defaults
96111
*/
97112
private resolveConfig(config: ConnectConfig): ConnectConfig {
98113
const environment = this.detectEnvironment(config.environment);
114+
const baseApiUrl = config.apiUrl || "https://api.us.nylas.com";
99115

100116
return {
101117
clientId: config.clientId || this.getEnvVar("NYLAS_CLIENT_ID"),
102118
redirectUri:
103119
config.redirectUri ||
104120
this.getEnvVar("NYLAS_REDIRECT_URI") ||
105121
this.detectRedirectUri(),
106-
apiUrl: config.apiUrl || "https://api.us.nylas.com",
122+
apiUrl: this.normalizeApiUrl(baseApiUrl),
107123
environment,
108124
defaultScopes: config.defaultScopes,
109125
debug: config.debug ?? environment === "development",

0 commit comments

Comments
 (0)