@base44-preview/sdk 0.8.57-pr.359.9ff5e66 → 0.8.57-pr.360.71a2580

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,9 @@ export declare const ANALYTICS_INITIALIZATION_EVENT_NAME = "__initialization_eve
6
6
  export declare const ANALYTICS_SESSION_DURATION_EVENT_NAME = "__session_duration_event__";
7
7
  export declare const ANALYTICS_CONFIG_ENABLE_URL_PARAM_KEY = "analytics-enable";
8
8
  export declare const ANALYTICS_SESSION_ID_LOCAL_STORAGE_KEY = "base44_analytics_session_id";
9
+ export declare const ANALYTICS_MODE_META_NAME = "base44-analytics";
10
+ /** Whether analytics may run and store the session id on this page. */
11
+ export declare function isAnalyticsAllowed(): boolean;
9
12
  export interface AnalyticsModuleArgs {
10
13
  axiosClient: AxiosInstance;
11
14
  serverUrl: string;
@@ -5,6 +5,25 @@ export const ANALYTICS_INITIALIZATION_EVENT_NAME = "__initialization_event__";
5
5
  export const ANALYTICS_SESSION_DURATION_EVENT_NAME = "__session_duration_event__";
6
6
  export const ANALYTICS_CONFIG_ENABLE_URL_PARAM_KEY = "analytics-enable";
7
7
  export const ANALYTICS_SESSION_ID_LOCAL_STORAGE_KEY = "base44_analytics_session_id";
8
+ // Set by the Base44 server from the app owner's choice: "off" never tracks,
9
+ // "consent" waits for the page's Google Consent Mode `analytics_storage` grant,
10
+ // "on" tracks until that consent is denied. Absent: track as before.
11
+ export const ANALYTICS_MODE_META_NAME = "base44-analytics";
12
+ function readAnalyticsMode() {
13
+ var _a;
14
+ if (typeof document === "undefined")
15
+ return null;
16
+ try {
17
+ const content = (_a = document
18
+ .querySelector(`meta[name="${ANALYTICS_MODE_META_NAME}"]`)) === null || _a === void 0 ? void 0 : _a.getAttribute("content");
19
+ return content === "off" || content === "consent" || content === "on"
20
+ ? content
21
+ : null;
22
+ }
23
+ catch (_b) {
24
+ return null;
25
+ }
26
+ }
8
27
  const defaultConfiguration = {
9
28
  // default to enabled //
10
29
  enabled: true,
@@ -33,15 +52,80 @@ const analyticsSharedState = getSharedInstance(ANALYTICS_SHARED_STATE_NAME, () =
33
52
  ...getAnalyticsConfigFromUrlParams(),
34
53
  },
35
54
  }));
