@base44-preview/sdk 0.8.48-pr.289.b9ea964 → 0.8.49-pr.280.04af9f9
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/actor.d.ts +2 -2
- package/dist/client.js +51 -7
- package/dist/client.types.d.ts +13 -3
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/modules/actors.types.d.ts +37 -132
- package/dist/modules/analytics-queue.d.ts +12 -0
- package/dist/modules/analytics-queue.js +128 -0
- package/dist/modules/analytics.d.ts +38 -4
- package/dist/modules/analytics.js +107 -154
- package/dist/modules/auth.js +34 -3
- package/dist/modules/auth.types.d.ts +13 -0
- package/dist/modules/entities.js +64 -27
- package/dist/modules/entities.types.d.ts +291 -8
- package/dist/modules/experiment-exposures.d.ts +19 -0
- package/dist/modules/experiment-exposures.js +30 -0
- package/dist/modules/experiments-config.types.d.ts +39 -0
- package/dist/modules/experiments-config.types.js +1 -0
- package/dist/modules/experiments-context.d.ts +10 -0
- package/dist/modules/experiments-context.js +44 -0
- package/dist/modules/experiments-evaluator.d.ts +11 -0
- package/dist/modules/experiments-evaluator.js +45 -0
- package/dist/modules/experiments-runtime.types.d.ts +19 -0
- package/dist/modules/experiments-runtime.types.js +11 -0
- package/dist/modules/experiments.d.ts +17 -0
- package/dist/modules/experiments.js +134 -0
- package/dist/modules/experiments.types.d.ts +124 -0
- package/dist/modules/experiments.types.js +1 -0
- package/dist/utils/axios-client.js +4 -5
- package/dist/utils/fetch-with-auth.js +6 -0
- package/package.json +1 -1
package/dist/actor.d.ts
CHANGED
|
@@ -25,8 +25,8 @@ export interface Storage {
|
|
|
25
25
|
get<T>(key: string): Promise<T | undefined>;
|
|
26
26
|
put(key: string, value: unknown): Promise<void>;
|
|
27
27
|
delete(key: string): Promise<boolean>;
|
|
28
|
-
/** Wipe the
|
|
29
|
-
*
|
|
28
|
+
/** Wipe the room's entire persisted storage (match-end cleanup). Safe: a
|
|
29
|
+
* later rejoin re-bootstraps exactly like a brand-new room. */
|
|
30
30
|
deleteAll(): Promise<void>;
|
|
31
31
|
}
|
|
32
32
|
/**
|
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
|
-
...
|
|
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,25 @@ 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
|
+
appId,
|
|
142
|
+
getAuth: () => userAuthModule,
|
|
143
|
+
trackExposure: exposureTracker.track,
|
|
144
|
+
flushExposures: exposureTracker.flush,
|
|
145
|
+
context: experimentsContext,
|
|
146
|
+
});
|
|
124
147
|
const userAuthModule = createAuthModule(axiosClient, functionsAxiosClient, appId, {
|
|
125
148
|
appBaseUrl: normalizedAppBaseUrl,
|
|
126
149
|
serverUrl,
|
|
127
150
|
token,
|
|
151
|
+
onAuthStateChange: experiments.onAuthStateChange,
|
|
128
152
|
});
|
|
129
153
|
// Apply the access token before any module that may issue authenticated
|
|
130
154
|
// requests during construction (notably analytics, which fires an init
|
|
@@ -136,11 +160,19 @@ export function createClient(config) {
|
|
|
136
160
|
userAuthModule.setToken(accessToken);
|
|
137
161
|
}
|
|
138
162
|
}
|
|
163
|
+
if (experimentsContext) {
|
|
164
|
+
const { userId, status } = experimentsContext.identity;
|
|
165
|
+
// The document's cookie identity may differ from this client's localStorage token.
|
|
166
|
+
const needsClientIdentity = typeof window !== "undefined" && userAuthModule.hasToken() &&
|
|
167
|
+
experimentsContext.config.experiments.some((experiment) => experiment.assign_by === "user");
|
|
168
|
+
experiments.onAuthStateChange(status === "pending" || needsClientIdentity ? { status: "pending" } :
|
|
169
|
+
userId ? { status: "authenticated", userId } : { status: "anonymous" });
|
|
170
|
+
}
|
|
139
171
|
const actorsModule = createActorsModule({
|
|
140
172
|
appId,
|
|
141
173
|
// serverUrl is often relative/empty (same-origin app); the proxy-fallback
|
|
142
174
|
// URL needs an absolute host, so fall back to the page origin.
|
|
143
|
-
host: resolveActorsHost(serverUrl, typeof window !== "undefined" ? (
|
|
175
|
+
host: resolveActorsHost(serverUrl, typeof window !== "undefined" ? (_d = window.location) === null || _d === void 0 ? void 0 : _d.origin : undefined),
|
|
144
176
|
functionsVersion,
|
|
145
177
|
getAuthToken: () => token || getAccessToken(),
|
|
146
178
|
mintConnectionToken: async (actorName, room, connectionId) => {
|
|
@@ -168,6 +200,7 @@ export function createClient(config) {
|
|
|
168
200
|
integrations: createIntegrationsModule(axiosClient, appId),
|
|
169
201
|
connectors: createUserConnectorsModule(axiosClient, appId),
|
|
170
202
|
auth: userAuthModule,
|
|
203
|
+
experiments: experiments.module,
|
|
171
204
|
functions: createFunctionsModule(functionsAxiosClient, appId, {
|
|
172
205
|
getAuthHeaders: () => {
|
|
173
206
|
const headers = {};
|
|
@@ -178,7 +211,7 @@ export function createClient(config) {
|
|
|
178
211
|
}
|
|
179
212
|
return headers;
|
|
180
213
|
},
|
|
181
|
-
baseURL: (
|
|
214
|
+
baseURL: (_e = functionsAxiosClient.defaults) === null || _e === void 0 ? void 0 : _e.baseURL,
|
|
182
215
|
}),
|
|
183
216
|
agents: createAgentsModule({
|
|
184
217
|
axios: axiosClient,
|
|
@@ -196,11 +229,14 @@ export function createClient(config) {
|
|
|
196
229
|
serverUrl,
|
|
197
230
|
appId,
|
|
198
231
|
userAuthModule,
|
|
199
|
-
enabled: (
|
|
232
|
+
enabled: (_f = analytics === null || analytics === void 0 ? void 0 : analytics.enabled) !== null && _f !== void 0 ? _f : true,
|
|
233
|
+
getVisitorId: experiments.visitorId,
|
|
234
|
+
experimentsContext,
|
|
200
235
|
}),
|
|
201
236
|
actors: actorsModule.module,
|
|
202
237
|
cleanup: () => {
|
|
203
238
|
userModules.analytics.cleanup();
|
|
239
|
+
experiments.cleanup();
|
|
204
240
|
actorsModule.closeAll();
|
|
205
241
|
if (socket) {
|
|
206
242
|
socket.disconnect();
|
|
@@ -225,7 +261,7 @@ export function createClient(config) {
|
|
|
225
261
|
}
|
|
226
262
|
return headers;
|
|
227
263
|
},
|
|
228
|
-
baseURL: (
|
|
264
|
+
baseURL: (_g = serviceRoleFunctionsAxiosClient.defaults) === null || _g === void 0 ? void 0 : _g.baseURL,
|
|
229
265
|
}),
|
|
230
266
|
agents: createAgentsModule({
|
|
231
267
|
axios: serviceRoleAxiosClient,
|
|
@@ -268,7 +304,10 @@ export function createClient(config) {
|
|
|
268
304
|
appId: String(appId),
|
|
269
305
|
serverUrl,
|
|
270
306
|
functionsVersion,
|
|
271
|
-
platformHeaders:
|
|
307
|
+
platformHeaders: {
|
|
308
|
+
...headers,
|
|
309
|
+
...(inheritedExperimentsContext ? { [EXPERIMENTS_CONTEXT_HEADER]: inheritedExperimentsContext } : {}),
|
|
310
|
+
},
|
|
272
311
|
}),
|
|
273
312
|
/**
|
|
274
313
|
* Sets a new authentication token for all subsequent requests.
|
|
@@ -420,6 +459,10 @@ export function createClientFromRequest(request) {
|
|
|
420
459
|
}
|
|
421
460
|
// Prepare additional headers to propagate
|
|
422
461
|
const additionalHeaders = {};
|
|
462
|
+
const encodedExperiments = request.headers.get(EXPERIMENTS_CONTEXT_HEADER);
|
|
463
|
+
const experimentsContext = readExperimentsContext(encodedExperiments, appId);
|
|
464
|
+
if (experimentsContext && encodedExperiments)
|
|
465
|
+
additionalHeaders[EXPERIMENTS_CONTEXT_HEADER] = encodedExperiments;
|
|
423
466
|
if (stateHeader) {
|
|
424
467
|
additionalHeaders["Base44-State"] = stateHeader;
|
|
425
468
|
}
|
|
@@ -440,5 +483,6 @@ export function createClientFromRequest(request) {
|
|
|
440
483
|
serviceToken: serviceRoleToken,
|
|
441
484
|
functionsVersion: functionsVersion !== null && functionsVersion !== void 0 ? functionsVersion : undefined,
|
|
442
485
|
headers: additionalHeaders,
|
|
486
|
+
experiments: experimentsContext ? { ...experimentsContext, pageUrl: request.url ? new URL(request.url).pathname : "/" } : undefined,
|
|
443
487
|
});
|
|
444
488
|
}
|
package/dist/client.types.d.ts
CHANGED
|
@@ -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
|
|
43
|
-
* no-ops. The SDK does not create an analytics session
|
|
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,7 +4,10 @@ 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
|
|
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";
|
|
10
|
+
export type { DeleteManyResult, DeleteResult, EntitiesModule, EntityAggregateResult, EntityAggregateSpec, EntityDateBucketUnit, EntityDistinctOptions, EntityFilterOperators, EntityFilterQuery, EntityFilterValue, EntityHandler, EntityListOptions, EntityPage, EntityRecord, EntityTypeRegistry, EntityUpsertOptions, EntityUpsertResult, 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";
|
|
10
13
|
export type { FunctionsModule, FunctionName, FunctionNameRegistry, } from "./modules/functions.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,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Extend this interface to add typed `subscribe` callbacks and `send` payloads
|
|
3
|
+
* for your deployed Actors.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* defines incoming messages that a client sends to the actor. `toClient` defines
|
|
7
|
-
* outgoing messages that the actor sends to connected clients.
|
|
5
|
+
* This is separate from {@link ActorNameRegistry} (which is auto-generated
|
|
6
|
+
* by `base44 types generate`), so there are no conflicts.
|
|
8
7
|
*
|
|
9
8
|
* @example
|
|
10
9
|
* ```typescript
|
|
@@ -21,10 +20,8 @@
|
|
|
21
20
|
export interface ActorRegistry {
|
|
22
21
|
}
|
|
23
22
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* The generated names provide autocomplete for deployed actors. To define
|
|
27
|
-
* incoming and outgoing message types manually, augment {@link ActorRegistry}.
|
|
23
|
+
* Auto-populated by `base44 types generate` with the names of your deployed actors.
|
|
24
|
+
* Do not edit this interface manually — use {@link ActorRegistry} for message types.
|
|
28
25
|
*/
|
|
29
26
|
export interface ActorNameRegistry {
|
|
30
27
|
}
|
|
@@ -35,163 +32,71 @@ type ToClientFor<N extends string> = N extends keyof ActorRegistry ? ActorRegist
|
|
|
35
32
|
type ToServerFor<N extends string> = N extends keyof ActorRegistry ? ActorRegistry[N] extends {
|
|
36
33
|
toServer: infer O;
|
|
37
34
|
} ? O : unknown : unknown;
|
|
38
|
-
/**
|
|
39
|
-
* Options for {@link ActorRef.connect}.
|
|
40
|
-
*/
|
|
35
|
+
/** Options for {@link ActorRef.connect}. */
|
|
41
36
|
export interface ActorConnectOptions {
|
|
42
37
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* reconnections from that client reuse the same connection ID. Omit this property
|
|
47
|
-
* to generate an ID.
|
|
38
|
+
* The connection id — becomes the actor's `conn.id`. Supply a stable value
|
|
39
|
+
* (e.g. persisted per tab) so a reconnect reuses the same server-side
|
|
40
|
+
* identity; omit for an auto-generated per-connection id.
|
|
48
41
|
*/
|
|
49
42
|
id?: string;
|
|
50
43
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Represents an outgoing-message listener registered with
|
|
53
|
-
* {@link Connection.subscribe}.
|
|
54
|
-
*/
|
|
44
|
+
/** Handle for one listener registered via {@link Connection.subscribe}. */
|
|
55
45
|
export interface ActorSubscription {
|
|
56
|
-
/**
|
|
57
|
-
* Removes this listener. Other listeners and the socket stay open.
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* ```typescript
|
|
61
|
-
* const sub = conn.subscribe((msg) => console.log(msg));
|
|
62
|
-
* sub.unsubscribe();
|
|
63
|
-
* ```
|
|
64
|
-
*/
|
|
46
|
+
/** Remove this listener; other listeners and the socket stay live. */
|
|
65
47
|
unsubscribe(): void;
|
|
66
48
|
}
|
|
67
49
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* The socket buffers messages sent during connection setup until it opens.
|
|
50
|
+
* A live connection to an actor instance, returned by {@link ActorRef.connect}.
|
|
51
|
+
* `subscribe`/`send` are always valid — you only get a `Connection` once the
|
|
52
|
+
* socket has been opened, so there's no pre-connect state to guard against.
|
|
72
53
|
*/
|
|
73
54
|
export interface Connection<N extends string = string> {
|
|
74
|
-
/**
|
|
55
|
+
/** The connection id (the value the actor sees as `conn.id`). */
|
|
75
56
|
readonly id: string;
|
|
76
|
-
/**
|
|
77
|
-
* Registers a listener for outgoing messages from the actor.
|
|
78
|
-
*
|
|
79
|
-
* You can register multiple listeners on the same connection.
|
|
80
|
-
*
|
|
81
|
-
* @param callback - Called with each outgoing message sent to this connection.
|
|
82
|
-
* @returns A handle you can use to remove this listener without closing the socket.
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```typescript
|
|
86
|
-
* const conn = base44.actors.Chat("session-1").connect();
|
|
87
|
-
* const sub = conn.subscribe((msg) => {
|
|
88
|
-
* if (msg.type === "message") console.log(msg.text);
|
|
89
|
-
* });
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
57
|
+
/** Register a message listener. Multiple are allowed; returns a per-listener unsubscribe. */
|
|
92
58
|
subscribe(callback: (data: ToClientFor<N>) => void): ActorSubscription;
|
|
93
|
-
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* The socket buffers messages until it opens. After {@link close}, the socket
|
|
97
|
-
* drops further sends.
|
|
98
|
-
*
|
|
99
|
-
* @param data - Incoming message to send. Typed through {@link ActorRegistry} when configured.
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* ```typescript
|
|
103
|
-
* conn.send({ type: "message", text: "Hello" });
|
|
104
|
-
* ```
|
|
105
|
-
*/
|
|
59
|
+
/** Send a message. Buffered by the socket until it's open; dropped after
|
|
60
|
+
* {@link close}. */
|
|
106
61
|
send(data: ToServerFor<N>): void;
|
|
107
62
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* permanently. See {@link ActorRef.connect} for how to open a fresh connection.
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* ```typescript
|
|
115
|
-
* conn.close();
|
|
116
|
-
* ```
|
|
63
|
+
* Tear down the socket, heartbeat, and all listeners. Safe to call more
|
|
64
|
+
* than once. A connection also closes itself when it fails permanently —
|
|
65
|
+
* see {@link ActorRef.connect}.
|
|
117
66
|
*/
|
|
118
67
|
close(): void;
|
|
119
68
|
}
|
|
120
69
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* Call {@link connect} to open the WebSocket and get a {@link Connection}.
|
|
70
|
+
* A handle to one actor instance — `base44.actors.MyActor(id)`. Call
|
|
71
|
+
* {@link connect} to open the socket and get a {@link Connection}.
|
|
124
72
|
*/
|
|
125
73
|
export interface ActorRef<N extends string = string> {
|
|
126
74
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* Repeated calls return the same connection until it closes. After a permanent
|
|
130
|
-
* failure, such as a missing actor or denied connection, fix the cause and call
|
|
131
|
-
* `connect()` again. Add subscriptions to the new connection.
|
|
75
|
+
* Open the WebSocket and return the {@link Connection}. Idempotent while the
|
|
76
|
+
* connection is open.
|
|
132
77
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* @
|
|
137
|
-
* ```typescript
|
|
138
|
-
* const conn = base44.actors.Chat("session-1").connect({ id: "tab-abc" });
|
|
139
|
-
* conn.subscribe((msg) => console.log(msg));
|
|
140
|
-
* ```
|
|
78
|
+
* A connection that fails permanently (for example, the actor doesn't exist
|
|
79
|
+
* or the caller isn't allowed to connect) closes itself and reports the
|
|
80
|
+
* error to the client's `onError` handler. Call `connect()` again after
|
|
81
|
+
* fixing the cause to get a fresh {@link Connection}, and re-subscribe.
|
|
141
82
|
*/
|
|
142
83
|
connect(options?: ActorConnectOptions): Connection<N>;
|
|
143
84
|
}
|
|
144
85
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
* {@link ActorNameRegistry}.
|
|
86
|
+
* Client for a single named Actor — call it with an instance id to get an
|
|
87
|
+
* {@link ActorRef}. Typed automatically when the actor is registered in
|
|
88
|
+
* {@link ActorRegistry}.
|
|
149
89
|
*/
|
|
150
90
|
export interface ActorClient<N extends string = string> {
|
|
151
|
-
/**
|
|
152
|
-
* Gets a reference to an actor session.
|
|
153
|
-
*
|
|
154
|
-
* Clients that specify the same actor name and session ID join the same session.
|
|
155
|
-
*
|
|
156
|
-
* @param instanceId - Session ID that identifies which session to connect to.
|
|
157
|
-
* @returns A reference to the actor session.
|
|
158
|
-
*
|
|
159
|
-
* @example
|
|
160
|
-
* ```typescript
|
|
161
|
-
* const session = base44.actors.Chat("lobby-1");
|
|
162
|
-
* const conn = session.connect();
|
|
163
|
-
* ```
|
|
164
|
-
*/
|
|
165
91
|
(instanceId: string): ActorRef<N>;
|
|
166
92
|
}
|
|
167
93
|
/**
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* Select an actor by name and session ID, then connect a client to the
|
|
171
|
-
* session. Clients in the same session can exchange realtime messages through the
|
|
172
|
-
* actor. The client works in the browser and in Node.
|
|
173
|
-
*
|
|
174
|
-
* ## Connection flow
|
|
175
|
-
*
|
|
176
|
-
* - Connect to a session with `base44.actors.<ActorName>(sessionId).connect()`.
|
|
177
|
-
* - Subscribe to outgoing messages with {@link Connection.subscribe}.
|
|
178
|
-
* - Send incoming messages with {@link Connection.send}.
|
|
179
|
-
* - Close the connection with {@link Connection.close}.
|
|
94
|
+
* The actors module provides access to Cloudflare Durable Object-backed
|
|
95
|
+
* Actors deployed by the Base44 platform.
|
|
180
96
|
*
|
|
181
|
-
* See [Actors Overview](/developers/backend/resources/actors/overview)
|
|
182
|
-
* for actor concepts and terminology, and the [Actor Class Reference](/developers/backend/resources/actors/reference)
|
|
183
|
-
* for the backend class API.
|
|
184
|
-
*
|
|
185
|
-
* ## Authentication modes
|
|
186
|
-
*
|
|
187
|
-
* This module is available in anonymous or user authentication mode
|
|
188
|
-
* (`base44.actors`). It isn't available with service role authentication. Apps that
|
|
189
|
-
* require login can reject anonymous connections in the actor's `handleConnect()` method.
|
|
190
|
-
*
|
|
191
|
-
* @example
|
|
192
97
|
* ```typescript
|
|
193
|
-
* const conn = base44.actors.
|
|
194
|
-
* const sub = conn.subscribe((msg) => console.log(msg));
|
|
98
|
+
* const conn = base44.actors.MyActor("room-1").connect();
|
|
99
|
+
* const sub = conn.subscribe((msg) => console.log(msg)); // typed via ActorRegistry
|
|
195
100
|
* conn.send({ type: "message", text: "hi" });
|
|
196
101
|
* sub.unsubscribe();
|
|
197
102
|
* conn.close();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AxiosInstance } from "axios";
|
|
2
|
+
import type { AnalyticsApiRequestData, AnalyticsModuleOptions } from "./analytics.types.js";
|
|
3
|
+
type Event = AnalyticsApiRequestData & {
|
|
4
|
+
event_id?: string;
|
|
5
|
+
};
|
|
6
|
+
/** @internal One transport queue per client, shared by goals and exposures. */
|
|
7
|
+
export declare function getAnalyticsQueue(axiosClient: AxiosInstance, appId: string, config: AnalyticsModuleOptions): {
|
|
8
|
+
enqueue(event: Event | Promise<Event>, authorization: string | null, userId: string | null | Promise<string | null>): void;
|
|
9
|
+
flush(): Promise<void>;
|
|
10
|
+
cleanup(): void;
|
|
11
|
+
};
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const DELIVERY_BUDGET_MS = 5000;
|
|
2
|
+
const queues = new WeakMap();
|
|
3
|
+
/** @internal One transport queue per client, shared by goals and exposures. */
|
|
4
|
+
export function getAnalyticsQueue(axiosClient, appId, config) {
|
|
5
|
+
let queue = queues.get(axiosClient);
|
|
6
|
+
if (!queue) {
|
|
7
|
+
queue = createAnalyticsQueue(axiosClient, appId, config);
|
|
8
|
+
queues.set(axiosClient, queue);
|
|
9
|
+
}
|
|
10
|
+
return queue;
|
|
11
|
+
}
|
|
12
|
+
function createAnalyticsQueue(axiosClient, appId, config) {
|
|
13
|
+
const entries = [];
|
|
14
|
+
const pending = new Set();
|
|
15
|
+
let timer;
|
|
16
|
+
function deliver(batch) {
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const deadlineAt = Date.now() + DELIVERY_BUDGET_MS;
|
|
19
|
+
const deadline = new Promise((resolve) => {
|
|
20
|
+
controller.signal.addEventListener("abort", () => resolve(), { once: true });
|
|
21
|
+
});
|
|
22
|
+
const timeout = setTimeout(() => controller.abort(), DELIVERY_BUDGET_MS);
|
|
23
|
+
function send(prepared) {
|
|
24
|
+
var _a;
|
|
25
|
+
const groups = new Map();
|
|
26
|
+
for (const entry of prepared) {
|
|
27
|
+
const key = JSON.stringify([entry.authorization, entry.userId, entry.event.session_id]);
|
|
28
|
+
const group = (_a = groups.get(key)) !== null && _a !== void 0 ? _a : [];
|
|
29
|
+
group.push(entry);
|
|
30
|
+
groups.set(key, group);
|
|
31
|
+
}
|
|
32
|
+
return Promise.all([...groups.values()].map(async (group) => {
|
|
33
|
+
var _a, _b;
|
|
34
|
+
const events = group.map(({ event }) => event);
|
|
35
|
+
const exposures = events.filter((event) => event.event_name === "__experiment_exposure__");
|
|
36
|
+
const attempts = exposures.length ? 3 : 1;
|
|
37
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
38
|
+
try {
|
|
39
|
+
if (controller.signal.aborted)
|
|
40
|
+
return;
|
|
41
|
+
await axiosClient.request({
|
|
42
|
+
method: "POST", url: `/apps/${appId}/analytics/track/batch`,
|
|
43
|
+
headers: { Authorization: group[0].authorization },
|
|
44
|
+
// Ordinary goals have no backend deduplication and remain single-attempt.
|
|
45
|
+
data: { events: attempt === 0 ? events : exposures },
|
|
46
|
+
timeout: Math.max(1, deadlineAt - Date.now()), signal: controller.signal,
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
const status = (_b = (_a = error.response) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : error.status;
|
|
52
|
+
if (controller.signal.aborted || attempt === attempts - 1 ||
|
|
53
|
+
(status !== undefined && (status < 500 || status >= 600)))
|
|
54
|
+
return;
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, attempt === 0 ? 100 : 500));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
const delivery = (async () => {
|
|
61
|
+
const ready = [];
|
|
62
|
+
const requests = [];
|
|
63
|
+
let wake = () => { };
|
|
64
|
+
let preparedAll = false;
|
|
65
|
+
const preparation = Promise.all(batch.map(async (entry) => {
|
|
66
|
+
const [event, userId] = await Promise.all([entry.event, entry.userId]);
|
|
67
|
+
if (event && !controller.signal.aborted)
|
|
68
|
+
ready.push({ event, userId, authorization: entry.authorization });
|
|
69
|
+
wake();
|
|
70
|
+
})).then(() => { preparedAll = true; wake(); });
|
|
71
|
+
while (!preparedAll || ready.length) {
|
|
72
|
+
if (!ready.length && !preparedAll)
|
|
73
|
+
await Promise.race([
|
|
74
|
+
new Promise((resolve) => { wake = resolve; }), deadline,
|
|
75
|
+
]);
|
|
76
|
+
if (controller.signal.aborted)
|
|
77
|
+
return;
|
|
78
|
+
// Coalesce this turn's resolved identities without waiting for unrelated auth I/O.
|
|
79
|
+
let turnTimer;
|
|
80
|
+
await Promise.race([preparation, new Promise((resolve) => { turnTimer = setTimeout(resolve, 0); })]);
|
|
81
|
+
clearTimeout(turnTimer);
|
|
82
|
+
if (ready.length)
|
|
83
|
+
requests.push(send(ready.splice(0)));
|
|
84
|
+
}
|
|
85
|
+
await Promise.all(requests);
|
|
86
|
+
})();
|
|
87
|
+
// Identity lookup and transports that ignore cancellation must also be bounded.
|
|
88
|
+
const settlement = Promise.race([delivery, deadline]).catch(() => { }).finally(() => {
|
|
89
|
+
clearTimeout(timeout);
|
|
90
|
+
controller.abort();
|
|
91
|
+
pending.delete(settlement);
|
|
92
|
+
});
|
|
93
|
+
pending.add(settlement);
|
|
94
|
+
}
|
|
95
|
+
function schedule() {
|
|
96
|
+
var _a;
|
|
97
|
+
if (timer || entries.length === 0)
|
|
98
|
+
return;
|
|
99
|
+
timer = setTimeout(() => {
|
|
100
|
+
var _a;
|
|
101
|
+
timer = undefined;
|
|
102
|
+
deliver(entries.splice(0, (_a = config.batchSize) !== null && _a !== void 0 ? _a : 30));
|
|
103
|
+
schedule();
|
|
104
|
+
}, (_a = config.throttleTime) !== null && _a !== void 0 ? _a : 1000);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
enqueue(event, authorization, userId) {
|
|
108
|
+
var _a;
|
|
109
|
+
if (entries.length >= ((_a = config.maxQueueSize) !== null && _a !== void 0 ? _a : 1000))
|
|
110
|
+
return;
|
|
111
|
+
entries.push({ event: Promise.resolve(event).catch(() => undefined), authorization,
|
|
112
|
+
userId: Promise.resolve(userId).catch(() => null) });
|
|
113
|
+
schedule();
|
|
114
|
+
},
|
|
115
|
+
async flush() {
|
|
116
|
+
var _a;
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
timer = undefined;
|
|
119
|
+
while (entries.length)
|
|
120
|
+
deliver(entries.splice(0, (_a = config.batchSize) !== null && _a !== void 0 ? _a : 30));
|
|
121
|
+
await Promise.all([...pending]);
|
|
122
|
+
},
|
|
123
|
+
cleanup() {
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
timer = undefined;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|