@base44-preview/sdk 0.8.48-pr.276.5321860 → 0.8.48-pr.280.7eec9ba

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.
Files changed (102) hide show
  1. package/dist/client.js +50 -7
  2. package/dist/client.types.d.ts +13 -3
  3. package/dist/index.d.ts +3 -11
  4. package/dist/index.js +1 -0
  5. package/dist/modules/analytics.d.ts +29 -4
  6. package/dist/modules/analytics.js +107 -82
  7. package/dist/modules/auth.js +34 -3
  8. package/dist/modules/auth.types.d.ts +13 -0
  9. package/dist/modules/experiment-exposures.d.ts +19 -0
  10. package/dist/modules/experiment-exposures.js +65 -0
  11. package/dist/modules/experiments-config.types.d.ts +39 -0
  12. package/dist/modules/experiments-context.d.ts +10 -0
  13. package/dist/modules/experiments-context.js +44 -0
  14. package/dist/modules/experiments-evaluator.d.ts +11 -0
  15. package/dist/modules/experiments-evaluator.js +45 -0
  16. package/dist/modules/experiments-runtime.types.d.ts +19 -0
  17. package/dist/modules/experiments-runtime.types.js +7 -0
  18. package/dist/modules/experiments.d.ts +16 -0
  19. package/dist/modules/experiments.js +134 -0
  20. package/dist/modules/experiments.types.d.ts +106 -0
  21. package/dist/utils/fetch-with-auth.js +6 -0
  22. package/package.json +1 -31
  23. package/dist/iap/account-token.d.ts +0 -14
  24. package/dist/iap/account-token.js +0 -43
  25. package/dist/iap/config.d.ts +0 -19
  26. package/dist/iap/config.js +0 -105
  27. package/dist/iap/errors.d.ts +0 -89
  28. package/dist/iap/errors.js +0 -66
  29. package/dist/iap/errors.types.d.ts +0 -74
  30. package/dist/iap/errors.types.js +0 -7
  31. package/dist/iap/events/emitter.d.ts +0 -28
  32. package/dist/iap/events/emitter.js +0 -33
  33. package/dist/iap/events/events.types.d.ts +0 -124
  34. package/dist/iap/iap.types.d.ts +0 -632
  35. package/dist/iap/index.d.ts +0 -120
  36. package/dist/iap/index.js +0 -145
  37. package/dist/iap/ingest/device.d.ts +0 -7
  38. package/dist/iap/ingest/device.js +0 -191
  39. package/dist/iap/ingest/device.types.d.ts +0 -80
  40. package/dist/iap/ingest/device.types.js +0 -1
  41. package/dist/iap/ingest/mappers.d.ts +0 -66
  42. package/dist/iap/ingest/mappers.js +0 -248
  43. package/dist/iap/ingest/matrix.d.ts +0 -55
  44. package/dist/iap/ingest/matrix.js +0 -202
  45. package/dist/iap/ingest/notifications.d.ts +0 -70
  46. package/dist/iap/ingest/notifications.js +0 -238
  47. package/dist/iap/read/derive.d.ts +0 -40
  48. package/dist/iap/read/derive.js +0 -112
  49. package/dist/iap/read/read.d.ts +0 -47
  50. package/dist/iap/read/read.js +0 -215
  51. package/dist/iap/read/read.types.d.ts +0 -174
  52. package/dist/iap/read/read.types.js +0 -1
  53. package/dist/iap/runtime/base64.d.ts +0 -34
  54. package/dist/iap/runtime/base64.js +0 -131
  55. package/dist/iap/runtime/clock.d.ts +0 -13
  56. package/dist/iap/runtime/clock.js +0 -11
  57. package/dist/iap/runtime/webcrypto.d.ts +0 -14
  58. package/dist/iap/runtime/webcrypto.js +0 -66
  59. package/dist/iap/server-api/client.d.ts +0 -16
  60. package/dist/iap/server-api/client.js +0 -140
  61. package/dist/iap/server-api/jwt.d.ts +0 -3
  62. package/dist/iap/server-api/jwt.js +0 -70
  63. package/dist/iap/server-api/server-api.types.d.ts +0 -155
  64. package/dist/iap/server-api/server-api.types.js +0 -12
  65. package/dist/iap/store/collapse.d.ts +0 -27
  66. package/dist/iap/store/collapse.js +0 -85
  67. package/dist/iap/store/descriptors.d.ts +0 -46
  68. package/dist/iap/store/descriptors.js +0 -90
  69. package/dist/iap/store/entities-store.d.ts +0 -43
  70. package/dist/iap/store/entities-store.js +0 -348
  71. package/dist/iap/store/rows.types.d.ts +0 -233
  72. package/dist/iap/store/rows.types.js +0 -1
  73. package/dist/iap/store/schemas.d.ts +0 -68
  74. package/dist/iap/store/schemas.js +0 -214
  75. package/dist/iap/store/store-errors.d.ts +0 -52
  76. package/dist/iap/store/store-errors.js +0 -103
  77. package/dist/iap/store/store.types.d.ts +0 -193
  78. package/dist/iap/store/store.types.js +0 -1
  79. package/dist/iap/verify/apple-roots.d.ts +0 -18
  80. package/dist/iap/verify/apple-roots.js +0 -121
  81. package/dist/iap/verify/apple-verifier.d.ts +0 -13
  82. package/dist/iap/verify/apple-verifier.js +0 -154
  83. package/dist/iap/verify/asn1.d.ts +0 -102
  84. package/dist/iap/verify/asn1.js +0 -247
  85. package/dist/iap/verify/chain.d.ts +0 -40
  86. package/dist/iap/verify/chain.js +0 -112
  87. package/dist/iap/verify/ecdsa.d.ts +0 -16
  88. package/dist/iap/verify/ecdsa.js +0 -86
  89. package/dist/iap/verify/jws.d.ts +0 -50
  90. package/dist/iap/verify/jws.js +0 -159
  91. package/dist/iap/verify/payload-checks.d.ts +0 -36
  92. package/dist/iap/verify/payload-checks.js +0 -76
  93. package/dist/iap/verify/verifier.d.ts +0 -30
  94. package/dist/iap/verify/verifier.js +0 -114
  95. package/dist/iap/verify/verify.types.d.ts +0 -292
  96. package/dist/iap/verify/verify.types.js +0 -15
  97. package/dist/iap/verify/x509.d.ts +0 -83
  98. package/dist/iap/verify/x509.js +0 -156
  99. package/dist/iap/version.d.ts +0 -13
  100. package/dist/iap/version.js +0 -13
  101. /package/dist/{iap/events/events.types.js → modules/experiments-config.types.js} +0 -0
  102. /package/dist/{iap/iap.types.js → modules/experiments.types.js} +0 -0
