@edge-base/web 0.1.1
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/README.md +352 -0
- package/dist/analytics.d.ts +60 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +146 -0
- package/dist/analytics.js.map +1 -0
- package/dist/auth-refresh.d.ts +5 -0
- package/dist/auth-refresh.d.ts.map +1 -0
- package/dist/auth-refresh.js +26 -0
- package/dist/auth-refresh.js.map +1 -0
- package/dist/auth.d.ts +314 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +518 -0
- package/dist/auth.js.map +1 -0
- package/dist/browser-storage.d.ts +7 -0
- package/dist/browser-storage.d.ts.map +1 -0
- package/dist/browser-storage.js +43 -0
- package/dist/browser-storage.js.map +1 -0
- package/dist/client.d.ts +145 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +310 -0
- package/dist/client.js.map +1 -0
- package/dist/database-live.d.ts +65 -0
- package/dist/database-live.d.ts.map +1 -0
- package/dist/database-live.js +486 -0
- package/dist/database-live.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/match-filter.d.ts +30 -0
- package/dist/match-filter.d.ts.map +1 -0
- package/dist/match-filter.js +86 -0
- package/dist/match-filter.js.map +1 -0
- package/dist/room-realtime-media.d.ts +96 -0
- package/dist/room-realtime-media.d.ts.map +1 -0
- package/dist/room-realtime-media.js +418 -0
- package/dist/room-realtime-media.js.map +1 -0
- package/dist/room.d.ts +450 -0
- package/dist/room.d.ts.map +1 -0
- package/dist/room.js +1506 -0
- package/dist/room.js.map +1 -0
- package/dist/token-manager.d.ts +73 -0
- package/dist/token-manager.d.ts.map +1 -0
- package/dist/token-manager.js +378 -0
- package/dist/token-manager.js.map +1 -0
- package/dist/turnstile.d.ts +56 -0
- package/dist/turnstile.d.ts.map +1 -0
- package/dist/turnstile.js +191 -0
- package/dist/turnstile.js.map +1 -0
- package/llms.txt +549 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
<h1 align="center">@edge-base/web</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<b>Browser SDK for EdgeBase</b><br>
|
|
5
|
+
Auth, database, realtime, storage, functions, analytics, and rooms for modern web apps
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://www.npmjs.com/package/@edge-base/web"><img src="https://img.shields.io/npm/v/%40edge-base%2Fweb?color=brightgreen" alt="npm"></a>
|
|
10
|
+
<a href="https://edgebase.fun/docs/database/client-sdk"><img src="https://img.shields.io/badge/docs-client_sdk-blue" alt="Docs"></a>
|
|
11
|
+
<a href="https://github.com/edge-base/edgebase/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
React · Next.js · Vite · Vanilla TS · PWAs
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="https://edgebase.fun/docs/getting-started/quickstart"><b>Quickstart</b></a> ·
|
|
20
|
+
<a href="https://edgebase.fun/docs/database/client-sdk"><b>Client SDK Docs</b></a> ·
|
|
21
|
+
<a href="https://edgebase.fun/docs/database/subscriptions"><b>Database Live</b></a> ·
|
|
22
|
+
<a href="https://edgebase.fun/docs/room/client-sdk"><b>Room Docs</b></a>
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
`@edge-base/web` is the main client SDK for browser environments.
|
|
28
|
+
|
|
29
|
+
It is designed for apps that need:
|
|
30
|
+
|
|
31
|
+
- user authentication with session persistence
|
|
32
|
+
- direct database access from the client through access rules
|
|
33
|
+
- live updates with `onSnapshot()`
|
|
34
|
+
- multiplayer and presence flows with rooms
|
|
35
|
+
- storage uploads and file URLs
|
|
36
|
+
- client-side function calls, analytics, and web push
|
|
37
|
+
|
|
38
|
+
If you need privileged or server-only access, use [`@edge-base/admin`](https://www.npmjs.com/package/@edge-base/admin) instead.
|
|
39
|
+
|
|
40
|
+
> Beta: the package is already usable, but some APIs may still evolve before general availability.
|
|
41
|
+
|
|
42
|
+
## Documentation Map
|
|
43
|
+
|
|
44
|
+
Use this README for the fast overview, then jump into the docs when you need depth:
|
|
45
|
+
|
|
46
|
+
- [Quickstart](https://edgebase.fun/docs/getting-started/quickstart)
|
|
47
|
+
Create a project and run EdgeBase locally
|
|
48
|
+
- [Client SDK](https://edgebase.fun/docs/database/client-sdk)
|
|
49
|
+
Full browser SDK reference and query patterns
|
|
50
|
+
- [Database Subscriptions](https://edgebase.fun/docs/database/subscriptions)
|
|
51
|
+
Live queries and `onSnapshot()` patterns
|
|
52
|
+
- [Authentication](https://edgebase.fun/docs/authentication)
|
|
53
|
+
Email/password, OAuth, MFA, sessions, passkeys
|
|
54
|
+
- [Room Client SDK](https://edgebase.fun/docs/room/client-sdk)
|
|
55
|
+
Presence, state, members, signals, and media
|
|
56
|
+
- [Functions Client SDK](https://edgebase.fun/docs/functions/client-sdk)
|
|
57
|
+
Calling EdgeBase functions from the browser
|
|
58
|
+
- [Analytics Client SDK](https://edgebase.fun/docs/analytics/client-sdk)
|
|
59
|
+
Event tracking from web clients
|
|
60
|
+
- [Push Client SDK](https://edgebase.fun/docs/push/client-sdk)
|
|
61
|
+
Web push registration and client-side handling
|
|
62
|
+
|
|
63
|
+
## For AI Coding Assistants
|
|
64
|
+
|
|
65
|
+
This package ships with an `llms.txt` file for AI-assisted development.
|
|
66
|
+
|
|
67
|
+
Use it when you want an agent or code assistant to:
|
|
68
|
+
|
|
69
|
+
- avoid common API mistakes
|
|
70
|
+
- use the correct method signatures
|
|
71
|
+
- choose the right database and auth patterns
|
|
72
|
+
- prefer the documented EdgeBase flow instead of guessing
|
|
73
|
+
|
|
74
|
+
You can find it:
|
|
75
|
+
|
|
76
|
+
- in this package after install: `node_modules/@edge-base/web/llms.txt`
|
|
77
|
+
- in the repository: [llms.txt](https://github.com/edge-base/edgebase/blob/main/packages/sdk/js/packages/web/llms.txt)
|
|
78
|
+
|
|
79
|
+
For deeper behavioral details, pair `llms.txt` with the docs linked above.
|
|
80
|
+
|
|
81
|
+
## Why This Package
|
|
82
|
+
|
|
83
|
+
Most browser SDKs stop at auth + CRUD.
|
|
84
|
+
|
|
85
|
+
`@edge-base/web` is meant to be the single browser entry point for the whole EdgeBase app surface:
|
|
86
|
+
|
|
87
|
+
| Capability | Included |
|
|
88
|
+
|---|---|
|
|
89
|
+
| Auth | Email/password, OAuth, sessions, auth state |
|
|
90
|
+
| Database | Query, insert, update, delete |
|
|
91
|
+
| Database Live | `onSnapshot()` subscriptions |
|
|
92
|
+
| Rooms | Presence, room state, signals, media-ready client surface |
|
|
93
|
+
| Storage | Uploads, bucket access, file URLs |
|
|
94
|
+
| Functions | Call EdgeBase functions from the browser |
|
|
95
|
+
| Analytics | Client-side analytics helpers |
|
|
96
|
+
| Push | Web push registration and message handling |
|
|
97
|
+
|
|
98
|
+
## Installation
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm install @edge-base/web
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Starting a brand new project?
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npm create edge-base@latest my-app
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
That scaffold creates a full EdgeBase app and wires in the local CLI for development and deployment.
|
|
111
|
+
|
|
112
|
+
Read more: [Quickstart](https://edgebase.fun/docs/getting-started/quickstart)
|
|
113
|
+
|
|
114
|
+
## Recommended Project Layout
|
|
115
|
+
|
|
116
|
+
If you already have a frontend app, a good default is to create EdgeBase inside that project as a dedicated subdirectory:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
cd your-frontend-project
|
|
120
|
+
npm create edge-base@latest edgebase
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
That gives you a layout like:
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
your-frontend-project/
|
|
127
|
+
src/
|
|
128
|
+
app/
|
|
129
|
+
package.json
|
|
130
|
+
edgebase/
|
|
131
|
+
edgebase.config.ts
|
|
132
|
+
functions/
|
|
133
|
+
package.json
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This is only a recommendation, not a requirement.
|
|
137
|
+
|
|
138
|
+
You can also:
|
|
139
|
+
|
|
140
|
+
- create EdgeBase as a completely separate project
|
|
141
|
+
- keep frontend and backend in different repos
|
|
142
|
+
- use a different subdirectory name if that fits your monorepo better
|
|
143
|
+
|
|
144
|
+
The main thing we recommend is avoiding scaffolding directly into an existing app root unless that is intentionally how you want to organize the repo.
|
|
145
|
+
|
|
146
|
+
## Quick Start
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { createClient } from '@edge-base/web';
|
|
150
|
+
|
|
151
|
+
const client = createClient('https://your-project.edgebase.fun');
|
|
152
|
+
|
|
153
|
+
await client.auth.signIn({
|
|
154
|
+
email: 'june@example.com',
|
|
155
|
+
password: 'pass1234',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const posts = await client
|
|
159
|
+
.db('app')
|
|
160
|
+
.table('posts')
|
|
161
|
+
.where('published', '==', true)
|
|
162
|
+
.orderBy('createdAt', 'desc')
|
|
163
|
+
.limit(10)
|
|
164
|
+
.getList();
|
|
165
|
+
|
|
166
|
+
const health = await client.functions.get('health');
|
|
167
|
+
|
|
168
|
+
console.log(posts.items, health);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`app` in the example above is your database block name from `edgebase.config.ts`.
|
|
172
|
+
|
|
173
|
+
For instance databases, pass both a namespace and an id:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
client.db('workspace', 'ws-123');
|
|
177
|
+
client.db('user', 'user-123');
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Read more: [Client SDK](https://edgebase.fun/docs/database/client-sdk)
|
|
181
|
+
|
|
182
|
+
## Core API
|
|
183
|
+
|
|
184
|
+
Once you create a client, these are the main surfaces you will use:
|
|
185
|
+
|
|
186
|
+
- `client.auth`
|
|
187
|
+
Sign up, sign in, sign out, OAuth, MFA flows, and auth state listeners
|
|
188
|
+
- `client.db(namespace, id?)`
|
|
189
|
+
Query tables, mutate records, and subscribe to live changes
|
|
190
|
+
- `client.storage`
|
|
191
|
+
Upload files and resolve bucket URLs
|
|
192
|
+
- `client.functions`
|
|
193
|
+
Call app functions from the browser
|
|
194
|
+
- `client.room(namespace, roomId)`
|
|
195
|
+
Join realtime rooms for presence, state sync, and signals
|
|
196
|
+
- `client.analytics`
|
|
197
|
+
Send analytics data from the client
|
|
198
|
+
- `client.push`
|
|
199
|
+
Register and manage web push flows
|
|
200
|
+
|
|
201
|
+
## Auth
|
|
202
|
+
|
|
203
|
+
### Email and password
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
await client.auth.signUp({
|
|
207
|
+
email: 'june@example.com',
|
|
208
|
+
password: 'pass1234',
|
|
209
|
+
data: {
|
|
210
|
+
displayName: 'June',
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await client.auth.signIn({
|
|
215
|
+
email: 'june@example.com',
|
|
216
|
+
password: 'pass1234',
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
client.auth.onAuthStateChange((user) => {
|
|
220
|
+
console.log('auth changed:', user);
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### OAuth
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
await client.auth.signInWithOAuth('google');
|
|
228
|
+
|
|
229
|
+
const result = await client.auth.handleOAuthCallback();
|
|
230
|
+
if (result) {
|
|
231
|
+
console.log('signed in with OAuth:', result.user);
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
By default, the browser SDK uses `/auth/callback` on the current origin as the redirect target.
|
|
236
|
+
|
|
237
|
+
Read more: [Authentication Docs](https://edgebase.fun/docs/authentication)
|
|
238
|
+
|
|
239
|
+
## Database Queries
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
type Post = {
|
|
243
|
+
id: string;
|
|
244
|
+
title: string;
|
|
245
|
+
published: boolean;
|
|
246
|
+
createdAt: string;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const posts = client.db('app').table<Post>('posts');
|
|
250
|
+
|
|
251
|
+
const latest = await posts
|
|
252
|
+
.where('published', '==', true)
|
|
253
|
+
.orderBy('createdAt', 'desc')
|
|
254
|
+
.limit(20)
|
|
255
|
+
.getList();
|
|
256
|
+
|
|
257
|
+
await posts.insert({
|
|
258
|
+
title: 'Hello EdgeBase',
|
|
259
|
+
published: true,
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Read more: [Database Client SDK](https://edgebase.fun/docs/database/client-sdk)
|
|
264
|
+
|
|
265
|
+
## Database Live
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
const unsubscribe = client
|
|
269
|
+
.db('app')
|
|
270
|
+
.table('posts')
|
|
271
|
+
.onSnapshot((change) => {
|
|
272
|
+
console.log(change.changeType, change.data);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// later
|
|
276
|
+
unsubscribe();
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Use this for feeds, counters, collaborative UIs, moderation dashboards, or any UI that should react instantly to server-side changes.
|
|
280
|
+
|
|
281
|
+
Read more: [Database Subscriptions](https://edgebase.fun/docs/database/subscriptions)
|
|
282
|
+
|
|
283
|
+
## Storage
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
const bucket = client.storage.bucket('avatars');
|
|
287
|
+
|
|
288
|
+
await bucket.upload('me.jpg', file);
|
|
289
|
+
|
|
290
|
+
const publicUrl = bucket.getUrl('me.jpg');
|
|
291
|
+
console.log(publicUrl);
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Read more: [Storage Docs](https://edgebase.fun/docs/storage)
|
|
295
|
+
|
|
296
|
+
## Functions
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
const result = await client.functions.post('contact/send', {
|
|
300
|
+
email: 'june@example.com',
|
|
301
|
+
message: 'Hello from the web SDK',
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
console.log(result);
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Read more: [Functions Client SDK](https://edgebase.fun/docs/functions/client-sdk)
|
|
308
|
+
|
|
309
|
+
## Rooms
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
const room = client.room('game', 'lobby-1');
|
|
313
|
+
|
|
314
|
+
await room.join();
|
|
315
|
+
|
|
316
|
+
room.leave();
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Use rooms when you need:
|
|
320
|
+
|
|
321
|
+
- presence
|
|
322
|
+
- room state
|
|
323
|
+
- peer signals
|
|
324
|
+
- multiplayer coordination
|
|
325
|
+
- media/session-style realtime flows
|
|
326
|
+
|
|
327
|
+
Read more: [Room Client SDK](https://edgebase.fun/docs/room/client-sdk)
|
|
328
|
+
|
|
329
|
+
## Which EdgeBase Package Should You Use?
|
|
330
|
+
|
|
331
|
+
| Package | Use it when |
|
|
332
|
+
|---|---|
|
|
333
|
+
| `@edge-base/web` | You are in the browser or another untrusted client runtime |
|
|
334
|
+
| `@edge-base/admin` | You need trusted server/admin access |
|
|
335
|
+
| `@edge-base/ssr` | You want cookie-based SSR helpers for frameworks like Next.js |
|
|
336
|
+
| `@edge-base/auth-ui-react` | You want headless React auth UI built on top of the web SDK |
|
|
337
|
+
| `@edge-base/react-native` | You are building a React Native app |
|
|
338
|
+
|
|
339
|
+
## Docs
|
|
340
|
+
|
|
341
|
+
- [Quickstart](https://edgebase.fun/docs/getting-started/quickstart)
|
|
342
|
+
- [Client SDK](https://edgebase.fun/docs/database/client-sdk)
|
|
343
|
+
- [Database Live](https://edgebase.fun/docs/database/subscriptions)
|
|
344
|
+
- [Room Client SDK](https://edgebase.fun/docs/room/client-sdk)
|
|
345
|
+
- [Authentication Docs](https://edgebase.fun/docs/authentication)
|
|
346
|
+
- [Functions Client SDK](https://edgebase.fun/docs/functions/client-sdk)
|
|
347
|
+
- [Analytics Client SDK](https://edgebase.fun/docs/analytics/client-sdk)
|
|
348
|
+
- [Push Client SDK](https://edgebase.fun/docs/push/client-sdk)
|
|
349
|
+
|
|
350
|
+
## License
|
|
351
|
+
|
|
352
|
+
MIT
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClientAnalytics — Browser-side custom event tracking for Client SDK
|
|
3
|
+
*
|
|
4
|
+
* Optimized for browser:
|
|
5
|
+
* - Batches events in memory (max 20 or 5s timer, whichever comes first)
|
|
6
|
+
* - Uses sendBeacon on page unload to avoid losing events
|
|
7
|
+
* - Auto-retries failed flushes once by re-queuing
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const client = createClient('https://my-app.edgebase.fun');
|
|
11
|
+
* client.analytics.track('page_view', { path: '/pricing' });
|
|
12
|
+
* client.analytics.track('button_click', { id: 'signup-cta', variant: 'A' });
|
|
13
|
+
*
|
|
14
|
+
* // Manual flush if needed
|
|
15
|
+
* await client.analytics.flush();
|
|
16
|
+
*/
|
|
17
|
+
import { type HttpClient, type GeneratedDbApi } from '@edge-base/core';
|
|
18
|
+
export declare class ClientAnalytics {
|
|
19
|
+
private httpClient;
|
|
20
|
+
private baseUrl;
|
|
21
|
+
private core?;
|
|
22
|
+
private queue;
|
|
23
|
+
private timer;
|
|
24
|
+
private readonly FLUSH_INTERVAL;
|
|
25
|
+
private readonly MAX_BATCH;
|
|
26
|
+
private boundVisibilityHandler;
|
|
27
|
+
private boundPageHideHandler;
|
|
28
|
+
constructor(httpClient: HttpClient, baseUrl: string, core?: GeneratedDbApi | undefined);
|
|
29
|
+
/**
|
|
30
|
+
* Track a custom event. Events are batched and sent automatically.
|
|
31
|
+
*
|
|
32
|
+
* @param name Event name (e.g. 'page_view', 'button_click', 'purchase')
|
|
33
|
+
* @param properties Optional key-value data (max 50 keys, max 4KB total)
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* client.analytics.track('page_view', { path: '/pricing' });
|
|
37
|
+
* client.analytics.track('purchase', { plan: 'pro', amount: 29.99 });
|
|
38
|
+
*/
|
|
39
|
+
track(name: string, properties?: Record<string, string | number | boolean>): void;
|
|
40
|
+
/**
|
|
41
|
+
* Manually flush all queued events to the server.
|
|
42
|
+
* Automatically called on timer expiry, batch size reached, or page unload.
|
|
43
|
+
*/
|
|
44
|
+
flush(): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Send remaining events via navigator.sendBeacon (used on page unload).
|
|
47
|
+
* sendBeacon cannot send Authorization headers, so these events arrive
|
|
48
|
+
* without JWT auth — protected by rate limiting only.
|
|
49
|
+
*
|
|
50
|
+
* NOTE: Direct HTTP — sendBeacon requires a raw URL + Blob. Cannot use
|
|
51
|
+
* generated core because sendBeacon bypasses normal fetch/XHR entirely.
|
|
52
|
+
*/
|
|
53
|
+
private sendBeacon;
|
|
54
|
+
/**
|
|
55
|
+
* Destroy the analytics client. Flushes remaining events and removes
|
|
56
|
+
* event listeners. Call this when unmounting the SDK.
|
|
57
|
+
*/
|
|
58
|
+
destroy(): void;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=analytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAY,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAQjF,qBAAa,eAAe;IAUxB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,IAAI,CAAC;IAXf,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAM;IAEhC,OAAO,CAAC,sBAAsB,CAA6B;IAC3D,OAAO,CAAC,oBAAoB,CAA6B;gBAG/C,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,cAAc,YAAA;IAc/B;;;;;;;;;OASG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI;IAUjF;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC5B;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;IAoBlB;;;OAGG;IACH,OAAO,IAAI,IAAI;CAiBhB"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClientAnalytics — Browser-side custom event tracking for Client SDK
|
|
3
|
+
*
|
|
4
|
+
* Optimized for browser:
|
|
5
|
+
* - Batches events in memory (max 20 or 5s timer, whichever comes first)
|
|
6
|
+
* - Uses sendBeacon on page unload to avoid losing events
|
|
7
|
+
* - Auto-retries failed flushes once by re-queuing
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const client = createClient('https://my-app.edgebase.fun');
|
|
11
|
+
* client.analytics.track('page_view', { path: '/pricing' });
|
|
12
|
+
* client.analytics.track('button_click', { id: 'signup-cta', variant: 'A' });
|
|
13
|
+
*
|
|
14
|
+
* // Manual flush if needed
|
|
15
|
+
* await client.analytics.flush();
|
|
16
|
+
*/
|
|
17
|
+
import { ApiPaths } from '@edge-base/core';
|
|
18
|
+
export class ClientAnalytics {
|
|
19
|
+
httpClient;
|
|
20
|
+
baseUrl;
|
|
21
|
+
core;
|
|
22
|
+
queue = [];
|
|
23
|
+
timer = null;
|
|
24
|
+
FLUSH_INTERVAL = 5_000; // 5 seconds
|
|
25
|
+
MAX_BATCH = 20; // 20 events per batch
|
|
26
|
+
boundVisibilityHandler = null;
|
|
27
|
+
boundPageHideHandler = null;
|
|
28
|
+
constructor(httpClient, baseUrl, core) {
|
|
29
|
+
this.httpClient = httpClient;
|
|
30
|
+
this.baseUrl = baseUrl;
|
|
31
|
+
this.core = core;
|
|
32
|
+
// Send remaining events on page unload
|
|
33
|
+
if (typeof window !== 'undefined') {
|
|
34
|
+
this.boundVisibilityHandler = () => {
|
|
35
|
+
if (document.visibilityState === 'hidden')
|
|
36
|
+
this.sendBeacon();
|
|
37
|
+
};
|
|
38
|
+
this.boundPageHideHandler = () => this.sendBeacon();
|
|
39
|
+
window.addEventListener('visibilitychange', this.boundVisibilityHandler);
|
|
40
|
+
window.addEventListener('pagehide', this.boundPageHideHandler);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Track a custom event. Events are batched and sent automatically.
|
|
45
|
+
*
|
|
46
|
+
* @param name Event name (e.g. 'page_view', 'button_click', 'purchase')
|
|
47
|
+
* @param properties Optional key-value data (max 50 keys, max 4KB total)
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* client.analytics.track('page_view', { path: '/pricing' });
|
|
51
|
+
* client.analytics.track('purchase', { plan: 'pro', amount: 29.99 });
|
|
52
|
+
*/
|
|
53
|
+
track(name, properties) {
|
|
54
|
+
this.queue.push({ name, properties, timestamp: Date.now() });
|
|
55
|
+
if (this.queue.length >= this.MAX_BATCH) {
|
|
56
|
+
void this.flush();
|
|
57
|
+
}
|
|
58
|
+
else if (!this.timer) {
|
|
59
|
+
this.timer = setTimeout(() => void this.flush(), this.FLUSH_INTERVAL);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Manually flush all queued events to the server.
|
|
64
|
+
* Automatically called on timer expiry, batch size reached, or page unload.
|
|
65
|
+
*/
|
|
66
|
+
async flush() {
|
|
67
|
+
if (this.timer) {
|
|
68
|
+
clearTimeout(this.timer);
|
|
69
|
+
this.timer = null;
|
|
70
|
+
}
|
|
71
|
+
if (!this.queue.length)
|
|
72
|
+
return;
|
|
73
|
+
const batch = this.queue.splice(0, this.MAX_BATCH);
|
|
74
|
+
try {
|
|
75
|
+
const payload = {
|
|
76
|
+
events: batch.map(e => ({
|
|
77
|
+
name: e.name,
|
|
78
|
+
properties: e.properties,
|
|
79
|
+
timestamp: e.timestamp,
|
|
80
|
+
})),
|
|
81
|
+
};
|
|
82
|
+
if (this.core) {
|
|
83
|
+
await this.core.trackEvents(payload);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
await this.httpClient.post(ApiPaths.TRACK_EVENTS, payload);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Failed — re-queue for one retry (don't retry infinitely)
|
|
91
|
+
this.queue.unshift(...batch);
|
|
92
|
+
}
|
|
93
|
+
// If there are still events in the queue, schedule another flush
|
|
94
|
+
if (this.queue.length > 0 && !this.timer) {
|
|
95
|
+
this.timer = setTimeout(() => void this.flush(), this.FLUSH_INTERVAL);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Send remaining events via navigator.sendBeacon (used on page unload).
|
|
100
|
+
* sendBeacon cannot send Authorization headers, so these events arrive
|
|
101
|
+
* without JWT auth — protected by rate limiting only.
|
|
102
|
+
*
|
|
103
|
+
* NOTE: Direct HTTP — sendBeacon requires a raw URL + Blob. Cannot use
|
|
104
|
+
* generated core because sendBeacon bypasses normal fetch/XHR entirely.
|
|
105
|
+
*/
|
|
106
|
+
sendBeacon() {
|
|
107
|
+
if (!this.queue.length)
|
|
108
|
+
return;
|
|
109
|
+
const batch = this.queue.splice(0);
|
|
110
|
+
const url = `${this.baseUrl}${ApiPaths.TRACK_EVENTS}`;
|
|
111
|
+
const body = JSON.stringify({
|
|
112
|
+
events: batch.map(e => ({
|
|
113
|
+
name: e.name,
|
|
114
|
+
properties: e.properties,
|
|
115
|
+
timestamp: e.timestamp,
|
|
116
|
+
})),
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }));
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// sendBeacon failed — page is unloading, nothing we can do
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Destroy the analytics client. Flushes remaining events and removes
|
|
127
|
+
* event listeners. Call this when unmounting the SDK.
|
|
128
|
+
*/
|
|
129
|
+
destroy() {
|
|
130
|
+
if (this.timer) {
|
|
131
|
+
clearTimeout(this.timer);
|
|
132
|
+
this.timer = null;
|
|
133
|
+
}
|
|
134
|
+
this.sendBeacon();
|
|
135
|
+
// Remove event listeners
|
|
136
|
+
if (typeof window !== 'undefined') {
|
|
137
|
+
if (this.boundVisibilityHandler) {
|
|
138
|
+
window.removeEventListener('visibilitychange', this.boundVisibilityHandler);
|
|
139
|
+
}
|
|
140
|
+
if (this.boundPageHideHandler) {
|
|
141
|
+
window.removeEventListener('pagehide', this.boundPageHideHandler);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-refresh.d.ts","sourceRoot":"","sources":["../src/auth-refresh.ts"],"names":[],"mappings":"AAQA,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;IACvF,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC,CAgCD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { EdgeBaseError } from '@edge-base/core';
|
|
2
|
+
export async function refreshAccessToken(baseUrl, refreshToken) {
|
|
3
|
+
let response;
|
|
4
|
+
try {
|
|
5
|
+
response = await fetch(`${baseUrl.replace(/\/$/, '')}/api/auth/refresh`, {
|
|
6
|
+
method: 'POST',
|
|
7
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8
|
+
body: JSON.stringify({ refreshToken }),
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
throw new EdgeBaseError(0, `Network error: ${error instanceof Error ? error.message : 'Failed to refresh access token.'}`);
|
|
13
|
+
}
|
|
14
|
+
const body = await response.json().catch(() => null);
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
throw new EdgeBaseError(response.status, typeof body?.message === 'string' ? body.message : 'Failed to refresh access token.');
|
|
17
|
+
}
|
|
18
|
+
if (!body?.accessToken || !body?.refreshToken) {
|
|
19
|
+
throw new EdgeBaseError(500, 'Invalid auth refresh response.');
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
accessToken: body.accessToken,
|
|
23
|
+
refreshToken: body.refreshToken,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=auth-refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-refresh.js","sourceRoot":"","sources":["../src/auth-refresh.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAQhD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAAe,EAAE,YAAoB;IAI5E,IAAI,QAAkB,CAAC;IAEvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,mBAAmB,EAAE;YACvE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;SACvC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,aAAa,CACrB,CAAC,EACD,kBAAkB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,iCAAiC,EAAE,CAC/F,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAA2B,CAAC;IAC/E,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,aAAa,CACrB,QAAQ,CAAC,MAAM,EACf,OAAO,IAAI,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,iCAAiC,CACrF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,IAAI,EAAE,WAAW,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC;QAC9C,MAAM,IAAI,aAAa,CAAC,GAAG,EAAE,gCAAgC,CAAC,CAAC;IACjE,CAAC;IAED,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,YAAY,EAAE,IAAI,CAAC,YAAY;KAChC,CAAC;AACJ,CAAC"}
|