55
+ ///////////////////////////////////////////////
56
+ //// visitor consent ////
57
+ ///////////////////////////////////////////////
58
+ // Its own shared instance: an older SDK copy on the page may own "analytics".
59
+ const consentSharedState = getSharedInstance("analytics-consent", () => {
60
+ const analyticsMode = readAnalyticsMode();
61
+ return {
62
+ analyticsMode,
63
+ // Whether the visitor may be tracked and the session id stored.
64
+ consentGranted: analyticsMode !== "consent",
65
+ isWatchingConsent: false,
66
+ listeners: [],
67
+ };
68
+ });
69
+ /** Whether analytics may run and store the session id on this page. */
70
+ export function isAnalyticsAllowed() {
71
+ const { analyticsMode, consentGranted } = consentSharedState;
72
+ return analyticsMode !== "off" && consentGranted;
73
+ }
74
+ function setConsentGranted(granted) {
75
+ var _a;
76
+ if (consentSharedState.consentGranted === granted)
77
+ return;
78
+ consentSharedState.consentGranted = granted;
79
+ if (!granted) {
80
+ try {
81
+ const sessionId = localStorage.getItem(ANALYTICS_SESSION_ID_LOCAL_STORAGE_KEY);
82
+ // Same id for the rest of the tab, so an anonymous agent chat keeps its owner.
83
+ if (sessionId)
84
+ (_a = analyticsSharedState.fallbackSessionId) !== null && _a !== void 0 ? _a : (analyticsSharedState.fallbackSessionId = sessionId);
85
+ localStorage.removeItem(ANALYTICS_SESSION_ID_LOCAL_STORAGE_KEY);
86
+ }
87
+ catch (_b) {
88
+ // storage unavailable: nothing was stored
89
+ }
90
+ }
91
+ for (const listener of [...consentSharedState.listeners]) {
92
+ listener(granted);
93
+ }
94
+ }
95
+ // Subscribes once to the consent listener Base44 injects next to the meta tag.
96
+ // Without it (e.g. a CSP blocked the inline script) "consent" never grants.
97
+ function watchAnalyticsConsent() {
98
+ const state = consentSharedState;
99
+ if (state.isWatchingConsent)
100
+ return;
101
+ state.isWatchingConsent = true;
102
+ if (state.analyticsMode !== "consent" && state.analyticsMode !== "on")
103
+ return;
104
+ const b44Consent = typeof window !== "undefined" ? window.__b44Consent : undefined;
105
+ b44Consent === null || b44Consent === void 0 ? void 0 : b44Consent.on("analytics_storage", () => setConsentGranted(true), () => setConsentGranted(false));
106
+ }
107
+ function onConsentChange(listener) {
108
+ consentSharedState.listeners.push(listener);
109
+ return () => {
110
+ const listeners = consentSharedState.listeners;
111
+ const index = listeners.indexOf(listener);
112
+ if (index >= 0)
113
+ listeners.splice(index, 1);
114
+ };
115
+ }
36
116
  export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthModule, enabled, }) => {
37
117
  var _a;
38
118
  // prevent overflow of events //
39
119
  const { maxQueueSize, throttleTime, batchSize } = analyticsSharedState.config;
120
+ watchAnalyticsConsent();
40
121
  // Disable analytics on React Native. It defines `window` but not `document`,
41
122
  // so the per-callsite `typeof window` guards below aren't enough to keep it
42
123
  // from touching `document` (e.g. `document.referrer` on init). Node/SSR is
43
124
  // still handled by those `window` guards, so this doesn't affect it.
44
- if (!enabled || !((_a = analyticsSharedState.config) === null || _a === void 0 ? void 0 : _a.enabled) || isReactNative) {
125
+ if (!enabled ||
126
+ !((_a = analyticsSharedState.config) === null || _a === void 0 ? void 0 : _a.enabled) ||
127
+ isReactNative ||
128
+ consentSharedState.analyticsMode === "off") {
45
129
  return {
46
130
  track: () => { },
47
131
  cleanup: () => { },
@@ -70,7 +154,8 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
70
154
  }
71
155
  };
72
156
  const flush = async (eventsData, options = {}) => {
73
- if (eventsData.length === 0)
157
+ // Consent may have been withdrawn while these events waited.
158
+ if (eventsData.length === 0 || !isAnalyticsAllowed())
74
159
  return;
75
160
  const sessionContext_ = await getSessionContext(userAuthModule);
76
161
  const events = eventsData.map(transformEventDataToApiRequestData(sessionContext_));
@@ -90,7 +175,8 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
90
175
  });
91
176
  };
92
177
  const track = (params) => {
93
- if (analyticsSharedState.requestsQueue.length >= maxQueueSize) {
178
+ if (!isAnalyticsAllowed() ||
179
+ analyticsSharedState.requestsQueue.length >= maxQueueSize) {
94
180
  return;
95
181
  }
96
182
  const intrinsicData = getEventIntrinsicData();
@@ -126,23 +212,45 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
126
212
  onDocVisible();
127
213
  }
128
214
  };
