Skip to content

Commit 0752b6c

Browse files
authored
fix: fixes trailing slash error in the js sdk (#24)
* fixes trailing slash erorr in the js sdk * adds test for invalid app url case
1 parent 44936b0 commit 0752b6c

File tree

6 files changed

+74
-40
lines changed

6 files changed

+74
-40
lines changed

packages/js/eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export default [
7171
{ "**/*.{ts,tsx}": "KEBAB_CASE" },
7272
{ ignoreMiddleExtensions: true },
7373
],
74-
"prettier/prettier": "error",
74+
"prettier/prettier": ["error", { trailingComma: "es5" }],
7575
},
7676
},
7777

packages/js/src/index.test.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { TFormbricks } from "./types/formbricks";
1111

1212
// Get the mocked function
1313
const mockLoadFormbricksToProxy = vi.mocked(
14-
loadFormbricksModule.loadFormbricksToProxy,
14+
loadFormbricksModule.loadFormbricksToProxy
1515
);
1616

1717
describe("formbricks proxy", () => {
@@ -61,7 +61,7 @@ describe("formbricks proxy", () => {
6161
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
6262
"setAttribute",
6363
key,
64-
value,
64+
value
6565
);
6666
});
6767

@@ -75,7 +75,7 @@ describe("formbricks proxy", () => {
7575

7676
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
7777
"setAttributes",
78-
attributes,
78+
attributes
7979
);
8080
});
8181

@@ -86,7 +86,7 @@ describe("formbricks proxy", () => {
8686

8787
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
8888
"setLanguage",
89-
language,
89+
language
9090
);
9191
});
9292

@@ -108,7 +108,7 @@ describe("formbricks proxy", () => {
108108
await formbricks.registerRouteChange();
109109

110110
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
111-
"registerRouteChange",
111+
"registerRouteChange"
112112
);
113113
});
114114

@@ -142,7 +142,7 @@ describe("formbricks proxy", () => {
142142
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
143143
"track",
144144
trackCode,
145-
properties,
145+
properties
146146
);
147147
});
148148

@@ -154,7 +154,7 @@ describe("formbricks proxy", () => {
154154

155155
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
156156
customMethod,
157-
args[0],
157+
args[0]
158158
);
159159
});
160160

@@ -172,7 +172,7 @@ describe("formbricks proxy", () => {
172172
mockLoadFormbricksToProxy.mockRejectedValue(error);
173173

174174
await expect(formbricks.setEmail("[email protected]")).rejects.toThrow(
175-
"Test error",
175+
"Test error"
176176
);
177177
});
178178

@@ -189,12 +189,12 @@ describe("formbricks proxy", () => {
189189
expect(mockLoadFormbricksToProxy).toHaveBeenCalledTimes(4);
190190
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
191191
"setEmail",
192-
192+
193193
);
194194
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
195195
"setAttribute",
196196
"userId",
197-
"user123",
197+
"user123"
198198
);
199199
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("track", "event1");
200200
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith("setLanguage", "en");
@@ -225,7 +225,7 @@ describe("proxy behavior", () => {
225225

226226
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
227227
"setUserId",
228-
"user123",
228+
"user123"
229229
);
230230
});
231231

@@ -235,7 +235,7 @@ describe("proxy behavior", () => {
235235
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
236236
"setAttribute",
237237
"key",
238-
"value",
238+
"value"
239239
);
240240
});
241241

@@ -249,7 +249,7 @@ describe("proxy behavior", () => {
249249

250250
expect(mockLoadFormbricksToProxy).toHaveBeenCalledWith(
251251
"setup",
252-
setupConfig,
252+
setupConfig
253253
);
254254
});
255255
});

packages/js/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const formbricksProxyHandler: ProxyHandler<TFormbricks> = {
2020

2121
const formbricks: TFormbricksCore = new Proxy(
2222
{} as TFormbricks,
23-
formbricksProxyHandler,
23+
formbricksProxyHandler
2424
);
2525

2626
export default formbricks;

packages/js/src/lib/load-formbricks.test.ts

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ const mockSetTimeoutImmediate = () => {
8686
}
8787

8888
return 1;
89-
},
89+
}
9090
);
9191
return originalSetTimeout;
9292
};
@@ -143,11 +143,42 @@ describe("load-formbricks", () => {
143143
src: `${setupArgs.appUrl}/js/formbricks.umd.cjs`,
144144
type: "text/javascript",
145145
async: true,
146-
}),
146+
})
147147
);
148148
expect(mockFormbricks.setup).toHaveBeenCalledWith(setupArgs);
149149
});
150150

