Skip to content

Commit c26f065

Browse files
authored
fix(analytics): allow clients to disable tracking (#269)
1 parent 164c2de commit c26f065

5 files changed

Lines changed: 69 additions & 2 deletions

File tree

‎src/client.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { createUsersModule } from "./modules/users.js";
1717
import { RoomsSocket, RoomsSocketConfig } from "./utils/socket-utils.js";
1818
import type {
1919
Base44Client,
20+
CreateClientAnalyticsConfig,
2021
CreateClientConfig,
2122
CreateClientOptions,
2223
} from "./client.types.js";
@@ -28,7 +29,12 @@ import {
2829
} from "./modules/actors.js";
2930

3031
// Re-export client types
31-
export type { Base44Client, CreateClientConfig, CreateClientOptions };
32+
export type {
33+
Base44Client,
34+
CreateClientAnalyticsConfig,
35+
CreateClientConfig,
36+
CreateClientOptions,
37+
};
3238

3339
/**
3440
* Creates a Base44 client.
@@ -71,6 +77,7 @@ export function createClient(config: CreateClientConfig): Base44Client {
7177
const {
7278
serverUrl = "https://base44.app",
7379
appId,
80+
analytics,
7481
token,
7582
serviceToken,
7683
requiresAuth = false,
@@ -248,6 +255,7 @@ export function createClient(config: CreateClientConfig): Base44Client {
248255
serverUrl,
249256
appId,
250257
userAuthModule,
258+
enabled: analytics?.enabled ?? true,
251259
}),
252260
actors: actorsModule.module,
253261
cleanup: () => {

‎src/client.types.ts‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@ export interface CreateClientOptions {
3636
actorsTransport?: "auto" | "proxy" | "direct";
3737
}
3838

39+
/**
40+
* Configuration for the SDK's app analytics module.
41+
*/
42+
export interface CreateClientAnalyticsConfig {
43+
/**
44+
* Whether app analytics is enabled for this client.
45+
*
46+
* When disabled, automatic analytics and calls to `analytics.track()` are
47+
* no-ops. The SDK does not create an analytics session identifier, start
48+
* heartbeat timers, or send analytics requests.
49+
*
50+
* @defaultValue `true`
51+
*/
52+
enabled: boolean;
53+
}
54+
3955
/**
4056
* Configuration for creating a Base44 client.
4157
*/
@@ -62,6 +78,12 @@ export interface CreateClientConfig {
6278
* It's the string between `/apps/` and `/editor/`.
6379
*/
6480
appId: string;
81+
/**
82+
* Controls app analytics for this client.
83+
*
84+
* Omit this option to preserve the default analytics behavior.
85+
*/
86+
analytics?: CreateClientAnalyticsConfig;
6587
/**
6688
* User authentication token. Used to authenticate as a specific user.
6789
*

‎src/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
createClient,
33
createClientFromRequest,
44
type Base44Client,
5+
type CreateClientAnalyticsConfig,
56
type CreateClientConfig,
67
type CreateClientOptions,
78
} from "./client.js";
@@ -25,6 +26,7 @@ export {
2526

2627
export type {
2728
Base44Client,
29+
CreateClientAnalyticsConfig,
2830
CreateClientConfig,
2931
CreateClientOptions,
3032
Base44ErrorJSON,

‎src/modules/analytics.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,15 @@ export interface AnalyticsModuleArgs {
6262
serverUrl: string;
6363
appId: string;
6464
userAuthModule: InternalAuthModule;
65+
enabled: boolean;
6566
}
6667

6768
export const createAnalyticsModule = ({
6869
axiosClient,
6970
serverUrl,
7071
appId,
7172
userAuthModule,
73+
enabled,
7274
}: AnalyticsModuleArgs) => {
7375
// prevent overflow of events //
7476
const { maxQueueSize, throttleTime, batchSize } = analyticsSharedState.config;
@@ -77,7 +79,7 @@ export const createAnalyticsModule = ({
7779
// so the per-callsite `typeof window` guards below aren't enough to keep it
7880
// from touching `document` (e.g. `document.referrer` on init). Node/SSR is
7981
// still handled by those `window` guards, so this doesn't affect it.
80-
if (!analyticsSharedState.config?.enabled || isReactNative) {
82+
if (!enabled || !analyticsSharedState.config?.enabled || isReactNative) {
8183
return {
8284
track: () => {},
8385
cleanup: () => {},

‎tests/unit/analytics.test.ts‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ describe("Analytics Module", () => {
6969
afterEach(() => {
7070
vi.clearAllMocks();
7171
base44.cleanup();
72+
vi.unstubAllGlobals();
7273
sharedState = null;
7374
});
7475

@@ -89,6 +90,38 @@ describe("Analytics Module", () => {
8990
});
9091
});
9192

93+
test("should have no analytics side effects when disabled in client config", () => {
94+
const storage = {
95+
getItem: vi.fn(() => null),
96+
setItem: vi.fn(),
97+
};
98+
const addEventListener = vi.fn();
99+
vi.stubGlobal("localStorage", storage);
100+
vi.stubGlobal("document", { referrer: "", visibilityState: "visible" });
101+
vi.stubGlobal("window", {
102+
addEventListener,
103+
removeEventListener: vi.fn(),
104+
history: { replaceState: vi.fn() },
105+
localStorage: storage,
106+
location: { origin: "https://example.com", pathname: "/", search: "" },
107+
});
108+
const setIntervalSpy = vi.spyOn(globalThis, "setInterval");
109+
110+
const disabled = createClient({
111+
serverUrl,
112+
appId,
113+
analytics: { enabled: false },
114+
});
115+
disabled.analytics.track({ eventName: "should-not-track" });
116+
117+
expect(sharedState?.requestsQueue).toEqual([]);
118+
expect(storage.setItem).not.toHaveBeenCalled();
119+
expect(setIntervalSpy).not.toHaveBeenCalled();
120+
expect(addEventListener).not.toHaveBeenCalled();
121+
122+
disabled.cleanup();
123+
});
124+
92125
test("should clear the memoized session context on reset", () => {
93126
expect(sharedState?.sessionContext).toEqual({ user_id: "test-user-id" });
94127

0 commit comments

Comments
 (0)