@zuzjs/flare 0.2.1 → 0.2.2
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/index.cjs +3 -3
- package/dist/index.d.cts +859 -163
- package/dist/index.d.ts +859 -163
- package/dist/index.js +2 -2
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,80 +1,174 @@
|
|
|
1
|
-
|
|
1
|
+
import { AuthToken, AuthGuard, ProviderId } from '@zuzjs/auth';
|
|
2
|
+
export { Anonymous, Apple, AuthGuard, AuthToken, CreateUserWithEmailAndPasswordInput, Credentials, Dropbox, Facebook, GitHub, Google, NormalizedProfile, OAuthProvider, ProviderId, Providers, SignInAnonymouslyInput, SignInWithEmailAndPasswordInput, Twitter, setupProvider } from '@zuzjs/auth';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Client Configuration
|
|
5
6
|
*/
|
|
6
7
|
interface FlareConfig {
|
|
7
|
-
/** WebSocket endpoint URL (e.g., 'http://localhost:5050' or 'wss://api.example.com') */
|
|
8
8
|
endpoint: string;
|
|
9
|
-
/** Application/Project ID */
|
|
10
|
-
appId: string;
|
|
11
|
-
/** API Key for authentication (optional) */
|
|
12
|
-
apiKey?: string;
|
|
13
9
|
/**
|
|
14
|
-
*
|
|
15
|
-
* When
|
|
16
|
-
*
|
|
10
|
+
* Optional HTTP base URL for auth API calls.
|
|
11
|
+
* When set, all auth HTTP calls go through this base instead of calling
|
|
12
|
+
* Flare directly. Use this to route calls through a Next.js proxy so CSRF
|
|
13
|
+
* is handled entirely server-side.
|
|
14
|
+
* Example: '/api/flare' (relative, browser resolves against current origin)
|
|
17
15
|
*/
|
|
16
|
+
httpBase?: string;
|
|
17
|
+
appId: string;
|
|
18
|
+
apiKey?: string;
|
|
18
19
|
publicKey?: string;
|
|
19
|
-
/** Enable automatic reconnection (default: true) */
|
|
20
20
|
autoReconnect?: boolean;
|
|
21
|
-
/** Initial reconnect delay in seconds (default: 2) */
|
|
22
21
|
reconnectDelay?: number;
|
|
23
|
-
/** Maximum reconnect delay in seconds (default: 60) */
|
|
24
22
|
maxReconnectDelay?: number;
|
|
25
|
-
/** Enable debug logging (default: false) */
|
|
26
23
|
debug?: boolean;
|
|
27
|
-
|
|
24
|
+
requestTiming?: boolean;
|
|
28
25
|
connectionTimeout?: number;
|
|
29
26
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
type FlareAuthProviderId = "credentials" | "anonymous" | "google" | "facebook" | "github" | "dropbox" | "apple" | "twitter";
|
|
28
|
+
interface FlareAuthProviderPublicConfig {
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
clientId?: string;
|
|
31
|
+
scopes?: string[];
|
|
32
|
+
}
|
|
33
|
+
interface FlareAuthConfig {
|
|
34
|
+
appId: string;
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
needsEmailVerification?: boolean;
|
|
37
|
+
autoSendVerificationEmail?: boolean;
|
|
38
|
+
redirectUri?: string;
|
|
39
|
+
csrfToken?: string;
|
|
40
|
+
cookie?: {
|
|
41
|
+
accessTokenName?: string;
|
|
42
|
+
refreshTokenName?: string;
|
|
43
|
+
csrfTokenName?: string;
|
|
44
|
+
domain?: string;
|
|
45
|
+
path?: string;
|
|
46
|
+
secure?: boolean;
|
|
47
|
+
sameSite?: "Lax" | "Strict" | "None";
|
|
48
|
+
accessTokenMaxAge?: number;
|
|
49
|
+
refreshTokenMaxAge?: number;
|
|
50
|
+
csrfTokenMaxAge?: number;
|
|
51
|
+
};
|
|
52
|
+
providers: Record<FlareAuthProviderId, FlareAuthProviderPublicConfig>;
|
|
53
|
+
}
|
|
54
|
+
interface FlareAuthSession {
|
|
55
|
+
uid: string;
|
|
56
|
+
accessToken: string;
|
|
57
|
+
refreshToken: string | null;
|
|
58
|
+
provider?: string;
|
|
59
|
+
email?: string | null;
|
|
60
|
+
emailVerified?: boolean;
|
|
61
|
+
}
|
|
62
|
+
type AuthStateListener = (session: FlareAuthSession | null) => void;
|
|
63
|
+
type AuthConfigListener = (conf: FlareAuthConfig) => void;
|
|
64
|
+
interface SubscribeOptions {
|
|
65
|
+
skipSnapshot?: boolean;
|
|
66
|
+
}
|
|
67
|
+
type QueryOperator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "in" | "not-in" | "array-contains" | "array-contains-any" | "like" | "not-like" | "contains" | "exists" | "not-exists";
|
|
33
68
|
interface QueryConfig {
|
|
34
69
|
field: string;
|
|
35
70
|
op: QueryOperator;
|
|
36
71
|
value: unknown;
|
|
37
72
|
}
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
73
|
+
/** OR group */
|
|
74
|
+
interface OrFilter {
|
|
75
|
+
or: QueryConfig[];
|
|
76
|
+
}
|
|
77
|
+
type AnyFilter = QueryConfig | OrFilter;
|
|
42
78
|
type WhereCondition = Record<string, string | number | boolean | any[]>;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
79
|
+
interface OrderByClause {
|
|
80
|
+
field: string;
|
|
81
|
+
dir?: "asc" | "desc";
|
|
82
|
+
}
|
|
83
|
+
interface GroupByClause {
|
|
84
|
+
fields: string[];
|
|
85
|
+
}
|
|
86
|
+
interface HavingClause {
|
|
87
|
+
field: string;
|
|
88
|
+
op: "==" | "!=" | "<" | "<=" | ">" | ">=";
|
|
89
|
+
value: number;
|
|
90
|
+
}
|
|
91
|
+
interface CursorValue {
|
|
92
|
+
values: unknown[];
|
|
93
|
+
}
|
|
94
|
+
type AggregateFunction = "count" | "sum" | "avg" | "min" | "max" | "distinct";
|
|
95
|
+
interface AggregateSpec {
|
|
96
|
+
fn: AggregateFunction;
|
|
97
|
+
field?: string;
|
|
98
|
+
alias?: string;
|
|
99
|
+
}
|
|
100
|
+
interface JoinClause {
|
|
101
|
+
from: string;
|
|
102
|
+
localField: string;
|
|
103
|
+
foreignField: string;
|
|
104
|
+
as: string;
|
|
105
|
+
single?: boolean;
|
|
106
|
+
}
|
|
107
|
+
interface VectorSearchClause {
|
|
108
|
+
field: string;
|
|
109
|
+
vector: number[];
|
|
110
|
+
k: number;
|
|
111
|
+
metric?: "cosine" | "euclidean" | "dotProduct";
|
|
112
|
+
minScore?: number;
|
|
113
|
+
}
|
|
114
|
+
/** Full structured query (Firestore + SQL feature set) */
|
|
115
|
+
interface StructuredQuery {
|
|
116
|
+
where?: AnyFilter[];
|
|
117
|
+
orderBy?: OrderByClause[];
|
|
118
|
+
limit?: number;
|
|
119
|
+
offset?: number;
|
|
120
|
+
startAt?: CursorValue;
|
|
121
|
+
startAfter?: CursorValue;
|
|
122
|
+
endAt?: CursorValue;
|
|
123
|
+
endBefore?: CursorValue;
|
|
124
|
+
aggregate?: AggregateSpec[];
|
|
125
|
+
groupBy?: GroupByClause;
|
|
126
|
+
having?: HavingClause[];
|
|
127
|
+
joins?: JoinClause[];
|
|
128
|
+
vectorSearch?: VectorSearchClause;
|
|
129
|
+
select?: string[];
|
|
130
|
+
distinctField?: string;
|
|
131
|
+
}
|
|
132
|
+
type ChangeOperation = 'insert' | 'update' | 'replace' | 'delete';
|
|
48
133
|
/**
|
|
49
|
-
*
|
|
134
|
+
* Fired once when the subscription is first established.
|
|
135
|
+
* `data` is always an array — the full matching collection snapshot.
|
|
50
136
|
*/
|
|
51
|
-
interface
|
|
137
|
+
interface SnapshotEvent<T = any> {
|
|
138
|
+
type: 'snapshot';
|
|
52
139
|
subscriptionId: string;
|
|
53
140
|
collection: string;
|
|
54
|
-
|
|
55
|
-
data: T | T[];
|
|
56
|
-
type: 'snapshot' | 'change';
|
|
57
|
-
operation?: 'insert' | 'update' | 'delete' | 'replace';
|
|
141
|
+
data: T[];
|
|
58
142
|
}
|
|
59
143
|
/**
|
|
60
|
-
*
|
|
144
|
+
* Fired on every subsequent document mutation that matches the subscription query.
|
|
145
|
+
* `data` is the single affected document (null on delete).
|
|
61
146
|
*/
|
|
147
|
+
interface ChangeEvent<T = any> {
|
|
148
|
+
type: 'change';
|
|
149
|
+
subscriptionId: string;
|
|
150
|
+
collection: string;
|
|
151
|
+
docId: string;
|
|
152
|
+
operation: ChangeOperation;
|
|
153
|
+
data: T | null;
|
|
154
|
+
}
|
|
155
|
+
/** Discriminated union — narrow on `event.type` to get the right shape. */
|
|
156
|
+
type SubscriptionData<T = any> = SnapshotEvent<T> | ChangeEvent<T>;
|
|
157
|
+
type SubscriptionCallback<T = any> = (data: SubscriptionData<T>) => void;
|
|
158
|
+
type DocAddedCallback<T = any> = (data: T, docId: string) => void;
|
|
159
|
+
type DocUpdatedCallback<T = any> = (data: T, docId: string) => void;
|
|
160
|
+
type DocDeletedCallback<T = any> = (docId: string) => void;
|
|
161
|
+
type DocChangedCallback<T = any> = (data: T | null, docId: string, operation: ChangeOperation) => void;
|
|
62
162
|
interface DocumentSnapshot<T = any> {
|
|
63
163
|
id: string;
|
|
64
164
|
data: T | null;
|
|
65
165
|
exists: boolean;
|
|
66
166
|
}
|
|
67
|
-
/**
|
|
68
|
-
* Collection query result
|
|
69
|
-
*/
|
|
70
167
|
interface QuerySnapshot<T = any> {
|
|
71
168
|
docs: DocumentSnapshot<T>[];
|
|
72
169
|
size: number;
|
|
73
170
|
empty: boolean;
|
|
74
171
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Offline operation
|
|
77
|
-
*/
|
|
78
172
|
interface OfflineOperation {
|
|
79
173
|
id: string;
|
|
80
174
|
type: 'write' | 'delete';
|
|
@@ -84,18 +178,54 @@ interface OfflineOperation {
|
|
|
84
178
|
merge?: boolean;
|
|
85
179
|
clientTs: number;
|
|
86
180
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Authentication result
|
|
89
|
-
*/
|
|
90
181
|
interface AuthResult {
|
|
91
182
|
uid: string;
|
|
92
183
|
token?: string;
|
|
93
184
|
}
|
|
94
|
-
/**
|
|
95
|
-
* Connection state
|
|
96
|
-
*/
|
|
97
185
|
type ConnectionState = 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'error';
|
|
186
|
+
interface AuthWithPendingVerificationResult {
|
|
187
|
+
verificationRequired: true;
|
|
188
|
+
created: true;
|
|
189
|
+
emailSent: boolean;
|
|
190
|
+
preview?: {
|
|
191
|
+
code: string;
|
|
192
|
+
link: string;
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
interface AuthWithTokenResult extends AuthResult {
|
|
196
|
+
accessToken: string;
|
|
197
|
+
refreshToken: string | null;
|
|
198
|
+
authToken: AuthToken;
|
|
199
|
+
created: boolean;
|
|
200
|
+
}
|
|
201
|
+
interface PresenceMember {
|
|
202
|
+
uid: string;
|
|
203
|
+
socketId: string;
|
|
204
|
+
room: string;
|
|
205
|
+
meta?: Record<string, unknown>;
|
|
206
|
+
joinedAt: number;
|
|
207
|
+
lastSeen: number;
|
|
208
|
+
}
|
|
209
|
+
type PresenceCallback = (members: PresenceMember[]) => void;
|
|
210
|
+
type PresenceJoinCallback = (member: PresenceMember) => void;
|
|
211
|
+
type PresenceLeaveCallback = (uid: string) => void;
|
|
212
|
+
/** Fields marked as vector will be auto-embedded before write */
|
|
213
|
+
type VectorFieldConfig = {
|
|
214
|
+
/** Dimensions of the vector (e.g. 1536 for OpenAI ada-002) */
|
|
215
|
+
dimensions: number;
|
|
216
|
+
/** Optional custom embedding function; defaults to client-configured embedder */
|
|
217
|
+
embed?: (text: string) => Promise<number[]>;
|
|
218
|
+
};
|
|
98
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Parse ORM-style where condition: { age: ">= 25", role: "admin" }
|
|
222
|
+
* Returns array of QueryConfig objects
|
|
223
|
+
*/
|
|
224
|
+
declare function parseWhereCondition(condition: WhereCondition): QueryConfig[];
|
|
225
|
+
/**
|
|
226
|
+
* Parse string value to appropriate type
|
|
227
|
+
*/
|
|
228
|
+
declare function parseValue(val: string): any;
|
|
99
229
|
/**
|
|
100
230
|
* Query builder for document operations (ORM-style)
|
|
101
231
|
* Supports: doc('users').update({...}).where({ id: 'alice' })
|
|
@@ -157,6 +287,7 @@ declare class DocumentQueryBuilder<T = any> implements PromiseLike<T | null | vo
|
|
|
157
287
|
*/
|
|
158
288
|
onSnapshot(callback: SubscriptionCallback<T>): () => void;
|
|
159
289
|
}
|
|
290
|
+
|
|
160
291
|
/**
|
|
161
292
|
* Legacy document reference (for backward compatibility)
|
|
162
293
|
*/
|
|
@@ -170,57 +301,88 @@ declare class DocumentReference<T = any> {
|
|
|
170
301
|
update(data: Partial<T>): Promise<void>;
|
|
171
302
|
delete(): Promise<void>;
|
|
172
303
|
onSnapshot(callback: SubscriptionCallback<T>): () => void;
|
|
304
|
+
/**
|
|
305
|
+
* Fires when this document is updated / replaced.
|
|
306
|
+
* Aliases: onDocModified, onDocChange
|
|
307
|
+
*/
|
|
308
|
+
onDocUpdated(callback: DocUpdatedCallback<T>): () => void;
|
|
309
|
+
/** Alias for onDocUpdated */
|
|
310
|
+
onDocModified(callback: DocUpdatedCallback<T>): () => void;
|
|
311
|
+
/** Alias for onDocUpdated */
|
|
312
|
+
onDocChange(callback: DocUpdatedCallback<T>): () => void;
|
|
313
|
+
/**
|
|
314
|
+
* Fires when this document is deleted.
|
|
315
|
+
*/
|
|
316
|
+
onDocDeleted(callback: DocDeletedCallback<T>): () => void;
|
|
317
|
+
/**
|
|
318
|
+
* Fires on any change to this document (update / delete).
|
|
319
|
+
* `data` is null on deletes.
|
|
320
|
+
*/
|
|
321
|
+
onDocChanged(callback: DocChangedCallback<T>): () => void;
|
|
173
322
|
}
|
|
174
|
-
|
|
175
|
-
* Collection reference with ORM-style query builder
|
|
176
|
-
*
|
|
177
|
-
* This class is thenable - you can await it directly without .get()
|
|
178
|
-
* @example
|
|
179
|
-
* await collection('users').where({ age: '>= 25' })
|
|
180
|
-
*/
|
|
323
|
+
|
|
181
324
|
declare class CollectionReference<T = any> implements PromiseLike<T[]> {
|
|
182
325
|
private client;
|
|
183
326
|
readonly collection: string;
|
|
184
|
-
private
|
|
327
|
+
private sq;
|
|
185
328
|
private promise?;
|
|
186
329
|
constructor(client: FlareClient, collection: string);
|
|
187
|
-
/**
|
|
188
|
-
* Get a document reference (legacy API)
|
|
189
|
-
*/
|
|
190
330
|
doc(id: string): DocumentReference<T>;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
* @example .where({ age: ">= 25", role: "admin" })
|
|
194
|
-
*/
|
|
331
|
+
private clone;
|
|
332
|
+
/** ORM shorthand: .where({ age: ">= 25", role: "admin" }) */
|
|
195
333
|
where(condition: WhereCondition): CollectionReference<T>;
|
|
334
|
+
/** Explicit field/op/value */
|
|
335
|
+
where(field: string, op: QueryConfig['op'], value: unknown): CollectionReference<T>;
|
|
336
|
+
/** OR group: .orWhere([{ field:"status", op:"==", value:"active" }, ...]) */
|
|
337
|
+
orWhere(filters: QueryConfig[]): CollectionReference<T>;
|
|
338
|
+
orderBy(field: string, dir?: "asc" | "desc"): CollectionReference<T>;
|
|
339
|
+
limit(n: number): CollectionReference<T>;
|
|
340
|
+
offset(n: number): CollectionReference<T>;
|
|
341
|
+
startAt(...values: unknown[]): CollectionReference<T>;
|
|
342
|
+
startAfter(...values: unknown[]): CollectionReference<T>;
|
|
343
|
+
endAt(...values: unknown[]): CollectionReference<T>;
|
|
344
|
+
endBefore(...values: unknown[]): CollectionReference<T>;
|
|
345
|
+
aggregate(...specs: AggregateSpec[]): CollectionReference<T>;
|
|
346
|
+
count(alias?: string): CollectionReference<T>;
|
|
347
|
+
sum(field: string, alias?: string): CollectionReference<T>;
|
|
348
|
+
avg(field: string, alias?: string): CollectionReference<T>;
|
|
349
|
+
min(field: string, alias?: string): CollectionReference<T>;
|
|
350
|
+
max(field: string, alias?: string): CollectionReference<T>;
|
|
351
|
+
distinct(field: string, alias?: string): CollectionReference<T>;
|
|
352
|
+
groupBy(...fields: string[]): CollectionReference<T>;
|
|
353
|
+
having(field: string, op: HavingClause['op'], value: number): CollectionReference<T>;
|
|
354
|
+
join(j: JoinClause): CollectionReference<T>;
|
|
355
|
+
select(...fields: string[]): CollectionReference<T>;
|
|
356
|
+
/** Returns unique values for a single field */
|
|
357
|
+
distinctField(field: string): CollectionReference<T>;
|
|
196
358
|
/**
|
|
197
|
-
*
|
|
198
|
-
* @
|
|
359
|
+
* KNN nearest-neighbour search (requires Atlas vector index).
|
|
360
|
+
* @example
|
|
361
|
+
* col.vectorSearch({ field: "embedding", vector: [...1536 numbers...], k: 10 })
|
|
199
362
|
*/
|
|
363
|
+
vectorSearch(opts: VectorSearchClause): CollectionReference<T>;
|
|
200
364
|
get(): Promise<T[]>;
|
|
201
|
-
|
|
202
|
-
* Internal execute method
|
|
203
|
-
*/
|
|
365
|
+
private _isStructured;
|
|
204
366
|
private _execute;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
*/
|
|
367
|
+
private _executeQuery;
|
|
368
|
+
private _executeSubscribe;
|
|
208
369
|
then<TResult1 = T[], TResult2 = never>(onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
|
|
209
370
|
/**
|
|
210
|
-
* Subscribe to real-time updates
|
|
371
|
+
* Subscribe to real-time updates.
|
|
372
|
+
* The full StructuredQuery (including orderBy, where, limit, offset) is sent
|
|
373
|
+
* to the server so the initial snapshot respects all constraints.
|
|
374
|
+
* Individual change events are then sorted / filtered client-side to keep
|
|
375
|
+
* the live result consistent.
|
|
211
376
|
*/
|
|
212
377
|
onSnapshot(callback: SubscriptionCallback<T[]>): () => void;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
378
|
+
onDocAdded(callback: DocAddedCallback<T>): () => void;
|
|
379
|
+
onDocUpdated(callback: DocUpdatedCallback<T>): () => void;
|
|
380
|
+
onDocModified(callback: DocUpdatedCallback<T>): () => void;
|
|
381
|
+
onDocChange(callback: DocUpdatedCallback<T>): () => void;
|
|
382
|
+
onDocDeleted(callback: DocDeletedCallback<T>): () => void;
|
|
383
|
+
onDocChanged(callback: DocChangedCallback<T>): () => void;
|
|
216
384
|
add(data: Partial<T>): Promise<DocumentReference<T>>;
|
|
217
|
-
/**
|
|
218
|
-
* Update documents matching the where condition
|
|
219
|
-
*/
|
|
220
385
|
update(data: Partial<T>): DocumentQueryBuilder<T>;
|
|
221
|
-
/**
|
|
222
|
-
* Delete documents matching the where condition
|
|
223
|
-
*/
|
|
224
386
|
delete(): DocumentQueryBuilder<T>;
|
|
225
387
|
}
|
|
226
388
|
|
|
@@ -233,8 +395,13 @@ declare enum FlareAction {
|
|
|
233
395
|
AUTH = "auth",
|
|
234
396
|
PING = "ping",
|
|
235
397
|
OFFLINE_SYNC = "offline_sync",
|
|
236
|
-
|
|
237
|
-
|
|
398
|
+
CALL = "call",
|
|
399
|
+
/** One-shot rich query (no real-time subscription) */
|
|
400
|
+
QUERY = "query",
|
|
401
|
+
/** Presence */
|
|
402
|
+
PRESENCE_JOIN = "presence_join",
|
|
403
|
+
PRESENCE_LEAVE = "presence_leave",
|
|
404
|
+
PRESENCE_HEARTBEAT = "presence_heartbeat"
|
|
238
405
|
}
|
|
239
406
|
/** Server Response */
|
|
240
407
|
declare enum FlareEvent {
|
|
@@ -245,116 +412,586 @@ declare enum FlareEvent {
|
|
|
245
412
|
PONG = "pong",
|
|
246
413
|
AUTH_OK = "auth_ok",
|
|
247
414
|
OFFLINE_ACK = "offline_ack",
|
|
248
|
-
|
|
249
|
-
|
|
415
|
+
CALL_RESPONSE = "call_response",
|
|
416
|
+
QUERY_RESULT = "query_result",
|
|
417
|
+
PRESENCE_STATE = "presence_state",
|
|
418
|
+
PRESENCE_JOIN = "presence_join",
|
|
419
|
+
PRESENCE_LEAVE = "presence_leave"
|
|
250
420
|
}
|
|
251
421
|
interface BaseMessage {
|
|
252
422
|
id: string;
|
|
253
423
|
type: FlareAction | FlareEvent;
|
|
254
424
|
ts: number;
|
|
255
425
|
}
|
|
426
|
+
interface SubscribeMessage extends BaseMessage {
|
|
427
|
+
type: FlareAction.SUBSCRIBE;
|
|
428
|
+
collection: string;
|
|
429
|
+
docId?: string;
|
|
430
|
+
query?: Record<string, unknown>;
|
|
431
|
+
skipSnapshot?: boolean;
|
|
432
|
+
resumeToken?: string;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
type TransportOptions = {
|
|
436
|
+
url: string;
|
|
437
|
+
onMessage: (data: any) => void;
|
|
438
|
+
onOpen?: () => void;
|
|
439
|
+
onClose?: () => void;
|
|
440
|
+
onError?: (error: Error) => void;
|
|
441
|
+
autoReconnect?: boolean;
|
|
442
|
+
reconnectDelay?: number;
|
|
443
|
+
maxReconnectDelay?: number;
|
|
444
|
+
debug?: boolean;
|
|
445
|
+
/** RSA public key (PEM). When set, all outgoing messages are RSA-OAEP encrypted. */
|
|
446
|
+
publicKey?: string;
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
declare class FlareTransport {
|
|
450
|
+
private socket;
|
|
451
|
+
private reconnectInterval;
|
|
452
|
+
private maxReconnectDelay;
|
|
453
|
+
private isConnected;
|
|
454
|
+
private shouldReconnect;
|
|
455
|
+
private options;
|
|
456
|
+
private messageQueue;
|
|
457
|
+
private heartbeatInterval;
|
|
458
|
+
private connectionTimeout;
|
|
459
|
+
constructor(options: TransportOptions);
|
|
460
|
+
connect(): void;
|
|
461
|
+
private handleReconnect;
|
|
462
|
+
private startHeartbeat;
|
|
463
|
+
private stopHeartbeat;
|
|
464
|
+
private flushQueue;
|
|
465
|
+
send(message: object): void;
|
|
466
|
+
disconnect(): void;
|
|
467
|
+
get connected(): boolean;
|
|
468
|
+
private log;
|
|
469
|
+
}
|
|
256
470
|
|
|
257
471
|
type ConnectionListener = (state: ConnectionState) => void;
|
|
258
472
|
type ErrorListener = (error: Error) => void;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
473
|
+
type ActiveSubscription = {
|
|
474
|
+
baseId: string;
|
|
475
|
+
liveId: string;
|
|
476
|
+
collection: string;
|
|
477
|
+
docId?: string;
|
|
478
|
+
query?: QueryConfig | StructuredQuery;
|
|
479
|
+
callback: SubscriptionCallback;
|
|
480
|
+
options: SubscribeOptions;
|
|
481
|
+
};
|
|
482
|
+
/** Embedder function registered by the user */
|
|
483
|
+
type EmbedFn = (text: string) => Promise<number[]>;
|
|
484
|
+
declare class FlareBase {
|
|
485
|
+
protected transport: FlareTransport;
|
|
486
|
+
protected readonly config: FlareConfig;
|
|
487
|
+
protected readonly pendingAcks: Map<string, (value: any) => void>;
|
|
488
|
+
protected readonly subscriptions: Map<string, SubscriptionCallback>;
|
|
489
|
+
protected readonly activeSubscriptions: Map<string, ActiveSubscription>;
|
|
490
|
+
protected readonly offlineQueue: any[];
|
|
491
|
+
protected currentState: ConnectionState;
|
|
492
|
+
protected connectionListeners: ConnectionListener[];
|
|
493
|
+
protected errorListeners: ErrorListener[];
|
|
494
|
+
protected isDebug: boolean;
|
|
495
|
+
protected socketAuthUid: string;
|
|
496
|
+
protected pendingSubscriptionReplay: boolean;
|
|
497
|
+
protected subscriptionReplayPromise: Promise<void>;
|
|
498
|
+
protected requestTraceSeq: number;
|
|
499
|
+
protected requestTimingEnabled: boolean;
|
|
500
|
+
protected presenceCallbacks: Map<string, PresenceCallback[]>;
|
|
501
|
+
protected presenceJoinCbs: Map<string, PresenceJoinCallback[]>;
|
|
502
|
+
protected presenceLeaveCbs: Map<string, PresenceLeaveCallback[]>;
|
|
503
|
+
protected presenceHeartbeatTimer?: ReturnType<typeof setInterval>;
|
|
504
|
+
protected embedder?: EmbedFn;
|
|
505
|
+
protected vectorSchema: Map<string, Map<string, VectorFieldConfig>>;
|
|
506
|
+
protected throwFetchFlareError(payload: unknown, fallbackMessage: string, fallbackCode: string): never;
|
|
507
|
+
protected nowMs(): number;
|
|
508
|
+
protected normalizeHeaders(headers?: HeadersInit): Record<string, string>;
|
|
509
|
+
protected redactHeaders(headers: Record<string, string>): Record<string, string>;
|
|
510
|
+
protected logHttpTiming(...args: any[]): void;
|
|
511
|
+
protected mergeHeaders(base: HeadersInit | undefined, extra: Record<string, string>): HeadersInit;
|
|
512
|
+
protected timedFetch(label: string, input: string, init?: RequestInit): Promise<{
|
|
513
|
+
response: {
|
|
514
|
+
status: number;
|
|
515
|
+
ok: boolean;
|
|
516
|
+
headers: {
|
|
517
|
+
get: (name: string) => string | null;
|
|
518
|
+
};
|
|
519
|
+
json: () => Promise<any>;
|
|
520
|
+
};
|
|
521
|
+
requestId: number;
|
|
522
|
+
startedAtMs: number;
|
|
523
|
+
networkMs: number;
|
|
524
|
+
method: string;
|
|
525
|
+
url: string;
|
|
526
|
+
}>;
|
|
527
|
+
protected parseJsonWithTiming(label: string, trace: {
|
|
528
|
+
requestId: number;
|
|
529
|
+
startedAtMs: number;
|
|
530
|
+
response: {
|
|
531
|
+
status: number;
|
|
532
|
+
ok: boolean;
|
|
533
|
+
headers: {
|
|
534
|
+
get: (name: string) => string | null;
|
|
535
|
+
};
|
|
536
|
+
json: () => Promise<any>;
|
|
537
|
+
};
|
|
538
|
+
networkMs: number;
|
|
539
|
+
method: string;
|
|
540
|
+
url: string;
|
|
541
|
+
}): Promise<any>;
|
|
542
|
+
protected getHttpBase(): string;
|
|
543
|
+
protected log(...args: any[]): void;
|
|
271
544
|
constructor(config: FlareConfig);
|
|
272
|
-
/**
|
|
273
|
-
* Connect to the server
|
|
274
|
-
*/
|
|
275
545
|
connect(): void;
|
|
276
|
-
/**
|
|
277
|
-
* Disconnect from the server
|
|
278
|
-
*/
|
|
279
546
|
disconnect(): void;
|
|
280
|
-
/**
|
|
281
|
-
* Get a collection reference
|
|
282
|
-
*/
|
|
283
|
-
collection<T = any>(name: string): CollectionReference<T>;
|
|
284
|
-
/**
|
|
285
|
-
* Get a document query builder (NEW ORM-style API)
|
|
286
|
-
* @example flare.doc('users').update({...}).where({ id: 'alice' })
|
|
287
|
-
*/
|
|
288
|
-
doc<T = any>(collection: string): DocumentQueryBuilder<T>;
|
|
289
|
-
/**
|
|
290
|
-
* Get a document reference (Legacy API - deprecated)
|
|
291
|
-
* @deprecated Use doc(collection).where({ id: '...' }) instead
|
|
292
|
-
*/
|
|
293
|
-
doc<T = any>(collection: string, id: string): DocumentReference<T>;
|
|
294
|
-
/**
|
|
295
|
-
* Authenticate with a token
|
|
296
|
-
*/
|
|
297
|
-
auth(token: string): Promise<AuthResult>;
|
|
298
|
-
/**
|
|
299
|
-
* Sign out
|
|
300
|
-
*/
|
|
301
|
-
signOut(): void;
|
|
302
|
-
/**
|
|
303
|
-
* Get current user ID
|
|
304
|
-
*/
|
|
305
|
-
get currentUser(): string | undefined;
|
|
306
|
-
/**
|
|
307
|
-
* Get connection state
|
|
308
|
-
*/
|
|
309
547
|
get connectionState(): ConnectionState;
|
|
310
|
-
/**
|
|
311
|
-
* Check if connected
|
|
312
|
-
*/
|
|
313
548
|
get isConnected(): boolean;
|
|
314
|
-
/**
|
|
315
|
-
* Listen to connection state changes
|
|
316
|
-
*/
|
|
317
549
|
onConnectionStateChange(listener: ConnectionListener): () => void;
|
|
550
|
+
onError(callback: ErrorListener): () => void;
|
|
551
|
+
collection<T = any>(name: string): CollectionReference<T>;
|
|
552
|
+
doc<T = any>(collection: string): DocumentQueryBuilder<T>;
|
|
553
|
+
doc<T = any>(collection: string, id: string): DocumentReference<T>;
|
|
554
|
+
ping(): Promise<number>;
|
|
555
|
+
call<T = Record<string, unknown>>(topic: string, payload?: Record<string, unknown>): Promise<T>;
|
|
556
|
+
query<T = Record<string, unknown>>(collection: string, q?: StructuredQuery): Promise<T[]>;
|
|
557
|
+
setEmbedder(fn: EmbedFn): void;
|
|
558
|
+
markVectorField(collection: string, field: string, config?: VectorFieldConfig): void;
|
|
559
|
+
embedVectorFields(collection: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
560
|
+
joinPresence(room: string, meta?: Record<string, unknown>): Promise<() => void>;
|
|
561
|
+
leavePresence(room: string): Promise<void>;
|
|
562
|
+
onPresenceState(room: string, cb: PresenceCallback): () => void;
|
|
563
|
+
onPresenceJoin(room: string, cb: PresenceJoinCallback): () => void;
|
|
564
|
+
onPresenceLeave(room: string, cb: PresenceLeaveCallback): () => void;
|
|
565
|
+
private _startPresenceHeartbeat;
|
|
566
|
+
private _stopPresenceHeartbeat;
|
|
567
|
+
syncOffline(): Promise<void>;
|
|
568
|
+
protected activateSubscription(entry: ActiveSubscription): Promise<void>;
|
|
569
|
+
protected replayActiveSubscriptions(): Promise<void>;
|
|
570
|
+
subscribe(subId: string, collection: string, docId: string | undefined, query: QueryConfig | StructuredQuery | undefined, callback: SubscriptionCallback, options?: SubscribeOptions): () => void;
|
|
571
|
+
send(type: FlareAction, payload: any): Promise<any>;
|
|
572
|
+
private handleTransportError;
|
|
573
|
+
protected onConnected(): void;
|
|
574
|
+
protected onDisconnected(): void;
|
|
575
|
+
protected setState(state: ConnectionState): void;
|
|
576
|
+
protected handleIncoming(msg: any): void;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* FlareAuth extends FlareBase with all authentication and CSRF logic.
|
|
581
|
+
*
|
|
582
|
+
* CSRF strategy
|
|
583
|
+
* ─────────────
|
|
584
|
+
* The server sets an HttpOnly cookie on /auth/config responses. The raw
|
|
585
|
+
* cookie value is NOT readable by JS (HttpOnly), but the server also echoes
|
|
586
|
+
* the token in the `x-flare-csrf` response header so the client can send
|
|
587
|
+
* it back as `x-flare-csrf` on every mutating request.
|
|
588
|
+
*
|
|
589
|
+
* Two environments are supported:
|
|
590
|
+
* 1. Browser – header is read from the /auth/config fetch; stored in
|
|
591
|
+
* `this.csrfToken` (in-memory only, never written to a
|
|
592
|
+
* readable cookie by the client).
|
|
593
|
+
* 2. Next.js SSR – use the standalone `createCsrfProxy()` handler
|
|
594
|
+
* (see Client/proxy.ts) which captures the Set-Cookie header
|
|
595
|
+
* from Flare server and sets it on the client domain too, so
|
|
596
|
+
* the browser owns two HttpOnly cookies: one from the Flare
|
|
597
|
+
* domain and one from the Next.js domain.
|
|
598
|
+
*/
|
|
599
|
+
declare class FlareAuth extends FlareBase {
|
|
600
|
+
protected authToken?: string;
|
|
601
|
+
protected userId?: string;
|
|
602
|
+
protected authGuard?: AuthGuard;
|
|
603
|
+
protected authConfig?: FlareAuthConfig;
|
|
604
|
+
/** In-memory CSRF token extracted from the `x-flare-csrf` response header */
|
|
605
|
+
protected csrfToken?: string;
|
|
606
|
+
protected csrfInitPromise?: Promise<void>;
|
|
607
|
+
protected csrfBootstrapAttempted: boolean;
|
|
608
|
+
protected authSession: FlareAuthSession | null;
|
|
609
|
+
protected authStateListeners: AuthStateListener[];
|
|
610
|
+
protected authConfigListeners: AuthConfigListener[];
|
|
611
|
+
private getDefaultCsrfCookieName;
|
|
612
|
+
getCsrfCookieName(): string;
|
|
318
613
|
/**
|
|
319
|
-
*
|
|
614
|
+
* Read the CSRF token from a browser-readable cookie (set by the server as
|
|
615
|
+
* a non-HttpOnly fallback) or fall back to the in-memory value captured
|
|
616
|
+
* from the response header.
|
|
617
|
+
*
|
|
618
|
+
* Priority:
|
|
619
|
+
* 1. Non-HttpOnly cookie on the current domain (browser only)
|
|
620
|
+
* 2. In-memory value from `x-flare-csrf` response header
|
|
320
621
|
*/
|
|
321
|
-
|
|
622
|
+
getCsrfToken(): string | null;
|
|
623
|
+
private getCookieValue;
|
|
322
624
|
/**
|
|
323
|
-
*
|
|
625
|
+
* Extract CSRF token from a server response.
|
|
626
|
+
* The server now sends it ONLY as the `x-flare-csrf` response header
|
|
627
|
+
* (not in the JSON body). We still support the body field as a fallback
|
|
628
|
+
* for older server versions.
|
|
324
629
|
*/
|
|
325
|
-
|
|
630
|
+
protected extractCsrfToken(json: unknown, response?: {
|
|
631
|
+
headers: {
|
|
632
|
+
get: (name: string) => string | null;
|
|
633
|
+
};
|
|
634
|
+
}): string | undefined;
|
|
635
|
+
getCsrfHeaders(): Record<string, string>;
|
|
326
636
|
/**
|
|
327
|
-
*
|
|
637
|
+
* Inject CSRF token directly (used by SSR middleware).
|
|
638
|
+
* This allows the server to bootstrap CSRF once and inject it into the client,
|
|
639
|
+
* preventing redundant /auth/config calls.
|
|
328
640
|
*
|
|
329
641
|
* @example
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
* @returns The object returned by the server handler.
|
|
336
|
-
* @throws If the server returns `success: false` or the request times out.
|
|
337
|
-
*/
|
|
338
|
-
call<T = Record<string, unknown>>(topic: string, payload?: Record<string, unknown>): Promise<T>;
|
|
339
|
-
/**
|
|
340
|
-
* Sync offline operations
|
|
642
|
+
* // In Next.js middleware
|
|
643
|
+
* const csrf = extractCsrfFromRequest(request, appId);
|
|
644
|
+
* if (csrf) {
|
|
645
|
+
* flareClient.setCsrfToken(csrf);
|
|
646
|
+
* }
|
|
341
647
|
*/
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
648
|
+
setCsrfToken(token: string): void;
|
|
649
|
+
ensureCsrfProtection(): Promise<void>;
|
|
650
|
+
loadAuthConfig(): Promise<FlareAuthConfig>;
|
|
651
|
+
protected fetchAuthConfig(): Promise<FlareAuthConfig>;
|
|
652
|
+
onAuthConfigLoaded(listener: AuthConfigListener): () => void;
|
|
653
|
+
protected setAuthSession(session: FlareAuthSession | null): void;
|
|
654
|
+
onAuthStateChanged(listener: AuthStateListener): () => void;
|
|
655
|
+
onAuthStateChange(listener: AuthStateListener): () => void;
|
|
656
|
+
get currentUser(): string | undefined;
|
|
657
|
+
getCurrentUser(): string | undefined;
|
|
658
|
+
protected syncSocketAuth(accessToken?: string | null): Promise<void>;
|
|
659
|
+
protected updateSocketIdentity(uid?: string, forceReplay?: boolean): Promise<void>;
|
|
660
|
+
protected onConnected(): void;
|
|
661
|
+
protected handleIncoming(msg: any): void;
|
|
662
|
+
auth(token: string): Promise<AuthResult>;
|
|
663
|
+
signInWithEmailAndPassword(email: string, password: string, options?: {
|
|
664
|
+
scope?: string[];
|
|
665
|
+
createIfMissing?: boolean;
|
|
666
|
+
}): Promise<AuthResult & {
|
|
667
|
+
kind?: string;
|
|
668
|
+
accessToken: string;
|
|
669
|
+
refreshToken: string | null;
|
|
670
|
+
authToken: AuthToken;
|
|
671
|
+
created?: boolean;
|
|
672
|
+
}>;
|
|
673
|
+
signInWithEmail(email: string, password: string, options?: {
|
|
674
|
+
scope?: string[];
|
|
675
|
+
createIfMissing?: boolean;
|
|
676
|
+
}): Promise<AuthResult & {
|
|
677
|
+
kind?: string;
|
|
678
|
+
accessToken: string;
|
|
679
|
+
refreshToken: string | null;
|
|
680
|
+
authToken: AuthToken;
|
|
681
|
+
created?: boolean;
|
|
682
|
+
}>;
|
|
683
|
+
createUserWithEmail(email: string, password: string, options?: {
|
|
684
|
+
scope?: string[];
|
|
685
|
+
additionalParams?: Record<string, string>;
|
|
686
|
+
signInIfAllowed?: boolean;
|
|
687
|
+
}): Promise<{
|
|
688
|
+
kind?: string;
|
|
689
|
+
verificationRequired: true;
|
|
690
|
+
emailSent: boolean;
|
|
691
|
+
preview?: {
|
|
692
|
+
code: string;
|
|
693
|
+
link: string;
|
|
694
|
+
};
|
|
695
|
+
} | (AuthResult & {
|
|
696
|
+
kind?: string;
|
|
697
|
+
accessToken: string;
|
|
698
|
+
refreshToken: string | null;
|
|
699
|
+
authToken: AuthToken;
|
|
700
|
+
verificationRequired?: false;
|
|
701
|
+
emailSent?: boolean;
|
|
702
|
+
preview?: {
|
|
703
|
+
code: string;
|
|
704
|
+
link: string;
|
|
705
|
+
};
|
|
706
|
+
})>;
|
|
707
|
+
createUserWithEmailAndPassword(email: string, password: string, options?: {
|
|
708
|
+
scope?: string[];
|
|
709
|
+
additionalParams?: Record<string, string>;
|
|
710
|
+
signInIfAllowed?: boolean;
|
|
711
|
+
}): Promise<{
|
|
712
|
+
kind?: string;
|
|
713
|
+
verificationRequired: true;
|
|
714
|
+
emailSent: boolean;
|
|
715
|
+
preview?: {
|
|
716
|
+
code: string;
|
|
717
|
+
link: string;
|
|
718
|
+
};
|
|
719
|
+
} | (AuthResult & {
|
|
720
|
+
kind?: string;
|
|
721
|
+
accessToken: string;
|
|
722
|
+
refreshToken: string | null;
|
|
723
|
+
authToken: AuthToken;
|
|
724
|
+
verificationRequired?: false;
|
|
725
|
+
emailSent?: boolean;
|
|
726
|
+
preview?: {
|
|
727
|
+
code: string;
|
|
728
|
+
link: string;
|
|
729
|
+
};
|
|
730
|
+
})>;
|
|
731
|
+
signInOrCreateWithEmail(email: string, password: string, options?: {
|
|
732
|
+
scope?: string[];
|
|
733
|
+
additionalParams?: Record<string, string>;
|
|
734
|
+
}): Promise<{
|
|
735
|
+
kind?: string;
|
|
736
|
+
verificationRequired: true;
|
|
737
|
+
created: true;
|
|
738
|
+
emailSent: boolean;
|
|
739
|
+
preview?: {
|
|
740
|
+
code: string;
|
|
741
|
+
link: string;
|
|
742
|
+
};
|
|
743
|
+
} | (AuthResult & {
|
|
744
|
+
accessToken: string;
|
|
745
|
+
refreshToken: string | null;
|
|
746
|
+
authToken: AuthToken;
|
|
747
|
+
created: boolean;
|
|
748
|
+
})>;
|
|
749
|
+
signInOrCreateWithEmailAndPassword(email: string, password: string, options?: {
|
|
750
|
+
scope?: string[];
|
|
751
|
+
additionalParams?: Record<string, string>;
|
|
752
|
+
}): Promise<{
|
|
753
|
+
kind?: string;
|
|
754
|
+
verificationRequired: true;
|
|
755
|
+
created: true;
|
|
756
|
+
emailSent: boolean;
|
|
757
|
+
preview?: {
|
|
758
|
+
code: string;
|
|
759
|
+
link: string;
|
|
760
|
+
};
|
|
761
|
+
} | (AuthResult & {
|
|
762
|
+
accessToken: string;
|
|
763
|
+
refreshToken: string | null;
|
|
764
|
+
authToken: AuthToken;
|
|
765
|
+
created: boolean;
|
|
766
|
+
})>;
|
|
767
|
+
sendEmailVerification(email: string): Promise<{
|
|
768
|
+
sent: boolean;
|
|
769
|
+
emailSent: boolean;
|
|
770
|
+
preview?: {
|
|
771
|
+
code: string;
|
|
772
|
+
link: string;
|
|
773
|
+
};
|
|
774
|
+
}>;
|
|
775
|
+
verifyEmailWithCode(email: string, code: string): Promise<{
|
|
776
|
+
verified: boolean;
|
|
777
|
+
email: string;
|
|
778
|
+
}>;
|
|
779
|
+
confirmEmailLink(token: string, email: string): Promise<{
|
|
780
|
+
verified: boolean;
|
|
781
|
+
email: string;
|
|
782
|
+
}>;
|
|
783
|
+
sendAccountRecovery(email: string): Promise<{
|
|
784
|
+
sent: boolean;
|
|
785
|
+
emailSent?: boolean;
|
|
786
|
+
preview?: {
|
|
787
|
+
code: string;
|
|
788
|
+
token: string;
|
|
789
|
+
};
|
|
790
|
+
}>;
|
|
791
|
+
recoverAccountWithCode(email: string, code: string, newPassword: string): Promise<{
|
|
792
|
+
recovered: boolean;
|
|
793
|
+
email: string;
|
|
794
|
+
sessionsRevoked?: number;
|
|
795
|
+
}>;
|
|
796
|
+
recoverAccountWithToken(token: string, newPassword: string): Promise<{
|
|
797
|
+
recovered: boolean;
|
|
798
|
+
email: string;
|
|
799
|
+
sessionsRevoked?: number;
|
|
800
|
+
}>;
|
|
801
|
+
signIn(providerId: ProviderId, options?: {
|
|
802
|
+
returnTo?: string;
|
|
803
|
+
metaTag?: string;
|
|
804
|
+
}): Promise<any>;
|
|
805
|
+
signIn(authGuard: Pick<AuthGuard, 'signIn'>, providerId: ProviderId, options?: {
|
|
806
|
+
returnTo?: string;
|
|
807
|
+
metaTag?: string;
|
|
808
|
+
}): Promise<any>;
|
|
809
|
+
signInWithGoogle(options?: {
|
|
810
|
+
returnTo?: string;
|
|
811
|
+
metaTag?: string;
|
|
812
|
+
}): Promise<any>;
|
|
813
|
+
signInWithGitHub(options?: {
|
|
814
|
+
returnTo?: string;
|
|
815
|
+
metaTag?: string;
|
|
816
|
+
}): Promise<any>;
|
|
817
|
+
signInWithFacebook(options?: {
|
|
818
|
+
returnTo?: string;
|
|
819
|
+
metaTag?: string;
|
|
820
|
+
}): Promise<any>;
|
|
821
|
+
signInWithDropbox(options?: {
|
|
822
|
+
returnTo?: string;
|
|
823
|
+
metaTag?: string;
|
|
824
|
+
}): Promise<any>;
|
|
825
|
+
handleSignInRedirect(autoRedirect?: boolean): Promise<(AuthResult & {
|
|
826
|
+
authToken: AuthToken;
|
|
827
|
+
provider?: ProviderId;
|
|
828
|
+
}) | null>;
|
|
829
|
+
handleSignInRedirect(authGuard: Pick<AuthGuard, 'handleRedirect'>, autoRedirect?: boolean): Promise<(AuthResult & {
|
|
830
|
+
authToken: AuthToken;
|
|
831
|
+
provider?: ProviderId;
|
|
832
|
+
}) | null>;
|
|
833
|
+
private exchangeProviderToken;
|
|
834
|
+
protected getAuthGuard(): Promise<AuthGuard>;
|
|
835
|
+
refreshAuthSession(refresh_token?: string): Promise<FlareAuthSession | null>;
|
|
836
|
+
issueSsrToken(ttlSeconds?: number): Promise<{
|
|
837
|
+
token: string;
|
|
838
|
+
token_type: string;
|
|
839
|
+
expires_in: number;
|
|
840
|
+
uid: string;
|
|
841
|
+
role: string;
|
|
842
|
+
email?: string;
|
|
843
|
+
}>;
|
|
844
|
+
signOut(): Promise<void>;
|
|
845
|
+
protected registerWithEmail(email: string, password: string, options?: {
|
|
846
|
+
scope?: string[];
|
|
847
|
+
additionalParams?: Record<string, string>;
|
|
848
|
+
signInIfAllowed?: boolean;
|
|
849
|
+
}): Promise<Record<string, any>>;
|
|
850
|
+
protected requestEmailPasswordToken(email: string, password: string, scope?: string[]): Promise<AuthToken>;
|
|
851
|
+
protected fetchAuthMe(token: string): Promise<{
|
|
852
|
+
id?: string;
|
|
853
|
+
email?: string | null;
|
|
854
|
+
email_verified?: boolean;
|
|
855
|
+
}>;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Client/index.ts ─ entry point
|
|
860
|
+
*
|
|
861
|
+
* FlareClient is the public-facing class. All logic lives in:
|
|
862
|
+
* - Client/base.ts → transport, subscriptions, presence, vector, offline
|
|
863
|
+
* - Client/auth.ts → CSRF capture, all auth & session methods
|
|
864
|
+
*
|
|
865
|
+
* CSRF Protection (SSR-Only by Default)
|
|
866
|
+
* ────────────────────────────────────
|
|
867
|
+
* FlareClient does NOT automatically fetch CSRF on construction. Instead:
|
|
868
|
+
*
|
|
869
|
+
* 1. SSR (Next.js): Middleware fetches /auth/config once, sets CSRF as HttpOnly
|
|
870
|
+
* cookie on response. Methods automatically use this cookie (no extra calls).
|
|
871
|
+
*
|
|
872
|
+
* 2. Browser-only (SPA): Explicitly call client.ensureCsrfProtection() before
|
|
873
|
+
* mutations to fetch /auth/config and cache CSRF token in memory.
|
|
874
|
+
*
|
|
875
|
+
* Why? Eliminates redundant /auth/config calls in SSR, where every method used
|
|
876
|
+
* to fetch it again internally. Now CSRF bootstrapping happens once (in middleware),
|
|
877
|
+
* and auth methods just use getCsrfHeaders() which returns the cached token
|
|
878
|
+
* (if available) or empty object (relying on HttpOnly cookie validation).
|
|
879
|
+
*
|
|
880
|
+
* This file wires FlareAuth together and leaves CSRF bootstrapping to the user.
|
|
881
|
+
*/
|
|
882
|
+
|
|
883
|
+
declare class FlareClient extends FlareAuth {
|
|
884
|
+
constructor(config: FlareConfig);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Client/proxy.ts ─ Next.js SSR CSRF proxy helper
|
|
889
|
+
*
|
|
890
|
+
* Why this exists
|
|
891
|
+
* ───────────────
|
|
892
|
+
* The Flare server now sets the CSRF token exclusively as an HttpOnly cookie
|
|
893
|
+
* on its own domain (e.g. api.flare.example.com) and also echoes it in the
|
|
894
|
+
* `x-flare-csrf` response header so the browser client can capture it in-
|
|
895
|
+
* memory and send it back as a request header.
|
|
896
|
+
*
|
|
897
|
+
* When your Next.js app runs server-side rendering (SSR / Route Handlers /
|
|
898
|
+
* Server Actions) it operates on a *different* domain (e.g. app.example.com).
|
|
899
|
+
* The browser's HttpOnly Flare cookie is scoped to the Flare domain and is
|
|
900
|
+
* therefore NOT automatically forwarded by the browser to your Next.js
|
|
901
|
+
* server-side fetch calls.
|
|
902
|
+
*
|
|
903
|
+
* Solution — two-cookie strategy
|
|
904
|
+
* ────────────────────────────────
|
|
905
|
+
* 1. Browser → Flare domain : Flare sets `__flare_csrf_<appId>` as HttpOnly
|
|
906
|
+
* on the Flare domain. Browser also reads the
|
|
907
|
+
* token from the `x-flare-csrf` header and keeps
|
|
908
|
+
* it in-memory for direct API calls.
|
|
909
|
+
*
|
|
910
|
+
* 2. Browser → Next.js domain : Mount `createCsrfProxy()` at a route such as
|
|
911
|
+
* `/api/flare/csrf`. On first browser load call
|
|
912
|
+
* this route; it hits Flare's /auth/config,
|
|
913
|
+
* reads the `x-flare-csrf` header, and sets
|
|
914
|
+
* it as an HttpOnly cookie on the *Next.js*
|
|
915
|
+
* domain too (`__flare_csrf_<appId>`).
|
|
916
|
+
* All subsequent SSR fetch calls from the
|
|
917
|
+
* Next.js server can then read this cookie from
|
|
918
|
+
* the incoming request and forward it as the
|
|
919
|
+
* `x-flare-csrf` header to Flare.
|
|
920
|
+
*
|
|
921
|
+
* Usage (Next.js App Router)
|
|
922
|
+
* ─────────────────────────────
|
|
923
|
+
* // app/api/flare/csrf/route.ts
|
|
924
|
+
* import { createCsrfProxy } from "@zuzjs/flare-client/proxy";
|
|
925
|
+
* export const GET = createCsrfProxy({ endpoint: "https://api.flare.example.com", appId: "my-app" });
|
|
926
|
+
*
|
|
927
|
+
* // In any Server Component / Route Handler:
|
|
928
|
+
* import { extractCsrfFromRequest, buildFlareHeaders } from "@zuzjs/flare-client/proxy";
|
|
929
|
+
* const csrf = extractCsrfFromRequest(request, "my-app");
|
|
930
|
+
* const res = await withGet(`${FLARE}/auth/whatever`, { headers: buildFlareHeaders(csrf), ignoreKind: true });
|
|
931
|
+
*
|
|
932
|
+
* Usage (Next.js Pages Router)
|
|
933
|
+
* ──────────────────────────────
|
|
934
|
+
* // pages/api/flare/csrf.ts
|
|
935
|
+
* import { createCsrfProxyHandler } from "@zuzjs/flare-client/proxy";
|
|
936
|
+
* export default createCsrfProxyHandler({ endpoint: "...", appId: "my-app" });
|
|
937
|
+
*/
|
|
938
|
+
interface CsrfProxyConfig {
|
|
939
|
+
/** Base URL of the Flare server, e.g. "https://api.flare.example.com" */
|
|
940
|
+
endpoint: string;
|
|
941
|
+
/** App ID passed to Flare's /auth/config */
|
|
942
|
+
appId: string;
|
|
943
|
+
/** Optional Flare API key */
|
|
944
|
+
apiKey?: string;
|
|
348
945
|
/**
|
|
349
|
-
*
|
|
946
|
+
* Name of the proxy cookie written on the Next.js domain.
|
|
947
|
+
* Defaults to `__flare_csrf_<appId>`.
|
|
350
948
|
*/
|
|
351
|
-
|
|
949
|
+
proxyCookieName?: string;
|
|
352
950
|
/**
|
|
353
|
-
*
|
|
951
|
+
* Max-Age for the proxy cookie in seconds.
|
|
952
|
+
* Defaults to 3600 (1 hour).
|
|
354
953
|
*/
|
|
355
|
-
|
|
356
|
-
private log;
|
|
954
|
+
proxyCookieMaxAge?: number;
|
|
357
955
|
}
|
|
956
|
+
/**
|
|
957
|
+
* Creates a Next.js App Router `GET` handler that:
|
|
958
|
+
* 1. Calls Flare's /auth/config and captures the `x-flare-csrf` header.
|
|
959
|
+
* 2. Sets it as `__flare_csrf_<appId>` HttpOnly cookie on the current domain.
|
|
960
|
+
* 3. Returns the token in JSON for the browser to store in-memory as well.
|
|
961
|
+
*
|
|
962
|
+
* Mount at: `app/api/flare/csrf/route.ts`
|
|
963
|
+
*/
|
|
964
|
+
declare function createCsrfProxy(config: CsrfProxyConfig): (_request: Request) => Promise<Response>;
|
|
965
|
+
/**
|
|
966
|
+
* Creates a Next.js Pages Router API handler (`pages/api/flare/csrf.ts`).
|
|
967
|
+
* Same behaviour as `createCsrfProxy()` but uses the Node.js req/res API.
|
|
968
|
+
*/
|
|
969
|
+
declare function createCsrfProxyHandler(config: CsrfProxyConfig): (req: any, res: any) => Promise<void>;
|
|
970
|
+
/**
|
|
971
|
+
* Extract the proxied CSRF token from an incoming Next.js request's cookies.
|
|
972
|
+
*
|
|
973
|
+
* Call this inside Server Components, Route Handlers, or `getServerSideProps`
|
|
974
|
+
* to retrieve the token that was set by `createCsrfProxy`.
|
|
975
|
+
*
|
|
976
|
+
* @example
|
|
977
|
+
* // App Router Route Handler
|
|
978
|
+
* const csrf = extractCsrfFromRequest(request, "my-app");
|
|
979
|
+
*/
|
|
980
|
+
declare function extractCsrfFromRequest(request: Request | {
|
|
981
|
+
cookies: Record<string, string> | {
|
|
982
|
+
get(name: string): {
|
|
983
|
+
value: string;
|
|
984
|
+
} | undefined;
|
|
985
|
+
};
|
|
986
|
+
}, appId: string, proxyCookieName?: string): string | null;
|
|
987
|
+
/**
|
|
988
|
+
* Build the headers object to attach to a server-side fetch call to Flare,
|
|
989
|
+
* forwarding the CSRF token and (optionally) the Authorization bearer token.
|
|
990
|
+
*/
|
|
991
|
+
declare function buildFlareHeaders(csrfToken: string | null, options?: {
|
|
992
|
+
accessToken?: string;
|
|
993
|
+
apiKey?: string;
|
|
994
|
+
}): Record<string, string>;
|
|
358
995
|
|
|
359
996
|
declare class FlareError extends Error {
|
|
360
997
|
readonly code: string;
|
|
@@ -362,6 +999,65 @@ declare class FlareError extends Error {
|
|
|
362
999
|
constructor(message: string, code: string, cause?: unknown | undefined);
|
|
363
1000
|
}
|
|
364
1001
|
|
|
1002
|
+
declare enum FlareErrors {
|
|
1003
|
+
authEmailNotVerified = "auth/email-not-verified",
|
|
1004
|
+
authEmailAlreadyVerified = "auth/email-already-verified",
|
|
1005
|
+
authInvalidToken = "auth/invalid-token",
|
|
1006
|
+
authUserDisabled = "auth/user-disabled",
|
|
1007
|
+
authUserNotFound = "auth/user-not-found",
|
|
1008
|
+
authWrongPassword = "auth/wrong-password",
|
|
1009
|
+
authEmailAlreadyInUse = "auth/email-already-in-use",
|
|
1010
|
+
authInvalidEmail = "auth/invalid-email",
|
|
1011
|
+
authWeakPassword = "auth/weak-password",
|
|
1012
|
+
authTooManyRequests = "auth/too-many-requests",
|
|
1013
|
+
authInternalError = "auth/internal-error"
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
declare enum FlareResponseCodes {
|
|
1017
|
+
health = "health",
|
|
1018
|
+
authConfig = "auth_config",
|
|
1019
|
+
authRegistration = "auth/registration",
|
|
1020
|
+
authRegistrationVerificationRequired = "auth/registration-verification-required",
|
|
1021
|
+
authSession = "auth/session",
|
|
1022
|
+
authExchange = "auth/exchange",
|
|
1023
|
+
authLogout = "auth/logout",
|
|
1024
|
+
authSsrBridge = "auth/ssr_bridge",
|
|
1025
|
+
authSsrVerify = "auth/ssr_verify",
|
|
1026
|
+
accountRecovery = "account/recovery",
|
|
1027
|
+
emailVerification = "email/verification",
|
|
1028
|
+
verificationDispatch = "verification/dispatch",
|
|
1029
|
+
authProfile = "auth/profile",
|
|
1030
|
+
adminToken = "admin/token",
|
|
1031
|
+
documentDelete = "document/delete",
|
|
1032
|
+
documentsDelete = "documents/delete",
|
|
1033
|
+
documents = "documents",
|
|
1034
|
+
document = "document",
|
|
1035
|
+
documentCreate = "document/create",
|
|
1036
|
+
documentUpdate = "document/update",
|
|
1037
|
+
oauthProviderResponse = "oauth_provider_response",
|
|
1038
|
+
success = "success",
|
|
1039
|
+
response = "response"
|
|
1040
|
+
}
|
|
1041
|
+
interface AuthConfigResponse {
|
|
1042
|
+
kind: string;
|
|
1043
|
+
appId: string;
|
|
1044
|
+
enabled: boolean;
|
|
1045
|
+
csrfToken?: string;
|
|
1046
|
+
cookie: {
|
|
1047
|
+
accessTokenName: string;
|
|
1048
|
+
refreshTokenName: string;
|
|
1049
|
+
csrfTokenName: string;
|
|
1050
|
+
path: string;
|
|
1051
|
+
secure: boolean;
|
|
1052
|
+
sameSite: 'Strict' | 'Lax' | 'None';
|
|
1053
|
+
accessTokenMaxAge: number;
|
|
1054
|
+
refreshTokenMaxAge: number;
|
|
1055
|
+
csrfTokenMaxAge: number;
|
|
1056
|
+
};
|
|
1057
|
+
providers: Record<string, any>;
|
|
1058
|
+
ssr: Record<string, any>;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
365
1061
|
/**
|
|
366
1062
|
* Initialize and connect to FlareServer
|
|
367
1063
|
* Returns a singleton instance
|
|
@@ -376,4 +1072,4 @@ declare const getFlare: () => FlareClient | null;
|
|
|
376
1072
|
*/
|
|
377
1073
|
declare const disconnectFlare: () => void;
|
|
378
1074
|
|
|
379
|
-
export { type AuthResult, type BaseMessage, CollectionReference, type ConnectionState, DocumentQueryBuilder, DocumentReference, type DocumentSnapshot, FlareAction, type FlareConfig, FlareError, FlareEvent, type OfflineOperation, type QueryConfig, type QueryOperator, type QuerySnapshot, type SubscriptionCallback, type SubscriptionData, type WhereCondition, connectApp, FlareClient as default, disconnectFlare, getFlare };
|
|
1075
|
+
export { type AggregateFunction, type AggregateSpec, type AnyFilter, type AuthConfigListener, type AuthConfigResponse, type AuthResult, type AuthStateListener, type AuthWithPendingVerificationResult, type AuthWithTokenResult, type BaseMessage, type ChangeEvent, type ChangeOperation, CollectionReference, type ConnectionState, type CsrfProxyConfig, type CursorValue, type DocAddedCallback, type DocChangedCallback, type DocDeletedCallback, type DocUpdatedCallback, DocumentQueryBuilder, DocumentReference, type DocumentSnapshot, FlareAction, type FlareAuthConfig, type FlareAuthProviderId, type FlareAuthProviderPublicConfig, type FlareAuthSession, type FlareConfig, FlareError, FlareErrors, FlareEvent, FlareResponseCodes, type GroupByClause, type HavingClause, type JoinClause, type OfflineOperation, type OrFilter, type OrderByClause, type PresenceCallback, type PresenceJoinCallback, type PresenceLeaveCallback, type PresenceMember, type QueryConfig, type QueryOperator, type QuerySnapshot, type SnapshotEvent, type StructuredQuery, type SubscribeMessage, type SubscribeOptions, type SubscriptionCallback, type SubscriptionData, type VectorFieldConfig, type VectorSearchClause, type WhereCondition, buildFlareHeaders, connectApp, createCsrfProxy, createCsrfProxyHandler, FlareClient as default, disconnectFlare, extractCsrfFromRequest, getFlare, parseValue, parseWhereCondition };
|