@base44-preview/sdk 0.8.48-pr.280.b5c6133 → 0.8.48-pr.280.dac0f98

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.
package/dist/client.js CHANGED
@@ -230,6 +230,7 @@ export function createClient(config) {
230
230
  userAuthModule,
231
231
  enabled: (_f = analytics === null || analytics === void 0 ? void 0 : analytics.enabled) !== null && _f !== void 0 ? _f : true,
232
232
  getVisitorId: experiments.visitorId,
233
+ experimentsContext,
233
234
  }),
234
235
  actors: actorsModule.module,
235
236
  cleanup: () => {
@@ -1,6 +1,7 @@
1
1
  import { AxiosInstance } from "axios";
2
- import { TrackEventParams, AnalyticsModuleOptions } from "./analytics.types";
2
+ import { TrackEventParams, TrackEventData, AnalyticsModuleOptions, SessionContext } from "./analytics.types";
3
3
  import type { InternalAuthModule } from "./auth.types";
4
+ import type { ExperimentsContext } from "./experiments-config.types.js";
4
5
  export declare const USER_HEARTBEAT_EVENT_NAME = "__user_heartbeat_event__";
5
6
  export declare const ANALYTICS_INITIALIZATION_EVENT_NAME = "__initialization_event__";
6
7
  export declare const ANALYTICS_SESSION_DURATION_EVENT_NAME = "__session_duration_event__";
@@ -13,10 +14,21 @@ export interface AnalyticsModuleArgs {
13
14
  userAuthModule: InternalAuthModule;
14
15
  enabled: boolean;
15
16
  getVisitorId?: () => string | undefined;
17
+ experimentsContext?: ExperimentsContext;
16
18
  }
17
19
  /** @internal */
18
- export declare function isAnalyticsEnabled(enabled: boolean): boolean;
19
- export declare const createAnalyticsModule: ({ axiosClient, serverUrl, appId, userAuthModule, enabled, getVisitorId, }: AnalyticsModuleArgs) => {
20
+ export declare function isAnalyticsEnabled(enabled: boolean, state?: {
21
+ requestsQueue: TrackEventData[];
22
+ isProcessing: boolean;
23
+ isHeartBeatProcessing: boolean;
24
+ wasInitializationTracked: boolean;
25
+ sessionContext: SessionContext | null;
26
+ sessionContextPromise: Promise<SessionContext> | null;
27
+ sessionStartTime: string | null;
28
+ fallbackSessionId: string | null;
29
+ config: Required<AnalyticsModuleOptions>;
30
+ }): boolean;
31
+ export declare const createAnalyticsModule: ({ axiosClient, serverUrl, appId, userAuthModule, enabled, getVisitorId, experimentsContext, }: AnalyticsModuleArgs) => {
20
32
  track: (params: TrackEventParams) => void;
21
33
  cleanup: () => void;
22
34
  };
@@ -30,6 +42,16 @@ export declare const createAnalyticsModule: ({ axiosClient, serverUrl, appId, us
30
42
  *
31
43
  * @internal
32
44
  */
33
- export declare function resetAnalyticsSessionContext(): void;
45
+ export declare function resetAnalyticsSessionContext(axiosClient?: AxiosInstance): void;
34
46
  export declare function getAnalyticsConfigFromUrlParams(): AnalyticsModuleOptions | undefined;
35
- export declare function getAnalyticsSessionId(): string;
47
+ export declare function getAnalyticsSessionId(state?: {
48
+ requestsQueue: TrackEventData[];
49
+ isProcessing: boolean;
50
+ isHeartBeatProcessing: boolean;
51
+ wasInitializationTracked: boolean;
52
+ sessionContext: SessionContext | null;
53
+ sessionContextPromise: Promise<SessionContext> | null;
54
+ sessionStartTime: string | null;
55
+ fallbackSessionId: string | null;
56
+ config: Required<AnalyticsModuleOptions>;
57
+ }): string;
@@ -18,34 +18,41 @@ const defaultConfiguration = {
18
18
  //// shared queue for analytics events ////
19
19
  ///////////////////////////////////////////////
20
20
  const ANALYTICS_SHARED_STATE_NAME = "analytics";
21
- // shared state//
22
- const analyticsSharedState = getSharedInstance(ANALYTICS_SHARED_STATE_NAME, () => ({
23
- requestsQueue: [],
24
- isProcessing: false,
25
- isHeartBeatProcessing: false,
26
- wasInitializationTracked: false,
27
- sessionContext: null,
28
- sessionStartTime: null,
29
- // Memoized session id for when `localStorage` can't persist one — see
30
- // getAnalyticsSessionId.
31
- fallbackSessionId: null,
32
- config: {
33
- ...defaultConfiguration,
34
- ...getAnalyticsConfigFromUrlParams(),
35
- },
36
- }));
21
+ function createAnalyticsState() {
22
+ return {
23
+ requestsQueue: [],
24
+ isProcessing: false,
25
+ isHeartBeatProcessing: false,
26
+ wasInitializationTracked: false,
27
+ sessionContext: null,
28
+ sessionContextPromise: null,
29
+ sessionStartTime: null,
30
+ // Memoized session id for when `localStorage` can't persist one — see
31
+ // getAnalyticsSessionId.
32
+ fallbackSessionId: null,
33
+ config: {
34
+ ...defaultConfiguration,
35
+ ...getAnalyticsConfigFromUrlParams(),
36
+ },
37
+ };
38
+ }
39
+ const analyticsSharedState = getSharedInstance(ANALYTICS_SHARED_STATE_NAME, createAnalyticsState);
40
+ const serverAnalyticsStates = new WeakMap();
37
41
  /** @internal */
38
- export function isAnalyticsEnabled(enabled) {
39
- return enabled && analyticsSharedState.config.enabled && !isReactNative;
42
+ export function isAnalyticsEnabled(enabled, state = analyticsSharedState) {
43
+ return enabled && state.config.enabled && !isReactNative;
40
44
  }
41
- export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthModule, enabled, getVisitorId, }) => {
45
+ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthModule, enabled, getVisitorId, experimentsContext, }) => {
46
+ const state = typeof window === "undefined" ? createAnalyticsState() : analyticsSharedState;
47
+ if (typeof window === "undefined")
48
+ serverAnalyticsStates.set(axiosClient, state);
42
49
  // prevent overflow of events //
43
- const { maxQueueSize, throttleTime, batchSize } = analyticsSharedState.config;
50
+ const { maxQueueSize, throttleTime, batchSize } = state.config;
44
51
  // Disable analytics on React Native. It defines `window` but not `document`,
45
52
  // so the per-callsite `typeof window` guards below aren't enough to keep it
46
53
  // from touching `document` (e.g. `document.referrer` on init). Node/SSR is
47
54
  // still handled by those `window` guards, so this doesn't affect it.
48
- if (!isAnalyticsEnabled(enabled)) {
55
+ if (!isAnalyticsEnabled(enabled, state)) {
49
56
  return {
50
57
  track: () => { },
51
58
  cleanup: () => { },
@@ -77,7 +84,7 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
77
84
  var _a;
78
85
  if (eventsData.length === 0)
79
86
  return;
80
- const sessionContext_ = await getSessionContext(userAuthModule);
87
+ const sessionContext_ = await getSessionContext(userAuthModule, state);
81
88
  const events = eventsData.map(transformEventDataToApiRequestData({ ...sessionContext_, session_id: (_a = getVisitorId === null || getVisitorId === void 0 ? void 0 : getVisitorId()) !== null && _a !== void 0 ? _a : sessionContext_.session_id }));
82
89
  try {
83
90
  if (!options.isBeacon || !beaconRequest(events)) {
@@ -92,16 +99,25 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
92
99
  startAnalyticsProcessor(flush, {
93
100
  throttleTime,
94
101
  batchSize,
95
- });
102
+ }, state);
96
103
  };
97
104
  const track = (params) => {
98
- if (analyticsSharedState.requestsQueue.length >= maxQueueSize) {
105
+ var _a;
106
+ if (state.requestsQueue.length >= maxQueueSize) {
99
107
  return;
100
108
  }
101
109
  const intrinsicData = getEventIntrinsicData();
102
- analyticsSharedState.requestsQueue.push({
110
+ const preview = Object.fromEntries(Object.entries((_a = experimentsContext === null || experimentsContext === void 0 ? void 0 : experimentsContext.preview) !== null && _a !== void 0 ? _a : {}).filter(([, value]) => typeof value === "boolean"));
111
+ const properties = { ...params.properties };
112
+ delete properties.__b44_experiment_preview;
113
+ if (Object.keys(preview).length) {
114
+ // Capture now: a queued event must retain its occurrence-time preview.
115
+ properties.__b44_experiment_preview = JSON.stringify(preview);
116
+ }
117
+ state.requestsQueue.push({
103
118
  ...params,
104
119
  ...intrinsicData,
120
+ properties: params.properties || Object.keys(properties).length ? properties : undefined,
105
121
  });
106
122
  startProcessing();
107
123
  };
@@ -109,16 +125,16 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
109
125
  startAnalyticsProcessor(flush, {
110
126
  throttleTime,
111
127
  batchSize,
112
- });
113
- clearHeartBeatProcessor = startHeartBeatProcessor(track);
114
- setSessionDurationTimerStart();
128
+ }, state);
129
+ clearHeartBeatProcessor = startHeartBeatProcessor(track, state);
130
+ setSessionDurationTimerStart(state);
115
131
  };
116
132
  const onDocHidden = () => {
117
- stopAnalyticsProcessor();
133
+ stopAnalyticsProcessor(state);
118
134
  clearHeartBeatProcessor === null || clearHeartBeatProcessor === void 0 ? void 0 : clearHeartBeatProcessor();
119
- trackSessionDurationEvent(track);
135
+ trackSessionDurationEvent(track, state);
120
136
  // flush entire queue on visibility change and hope for the best //
121
- const eventsData = analyticsSharedState.requestsQueue.splice(0);
137
+ const eventsData = state.requestsQueue.splice(0);
122
138
  flush(eventsData, { isBeacon: true });
123
139
  };
124
140
  const onVisibilityChange = () => {
@@ -132,7 +148,7 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
132
148
  }
133
149
  };
134
150
  const cleanup = () => {
135
- stopAnalyticsProcessor();
151
+ stopAnalyticsProcessor(state);
136
152
  clearHeartBeatProcessor === null || clearHeartBeatProcessor === void 0 ? void 0 : clearHeartBeatProcessor();
137
153
  if (typeof window !== "undefined") {
138
154
  window.removeEventListener("visibilitychange", onVisibilityChange);
@@ -141,9 +157,9 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
141
157
  // start the flusing process ///
142
158
  startProcessing();
143
159
  // start the heart beat processor //
144
- clearHeartBeatProcessor = startHeartBeatProcessor(track);
160
+ clearHeartBeatProcessor = startHeartBeatProcessor(track, state);
145
161
  // track the referrer event //
146
- trackInitializationEvent(track);
162
+ trackInitializationEvent(track, state);
147
163
  // start the visibility change listener //
148
164
  if (typeof window !== "undefined") {
149
165
  window.addEventListener("visibilitychange", onVisibilityChange);
@@ -153,50 +169,50 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
153
169
  cleanup,
154
170
  };
155
171
  };
156
- function stopAnalyticsProcessor() {
157
- analyticsSharedState.isProcessing = false;
172
+ function stopAnalyticsProcessor(state) {
173
+ state.isProcessing = false;
158
174
  }
159
- async function startAnalyticsProcessor(handleTrack, options) {
160
- if (analyticsSharedState.isProcessing) {
175
+ async function startAnalyticsProcessor(handleTrack, options, state) {
176
+ if (state.isProcessing) {
161
177
  // only one instance of the analytics processor can be running at a time //
162
178
  return;
163
179
  }
164
- analyticsSharedState.isProcessing = true;
180
+ state.isProcessing = true;
165
181
  const { throttleTime = 1000, batchSize = 30 } = options !== null && options !== void 0 ? options : {};
166
- while (analyticsSharedState.isProcessing &&
167
- analyticsSharedState.requestsQueue.length > 0) {
168
- const requests = analyticsSharedState.requestsQueue.splice(0, batchSize);
182
+ while (state.isProcessing &&
183
+ state.requestsQueue.length > 0) {
184
+ const requests = state.requestsQueue.splice(0, batchSize);
169
185
  requests.length && (await handleTrack(requests));
170
186
  await new Promise((resolve) => setTimeout(resolve, throttleTime));
171
187
  }
172
- analyticsSharedState.isProcessing = false;
188
+ state.isProcessing = false;
173
189
  }
174
- function startHeartBeatProcessor(track) {
190
+ function startHeartBeatProcessor(track, state) {
175
191
  var _a;
176
192
  // Browser-only, like the other automatic events here (initialization, session
177
193
  // duration, visibility). Outside a browser this timer fired a `me()` every
178
194
  // interval for the lifetime of a long-lived server-side client, and kept the
179
195
  // Node event loop alive. Explicit `analytics.track()` calls still work.
180
196
  if (typeof window === "undefined" ||
181
- analyticsSharedState.isHeartBeatProcessing ||
182
- ((_a = analyticsSharedState.config.heartBeatInterval) !== null && _a !== void 0 ? _a : 0) < 10) {
197
+ state.isHeartBeatProcessing ||
198
+ ((_a = state.config.heartBeatInterval) !== null && _a !== void 0 ? _a : 0) < 10) {
183
199
  return () => { };
184
200
  }
185
- analyticsSharedState.isHeartBeatProcessing = true;
201
+ state.isHeartBeatProcessing = true;
186
202
  const interval = setInterval(() => {
187
203
  track({ eventName: USER_HEARTBEAT_EVENT_NAME });
188
- }, analyticsSharedState.config.heartBeatInterval);
204
+ }, state.config.heartBeatInterval);
189
205
  return () => {
190
206
  clearInterval(interval);
191
- analyticsSharedState.isHeartBeatProcessing = false;
207
+ state.isHeartBeatProcessing = false;
192
208
  };
193
209
  }
194
- function trackInitializationEvent(track) {
210
+ function trackInitializationEvent(track, state) {
195
211
  if (typeof window === "undefined" ||
196
- analyticsSharedState.wasInitializationTracked) {
212
+ state.wasInitializationTracked) {
197
213
  return;
198
214
  }
199
- analyticsSharedState.wasInitializationTracked = true;
215
+ state.wasInitializationTracked = true;
200
216
  track({
201
217
  eventName: ANALYTICS_INITIALIZATION_EVENT_NAME,
202
218
  properties: {
@@ -204,20 +220,20 @@ function trackInitializationEvent(track) {
204
220
  },
205
221
  });
206
222
  }
207
- function setSessionDurationTimerStart() {
223
+ function setSessionDurationTimerStart(state) {
208
224
  if (typeof window === "undefined" ||
209
- analyticsSharedState.sessionStartTime !== null) {
225
+ state.sessionStartTime !== null) {
210
226
  return;
211
227
  }
212
- analyticsSharedState.sessionStartTime = new Date().toISOString();
228
+ state.sessionStartTime = new Date().toISOString();
213
229
  }
214
- function trackSessionDurationEvent(track) {
230
+ function trackSessionDurationEvent(track, state) {
215
231
  if (typeof window === "undefined" ||
216
- analyticsSharedState.sessionStartTime === null)
232
+ state.sessionStartTime === null)
217
233
  return;
218
234
  const sessionDuration = new Date().getTime() -
219
- new Date(analyticsSharedState.sessionStartTime).getTime();
220
- analyticsSharedState.sessionStartTime = null;
235
+ new Date(state.sessionStartTime).getTime();
236
+ state.sessionStartTime = null;
221
237
  track({
222
238
  eventName: ANALYTICS_SESSION_DURATION_EVENT_NAME,
223
239
  properties: { sessionDuration },
@@ -240,7 +256,6 @@ function transformEventDataToApiRequestData(sessionContext) {
240
256
  ...sessionContext,
241
257
  });
242
258
  }
243
- let sessionContextPromise = null;
244
259
  /**
245
260
  * Clears the memoized analytics session context.
246
261
  *
@@ -251,22 +266,24 @@ let sessionContextPromise = null;
251
266
  *
252
267
  * @internal
253
268
  */
254
- export function resetAnalyticsSessionContext() {
255
- analyticsSharedState.sessionContext = null;
256
- sessionContextPromise = null;
269
+ export function resetAnalyticsSessionContext(axiosClient) {
270
+ var _a;
271
+ const state = axiosClient ? (_a = serverAnalyticsStates.get(axiosClient)) !== null && _a !== void 0 ? _a : analyticsSharedState : analyticsSharedState;
272
+ state.sessionContext = null;
273
+ state.sessionContextPromise = null;
257
274
  }
258
- async function getSessionContext(userAuthModule) {
259
- if (!analyticsSharedState.sessionContext) {
275
+ async function getSessionContext(userAuthModule, state) {
276
+ if (!state.sessionContext) {
260
277
  // With no token there is no identity to resolve: `me()` can only answer 401,
261
278
  // which the browser logs to the console before any handler here sees it. On
262
279
  // a public page that request is the sole reason an error appears, so skip
263
280
  // it. This is not memoized — a visitor who logs in later must still resolve.
264
281
  if (!userAuthModule.hasToken()) {
265
- return { user_id: null, session_id: getAnalyticsSessionId() };
282
+ return { user_id: null, session_id: getAnalyticsSessionId(state) };
266
283
  }
267
- if (!sessionContextPromise) {
268
- const sessionId = getAnalyticsSessionId();
269
- sessionContextPromise = userAuthModule
284
+ if (!state.sessionContextPromise) {
285
+ const sessionId = getAnalyticsSessionId(state);
286
+ state.sessionContextPromise = userAuthModule
270
287
  .me()
271
288
  .then((user) => ({
272
289
  user_id: user.id,
@@ -277,7 +294,7 @@ async function getSessionContext(userAuthModule) {
277
294
  session_id: sessionId,
278
295
  }));
279
296
  }
280
- const pending = sessionContextPromise;
297
+ const pending = state.sessionContextPromise;
281
298
  const context = await pending;
282
299
  // Publish only if this lookup is still the current one. A reset that lands
283
300
  // while the request is in flight nulls `sessionContextPromise`, and an
@@ -285,12 +302,12 @@ async function getSessionContext(userAuthModule) {
285
302
  // for the rest of the session. The awaited value is still returned: these
286
303
  // events were queued before the identity changed, so that is who they
287
304
  // belong to.
288
- if (sessionContextPromise === pending) {
289
- analyticsSharedState.sessionContext = context;
305
+ if (state.sessionContextPromise === pending) {
306
+ state.sessionContext = context;
290
307
  }
291
308
  return context;
292
309
  }
293
- return analyticsSharedState.sessionContext;
310
+ return state.sessionContext;
294
311
  }
295
312
  export function getAnalyticsConfigFromUrlParams() {
296
313
  // `window.location` is absent on React Native. This runs at module load (via
@@ -312,19 +329,18 @@ export function getAnalyticsConfigFromUrlParams() {
312
329
  // return the config object //
313
330
  return { enabled: analyticsEnable === "true" };
314
331
  }
315
- // When the id can't be persisted (React Native has no `localStorage`), keep
316
- // it stable for the process instead of minting a fresh one per call.
317
- function getFallbackSessionId() {
332
+ // Without persistent storage, keep the id stable within this analytics state.
333
+ function getFallbackSessionId(state) {
318
334
  var _a;
319
- return ((_a = analyticsSharedState.fallbackSessionId) !== null && _a !== void 0 ? _a : (analyticsSharedState.fallbackSessionId = generateUuid()));
335
+ return ((_a = state.fallbackSessionId) !== null && _a !== void 0 ? _a : (state.fallbackSessionId = generateUuid()));
320
336
  }
321
- export function getAnalyticsSessionId() {
337
+ export function getAnalyticsSessionId(state = analyticsSharedState) {
322
338
  var _a;
323
339
  const visitorId = (_a = getExperimentsRuntime()) === null || _a === void 0 ? void 0 : _a.visitorId;
324
340
  if (visitorId && visitorId !== "anon")
325
341
  return visitorId;
326
342
  if (typeof window === "undefined") {
327
- return getFallbackSessionId();
343
+ return getFallbackSessionId(state);
328
344
  }
329
345
  try {
330
346
  const sessionId = localStorage.getItem(ANALYTICS_SESSION_ID_LOCAL_STORAGE_KEY);
@@ -336,6 +352,6 @@ export function getAnalyticsSessionId() {
336
352
  return sessionId;
337
353
  }
338
354
  catch (_b) {
339
- return getFallbackSessionId();
355
+ return getFallbackSessionId(state);
340
356
  }
341
357
  }
@@ -177,7 +177,7 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
177
177
  // Drop identity resolved under the previous session: a `me()` already in
178
178
  // flight would otherwise resolve into callers that run after the logout.
179
179
  clearPendingMe();
180
- resetAnalyticsSessionContext();
180
+ resetAnalyticsSessionContext(axios);
181
181
  hasAccessToken = false;
182
182
  notifyAuthState({ status: "anonymous" });
183
183
  // Only do the rest if in a browser environment
@@ -207,7 +207,7 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
207
207
  // Same reasoning as in `logout`: the identity changes here, so anything
208
208
  // resolved for the previous one must not be handed to later callers.
209
209
  clearPendingMe();
210
- resetAnalyticsSessionContext();
210
+ resetAnalyticsSessionContext(axios);
211
211
  hasAccessToken = true;
212
212
  // handle token change for axios clients
213
213
  axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44-preview/sdk",
3
- "version": "0.8.48-pr.280.b5c6133",
3
+ "version": "0.8.48-pr.280.dac0f98",
4
4
  "description": "JavaScript SDK for Base44 API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",