@base44-preview/sdk 0.8.48-pr.276.4f760f9 → 0.8.48-pr.280.2c0d8bc
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 +18 -5
- package/dist/client.types.d.ts +6 -3
- package/dist/index.d.ts +1 -11
- package/dist/modules/analytics.d.ts +2 -0
- package/dist/modules/analytics.js +5 -2
- package/dist/modules/auth.js +30 -1
- package/dist/modules/auth.types.d.ts +13 -0
- package/dist/modules/experiment-exposures.d.ts +16 -0
- package/dist/modules/experiment-exposures.js +45 -0
- package/dist/modules/experiments-runtime.types.d.ts +19 -0
- package/dist/modules/experiments-runtime.types.js +7 -0
- package/dist/modules/experiments.d.ts +12 -0
- package/dist/modules/experiments.js +150 -0
- package/dist/modules/experiments.types.d.ts +95 -0
- package/package.json +1 -31
- package/dist/iap/account-token.d.ts +0 -14
- package/dist/iap/account-token.js +0 -43
- package/dist/iap/config.d.ts +0 -18
- package/dist/iap/config.js +0 -99
- package/dist/iap/errors.d.ts +0 -89
- package/dist/iap/errors.js +0 -66
- package/dist/iap/errors.types.d.ts +0 -74
- package/dist/iap/errors.types.js +0 -7
- package/dist/iap/events/emitter.d.ts +0 -28
- package/dist/iap/events/emitter.js +0 -33
- package/dist/iap/events/events.types.d.ts +0 -124
- package/dist/iap/iap.types.d.ts +0 -618
- package/dist/iap/iap.types.js +0 -1
- package/dist/iap/index.d.ts +0 -120
- package/dist/iap/index.js +0 -140
- package/dist/iap/ingest/device.d.ts +0 -7
- package/dist/iap/ingest/device.js +0 -191
- package/dist/iap/ingest/device.types.d.ts +0 -80
- package/dist/iap/ingest/device.types.js +0 -1
- package/dist/iap/ingest/mappers.d.ts +0 -66
- package/dist/iap/ingest/mappers.js +0 -248
- package/dist/iap/ingest/matrix.d.ts +0 -55
- package/dist/iap/ingest/matrix.js +0 -202
- package/dist/iap/ingest/notifications.d.ts +0 -70
- package/dist/iap/ingest/notifications.js +0 -238
- package/dist/iap/read/derive.d.ts +0 -40
- package/dist/iap/read/derive.js +0 -112
- package/dist/iap/read/read.d.ts +0 -47
- package/dist/iap/read/read.js +0 -215
- package/dist/iap/read/read.types.d.ts +0 -174
- package/dist/iap/read/read.types.js +0 -1
- package/dist/iap/runtime/base64.d.ts +0 -34
- package/dist/iap/runtime/base64.js +0 -131
- package/dist/iap/runtime/clock.d.ts +0 -13
- package/dist/iap/runtime/clock.js +0 -11
- package/dist/iap/runtime/webcrypto.d.ts +0 -14
- package/dist/iap/runtime/webcrypto.js +0 -66
- package/dist/iap/server-api/client.d.ts +0 -16
- package/dist/iap/server-api/client.js +0 -140
- package/dist/iap/server-api/jwt.d.ts +0 -3
- package/dist/iap/server-api/jwt.js +0 -70
- package/dist/iap/server-api/server-api.types.d.ts +0 -155
- package/dist/iap/server-api/server-api.types.js +0 -12
- package/dist/iap/store/collapse.d.ts +0 -27
- package/dist/iap/store/collapse.js +0 -85
- package/dist/iap/store/descriptors.d.ts +0 -46
- package/dist/iap/store/descriptors.js +0 -90
- package/dist/iap/store/entities-store.d.ts +0 -43
- package/dist/iap/store/entities-store.js +0 -348
- package/dist/iap/store/rows.types.d.ts +0 -233
- package/dist/iap/store/rows.types.js +0 -1
- package/dist/iap/store/schemas.d.ts +0 -68
- package/dist/iap/store/schemas.js +0 -214
- package/dist/iap/store/store-errors.d.ts +0 -52
- package/dist/iap/store/store-errors.js +0 -103
- package/dist/iap/store/store.types.d.ts +0 -193
- package/dist/iap/store/store.types.js +0 -1
- package/dist/iap/verify/apple-roots.d.ts +0 -18
- package/dist/iap/verify/apple-roots.js +0 -121
- package/dist/iap/verify/payload-checks.d.ts +0 -30
- package/dist/iap/verify/payload-checks.js +0 -20
- package/dist/iap/verify/verifier.d.ts +0 -36
- package/dist/iap/verify/verifier.js +0 -161
- package/dist/iap/verify/verify.types.d.ts +0 -292
- package/dist/iap/verify/verify.types.js +0 -15
- package/dist/iap/version.d.ts +0 -13
- package/dist/iap/version.js +0 -13
- /package/dist/{iap/events/events.types.js → modules/experiments.types.js} +0 -0
package/dist/client.js
CHANGED
|
@@ -14,6 +14,8 @@ 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";
|
|
17
19
|
import { createActorsModule, resolveActorsHost, } from "./modules/actors.js";
|
|
18
20
|
/**
|
|
19
21
|
* Creates a Base44 client.
|
|
@@ -53,7 +55,7 @@ import { createActorsModule, resolveActorsHost, } from "./modules/actors.js";
|
|
|
53
55
|
* ```
|
|
54
56
|
*/
|
|
55
57
|
export function createClient(config) {
|
|
56
|
-
var _a, _b, _c, _d;
|
|
58
|
+
var _a, _b, _c, _d, _e;
|
|
57
59
|
const { serverUrl = "https://base44.app", appId, analytics, token, serviceToken, requiresAuth = false, appBaseUrl, options, functionsVersion, headers: optionalHeaders, } = config;
|
|
58
60
|
// Normalize appBaseUrl to always be a string (empty if not provided or invalid)
|
|
59
61
|
const normalizedAppBaseUrl = typeof appBaseUrl === "string" ? appBaseUrl : "";
|
|
@@ -121,10 +123,19 @@ export function createClient(config) {
|
|
|
121
123
|
baseURL: `${serverUrl}/api`,
|
|
122
124
|
headers,
|
|
123
125
|
});
|
|
126
|
+
const experiments = createExperimentsModule({
|
|
127
|
+
getAuth: () => userAuthModule,
|
|
128
|
+
trackExposure: createExposureTracker({
|
|
129
|
+
axiosClient,
|
|
130
|
+
appId,
|
|
131
|
+
enabled: (_a = analytics === null || analytics === void 0 ? void 0 : analytics.enabled) !== null && _a !== void 0 ? _a : true,
|
|
132
|
+
}).track,
|
|
133
|
+
});
|
|
124
134
|
const userAuthModule = createAuthModule(axiosClient, functionsAxiosClient, appId, {
|
|
125
135
|
appBaseUrl: normalizedAppBaseUrl,
|
|
126
136
|
serverUrl,
|
|
127
137
|
token,
|
|
138
|
+
onAuthStateChange: experiments.onAuthStateChange,
|
|
128
139
|
});
|
|
129
140
|
// Apply the access token before any module that may issue authenticated
|
|
130
141
|
// requests during construction (notably analytics, which fires an init
|
|
@@ -140,7 +151,7 @@ export function createClient(config) {
|
|
|
140
151
|
appId,
|
|
141
152
|
// serverUrl is often relative/empty (same-origin app); the proxy-fallback
|
|
142
153
|
// URL needs an absolute host, so fall back to the page origin.
|
|
143
|
-
host: resolveActorsHost(serverUrl, typeof window !== "undefined" ? (
|
|
154
|
+
host: resolveActorsHost(serverUrl, typeof window !== "undefined" ? (_b = window.location) === null || _b === void 0 ? void 0 : _b.origin : undefined),
|
|
144
155
|
functionsVersion,
|
|
145
156
|
getAuthToken: () => token || getAccessToken(),
|
|
146
157
|
mintConnectionToken: async (actorName, room, connectionId) => {
|
|
@@ -168,6 +179,7 @@ export function createClient(config) {
|
|
|
168
179
|
integrations: createIntegrationsModule(axiosClient, appId),
|
|
169
180
|
connectors: createUserConnectorsModule(axiosClient, appId),
|
|
170
181
|
auth: userAuthModule,
|
|
182
|
+
experiments: experiments.module,
|
|
171
183
|
functions: createFunctionsModule(functionsAxiosClient, appId, {
|
|
172
184
|
getAuthHeaders: () => {
|
|
173
185
|
const headers = {};
|
|
@@ -178,7 +190,7 @@ export function createClient(config) {
|
|
|
178
190
|
}
|
|
179
191
|
return headers;
|
|
180
192
|
},
|
|
181
|
-
baseURL: (
|
|
193
|
+
baseURL: (_c = functionsAxiosClient.defaults) === null || _c === void 0 ? void 0 : _c.baseURL,
|
|
182
194
|
}),
|
|
183
195
|
agents: createAgentsModule({
|
|
184
196
|
axios: axiosClient,
|
|
@@ -196,11 +208,12 @@ export function createClient(config) {
|
|
|
196
208
|
serverUrl,
|
|
197
209
|
appId,
|
|
198
210
|
userAuthModule,
|
|
199
|
-
enabled: (
|
|
211
|
+
enabled: (_d = analytics === null || analytics === void 0 ? void 0 : analytics.enabled) !== null && _d !== void 0 ? _d : true,
|
|
200
212
|
}),
|
|
201
213
|
actors: actorsModule.module,
|
|
202
214
|
cleanup: () => {
|
|
203
215
|
userModules.analytics.cleanup();
|
|
216
|
+
experiments.cleanup();
|
|
204
217
|
actorsModule.closeAll();
|
|
205
218
|
if (socket) {
|
|
206
219
|
socket.disconnect();
|
|
@@ -225,7 +238,7 @@ export function createClient(config) {
|
|
|
225
238
|
}
|
|
226
239
|
return headers;
|
|
227
240
|
},
|
|
228
|
-
baseURL: (
|
|
241
|
+
baseURL: (_e = serviceRoleFunctionsAxiosClient.defaults) === null || _e === void 0 ? void 0 : _e.baseURL,
|
|
229
242
|
}),
|
|
230
243
|
agents: createAgentsModule({
|
|
231
244
|
axios: serviceRoleAxiosClient,
|
package/dist/client.types.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ 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";
|
|
12
13
|
import type { ActorsModule } from "./modules/actors.types.js";
|
|
13
14
|
import type { FetchWithAuthInit } from "./utils/fetch-with-auth.js";
|
|
14
15
|
/**
|
|
@@ -39,9 +40,9 @@ export interface CreateClientAnalyticsConfig {
|
|
|
39
40
|
/**
|
|
40
41
|
* Whether app analytics is enabled for this client.
|
|
41
42
|
*
|
|
42
|
-
* When disabled, automatic analytics and calls to
|
|
43
|
-
* no-ops. The SDK does not create an analytics session
|
|
44
|
-
* heartbeat timers, or send analytics requests.
|
|
43
|
+
* When disabled, automatic analytics, experiment exposures and calls to
|
|
44
|
+
* `analytics.track()` are no-ops. The SDK does not create an analytics session
|
|
45
|
+
* identifier, start heartbeat timers, or send analytics requests.
|
|
45
46
|
*
|
|
46
47
|
* @defaultValue `true`
|
|
47
48
|
*/
|
|
@@ -134,6 +135,8 @@ export interface Base44Client {
|
|
|
134
135
|
connectors: UserConnectorsModule;
|
|
135
136
|
/** {@link EntitiesModule | Entities module} for CRUD operations on your data models. */
|
|
136
137
|
entities: EntitiesModule;
|
|
138
|
+
/** {@link ExperimentsModule | Experiments module} for browser feature flags and exposures. */
|
|
139
|
+
experiments: ExperimentsModule;
|
|
137
140
|
/** {@link FunctionsModule | Functions module} for invoking custom backend functions. */
|
|
138
141
|
functions: FunctionsModule;
|
|
139
142
|
/** {@link IntegrationsModule | Integrations module} for calling pre-built integration endpoints. */
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ 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 type { ExperimentsModule, ExperimentsSnapshot, } from "./modules/experiments.types.js";
|
|
7
8
|
export type { DeleteManyResult, DeleteResult, EntitiesModule, EntityFilterOperators, EntityFilterQuery, EntityFilterValue, EntityHandler, EntityRecord, EntityTypeRegistry, ImportResult, RealtimeEventType, RealtimeEvent, RealtimeCallback, SortField, UpdateManyResult, } from "./modules/entities.types.js";
|
|
8
9
|
export type { AuthModule, LoginResponse, RegisterParams, VerifyOtpParams, ChangePasswordParams, ResetPasswordParams, User, } from "./modules/auth.types.js";
|
|
9
10
|
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 +19,3 @@ export { Actor, type Conn } from "./actor.js";
|
|
|
18
19
|
export type { ConnectorsModule, UserConnectorsModule, ConnectorApiRequest, ConnectorApiResponse, ConnectorApiResponsePhase, } from "./modules/connectors.types.js";
|
|
19
20
|
export type { CustomIntegrationsModule, CustomIntegrationCallParams, CustomIntegrationCallResponse, } from "./modules/custom-integrations.types.js";
|
|
20
21
|
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";
|
|
@@ -13,6 +13,8 @@ export interface AnalyticsModuleArgs {
|
|
|
13
13
|
userAuthModule: InternalAuthModule;
|
|
14
14
|
enabled: boolean;
|
|
15
15
|
}
|
|
16
|
+
/** @internal */
|
|
17
|
+
export declare function isAnalyticsEnabled(enabled: boolean): boolean;
|
|
16
18
|
export declare const createAnalyticsModule: ({ axiosClient, serverUrl, appId, userAuthModule, enabled, }: AnalyticsModuleArgs) => {
|
|
17
19
|
track: (params: TrackEventParams) => void;
|
|
18
20
|
cleanup: () => void;
|
|
@@ -33,15 +33,18 @@ const analyticsSharedState = getSharedInstance(ANALYTICS_SHARED_STATE_NAME, () =
|
|
|
33
33
|
...getAnalyticsConfigFromUrlParams(),
|
|
34
34
|
},
|
|
35
35
|
}));
|
|
36
|
+
/** @internal */
|
|
37
|
+
export function isAnalyticsEnabled(enabled) {
|
|
38
|
+
return enabled && analyticsSharedState.config.enabled && !isReactNative;
|
|
39
|
+
}
|
|
36
40
|
export const createAnalyticsModule = ({ axiosClient, serverUrl, appId, userAuthModule, enabled, }) => {
|
|
37
|
-
var _a;
|
|
38
41
|
// prevent overflow of events //
|
|
39
42
|
const { maxQueueSize, throttleTime, batchSize } = analyticsSharedState.config;
|
|
40
43
|
// Disable analytics on React Native. It defines `window` but not `document`,
|
|
41
44
|
// so the per-callsite `typeof window` guards below aren't enough to keep it
|
|
42
45
|
// from touching `document` (e.g. `document.referrer` on init). Node/SSR is
|
|
43
46
|
// still handled by those `window` guards, so this doesn't affect it.
|
|
44
|
-
if (!
|
|
47
|
+
if (!isAnalyticsEnabled(enabled)) {
|
|
45
48
|
return {
|
|
46
49
|
track: () => { },
|
|
47
50
|
cleanup: () => { },
|
package/dist/modules/auth.js
CHANGED
|
@@ -75,20 +75,47 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
75
75
|
// requests would leave the app rendering a stale identity after logout or a
|
|
76
76
|
// session swap.
|
|
77
77
|
let pendingMe = null;
|
|
78
|
+
let identityGeneration = 0;
|
|
78
79
|
const clearPendingMe = () => {
|
|
80
|
+
identityGeneration += 1;
|
|
79
81
|
pendingMe = null;
|
|
80
82
|
};
|
|
81
83
|
// Tracked here rather than read off `axios.defaults` so the answer stays tied
|
|
82
84
|
// to the identity transitions below (`setToken`, `logout`) instead of to the
|
|
83
85
|
// header a caller may have set on the instance directly.
|
|
84
86
|
let hasAccessToken = Boolean(options.token);
|
|
87
|
+
const notifyAuthState = (state) => {
|
|
88
|
+
var _a;
|
|
89
|
+
try {
|
|
90
|
+
(_a = options.onAuthStateChange) === null || _a === void 0 ? void 0 : _a.call(options, state);
|
|
91
|
+
}
|
|
92
|
+
catch (_b) {
|
|
93
|
+
// Optional observers must not interrupt authentication or logout redirects.
|
|
94
|
+
}
|
|
95
|
+
};
|
|
85
96
|
return {
|
|
86
97
|
hasToken() {
|
|
87
98
|
return hasAccessToken;
|
|
88
99
|
},
|
|
89
100
|
// Get current user information
|
|
90
101
|
async me() {
|
|
91
|
-
const
|
|
102
|
+
const generation = identityGeneration;
|
|
103
|
+
const request = pendingMe !== null && pendingMe !== void 0 ? pendingMe : axios.get(`/apps/${appId}/entities/User/me`).then((user) => {
|
|
104
|
+
if (generation === identityGeneration) {
|
|
105
|
+
notifyAuthState({ status: "authenticated", userId: user.id });
|
|
106
|
+
}
|
|
107
|
+
return user;
|
|
108
|
+
}, (error) => {
|
|
109
|
+
var _a, _b;
|
|
110
|
+
if (generation === identityGeneration) {
|
|
111
|
+
const authError = error;
|
|
112
|
+
const status = (_a = authError === null || authError === void 0 ? void 0 : authError.status) !== null && _a !== void 0 ? _a : (_b = authError === null || authError === void 0 ? void 0 : authError.response) === null || _b === void 0 ? void 0 : _b.status;
|
|
113
|
+
notifyAuthState({
|
|
114
|
+
status: status === 401 || status === 403 ? "anonymous" : "error",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}).finally(() => {
|
|
92
119
|
// Only retire this request if it is still the shared one. An identity
|
|
93
120
|
// change mid-flight clears `pendingMe` and the next caller starts a
|
|
94
121
|
// fresh request; an unconditional clear here would retire that newer
|
|
@@ -152,6 +179,7 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
152
179
|
clearPendingMe();
|
|
153
180
|
resetAnalyticsSessionContext();
|
|
154
181
|
hasAccessToken = false;
|
|
182
|
+
notifyAuthState({ status: "anonymous" });
|
|
155
183
|
// Only do the rest if in a browser environment
|
|
156
184
|
if (typeof window !== "undefined") {
|
|
157
185
|
// Remove token from localStorage
|
|
@@ -184,6 +212,7 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
184
212
|
// handle token change for axios clients
|
|
185
213
|
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
|
|
186
214
|
functionsAxiosClient.defaults.headers.common["Authorization"] = `Bearer ${token}`;
|
|
215
|
+
notifyAuthState({ status: "pending" });
|
|
187
216
|
// Save token to localStorage if requested
|
|
188
217
|
if (saveToStorage &&
|
|
189
218
|
typeof window !== "undefined" &&
|
|
@@ -84,6 +84,17 @@ export interface ResetPasswordParams {
|
|
|
84
84
|
/** New password to set. */
|
|
85
85
|
newPassword: string;
|
|
86
86
|
}
|
|
87
|
+
/** @internal */
|
|
88
|
+
export type AuthState = {
|
|
89
|
+
status: "pending";
|
|
90
|
+
} | {
|
|
91
|
+
status: "anonymous";
|
|
92
|
+
} | {
|
|
93
|
+
status: "authenticated";
|
|
94
|
+
userId: string;
|
|
95
|
+
} | {
|
|
96
|
+
status: "error";
|
|
97
|
+
};
|
|
87
98
|
/**
|
|
88
99
|
* Configuration options for the auth module.
|
|
89
100
|
*/
|
|
@@ -98,6 +109,8 @@ export interface AuthModuleOptions {
|
|
|
98
109
|
* which is how the server-side SDK reports a token it never sets explicitly.
|
|
99
110
|
*/
|
|
100
111
|
token?: string;
|
|
112
|
+
/** @internal */
|
|
113
|
+
onAuthStateChange?: (state: AuthState) => void;
|
|
101
114
|
}
|
|
102
115
|
/**
|
|
103
116
|
* Authentication module for managing user authentication and authorization. The module automatically stores tokens in local storage when available and manages authorization headers for API requests.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AxiosInstance } from "axios";
|
|
2
|
+
/** @internal */
|
|
3
|
+
export declare function createExposureTracker({ axiosClient, appId, enabled, }: {
|
|
4
|
+
axiosClient: AxiosInstance;
|
|
5
|
+
appId: string;
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
}): {
|
|
8
|
+
track(assignment: {
|
|
9
|
+
experiment_id: string;
|
|
10
|
+
run_version: number;
|
|
11
|
+
variant_key: string;
|
|
12
|
+
}, identity: {
|
|
13
|
+
visitorId: string;
|
|
14
|
+
userId: string | null;
|
|
15
|
+
}): void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { isAnalyticsEnabled } from "./analytics.js";
|
|
2
|
+
/** @internal */
|
|
3
|
+
export function createExposureTracker({ axiosClient, appId, enabled, }) {
|
|
4
|
+
const exposed = new Set();
|
|
5
|
+
return {
|
|
6
|
+
track(assignment, identity) {
|
|
7
|
+
var _a;
|
|
8
|
+
if (typeof window === "undefined" || !isAnalyticsEnabled(enabled))
|
|
9
|
+
return;
|
|
10
|
+
const { experiment_id, run_version, variant_key } = assignment;
|
|
11
|
+
const key = JSON.stringify([
|
|
12
|
+
experiment_id,
|
|
13
|
+
run_version,
|
|
14
|
+
variant_key,
|
|
15
|
+
identity.userId,
|
|
16
|
+
identity.visitorId,
|
|
17
|
+
]);
|
|
18
|
+
if (exposed.has(key))
|
|
19
|
+
return;
|
|
20
|
+
exposed.add(key);
|
|
21
|
+
// Pin the event's auth before an account change can alter Axios defaults.
|
|
22
|
+
const authorization = identity.userId
|
|
23
|
+
? ((_a = axiosClient.defaults.headers.common.Authorization) !== null && _a !== void 0 ? _a : null)
|
|
24
|
+
: null;
|
|
25
|
+
void axiosClient
|
|
26
|
+
.request({
|
|
27
|
+
method: "POST",
|
|
28
|
+
url: `/apps/${appId}/analytics/track/batch`,
|
|
29
|
+
headers: { Authorization: authorization },
|
|
30
|
+
data: {
|
|
31
|
+
events: [
|
|
32
|
+
{
|
|
33
|
+
event_name: "__experiment_exposure__",
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
session_id: identity.visitorId,
|
|
36
|
+
page_url: window.location.pathname,
|
|
37
|
+
properties: { experiment_id, run_version, variant_key },
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
.catch(() => exposed.delete(key));
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** @internal */
|
|
2
|
+
export interface ExperimentAssignment {
|
|
3
|
+
experiment_id: string;
|
|
4
|
+
flag_key: string;
|
|
5
|
+
run_version: number;
|
|
6
|
+
variant_key: string;
|
|
7
|
+
preview: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** @internal */
|
|
10
|
+
export interface ExperimentsRuntime {
|
|
11
|
+
flags: Record<string, boolean>;
|
|
12
|
+
assignments: ExperimentAssignment[];
|
|
13
|
+
visitorId: string;
|
|
14
|
+
userId: string | null;
|
|
15
|
+
pendingUser: boolean;
|
|
16
|
+
setUser(id: string | null): void;
|
|
17
|
+
}
|
|
18
|
+
/** @internal */
|
|
19
|
+
export declare function getExperimentsRuntime(): ExperimentsRuntime | undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AuthState, InternalAuthModule } from "./auth.types.js";
|
|
2
|
+
import type { ExperimentsModule } from "./experiments.types.js";
|
|
3
|
+
import type { createExposureTracker } from "./experiment-exposures.js";
|
|
4
|
+
/** @internal */
|
|
5
|
+
export declare function createExperimentsModule({ getAuth, trackExposure, }: {
|
|
6
|
+
getAuth: () => InternalAuthModule;
|
|
7
|
+
trackExposure: ReturnType<typeof createExposureTracker>["track"];
|
|
8
|
+
}): {
|
|
9
|
+
module: ExperimentsModule;
|
|
10
|
+
onAuthStateChange: (next: AuthState) => void;
|
|
11
|
+
cleanup(): void;
|
|
12
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { getExperimentsRuntime, } from "./experiments-runtime.types.js";
|
|
2
|
+
const EMPTY = Object.freeze({
|
|
3
|
+
flags: Object.freeze({}),
|
|
4
|
+
isLoading: false,
|
|
5
|
+
});
|
|
6
|
+
/** @internal */
|
|
7
|
+
export function createExperimentsModule({ getAuth, trackExposure, }) {
|
|
8
|
+
let runtime;
|
|
9
|
+
let state;
|
|
10
|
+
let snapshot = EMPTY;
|
|
11
|
+
let active = false;
|
|
12
|
+
let disposed = false;
|
|
13
|
+
let generation = 0;
|
|
14
|
+
let pending;
|
|
15
|
+
const listeners = new Set();
|
|
16
|
+
const readyWaiters = new Set();
|
|
17
|
+
function settleReady() {
|
|
18
|
+
if (snapshot.isLoading)
|
|
19
|
+
return;
|
|
20
|
+
for (const resolve of readyWaiters)
|
|
21
|
+
resolve(snapshot);
|
|
22
|
+
readyWaiters.clear();
|
|
23
|
+
}
|
|
24
|
+
function publish() {
|
|
25
|
+
const isLoading = !!runtime && (state === null || state === void 0 ? void 0 : state.status) === "pending";
|
|
26
|
+
const flags = runtime &&
|
|
27
|
+
((state === null || state === void 0 ? void 0 : state.status) === "authenticated" || (state === null || state === void 0 ? void 0 : state.status) === "anonymous")
|
|
28
|
+
? runtime.flags
|
|
29
|
+
: EMPTY.flags;
|
|
30
|
+
if (snapshot.isLoading === isLoading &&
|
|
31
|
+
Object.keys(snapshot.flags).length === Object.keys(flags).length &&
|
|
32
|
+
Object.keys(flags).every((key) => Object.prototype.hasOwnProperty.call(snapshot.flags, key) &&
|
|
33
|
+
snapshot.flags[key] === flags[key])) {
|
|
34
|
+
settleReady();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
snapshot = Object.freeze({ flags: Object.freeze({ ...flags }), isLoading });
|
|
38
|
+
settleReady();
|
|
39
|
+
for (const listener of listeners) {
|
|
40
|
+
try {
|
|
41
|
+
listener();
|
|
42
|
+
}
|
|
43
|
+
catch (_a) {
|
|
44
|
+
/* Observers must not interrupt authentication. */
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function applyIdentity() {
|
|
49
|
+
if (runtime) {
|
|
50
|
+
const userId = (state === null || state === void 0 ? void 0 : state.status) === "authenticated" ? state.userId : null;
|
|
51
|
+
if (runtime.userId !== userId || runtime.pendingUser)
|
|
52
|
+
runtime.setUser(userId);
|
|
53
|
+
}
|
|
54
|
+
publish();
|
|
55
|
+
}
|
|
56
|
+
function resolveIdentity() {
|
|
57
|
+
if (!runtime || pending || disposed)
|
|
58
|
+
return;
|
|
59
|
+
state = { status: "pending" };
|
|
60
|
+
applyIdentity();
|
|
61
|
+
const currentGeneration = generation;
|
|
62
|
+
pending = getAuth()
|
|
63
|
+
.me()
|
|
64
|
+
.then(() => { }, () => { })
|
|
65
|
+
.finally(() => {
|
|
66
|
+
if (currentGeneration === generation)
|
|
67
|
+
pending = undefined;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function activate() {
|
|
71
|
+
if (disposed)
|
|
72
|
+
return;
|
|
73
|
+
active = true;
|
|
74
|
+
runtime = getExperimentsRuntime();
|
|
75
|
+
if (!runtime) {
|
|
76
|
+
publish();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!state)
|
|
80
|
+
state = getAuth().hasToken()
|
|
81
|
+
? { status: "pending" }
|
|
82
|
+
: { status: "anonymous" };
|
|
83
|
+
applyIdentity();
|
|
84
|
+
if (state.status === "pending")
|
|
85
|
+
resolveIdentity();
|
|
86
|
+
}
|
|
87
|
+
function onAuthStateChange(next) {
|
|
88
|
+
if (disposed)
|
|
89
|
+
return;
|
|
90
|
+
state = next;
|
|
91
|
+
if (next.status === "pending" || next.status === "anonymous") {
|
|
92
|
+
generation++;
|
|
93
|
+
pending = undefined;
|
|
94
|
+
}
|
|
95
|
+
if (!active)
|
|
96
|
+
return;
|
|
97
|
+
runtime = getExperimentsRuntime();
|
|
98
|
+
applyIdentity();
|
|
99
|
+
if (next.status === "pending")
|
|
100
|
+
resolveIdentity();
|
|
101
|
+
}
|
|
102
|
+
const module = {
|
|
103
|
+
isEnabled(flagKey, fallback = false) {
|
|
104
|
+
activate();
|
|
105
|
+
if (!Object.prototype.hasOwnProperty.call(snapshot.flags, flagKey))
|
|
106
|
+
return fallback;
|
|
107
|
+
const assignment = runtime === null || runtime === void 0 ? void 0 : runtime.assignments.find((item) => item.flag_key === flagKey && !item.preview);
|
|
108
|
+
if (runtime && assignment)
|
|
109
|
+
trackExposure(assignment, runtime);
|
|
110
|
+
return snapshot.flags[flagKey];
|
|
111
|
+
},
|
|
112
|
+
getSnapshot() {
|
|
113
|
+
activate();
|
|
114
|
+
return snapshot;
|
|
115
|
+
},
|
|
116
|
+
subscribe(listener) {
|
|
117
|
+
activate();
|
|
118
|
+
if (!disposed)
|
|
119
|
+
listeners.add(listener);
|
|
120
|
+
return () => {
|
|
121
|
+
listeners.delete(listener);
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
async ready() {
|
|
125
|
+
activate();
|
|
126
|
+
if ((state === null || state === void 0 ? void 0 : state.status) === "error") {
|
|
127
|
+
// Wait for auth.me() to release its shared, failed request before retrying.
|
|
128
|
+
await pending;
|
|
129
|
+
if ((state === null || state === void 0 ? void 0 : state.status) === "error")
|
|
130
|
+
resolveIdentity();
|
|
131
|
+
}
|
|
132
|
+
if (snapshot.isLoading)
|
|
133
|
+
return new Promise((resolve) => readyWaiters.add(resolve));
|
|
134
|
+
return snapshot;
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
module,
|
|
139
|
+
onAuthStateChange,
|
|
140
|
+
cleanup() {
|
|
141
|
+
disposed = true;
|
|
142
|
+
generation++;
|
|
143
|
+
pending = undefined;
|
|
144
|
+
runtime = undefined;
|
|
145
|
+
snapshot = EMPTY;
|
|
146
|
+
settleReady();
|
|
147
|
+
listeners.clear();
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/** A stable, read-only view of the browser's current feature flags. */
|
|
2
|
+
export interface ExperimentsSnapshot {
|
|
3
|
+
/** Resolved flags. Empty when the runtime is absent or identity is unresolved. */
|
|
4
|
+
readonly flags: Readonly<Record<string, boolean>>;
|
|
5
|
+
/** Whether the SDK is resolving the signed-in user's identity. */
|
|
6
|
+
readonly isLoading: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Reads feature flags evaluated by the Base44 browser runtime.
|
|
10
|
+
*
|
|
11
|
+
* - Reads flags and reports experiment exposures when a flag is used.
|
|
12
|
+
* - Synchronizes assignments with this client's SDK login, token changes, and logout.
|
|
13
|
+
* - Provides readiness and subscriptions without requiring React.
|
|
14
|
+
*
|
|
15
|
+
* Available as `base44.experiments` for anonymous and signed-in app visitors,
|
|
16
|
+
* not in service role mode. Use one client for the app whose runtime is on the page.
|
|
17
|
+
* The platform must inject the Experiments runtime before this module can evaluate
|
|
18
|
+
* flags. Without it, including on servers and Workers, reads return their fallback;
|
|
19
|
+
* this module does not provide server-side evaluation or hydration guarantees.
|
|
20
|
+
* Goal conversions use the existing {@link AnalyticsModule | analytics module}.
|
|
21
|
+
* Visitor-keyed conversion attribution requires matching runtime and analytics
|
|
22
|
+
* visitor IDs; blocked browser storage is not currently supported for attribution.
|
|
23
|
+
*/
|
|
24
|
+
export interface ExperimentsModule {
|
|
25
|
+
/**
|
|
26
|
+
* Reads a flag and reports a best-effort exposure for its current assignment.
|
|
27
|
+
*
|
|
28
|
+
* The first use resolves identity through {@link AuthModule.me | auth.me()}
|
|
29
|
+
* when the client has a token. Reads return the fallback while identity is
|
|
30
|
+
* pending or could not be resolved. Await {@link ExperimentsModule.ready | ready()}
|
|
31
|
+
* or subscribe to updates before displaying authenticated variants.
|
|
32
|
+
*
|
|
33
|
+
* Call only where the feature is used: a read counts as exposure, not proof of
|
|
34
|
+
* visibility. Preview overrides and flags without an assignment are not tracked.
|
|
35
|
+
* Exposures respect the client's analytics setting, are deduplicated per client,
|
|
36
|
+
* experiment run, variant and identity, and retry only on a later read after failure.
|
|
37
|
+
*
|
|
38
|
+
* @param flagKey - Feature flag key defined in your app.
|
|
39
|
+
* @param fallback - Value for an unavailable flag or unresolved identity. Defaults to `false`.
|
|
40
|
+
* @returns The evaluated boolean, or the fallback when unavailable.
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* await base44.experiments.ready();
|
|
44
|
+
* const showNewCheckout = base44.experiments.isEnabled('new_checkout');
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
isEnabled(flagKey: string, fallback?: boolean): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Returns the current flags and identity-loading state without tracking exposures.
|
|
50
|
+
*
|
|
51
|
+
* Starts lazy identity resolution if needed. The returned object retains its
|
|
52
|
+
* reference until its values change, for use with external-store subscriptions.
|
|
53
|
+
* Use {@link ExperimentsModule.isEnabled | isEnabled()} at the feature boundary
|
|
54
|
+
* to record exposure rather than displaying a variant directly from this snapshot.
|
|
55
|
+
*
|
|
56
|
+
* @returns A stable, read-only snapshot.
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const { isLoading } = base44.experiments.getSnapshot();
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
getSnapshot(): ExperimentsSnapshot;
|
|
63
|
+
/**
|
|
64
|
+
* Listens for flag or loading-state changes caused by this client's SDK auth flows.
|
|
65
|
+
*
|
|
66
|
+
* Does not poll for platform configuration changes or observe token writes outside
|
|
67
|
+
* the SDK. {@link Base44Client.cleanup | cleanup()} removes all listeners.
|
|
68
|
+
*
|
|
69
|
+
* @param listener - Callback invoked when the snapshot changes.
|
|
70
|
+
* @returns A function that removes the listener.
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const unsubscribe = base44.experiments.subscribe(() => {
|
|
74
|
+
* renderCheckout(base44.experiments.isEnabled('new_checkout'));
|
|
75
|
+
* });
|
|
76
|
+
* unsubscribe();
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
subscribe(listener: () => void): () => void;
|
|
80
|
+
/**
|
|
81
|
+
* Waits for the current identity lookup, including a token change during that lookup.
|
|
82
|
+
*
|
|
83
|
+
* Resolves with empty flags after an identity lookup failure; calling again retries
|
|
84
|
+
* the lookup. Missing runtimes resolve immediately. This does not wait for a future
|
|
85
|
+
* runtime injection or for exposure delivery, and never records an exposure itself.
|
|
86
|
+
*
|
|
87
|
+
* @returns A snapshot after the current identity lookup settles.
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* await base44.experiments.ready();
|
|
91
|
+
* renderCheckout(base44.experiments.isEnabled('new_checkout'));
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
ready(): Promise<ExperimentsSnapshot>;
|
|
95
|
+
}
|