151+
test("should handle setup call with a trailing slash in the appUrl", async () => {
152+
const validAppUrl = "https://app.formbricks.com";
153+
const invalidAppUrl = "https://app.formbricks.com/";
154+
const environmentId = "env123";
155+
156+
const invalidSetupArgs = {
157+
appUrl: invalidAppUrl,
158+
environmentId,
159+
};
160+
161+
const validateSetupArgs = {
162+
appUrl: validAppUrl,
163+
environmentId,
164+
};
165+
166+
const mockAppendChild = vi
167+
.spyOn(document.head, "appendChild")
168+
.mockImplementation(createSuccessfulScriptMock());
169+
170+
await loadFormbricksToProxy("setup", invalidSetupArgs);
171+
172+
expect(mockAppendChild).toHaveBeenCalledWith(
173+
expect.objectContaining({
174+
src: `${validAppUrl}/js/formbricks.umd.cjs`,
175+
type: "text/javascript",
176+
async: true,
177+
})
178+
);
179+
expect(mockFormbricks.setup).toHaveBeenCalledWith(validateSetupArgs);
180+
});
181+
151182
test("should log error when appUrl is missing", async () => {
152183
const consoleSpy = createConsoleErrorSpy();
153184

@@ -156,7 +187,7 @@ describe("load-formbricks", () => {
156187
});
157188

158189
expect(consoleSpy).toHaveBeenCalledWith(
159-
"🧱 Formbricks - Error: appUrl is required",
190+
"🧱 Formbricks - Error: appUrl is required"
160191
);
161192
});
162193

@@ -168,7 +199,7 @@ describe("load-formbricks", () => {
168199
});
169200

170201
expect(consoleSpy).toHaveBeenCalledWith(
171-
"🧱 Formbricks - Error: environmentId is required",
202+
"🧱 Formbricks - Error: environmentId is required"
172203
);
173204
});
174205

@@ -199,15 +230,15 @@ describe("load-formbricks", () => {
199230
};
200231

201232
vi.spyOn(document.head, "appendChild").mockImplementation(
202-
createTimeoutScriptMock(),
233+
createTimeoutScriptMock()
203234
);
204235

205236
const originalSetTimeout = mockSetTimeoutImmediate();
206237

207238
await loadFormbricksToProxy("setup", setupArgs);
208239

209240
expect(consoleSpy).toHaveBeenCalledWith(
210-
"🧱 Formbricks - Error: Failed to load Formbricks SDK",
241+
"🧱 Formbricks - Error: Failed to load Formbricks SDK"
211242
);
212243

213244
// Restore setTimeout
@@ -222,13 +253,13 @@ describe("load-formbricks", () => {
222253
};
223254

224255
vi.spyOn(document.head, "appendChild").mockImplementation(
225-
createErrorScriptMock(),
256+
createErrorScriptMock()
226257
);
227258

228259
await loadFormbricksToProxy("setup", setupArgs);
229260

230261
expect(consoleSpy).toHaveBeenCalledWith(
231-
"🧱 Formbricks - Error: Failed to load Formbricks SDK",
262+
"🧱 Formbricks - Error: Failed to load Formbricks SDK"
232263
);
233264
});
234265

@@ -240,14 +271,14 @@ describe("load-formbricks", () => {
240271
};
241272

242273
vi.spyOn(document.head, "appendChild").mockImplementation(
243-
createSetupFailureMock(),
274+
createSetupFailureMock()
244275
);
245276

246277
await loadFormbricksToProxy("setup", setupArgs);
247278

248279
expect(consoleSpy).toHaveBeenCalledWith(
249280
"🧱 Formbricks - Error: setup failed",
250-
expect.any(Error),
281+
expect.any(Error)
251282
);
252283
});
253284
});
@@ -259,7 +290,7 @@ describe("load-formbricks", () => {
259290
await loadFormbricksToProxy("track", "test-event");
260291

261292
expect(consoleSpy).toHaveBeenCalledWith(
262-
"🧱 Formbricks - Warning: Formbricks not initialized. This method will be queued and executed after initialization.",
293+
"🧱 Formbricks - Warning: Formbricks not initialized. This method will be queued and executed after initialization."
263294
);
264295
});
265296