package/dist/client.js CHANGED
@@ -14,6 +14,9 @@ import { createAppModule } from "./modules/app.js";
14
14
  import { createUsersModule } from "./modules/users.js";
15
15
  import { RoomsSocket } from "./utils/socket-utils.js";
16
16
  import { createAnalyticsModule } from "./modules/analytics.js";
17
+ import { createExperimentsModule } from "./modules/experiments.js";
18
+ import { createExposureTracker } from "./modules/experiment-exposures.js";
19
+ import { EXPERIMENTS_CONTEXT_HEADER, getBrowserExperimentsContext, readExperimentsContext } from "./modules/experiments-context.js";
17
20
  import { createActorsModule, resolveActorsHost, } from "./modules/actors.js";
18
21
  /**
19
22
  * Creates a Base44 client.
@@ -53,10 +56,11 @@ import { createActorsModule, resolveActorsHost, } from "./modules/actors.js";
53
56
  * ```
54
57
  */
55
58
  export function createClient(config) {
56
- var _a, _b, _c, _d;
59
+ var _a, _b, _c, _d, _e, _f, _g;
57
60
  const { serverUrl = "https://base44.app", appId, analytics, token, serviceToken, requiresAuth = false, appBaseUrl, options, functionsVersion, headers: optionalHeaders, } = config;
58
61
  // Normalize appBaseUrl to always be a string (empty if not provided or invalid)
59
62
  const normalizedAppBaseUrl = typeof appBaseUrl === "string" ? appBaseUrl : "";
63
+ const experimentsContext = (_a = config.experiments) !== null && _a !== void 0 ? _a : getBrowserExperimentsContext(appId);
60
64
  const socketConfig = {
61
65
  serverUrl,
62
66
  mountPath: "/ws-user-apps/socket.io/",
@@ -73,9 +77,14 @@ export function createClient(config) {
73
77
  }
74
78
  return socket;
75
79
  };
80
+ const { [EXPERIMENTS_CONTEXT_HEADER]: inheritedExperimentsContext, ...requestHeaders } = optionalHeaders !== null && optionalHeaders !== void 0 ? optionalHeaders : {};
76
81
  const headers = {
77
- ...optionalHeaders,
82
+ ...requestHeaders,
78
83
  "X-App-Id": String(appId),
84
+ ...(experimentsContext ? {
85
+ "Base44-Visitor-Id": experimentsContext.identity.visitorId,
86
+ "Base44-Experiment-Preview": JSON.stringify((_b = experimentsContext.preview) !== null && _b !== void 0 ? _b : {}),
87
+ } : {}),
79
88
  };
80
89
  const functionHeaders = functionsVersion
81
90
  ? {
@@ -121,10 +130,24 @@ export function createClient(config) {
121
130
  baseURL: `${serverUrl}/api`,
122
131
  headers,
123
132
  });
133
+ const exposureTracker = createExposureTracker({
134
+ axiosClient,
135
+ appId,
136
+ enabled: (_c = analytics === null || analytics === void 0 ? void 0 : analytics.enabled) !== null && _c !== void 0 ? _c : true,
137
+ source: typeof window === "undefined" ? "backend" : "browser",
138
+ pageUrl: experimentsContext === null || experimentsContext === void 0 ? void 0 : experimentsContext.pageUrl,
139
+ });
140
+ const experiments = createExperimentsModule({
141
+ getAuth: () => userAuthModule,
142
+ trackExposure: exposureTracker.track,
143
+ flushExposures: exposureTracker.flush,
144
+ context: experimentsContext,
145
+ });
124
146
  const userAuthModule = createAuthModule(axiosClient, functionsAxiosClient, appId, {
125
147
  appBaseUrl: normalizedAppBaseUrl,
126
148
  serverUrl,
127
149
  token,
150
+ onAuthStateChange: experiments.onAuthStateChange,
128
151
  });
129
152
  // Apply the access token before any module that may issue authenticated
130
153
  // requests during construction (notably analytics, which fires an init
@@ -136,11 +159,19 @@ export function createClient(config) {
136
159
  userAuthModule.setToken(accessToken);
137
160
  }
138
161
  }