129
- const cleanup = () => {
215
+ let isStarted = false;
216
+ const start = () => {
217
+ if (isStarted)
218
+ return;
219
+ isStarted = true;
220
+ // start the flusing process ///
221
+ startProcessing();
222
+ // start the heart beat processor //
223
+ clearHeartBeatProcessor = startHeartBeatProcessor(track);
224
+ // track the referrer event //
225
+ trackInitializationEvent(track);
226
+ // start the visibility change listener //
227
+ if (typeof window !== "undefined") {
228
+ window.addEventListener("visibilitychange", onVisibilityChange);
229
+ }
230
+ };
231
+ const stop = () => {
232
+ isStarted = false;
130
233
  stopAnalyticsProcessor();
131
234
  clearHeartBeatProcessor === null || clearHeartBeatProcessor === void 0 ? void 0 : clearHeartBeatProcessor();
132
235
  if (typeof window !== "undefined") {
133
236
  window.removeEventListener("visibilitychange", onVisibilityChange);
134
237
  }
135
238
  };
136
- // start the flusing process ///
137
- startProcessing();
138
- // start the heart beat processor //
139
- clearHeartBeatProcessor = startHeartBeatProcessor(track);
140
- // track the referrer event //
141
- trackInitializationEvent(track);
142
- // start the visibility change listener //
143
- if (typeof window !== "undefined") {
144
- window.addEventListener("visibilitychange", onVisibilityChange);
145
- }
239
+ const stopWatchingConsent = onConsentChange((granted) => {
240
+ if (granted) {
241
+ start();
242
+ }
243
+ else {
244
+ stop();
245
+ analyticsSharedState.requestsQueue.splice(0);
246
+ }
247
+ });
248
+ const cleanup = () => {
249
+ stopWatchingConsent();
250
+ stop();
251
+ };
252
+ if (isAnalyticsAllowed())
253
+ start();
146
254
  return {
147
255
  track,
148
256
  cleanup,
@@ -338,19 +446,21 @@ function getFallbackSessionId() {
338
446
  return ((_a = analyticsSharedState.fallbackSessionId) !== null && _a !== void 0 ? _a : (analyticsSharedState.fallbackSessionId = generateUuid()));
339
447
  }
340
448
  export function getAnalyticsSessionId() {
341
- if (typeof window === "undefined") {
449
+ var _a;
450
+ if (typeof window === "undefined" || !isAnalyticsAllowed()) {
342
451
  return getFallbackSessionId();
343
452
  }
344
453
  try {
345
454
  const sessionId = localStorage.getItem(ANALYTICS_SESSION_ID_LOCAL_STORAGE_KEY);
346
455
  if (!sessionId) {
347
- const newSessionId = generateUuid();
456
+ // An id used before consent was granted carries on, so it is stored now.
457
+ const newSessionId = (_a = analyticsSharedState.fallbackSessionId) !== null && _a !== void 0 ? _a : generateUuid();
348
458
  localStorage.setItem(ANALYTICS_SESSION_ID_LOCAL_STORAGE_KEY, newSessionId);
349
459
  return newSessionId;
350
460
  }
351
461
  return sessionId;
352
462
  }
353
- catch (_a) {
463
+ catch (_b) {
354
464
  return getFallbackSessionId();
355
465
  }
356
466
  }
@@ -1,3 +1,4 @@
1
+ import { isAnalyticsAllowed } from "./analytics.js";
1
2
  /**
2
3
  * Creates the app logs module for the Base44 SDK.
3
4
  *
@@ -11,6 +12,9 @@ export function createAppLogsModule(axios, appId) {
11
12
  return {
12
13
  // Log user activity in the app
13
14
  async logUserInApp(pageName) {
15
+ // A page visit feeds App Analytics, so it follows the same consent.
16
+ if (!isAnalyticsAllowed())
17
+ return;
14
18
  await axios.post(`${baseURL}/log-user-in-app/${pageName}`);
15
19
  },
16
20
  // Fetch app logs with optional parameters
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44-preview/sdk",
3
- "version": "0.8.57-pr.359.9ff5e66",
3
+ "version": "0.8.57-pr.360.71a2580",
4
4
  "description": "JavaScript SDK for Base44 API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",