@@ -268,7 +299,7 @@ describe("load-formbricks", () => {
268299
await loadFormbricksToProxy("track", "queued-event");
269300
expect(warnSpy).toHaveBeenCalled();
270301
vi.spyOn(document.head, "appendChild").mockImplementation(
271-
createSuccessfulScriptMock(),
302+
createSuccessfulScriptMock()
272303
);
273304
await loadFormbricksToProxy("setup", {
274305
appUrl: "https://app.formbricks.com",
@@ -286,7 +317,7 @@ describe("load-formbricks", () => {
286317
};
287318

288319
vi.spyOn(document.head, "appendChild").mockImplementation(
289-
createSuccessfulScriptMock(),
320+
createSuccessfulScriptMock()
290321
);
291322

292323
// First, set up the SDK

packages/js/src/lib/load-formbricks.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ let isInitializing = false;
1111
let isInitialized = false;
1212
// Load the SDK, return the result
1313
const loadFormbricksSDK = async (
14-
apiHostParam: string,
14+
apiHostParam: string
1515
): Promise<Result<void>> => {
1616
if (!(globalThis as unknown as Record<string, unknown>).formbricks) {
1717
const scriptTag = document.createElement("script");
@@ -50,7 +50,7 @@ const loadFormbricksSDK = async (
5050
const functionsToProcess: { prop: string; args: unknown[] }[] = [];
5151

5252
const validateSetupArgs = (
53-
args: unknown[],
53+
args: unknown[]
5454
): { appUrl: string; environmentId: string } | null => {
5555
const argsTyped = args[0] as { appUrl: string; environmentId: string };
5656
const { appUrl, environmentId } = argsTyped;
@@ -65,7 +65,12 @@ const validateSetupArgs = (
6565
return null;
6666
}
6767

68-
return { appUrl, environmentId };
68+
// Removing trailing slash
69+
const appUrlWithoutTrailingSlash = appUrl.endsWith("/")
70+
? appUrl.slice(0, -1)
71+
: appUrl;
72+
73+
return { appUrl: appUrlWithoutTrailingSlash, environmentId };
6974
};
7075

7176
const processQueuedFunctions = (formbricksInstance: TFormbricks): void => {
@@ -76,7 +81,7 @@ const processQueuedFunctions = (formbricksInstance: TFormbricks): void => {
7681
] !== "function"
7782
) {
7883
console.error(
79-
`🧱 Formbricks - Error: Method ${functionProp} does not exist on formbricks`,
84+
`🧱 Formbricks - Error: Method ${functionProp} does not exist on formbricks`
8085
);
8186
continue;
8287
}
@@ -88,7 +93,7 @@ const processQueuedFunctions = (formbricksInstance: TFormbricks): void => {
8893
const handleSetupCall = async (args: unknown[]): Promise<void> => {
8994
if (isInitializing) {
9095
console.warn(
91-
"🧱 Formbricks - Warning: Formbricks is already initializing.",
96+
"🧱 Formbricks - Warning: Formbricks is already initializing."
9297
);
9398
return;
9499
}
@@ -99,14 +104,12 @@ const handleSetupCall = async (args: unknown[]): Promise<void> => {
99104
const loadSDKResult = await loadFormbricksSDK(validatedArgs.appUrl);
100105
const formbricksInstance = (
101106
globalThis as unknown as Record<string, unknown>
102-
).formbricks;
107+
).formbricks as TFormbricks;
103108
if (!loadSDKResult.ok || !formbricksInstance) {
104109
console.error("🧱 Formbricks - Error: Failed to load Formbricks SDK");
105110
return;
106111
}
107-
// @ts-expect-error -- Required for dynamic function calls
108-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call -- Required for dynamic function calls
109-
await formbricksInstance.setup(...args);
112+
await formbricksInstance.setup({ ...validatedArgs });
110113
isInitialized = true;
111114
processQueuedFunctions(formbricks);
112115
} catch (err) {
@@ -118,7 +121,7 @@ const handleSetupCall = async (args: unknown[]): Promise<void> => {
118121

119122
const executeFormbricksMethod = async (
120123
prop: string,
121-
args: unknown[],
124+
args: unknown[]
122125
): Promise<void> => {
123126
const formbricksInstance = (globalThis as unknown as Record<string, unknown>)
124127
.formbricks;
@@ -140,7 +143,7 @@ export const loadFormbricksToProxy = async (
140143
await handleSetupCall(args);
141144
} else {
142145
console.warn(
143-
"🧱 Formbricks - Warning: Formbricks not initialized. This method will be queued and executed after initialization.",
146+
"🧱 Formbricks - Warning: Formbricks not initialized. This method will be queued and executed after initialization."
144147
);
145148
functionsToProcess.push({ prop, args });
146149
}

packages/js/src/types/formbricks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export interface TFormbricks {
2626
code: string,
2727
properties?: {
2828
hiddenFields: Record<string | number, string | number | string[]>;
29-
},
29+
}
3030
) => Promise<void>;
3131

3232
logout: () => Promise<void>;

0 commit comments

Comments
 (0)