162
+ if (experimentsContext) {
163
+ const { userId, status } = experimentsContext.identity;
164
+ // The document's cookie identity may differ from this client's localStorage token.
165
+ const needsClientIdentity = typeof window !== "undefined" && userAuthModule.hasToken() &&
166
+ experimentsContext.config.experiments.some((experiment) => experiment.assign_by === "user");
167
+ experiments.onAuthStateChange(status === "pending" || needsClientIdentity ? { status: "pending" } :
168
+ userId ? { status: "authenticated", userId } : { status: "anonymous" });
169
+ }
139
170
  const actorsModule = createActorsModule({
140
171
  appId,
141
172
  // serverUrl is often relative/empty (same-origin app); the proxy-fallback
142
173
  // URL needs an absolute host, so fall back to the page origin.
143
- host: resolveActorsHost(serverUrl, typeof window !== "undefined" ? (_a = window.location) === null || _a === void 0 ? void 0 : _a.origin : undefined),
174
+ host: resolveActorsHost(serverUrl, typeof window !== "undefined" ? (_d = window.location) === null || _d === void 0 ? void 0 : _d.origin : undefined),
144
175
  functionsVersion,
145
176
  getAuthToken: () => token || getAccessToken(),
146
177
  mintConnectionToken: async (actorName, room, connectionId) => {
@@ -168,6 +199,7 @@ export function createClient(config) {
168
199
  integrations: createIntegrationsModule(axiosClient, appId),
169
200
  connectors: createUserConnectorsModule(axiosClient, appId),
170
201
  auth: userAuthModule,
202
+ experiments: experiments.module,
171
203
  functions: createFunctionsModule(functionsAxiosClient, appId, {
172
204
  getAuthHeaders: () => {
173
205
  const headers = {};
@@ -178,7 +210,7 @@ export function createClient(config) {
178
210
  }
179
211
  return headers;
180
212
  },
181
- baseURL: (_b = functionsAxiosClient.defaults) === null || _b === void 0 ? void 0 : _b.baseURL,
213
+ baseURL: (_e = functionsAxiosClient.defaults) === null || _e === void 0 ? void 0 : _e.baseURL,
182
214
  }),
183
215
  agents: createAgentsModule({
184
216
  axios: axiosClient,
@@ -196,11 +228,14 @@ export function createClient(config) {
196
228
  serverUrl,
197
229
  appId,
198
230
  userAuthModule,
199
- enabled: (_c = analytics === null || analytics === void 0 ? void 0 : analytics.enabled) !== null && _c !== void 0 ? _c : true,
231
+ enabled: (_f = analytics === null || analytics === void 0 ? void 0 : analytics.enabled) !== null && _f !== void 0 ? _f : true,
232
+ getVisitorId: experiments.visitorId,
233
+ experimentsContext,
200
234
  }),
201
235
  actors: actorsModule.module,
202
236
  cleanup: () => {
203
237
  userModules.analytics.cleanup();
238
+ experiments.cleanup();
204
239
  actorsModule.closeAll();
205
240
  if (socket) {
206
241
  socket.disconnect();
@@ -225,7 +260,7 @@ export function createClient(config) {
225
260
  }
226
261
  return headers;
227
262
  },
228
- baseURL: (_d = serviceRoleFunctionsAxiosClient.defaults) === null || _d === void 0 ? void 0 : _d.baseURL,
263
+ baseURL: (_g = serviceRoleFunctionsAxiosClient.defaults) === null || _g === void 0 ? void 0 : _g.baseURL,
229
264
  }),
230
265
  agents: createAgentsModule({
231
266
  axios: serviceRoleAxiosClient,
@@ -268,7 +303,10 @@ export function createClient(config) {
268
303
  appId: String(appId),
269
304
  serverUrl,
270
305
  functionsVersion,
271
- platformHeaders: optionalHeaders,
306
+ platformHeaders: {
307
+ ...headers,
308
+ ...(inheritedExperimentsContext ? { [EXPERIMENTS_CONTEXT_HEADER]: inheritedExperimentsContext } : {}),
309
+ },
272
310
  }),
273
311
  /**
274
312
  * Sets a new authentication token for all subsequent requests.
@@ -420,6 +458,10 @@ export function createClientFromRequest(request) {
420
458
  }
421
459
  // Prepare additional headers to propagate
422
460
  const additionalHeaders = {};
461
+ const encodedExperiments = request.headers.get(EXPERIMENTS_CONTEXT_HEADER);
462
+ const experimentsContext = readExperimentsContext(encodedExperiments, appId);
463
+ if (experimentsContext && encodedExperiments)
464
+ additionalHeaders[EXPERIMENTS_CONTEXT_HEADER] = encodedExperiments;
423
465
  if (stateHeader) {
424
466
  additionalHeaders["Base44-State"] = stateHeader;
425
467
  }
@@ -440,5 +482,6 @@ export function createClientFromRequest(request) {
440
482
  serviceToken: serviceRoleToken,
441
483
  functionsVersion: functionsVersion !== null && functionsVersion !== void 0 ? functionsVersion : undefined,
442
484
  headers: additionalHeaders,
485
+ experiments: experimentsContext ? { ...experimentsContext, pageUrl: request.url ? new URL(request.url).pathname : "/" } : undefined,
443
486
  });
444
487
  }
@@ -9,6 +9,8 @@ import type { AiGatewayModule } from "./modules/ai-gateway.types.js";
9
9
  import type { AppLogsModule } from "./modules/app-logs.types.js";
10
10
  import type { AppModule } from "./modules/app.types.js";
11
11
  import type { AnalyticsModule } from "./modules/analytics.types.js";
12
+ import type { ExperimentsModule } from "./modules/experiments.types.js";
13
+ import type { ExperimentsContext } from "./modules/experiments-config.types.js";
12
14
  import type { ActorsModule } from "./modules/actors.types.js";
13
15
  import type { FetchWithAuthInit } from "./utils/fetch-with-auth.js";
14
16
  /**
@@ -39,9 +41,9 @@ export interface CreateClientAnalyticsConfig {
39
41
  /**
40
42
  * Whether app analytics is enabled for this client.
41
43
  *
42
- * When disabled, automatic analytics and calls to `analytics.track()` are
43
- * no-ops. The SDK does not create an analytics session identifier, start
44
- * heartbeat timers, or send analytics requests.
44
+ * When disabled, automatic analytics, experiment exposures and calls to
45
+ * `analytics.track()` are no-ops. The SDK does not create an analytics session
46
+ * identifier, start heartbeat timers, or send analytics requests.
45
47
  *
46
48
  * @defaultValue `true`
47
49
  */
@@ -79,6 +81,12 @@ export interface CreateClientConfig {
79
81
  * Omit this option to preserve the default analytics behavior.
80
82
  */
81
83
  analytics?: CreateClientAnalyticsConfig;
84
+ /**
85
+ * Platform-validated context for local flag evaluation. Request-scoped on servers.
86
+ * Automatically read from the platform bootstrap in browsers and trusted headers
87
+ * by createClientFromRequest(). Not an authorization credential.
88
+ */
89
+ experiments?: ExperimentsContext;
82
90
  /**
83
91
  * User authentication token. Used to authenticate as a specific user.
84
92
  *
@@ -134,6 +142,8 @@ export interface Base44Client {
134
142
  connectors: UserConnectorsModule;
135
143
  /** {@link EntitiesModule | Entities module} for CRUD operations on your data models. */
136
144
  entities: EntitiesModule;
145
+ /** {@link ExperimentsModule | Experiments module} for local feature flags and exposures. */
146
+ experiments: ExperimentsModule;
137
147
  /** {@link FunctionsModule | Functions module} for invoking custom backend functions. */
138
148
  functions: FunctionsModule;
139
149
  /** {@link IntegrationsModule | Integrations module} for calling pre-built integration endpoints. */
package/dist/index.d.ts CHANGED
@@ -4,6 +4,9 @@ import { getAccessToken, saveAccessToken, removeAccessToken, getLoginUrl } from
4
4
  export { createClient, createClientFromRequest, Base44Error, getAccessToken, saveAccessToken, removeAccessToken, getLoginUrl, };
5
5
  export type { Base44Client, CreateClientAnalyticsConfig, CreateClientConfig, CreateClientOptions, Base44ErrorJSON, };
6
6
  export * from "./types.js";
7
+ export { evaluateExperiments } from "./modules/experiments-evaluator.js";
8
+ export type { ExperimentsConfig, ExperimentsContext, ExperimentsIdentity } from "./modules/experiments-config.types.js";
9
+ export type { ExperimentsModule, ExperimentsSnapshot, } from "./modules/experiments.types.js";
7
10
  export type { DeleteManyResult, DeleteResult, EntitiesModule, EntityFilterOperators, EntityFilterQuery, EntityFilterValue, EntityHandler, EntityRecord, EntityTypeRegistry, ImportResult, RealtimeEventType, RealtimeEvent, RealtimeCallback, SortField, UpdateManyResult, } from "./modules/entities.types.js";
8
11
  export type { AuthModule, LoginResponse, RegisterParams, VerifyOtpParams, ChangePasswordParams, ResetPasswordParams, User, } from "./modules/auth.types.js";
9
12
  export type { IntegrationsModule, IntegrationEndpointFunction, CoreIntegrations, InvokeLLMParams, GenerateImageParams, GenerateImageResult, UploadFileParams, UploadFileResult, SendEmailParams, SendEmailResult, ExtractDataFromUploadedFileParams, ExtractDataFromUploadedFileResult, UploadPrivateFileParams, UploadPrivateFileResult, CreateFileSignedUrlParams, CreateFileSignedUrlResult, } from "./modules/integrations.types.js";
@@ -18,14 +21,3 @@ export { Actor, type Conn } from "./actor.js";
18
21
  export type { ConnectorsModule, UserConnectorsModule, ConnectorApiRequest, ConnectorApiResponse, ConnectorApiResponsePhase, } from "./modules/connectors.types.js";
19
22
  export type { CustomIntegrationsModule, CustomIntegrationCallParams, CustomIntegrationCallResponse, } from "./modules/custom-integrations.types.js";
20
23
  export type { GetAccessTokenOptions, SaveAccessTokenOptions, RemoveAccessTokenOptions, GetLoginUrlOptions, } from "./utils/auth-utils.types.js";
21
- export type { CreateIapClientOptions, IapInternalOptions, } from "./iap/index.js";
22
- export type { IapConfig, IapConfiguredProductType, IapModule, IapProductConfig, IapSetupReport, } from "./iap/iap.types.js";
23
- export type { IapEvent, IapEventHandler, IapEventType, IapExpiryReason, IapRenewReason, IapStartReason, } from "./iap/events/events.types.js";
24
- export type { EntitlementQuery, Entitlements, IapExpirationReason, IapRevocation, IapSubscriptionOffer, IapSubscriptionStatus, OwnedNonConsumable, OwnedNonRenewingSubscription, SubscriptionQuery, SubscriptionState, TransactionQuery, } from "./iap/read/read.types.js";
25
- export type { HandleNotificationResult } from "./iap/ingest/notifications.js";
26
- export type { RecordTransactionOptions, RecordTransactionResult, SyncPayload, SyncResult, } from "./iap/ingest/device.types.js";
27
- export type { IapConsumptionOutcome, IapConsumptionRequestRecord, IapNotificationOutcome, IapNotificationRecord, IapRecordSource, IapSubscriptionRecord, IapTransactionRecord, } from "./iap/store/rows.types.js";
28
- export type { IapEntityName, IapEntitySchema, IapSchemaField, } from "./iap/store/schemas.js";
29
- export type { ConsumptionRequestBody, IapDeliveryStatus, IapRefundPreference, IapServerApiConfig, IapServerApiModule, SendAttempt, SendAttemptResult, TestNotificationResult, TestNotificationStatus, } from "./iap/server-api/server-api.types.js";
30
- export type { IapApiErrorCode, IapConfigErrorCode, IapSetupErrorCode, IapStoreErrorCode, IapVerificationErrorCode, } from "./iap/errors.types.js";
31
- export type { DecodedNotification, DecodedNotificationData, DecodedNotificationSummary, DecodedRenewalInfo, DecodedTransaction, IapAppleSubscriptionStatus, IapConsumptionRequestReason, IapEnvironment, IapExpirationIntent, IapOfferDiscountType, IapOfferType, IapOwnershipType, IapProductType, IapRevocationType, IapTransactionReason, } from "./iap/verify/verify.types.js";
package/dist/index.js CHANGED
@@ -3,4 +3,5 @@ import { Base44Error } from "./utils/axios-client.js";
3
3
  import { getAccessToken, saveAccessToken, removeAccessToken, getLoginUrl, } from "./utils/auth-utils.js";
4
4
  export { createClient, createClientFromRequest, Base44Error, getAccessToken, saveAccessToken, removeAccessToken, getLoginUrl, };
5
5
  export * from "./types.js";
6
+ export { evaluateExperiments } from "./modules/experiments-evaluator.js";
6
7
  export { Actor } from "./actor.js";
@@ -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__";
@@ -12,8 +13,22 @@ export interface AnalyticsModuleArgs {
12
13
  appId: string;
13
14
  userAuthModule: InternalAuthModule;
14
15
  enabled: boolean;
16
+ getVisitorId?: () => string | undefined;
17
+ experimentsContext?: ExperimentsContext;
15
18
  }
16
- export declare const createAnalyticsModule: ({ axiosClient, serverUrl, appId, userAuthModule, enabled, }: AnalyticsModuleArgs) => {
19
+ /** @internal */
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) => {
17
32
  track: (params: TrackEventParams) => void;
18
33
  cleanup: () => void;
19
34
  };
@@ -27,6 +42,16 @@ export declare const createAnalyticsModule: ({ axiosClient, serverUrl, appId, us
27
42
  *
28
43
  * @internal
29
44
  */
30
- export declare function resetAnalyticsSessionContext(): void;
45
+ export declare function resetAnalyticsSessionContext(axiosClient?: AxiosInstance): void;
31
46
  export declare function getAnalyticsConfigFromUrlParams(): AnalyticsModuleOptions | undefined;
32
- 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;
@@ -1,5 +1,6 @@
1
1
  import { getSharedInstance } from "../utils/sharedInstance.js";
2
2
  import { generateUuid, isReactNative } from "../utils/common.js";
3
+ import { getExperimentsRuntime } from "./experiments-runtime.types.js";
3
4
  export const USER_HEARTBEAT_EVENT_NAME = "__user_heartbeat_event__";
4
5
  export const ANALYTICS_INITIALIZATION_EVENT_NAME = "__initialization_event__";
5
6
  export const ANALYTICS_SESSION_DURATION_EVENT_NAME = "__session_duration_event__";
@@ -17,31 +18,41 @@ const defaultConfiguration = {
17
18
  //// shared queue for analytics events ////
18
19
  ///////////////////////////////////////////////
19
20
  const ANALYTICS_SHARED_STATE_NAME = "analytics";
20
- // shared state//
21
- const analyticsSharedState = getSharedInstance(ANALYTICS_SHARED_STATE_NAME, () => ({
22
- requestsQueue: [],
23
- isProcessing: false,
24
- isHeartBeatProcessing: false,
25
- wasInitializationTracked: false,
26
- sessionContext: null,
27
- sessionStartTime: null,
28
- // Memoized session id for when `localStorage` can't persist one — see
29
- // getAnalyticsSessionId.
30
- fallbackSessionId: null,
31
- config: {
32
- ...defaultConfiguration,
33
- ...getAnalyticsConfigFromUrlParams(),
34
- },
35
- }));
36
- export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthModule, enabled, }) => {
37
- var _a;
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();
41
+ /** @internal */
42
+ export function isAnalyticsEnabled(enabled, state = analyticsSharedState) {
43
+ return enabled && state.config.enabled && !isReactNative;
44
+ }
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);
38
49
  // prevent overflow of events //
39
- const { maxQueueSize, throttleTime, batchSize } = analyticsSharedState.config;
50
+ const { maxQueueSize, throttleTime, batchSize } = state.config;
40
51
  // Disable analytics on React Native. It defines `window` but not `document`,
41
52
  // so the per-callsite `typeof window` guards below aren't enough to keep it
42
53
  // from touching `document` (e.g. `document.referrer` on init). Node/SSR is
43
54
  // 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) {
55
+ if (!isAnalyticsEnabled(enabled, state)) {
45
56
  return {
46
57
  track: () => { },
47
58
  cleanup: () => { },
@@ -70,16 +81,17 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
70
81
  }
71
82
  };
72
83
  const flush = async (eventsData, options = {}) => {
84
+ var _a;
73
85
  if (eventsData.length === 0)
74
86
  return;
75
- const sessionContext_ = await getSessionContext(userAuthModule);
76
- const events = eventsData.map(transformEventDataToApiRequestData(sessionContext_));
87
+ const sessionContext_ = await getSessionContext(userAuthModule, state);
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 }));
77
89
  try {
78
90
  if (!options.isBeacon || !beaconRequest(events)) {
79
91
  await batchRequestFallback(events);
80
92
  }
81
93
  }
82
- catch (_a) {
94
+ catch (_b) {
83
95
  // do nothing
84
96
  }
85
97
  };
@@ -87,16 +99,25 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
87
99
  startAnalyticsProcessor(flush, {
88
100
  throttleTime,
89
101
  batchSize,
90
- });
102
+ }, state);
91
103
  };
92
104
  const track = (params) => {
93
- if (analyticsSharedState.requestsQueue.length >= maxQueueSize) {
105
+ var _a;
106
+ if (state.requestsQueue.length >= maxQueueSize) {
94
107
  return;
95
108
  }
96
109
  const intrinsicData = getEventIntrinsicData();
97
- 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({
98
118
  ...params,
99
119
  ...intrinsicData,
120
+ properties: params.properties || Object.keys(properties).length ? properties : undefined,
100
121
  });
101
122
  startProcessing();
102
123
  };
@@ -104,16 +125,16 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
104
125
  startAnalyticsProcessor(flush, {
105
126
  throttleTime,
106
127
  batchSize,
107
- });
108
- clearHeartBeatProcessor = startHeartBeatProcessor(track);
109
- setSessionDurationTimerStart();
128
+ }, state);
129
+ clearHeartBeatProcessor = startHeartBeatProcessor(track, state);
130
+ setSessionDurationTimerStart(state);
110
131
  };
111
132
  const onDocHidden = () => {
112
- stopAnalyticsProcessor();
133
+ stopAnalyticsProcessor(state);
113
134
  clearHeartBeatProcessor === null || clearHeartBeatProcessor === void 0 ? void 0 : clearHeartBeatProcessor();
114
- trackSessionDurationEvent(track);
135
+ trackSessionDurationEvent(track, state);
115
136
  // flush entire queue on visibility change and hope for the best //
116
- const eventsData = analyticsSharedState.requestsQueue.splice(0);
137
+ const eventsData = state.requestsQueue.splice(0);
117
138
  flush(eventsData, { isBeacon: true });
118
139
  };
119
140
  const onVisibilityChange = () => {
@@ -127,7 +148,7 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
127
148
  }
128
149
  };
129
150
  const cleanup = () => {
130
- stopAnalyticsProcessor();
151
+ stopAnalyticsProcessor(state);
131
152
  clearHeartBeatProcessor === null || clearHeartBeatProcessor === void 0 ? void 0 : clearHeartBeatProcessor();
132
153
  if (typeof window !== "undefined") {
133
154
  window.removeEventListener("visibilitychange", onVisibilityChange);
@@ -136,9 +157,9 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
136
157
  // start the flusing process ///
137
158
  startProcessing();
138
159
  // start the heart beat processor //
139
- clearHeartBeatProcessor = startHeartBeatProcessor(track);
160
+ clearHeartBeatProcessor = startHeartBeatProcessor(track, state);
140
161
  // track the referrer event //
141
- trackInitializationEvent(track);
162
+ trackInitializationEvent(track, state);
142
163
  // start the visibility change listener //
143
164
  if (typeof window !== "undefined") {
144
165
  window.addEventListener("visibilitychange", onVisibilityChange);
@@ -148,50 +169,50 @@ export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthM
148
169
  cleanup,
149
170
  };
150
171
  };
151
- function stopAnalyticsProcessor() {
152
- analyticsSharedState.isProcessing = false;
172
+ function stopAnalyticsProcessor(state) {
173
+ state.isProcessing = false;
153
174
  }
154
- async function startAnalyticsProcessor(handleTrack, options) {
155
- if (analyticsSharedState.isProcessing) {
175
+ async function startAnalyticsProcessor(handleTrack, options, state) {
176
+ if (state.isProcessing) {
156
177
  // only one instance of the analytics processor can be running at a time //
157
178
  return;
158
179
  }
159
- analyticsSharedState.isProcessing = true;
180
+ state.isProcessing = true;
160
181
  const { throttleTime = 1000, batchSize = 30 } = options !== null && options !== void 0 ? options : {};
161
- while (analyticsSharedState.isProcessing &&
162
- analyticsSharedState.requestsQueue.length > 0) {
163
- const requests = analyticsSharedState.requestsQueue.splice(0, batchSize);
182
+ while (state.isProcessing &&
183
+ state.requestsQueue.length > 0) {
184
+ const requests = state.requestsQueue.splice(0, batchSize);
164
185
  requests.length && (await handleTrack(requests));
165
186
  await new Promise((resolve) => setTimeout(resolve, throttleTime));
166
187
  }
167
- analyticsSharedState.isProcessing = false;
188
+ state.isProcessing = false;
168
189
  }
169
- function startHeartBeatProcessor(track) {
190
+ function startHeartBeatProcessor(track, state) {
170
191
  var _a;
171
192
  // Browser-only, like the other automatic events here (initialization, session
172
193
  // duration, visibility). Outside a browser this timer fired a `me()` every
173
194
  // interval for the lifetime of a long-lived server-side client, and kept the
174
195
  // Node event loop alive. Explicit `analytics.track()` calls still work.
175
196
  if (typeof window === "undefined" ||
176
- analyticsSharedState.isHeartBeatProcessing ||
177
- ((_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) {
178
199
  return () => { };
179
200
  }
180
- analyticsSharedState.isHeartBeatProcessing = true;
201
+ state.isHeartBeatProcessing = true;
181
202
  const interval = setInterval(() => {
182
203
  track({ eventName: USER_HEARTBEAT_EVENT_NAME });
183
- }, analyticsSharedState.config.heartBeatInterval);
204
+ }, state.config.heartBeatInterval);
184
205
  return () => {
185
206
  clearInterval(interval);
186
- analyticsSharedState.isHeartBeatProcessing = false;
207
+ state.isHeartBeatProcessing = false;
187
208
  };
188
209
  }
189
- function trackInitializationEvent(track) {
210
+ function trackInitializationEvent(track, state) {
190
211
  if (typeof window === "undefined" ||
191
- analyticsSharedState.wasInitializationTracked) {
212
+ state.wasInitializationTracked) {
192
213
  return;
193
214
  }
194
- analyticsSharedState.wasInitializationTracked = true;
215
+ state.wasInitializationTracked = true;
195
216
  track({
196
217
  eventName: ANALYTICS_INITIALIZATION_EVENT_NAME,
197
218
  properties: {
@@ -199,20 +220,20 @@ function trackInitializationEvent(track) {
199
220
  },
200
221
  });
201
222
  }
202
- function setSessionDurationTimerStart() {
223
+ function setSessionDurationTimerStart(state) {
203
224
  if (typeof window === "undefined" ||
204
- analyticsSharedState.sessionStartTime !== null) {
225
+ state.sessionStartTime !== null) {
205
226
  return;
206
227
  }
207
- analyticsSharedState.sessionStartTime = new Date().toISOString();
228
+ state.sessionStartTime = new Date().toISOString();
208
229
  }
209
- function trackSessionDurationEvent(track) {
230
+ function trackSessionDurationEvent(track, state) {
210
231
  if (typeof window === "undefined" ||
211
- analyticsSharedState.sessionStartTime === null)
232
+ state.sessionStartTime === null)
212
233
  return;
213
234
  const sessionDuration = new Date().getTime() -
214
- new Date(analyticsSharedState.sessionStartTime).getTime();
215
- analyticsSharedState.sessionStartTime = null;
235
+ new Date(state.sessionStartTime).getTime();
236
+ state.sessionStartTime = null;
216
237
  track({
217
238
  eventName: ANALYTICS_SESSION_DURATION_EVENT_NAME,
218
239
  properties: { sessionDuration },
@@ -235,7 +256,6 @@ function transformEventDataToApiRequestData(sessionContext) {
235
256
  ...sessionContext,
236
257
  });
237
258
  }
238
- let sessionContextPromise = null;
239
259
  /**
240
260
  * Clears the memoized analytics session context.
241
261
  *
@@ -246,22 +266,24 @@ let sessionContextPromise = null;
246
266
  *
247
267
  * @internal
248
268
  */
249
- export function resetAnalyticsSessionContext() {
250
- analyticsSharedState.sessionContext = null;
251
- 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;
252
274
  }
253
- async function getSessionContext(userAuthModule) {
254
- if (!analyticsSharedState.sessionContext) {
275
+ async function getSessionContext(userAuthModule, state) {
276
+ if (!state.sessionContext) {
255
277
  // With no token there is no identity to resolve: `me()` can only answer 401,
256
278
  // which the browser logs to the console before any handler here sees it. On
257
279
  // a public page that request is the sole reason an error appears, so skip
258
280
  // it. This is not memoized — a visitor who logs in later must still resolve.
259
281
  if (!userAuthModule.hasToken()) {
260
- return { user_id: null, session_id: getAnalyticsSessionId() };
282
+ return { user_id: null, session_id: getAnalyticsSessionId(state) };
261
283
  }
262
- if (!sessionContextPromise) {
263
- const sessionId = getAnalyticsSessionId();
264
- sessionContextPromise = userAuthModule
284
+ if (!state.sessionContextPromise) {
285
+ const sessionId = getAnalyticsSessionId(state);
286
+ state.sessionContextPromise = userAuthModule
265
287
  .me()
266
288
  .then((user) => ({
267
289
  user_id: user.id,
@@ -272,7 +294,7 @@ async function getSessionContext(userAuthModule) {
272
294
  session_id: sessionId,
273
295
  }));
274
296
  }
275
- const pending = sessionContextPromise;
297
+ const pending = state.sessionContextPromise;
276
298
  const context = await pending;
277
299
  // Publish only if this lookup is still the current one. A reset that lands
278
300
  // while the request is in flight nulls `sessionContextPromise`, and an
@@ -280,12 +302,12 @@ async function getSessionContext(userAuthModule) {
280
302
  // for the rest of the session. The awaited value is still returned: these
281
303
  // events were queued before the identity changed, so that is who they
282
304
  // belong to.
283
- if (sessionContextPromise === pending) {
284
- analyticsSharedState.sessionContext = context;
305
+ if (state.sessionContextPromise === pending) {
306
+ state.sessionContext = context;
285
307
  }
286
308
  return context;
287
309
  }
288
- return analyticsSharedState.sessionContext;
310
+ return state.sessionContext;
289
311
  }
290
312
  export function getAnalyticsConfigFromUrlParams() {
291
313
  // `window.location` is absent on React Native. This runs at module load (via
@@ -307,15 +329,18 @@ export function getAnalyticsConfigFromUrlParams() {
307
329
  // return the config object //
308
330
  return { enabled: analyticsEnable === "true" };
309
331
  }
310
- // When the id can't be persisted (React Native has no `localStorage`), keep
311
- // it stable for the process instead of minting a fresh one per call.
312
- function getFallbackSessionId() {
332
+ // Without persistent storage, keep the id stable within this analytics state.
333
+ function getFallbackSessionId(state) {
313
334
  var _a;
314
- 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()));
315
336
  }
316
- export function getAnalyticsSessionId() {
337
+ export function getAnalyticsSessionId(state = analyticsSharedState) {
338
+ var _a;
339
+ const visitorId = (_a = getExperimentsRuntime()) === null || _a === void 0 ? void 0 : _a.visitorId;
340
+ if (visitorId && visitorId !== "anon")
341
+ return visitorId;
317
342
  if (typeof window === "undefined") {
318
- return getFallbackSessionId();
343
+ return getFallbackSessionId(state);
319
344
  }
320
345
  try {
321
346
  const sessionId = localStorage.getItem(ANALYTICS_SESSION_ID_LOCAL_STORAGE_KEY);
@@ -326,7 +351,7 @@ export function getAnalyticsSessionId() {
326
351
  }
327
352
  return sessionId;
328
353
  }
329
- catch (_a) {
330
- return getFallbackSessionId();
354
+ catch (_b) {
355
+ return getFallbackSessionId(state);
331
356
  }
332
357
  }