@edge-base/react-native 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 +230 -0
- package/dist/index.cjs +3137 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1246 -0
- package/dist/index.d.ts +1246 -0
- package/dist/index.js +3118 -0
- package/dist/index.js.map +1 -0
- package/llms.txt +124 -0
- package/package.json +60 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1246 @@
|
|
|
1
|
+
import { HttpClient, GeneratedDbApi, IDatabaseLiveSubscriber, ContextManager, StorageClient, FunctionsClient, DbRef, ContextValue } from '@edge-base/core';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Token management for React Native — AsyncStorage based.
|
|
6
|
+
*: Access Token in memory, Refresh Token in persistent storage
|
|
7
|
+
*: onAuthStateChange
|
|
8
|
+
*
|
|
9
|
+
* Key differences from @edge-base/web TokenManager:
|
|
10
|
+
* - Uses AsyncStorage instead of localStorage (async reads/writes)
|
|
11
|
+
* - No BroadcastChannel (no multi-tab in RN)
|
|
12
|
+
* - No storage event listener (RN has no cross-tab concept)
|
|
13
|
+
* - Simpler: single-tab, single-process model
|
|
14
|
+
*/
|
|
15
|
+
/** Minimal interface compatible with @react-native-async-storage/async-storage */
|
|
16
|
+
interface AsyncStorageAdapter {
|
|
17
|
+
getItem(key: string): Promise<string | null>;
|
|
18
|
+
setItem(key: string, value: string): Promise<void>;
|
|
19
|
+
removeItem(key: string): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
interface TokenPair {
|
|
22
|
+
accessToken: string;
|
|
23
|
+
refreshToken: string;
|
|
24
|
+
}
|
|
25
|
+
interface TokenUser {
|
|
26
|
+
id: string;
|
|
27
|
+
email?: string;
|
|
28
|
+
displayName?: string;
|
|
29
|
+
avatarUrl?: string;
|
|
30
|
+
role?: string;
|
|
31
|
+
isAnonymous?: boolean;
|
|
32
|
+
emailVisibility?: string;
|
|
33
|
+
custom?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
type AuthStateChangeHandler = (user: TokenUser | null) => void;
|
|
36
|
+
declare class TokenManager {
|
|
37
|
+
private baseUrl;
|
|
38
|
+
private accessToken;
|
|
39
|
+
private refreshToken;
|
|
40
|
+
private refreshPromise;
|
|
41
|
+
private authStateListeners;
|
|
42
|
+
private cachedUser;
|
|
43
|
+
private storage;
|
|
44
|
+
private initialized;
|
|
45
|
+
private initPromise;
|
|
46
|
+
constructor(baseUrl: string, storage: AsyncStorageAdapter);
|
|
47
|
+
/** Wait for storage restore to complete */
|
|
48
|
+
ready(): Promise<void>;
|
|
49
|
+
private restore;
|
|
50
|
+
/** Get valid access token, refreshing if needed */
|
|
51
|
+
getAccessToken(doRefresh: (refreshToken: string) => Promise<TokenPair>): Promise<string | null>;
|
|
52
|
+
/** Set tokens after successful auth (sync in-memory + async persist) */
|
|
53
|
+
setTokens(tokens: TokenPair): void;
|
|
54
|
+
/** Get stored refresh token (sync from memory cache) */
|
|
55
|
+
getRefreshToken(): string | null;
|
|
56
|
+
/** Drop the current access token so the next request must refresh or fail fast. */
|
|
57
|
+
invalidateAccessToken(): void;
|
|
58
|
+
/** Read-only access to current access token (for websocket re-auth). */
|
|
59
|
+
get currentAccessToken(): string | null;
|
|
60
|
+
/** Clear all tokens on sign-out */
|
|
61
|
+
clearTokens(): void;
|
|
62
|
+
/** Get current user (from cached JWT payload) */
|
|
63
|
+
getCurrentUser(): TokenUser | null;
|
|
64
|
+
/** Subscribe to auth state changes. Fires immediately with current state. */
|
|
65
|
+
onAuthStateChange(handler: AuthStateChangeHandler): () => void;
|
|
66
|
+
private updateUser;
|
|
67
|
+
private emitAuthStateChange;
|
|
68
|
+
/** Clean up (no-op in RN, kept for API parity with web SDK) */
|
|
69
|
+
destroy(): void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Auth client for React Native — API parity with @edge-base/web AuthClient.
|
|
74
|
+
*: onAuthStateChange
|
|
75
|
+
*: signInAnonymously
|
|
76
|
+
*: signUp with data
|
|
77
|
+
*: React Native OAuth via Linking API + deep link callback
|
|
78
|
+
*
|
|
79
|
+
* Key differences from web AuthClient:
|
|
80
|
+
* - signInWithOAuth uses Linking.openURL() instead of window.location.href
|
|
81
|
+
* - Handles deep link OAuth callback via Linking.addEventListener
|
|
82
|
+
* - TokenManager.getRefreshToken() is async (AsyncStorage)
|
|
83
|
+
* - Captcha is handled via TurnstileWebView component (see turnstile.tsx)
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
interface SignUpOptions {
|
|
87
|
+
email: string;
|
|
88
|
+
password: string;
|
|
89
|
+
data?: {
|
|
90
|
+
displayName?: string;
|
|
91
|
+
avatarUrl?: string;
|
|
92
|
+
[key: string]: unknown;
|
|
93
|
+
};
|
|
94
|
+
/** Captcha token from TurnstileWebView */
|
|
95
|
+
captchaToken?: string;
|
|
96
|
+
}
|
|
97
|
+
interface SignInOptions {
|
|
98
|
+
email: string;
|
|
99
|
+
password: string;
|
|
100
|
+
/** Captcha token from TurnstileWebView */
|
|
101
|
+
captchaToken?: string;
|
|
102
|
+
}
|
|
103
|
+
interface AuthResult {
|
|
104
|
+
user: TokenUser;
|
|
105
|
+
accessToken: string;
|
|
106
|
+
refreshToken: string;
|
|
107
|
+
}
|
|
108
|
+
/** Returned when MFA is required during sign-in */
|
|
109
|
+
interface MfaRequiredResult {
|
|
110
|
+
mfaRequired: true;
|
|
111
|
+
mfaTicket: string;
|
|
112
|
+
factors: MfaFactor[];
|
|
113
|
+
}
|
|
114
|
+
interface MfaFactor {
|
|
115
|
+
id: string;
|
|
116
|
+
type: string;
|
|
117
|
+
}
|
|
118
|
+
type SignInResult = AuthResult | MfaRequiredResult;
|
|
119
|
+
interface TotpEnrollResult {
|
|
120
|
+
factorId: string;
|
|
121
|
+
secret: string;
|
|
122
|
+
qrCodeUri: string;
|
|
123
|
+
recoveryCodes: string[];
|
|
124
|
+
}
|
|
125
|
+
interface DisableTotpOptions {
|
|
126
|
+
password?: string;
|
|
127
|
+
code?: string;
|
|
128
|
+
}
|
|
129
|
+
interface Session {
|
|
130
|
+
id: string;
|
|
131
|
+
createdAt: string;
|
|
132
|
+
userAgent?: string;
|
|
133
|
+
ip?: string;
|
|
134
|
+
}
|
|
135
|
+
interface UpdateProfileOptions {
|
|
136
|
+
displayName?: string;
|
|
137
|
+
avatarUrl?: string;
|
|
138
|
+
emailVisibility?: string;
|
|
139
|
+
}
|
|
140
|
+
interface PasskeysAuthOptions {
|
|
141
|
+
email?: string;
|
|
142
|
+
}
|
|
143
|
+
interface LinkedIdentity {
|
|
144
|
+
id: string;
|
|
145
|
+
kind: 'oauth';
|
|
146
|
+
provider: string;
|
|
147
|
+
providerUserId: string;
|
|
148
|
+
createdAt: string;
|
|
149
|
+
canUnlink: boolean;
|
|
150
|
+
}
|
|
151
|
+
interface IdentityMethods {
|
|
152
|
+
total: number;
|
|
153
|
+
hasPassword: boolean;
|
|
154
|
+
hasMagicLink: boolean;
|
|
155
|
+
hasEmailOtp: boolean;
|
|
156
|
+
hasPhone: boolean;
|
|
157
|
+
passkeyCount: number;
|
|
158
|
+
oauthCount: number;
|
|
159
|
+
email?: string | null;
|
|
160
|
+
phone?: string | null;
|
|
161
|
+
}
|
|
162
|
+
interface IdentitiesResult {
|
|
163
|
+
ok?: boolean;
|
|
164
|
+
identities: LinkedIdentity[];
|
|
165
|
+
methods: IdentityMethods;
|
|
166
|
+
}
|
|
167
|
+
/** Minimal Linking interface — compatible with react-native Linking API */
|
|
168
|
+
interface LinkingAdapter {
|
|
169
|
+
openURL(url: string): Promise<void>;
|
|
170
|
+
addEventListener(type: 'url', handler: (event: {
|
|
171
|
+
url: string;
|
|
172
|
+
}) => void): {
|
|
173
|
+
remove: () => void;
|
|
174
|
+
};
|
|
175
|
+
getInitialURL(): Promise<string | null>;
|
|
176
|
+
}
|
|
177
|
+
type OAuthStartOptions = {
|
|
178
|
+
provider: string;
|
|
179
|
+
redirectUrl?: string;
|
|
180
|
+
captchaToken?: string;
|
|
181
|
+
};
|
|
182
|
+
declare class AuthClient {
|
|
183
|
+
private client;
|
|
184
|
+
private tokenManager;
|
|
185
|
+
private core;
|
|
186
|
+
private corePublic;
|
|
187
|
+
private linking?;
|
|
188
|
+
private baseUrl;
|
|
189
|
+
constructor(client: HttpClient, tokenManager: TokenManager, core: GeneratedDbApi, corePublic: GeneratedDbApi, linking?: LinkingAdapter | undefined);
|
|
190
|
+
/** Register a new user. Optionally include user metadata. */
|
|
191
|
+
signUp(options: SignUpOptions): Promise<AuthResult>;
|
|
192
|
+
/** Sign in with email and password. Returns MfaRequiredResult if MFA is enabled. */
|
|
193
|
+
signIn(options: SignInOptions): Promise<SignInResult>;
|
|
194
|
+
/** Sign out — revokes current session on server and clears local tokens. */
|
|
195
|
+
signOut(): Promise<void>;
|
|
196
|
+
/** Refresh the current session using the stored refresh token. */
|
|
197
|
+
refreshSession(): Promise<AuthResult>;
|
|
198
|
+
/**
|
|
199
|
+
* Start OAuth sign-in flow.
|
|
200
|
+
* Opens the OAuth URL via Linking.openURL() and listens for the deep link callback.
|
|
201
|
+
* The app must be configured with a deep link scheme (e.g. myapp://auth/callback).
|
|
202
|
+
*
|
|
203
|
+
* @param provider - OAuth provider name (e.g. 'google', 'github')
|
|
204
|
+
* @param options.redirectUrl - Deep link URL to redirect back to after OAuth (required for RN)
|
|
205
|
+
* @param options.captchaToken - Optional captcha token
|
|
206
|
+
* @returns Promise that resolves with AuthResult when OAuth completes
|
|
207
|
+
*
|
|
208
|
+
* NOTE: Not delegated to Generated Core — this is URL construction + redirect, not a standard HTTP call.
|
|
209
|
+
*/
|
|
210
|
+
signInWithOAuth(providerOrOptions: string | OAuthStartOptions, options?: {
|
|
211
|
+
redirectUrl?: string;
|
|
212
|
+
captchaToken?: string;
|
|
213
|
+
}): {
|
|
214
|
+
url: string;
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* Handle OAuth deep link callback.
|
|
218
|
+
* Call this when your app receives a deep link URL with auth tokens.
|
|
219
|
+
* Extract tokens from query params and store them.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* // In your navigation/linking config:
|
|
223
|
+
* Linking.addEventListener('url', ({ url }) => client.auth.handleOAuthCallback(url));
|
|
224
|
+
*/
|
|
225
|
+
handleOAuthCallback(url: string): Promise<AuthResult | null>;
|
|
226
|
+
/** Sign in anonymously. */
|
|
227
|
+
signInAnonymously(options?: {
|
|
228
|
+
captchaToken?: string;
|
|
229
|
+
}): Promise<AuthResult>;
|
|
230
|
+
/**
|
|
231
|
+
* Send a magic link (passwordless login) email.
|
|
232
|
+
* If the email is not registered and autoCreate is enabled (server config), a new account is created.
|
|
233
|
+
*/
|
|
234
|
+
signInWithMagicLink(options: {
|
|
235
|
+
email: string;
|
|
236
|
+
captchaToken?: string;
|
|
237
|
+
}): Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* Verify a magic link token and sign in.
|
|
240
|
+
* Called after user clicks the link from their email.
|
|
241
|
+
*/
|
|
242
|
+
verifyMagicLink(token: string): Promise<AuthResult>;
|
|
243
|
+
/**
|
|
244
|
+
* Send an SMS verification code to the given phone number.
|
|
245
|
+
* If the phone is not registered and autoCreate is enabled (server config), a new account is created on verify.
|
|
246
|
+
*/
|
|
247
|
+
signInWithPhone(options: {
|
|
248
|
+
phone: string;
|
|
249
|
+
captchaToken?: string;
|
|
250
|
+
}): Promise<void>;
|
|
251
|
+
/**
|
|
252
|
+
* Verify the SMS code and sign in.
|
|
253
|
+
* Called after user receives the code from signInWithPhone.
|
|
254
|
+
*/
|
|
255
|
+
verifyPhone(options: {
|
|
256
|
+
phone: string;
|
|
257
|
+
code: string;
|
|
258
|
+
}): Promise<AuthResult>;
|
|
259
|
+
/** Link current account with a phone number. Sends an SMS code. */
|
|
260
|
+
linkWithPhone(options: {
|
|
261
|
+
phone: string;
|
|
262
|
+
}): Promise<void>;
|
|
263
|
+
/** Verify phone link code. Completes phone linking for the current account. */
|
|
264
|
+
verifyLinkPhone(options: {
|
|
265
|
+
phone: string;
|
|
266
|
+
code: string;
|
|
267
|
+
}): Promise<void>;
|
|
268
|
+
/** Link anonymous account to email/password. */
|
|
269
|
+
linkWithEmail(options: {
|
|
270
|
+
email: string;
|
|
271
|
+
password: string;
|
|
272
|
+
}): Promise<AuthResult>;
|
|
273
|
+
/**
|
|
274
|
+
* Link anonymous account to OAuth provider. Returns URL to open in browser.
|
|
275
|
+
*
|
|
276
|
+
* NOTE: Not delegated — Generated Core's oauthLinkStart(provider) takes no body,
|
|
277
|
+
* but we need to pass { redirectUrl }.
|
|
278
|
+
*/
|
|
279
|
+
linkWithOAuth(providerOrOptions: string | {
|
|
280
|
+
provider: string;
|
|
281
|
+
redirectUrl?: string;
|
|
282
|
+
}, options?: {
|
|
283
|
+
redirectUrl?: string;
|
|
284
|
+
}): Promise<{
|
|
285
|
+
redirectUrl: string;
|
|
286
|
+
}>;
|
|
287
|
+
/** Subscribe to authentication state changes. */
|
|
288
|
+
onAuthStateChange(callback: AuthStateChangeHandler): () => void;
|
|
289
|
+
/** Get current authenticated user (from cached JWT). */
|
|
290
|
+
get currentUser(): TokenUser | null;
|
|
291
|
+
/** List active sessions. */
|
|
292
|
+
listSessions(): Promise<Session[]>;
|
|
293
|
+
/** Revoke a specific session. */
|
|
294
|
+
revokeSession(sessionId: string): Promise<void>;
|
|
295
|
+
/** Update current user's profile. */
|
|
296
|
+
updateProfile(data: UpdateProfileOptions): Promise<TokenUser>;
|
|
297
|
+
/** Verify email address with token. */
|
|
298
|
+
verifyEmail(token: string): Promise<void>;
|
|
299
|
+
/** Request a verification email for the current user. */
|
|
300
|
+
requestEmailVerification(options?: {
|
|
301
|
+
redirectUrl?: string;
|
|
302
|
+
}): Promise<void>;
|
|
303
|
+
/** Verify a pending email change using the emailed token. */
|
|
304
|
+
verifyEmailChange(token: string): Promise<void>;
|
|
305
|
+
/** Request password reset email. */
|
|
306
|
+
requestPasswordReset(email: string, options?: {
|
|
307
|
+
captchaToken?: string;
|
|
308
|
+
}): Promise<void>;
|
|
309
|
+
/** Reset password with token. */
|
|
310
|
+
resetPassword(token: string, newPassword: string): Promise<void>;
|
|
311
|
+
/** Change password for authenticated user. */
|
|
312
|
+
changePassword(options: {
|
|
313
|
+
currentPassword: string;
|
|
314
|
+
newPassword: string;
|
|
315
|
+
}): Promise<AuthResult>;
|
|
316
|
+
/** Request an email change for the authenticated user. */
|
|
317
|
+
changeEmail(options: {
|
|
318
|
+
newEmail: string;
|
|
319
|
+
password: string;
|
|
320
|
+
redirectUrl?: string;
|
|
321
|
+
}): Promise<void>;
|
|
322
|
+
/** List linked sign-in identities for the current user. */
|
|
323
|
+
listIdentities(): Promise<IdentitiesResult>;
|
|
324
|
+
/** Unlink a linked OAuth identity by its identity ID. */
|
|
325
|
+
unlinkIdentity(identityId: string): Promise<IdentitiesResult>;
|
|
326
|
+
/** Send an email OTP code for sign-in. */
|
|
327
|
+
signInWithEmailOtp(options: {
|
|
328
|
+
email: string;
|
|
329
|
+
}): Promise<void>;
|
|
330
|
+
/** Verify an email OTP code and sign in. */
|
|
331
|
+
verifyEmailOtp(options: {
|
|
332
|
+
email: string;
|
|
333
|
+
code: string;
|
|
334
|
+
}): Promise<AuthResult>;
|
|
335
|
+
/** Generate WebAuthn registration options for the current authenticated user. */
|
|
336
|
+
passkeysRegisterOptions(): Promise<unknown>;
|
|
337
|
+
/** Verify and store a passkey registration response from the platform credential API. */
|
|
338
|
+
passkeysRegister(response: unknown): Promise<unknown>;
|
|
339
|
+
/** Generate WebAuthn authentication options. */
|
|
340
|
+
passkeysAuthOptions(options?: PasskeysAuthOptions): Promise<unknown>;
|
|
341
|
+
/** Verify a WebAuthn assertion and establish a session. */
|
|
342
|
+
passkeysAuthenticate(response: unknown): Promise<AuthResult>;
|
|
343
|
+
/** List registered passkeys for the current authenticated user. */
|
|
344
|
+
passkeysList(): Promise<unknown>;
|
|
345
|
+
/** Delete a registered passkey by credential ID. */
|
|
346
|
+
passkeysDelete(credentialId: string): Promise<unknown>;
|
|
347
|
+
/** MFA sub-namespace for TOTP enrollment, verification, and management. */
|
|
348
|
+
get mfa(): {
|
|
349
|
+
/** Enroll TOTP — returns secret, QR code URI, and recovery codes. */
|
|
350
|
+
enrollTotp(): Promise<TotpEnrollResult>;
|
|
351
|
+
/** Verify TOTP enrollment with factorId and a TOTP code. */
|
|
352
|
+
verifyTotpEnrollment(factorId: string, code: string): Promise<{
|
|
353
|
+
ok: true;
|
|
354
|
+
}>;
|
|
355
|
+
/** Verify TOTP code during MFA challenge (after signIn returns mfaRequired). */
|
|
356
|
+
verifyTotp(mfaTicket: string, code: string): Promise<AuthResult>;
|
|
357
|
+
/** Use a recovery code during MFA challenge. */
|
|
358
|
+
useRecoveryCode(mfaTicket: string, recoveryCode: string): Promise<AuthResult>;
|
|
359
|
+
/**
|
|
360
|
+
* Disable TOTP for the current user. Requires password or TOTP code.
|
|
361
|
+
*/
|
|
362
|
+
disableTotp(options?: DisableTotpOptions): Promise<{
|
|
363
|
+
ok: true;
|
|
364
|
+
}>;
|
|
365
|
+
/** List enrolled MFA factors for the current user. */
|
|
366
|
+
listFactors(): Promise<{
|
|
367
|
+
factors: MfaFactor[];
|
|
368
|
+
}>;
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
type ChangeType = 'added' | 'modified' | 'removed';
|
|
373
|
+
type ErrorHandler$1 = (error: {
|
|
374
|
+
code: string;
|
|
375
|
+
message: string;
|
|
376
|
+
}) => void;
|
|
377
|
+
interface DbChange<T = Record<string, unknown>> {
|
|
378
|
+
changeType: ChangeType;
|
|
379
|
+
table: string;
|
|
380
|
+
docId: string;
|
|
381
|
+
data: T | null;
|
|
382
|
+
timestamp: string;
|
|
383
|
+
}
|
|
384
|
+
interface DatabaseLiveOptions {
|
|
385
|
+
autoReconnect?: boolean;
|
|
386
|
+
maxReconnectAttempts?: number;
|
|
387
|
+
reconnectBaseDelay?: number;
|
|
388
|
+
}
|
|
389
|
+
declare class DatabaseLiveClient implements IDatabaseLiveSubscriber {
|
|
390
|
+
private baseUrl;
|
|
391
|
+
private tokenManager;
|
|
392
|
+
private ws;
|
|
393
|
+
private connectingPromise;
|
|
394
|
+
private subscriptions;
|
|
395
|
+
private connectedChannels;
|
|
396
|
+
private channelFilters;
|
|
397
|
+
private channelOrFilters;
|
|
398
|
+
private errorHandlers;
|
|
399
|
+
private reconnectAttempts;
|
|
400
|
+
private connected;
|
|
401
|
+
private authenticated;
|
|
402
|
+
private waitingForAuth;
|
|
403
|
+
private authRecoveryPromise;
|
|
404
|
+
private heartbeatTimer;
|
|
405
|
+
private unsubAuthState;
|
|
406
|
+
private options;
|
|
407
|
+
constructor(baseUrl: string, tokenManager: TokenManager, options?: DatabaseLiveOptions, contextManager?: ContextManager);
|
|
408
|
+
onSnapshot<T>(channel: string, callback: (change: DbChange<T>) => void, clientFilters?: unknown, serverFilters?: unknown, serverOrFilters?: unknown): () => void;
|
|
409
|
+
onError(handler: ErrorHandler$1): () => void;
|
|
410
|
+
connect(channel: string): Promise<void>;
|
|
411
|
+
reconnect(): void;
|
|
412
|
+
disconnect(): void;
|
|
413
|
+
private establishConnection;
|
|
414
|
+
private authenticate;
|
|
415
|
+
private handleMessage;
|
|
416
|
+
private sendSubscribe;
|
|
417
|
+
private sendUnsubscribe;
|
|
418
|
+
private resubscribeAll;
|
|
419
|
+
private refreshAuth;
|
|
420
|
+
private handleAuthStateChange;
|
|
421
|
+
private handleAuthenticationFailure;
|
|
422
|
+
private resyncFilters;
|
|
423
|
+
private scheduleReconnect;
|
|
424
|
+
private buildWsUrl;
|
|
425
|
+
private sendRaw;
|
|
426
|
+
private hasAuthContext;
|
|
427
|
+
private recoverAuthentication;
|
|
428
|
+
private startHeartbeat;
|
|
429
|
+
private stopHeartbeat;
|
|
430
|
+
private handleContextChange;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* RoomClient v2 — Client-side room connection for real-time multiplayer state.
|
|
435
|
+
*
|
|
436
|
+
*: Complete redesign from v1.
|
|
437
|
+
* - 3 state areas: sharedState (all clients), playerState (per-player), serverState (server-only, not sent)
|
|
438
|
+
* - Client can only read + subscribe + send(). All writes are server-only.
|
|
439
|
+
* - send() returns a Promise resolved by requestId matching
|
|
440
|
+
* - Subscription returns { unsubscribe() } object
|
|
441
|
+
* - namespace + roomId identification (replaces single roomId)
|
|
442
|
+
*/
|
|
443
|
+
|
|
444
|
+
interface RoomOptions {
|
|
445
|
+
/** Auto-reconnect on disconnect (default: true) */
|
|
446
|
+
autoReconnect?: boolean;
|
|
447
|
+
/** Max reconnect attempts (default: 10) */
|
|
448
|
+
maxReconnectAttempts?: number;
|
|
449
|
+
/** Base delay for reconnect backoff in ms (default: 1000) */
|
|
450
|
+
reconnectBaseDelay?: number;
|
|
451
|
+
/** Timeout for send() requests in ms (default: 10000) */
|
|
452
|
+
sendTimeout?: number;
|
|
453
|
+
}
|
|
454
|
+
interface Subscription {
|
|
455
|
+
unsubscribe(): void;
|
|
456
|
+
}
|
|
457
|
+
type SharedStateHandler = (state: Record<string, unknown>, changes: Record<string, unknown>) => void;
|
|
458
|
+
type PlayerStateHandler = (state: Record<string, unknown>, changes: Record<string, unknown>) => void;
|
|
459
|
+
type MessageHandler = (data: unknown) => void;
|
|
460
|
+
type ErrorHandler = (error: {
|
|
461
|
+
code: string;
|
|
462
|
+
message: string;
|
|
463
|
+
}) => void;
|
|
464
|
+
type KickedHandler = () => void;
|
|
465
|
+
type RoomConnectionState = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'auth_lost' | 'kicked';
|
|
466
|
+
type RoomMemberLeaveReason = 'leave' | 'timeout' | 'kicked';
|
|
467
|
+
interface RoomSignalMeta {
|
|
468
|
+
memberId?: string | null;
|
|
469
|
+
userId?: string | null;
|
|
470
|
+
connectionId?: string | null;
|
|
471
|
+
sentAt?: number;
|
|
472
|
+
serverSent?: boolean;
|
|
473
|
+
}
|
|
474
|
+
interface RoomMember {
|
|
475
|
+
memberId: string;
|
|
476
|
+
userId: string;
|
|
477
|
+
connectionId?: string;
|
|
478
|
+
connectionCount?: number;
|
|
479
|
+
role?: string;
|
|
480
|
+
state: Record<string, unknown>;
|
|
481
|
+
}
|
|
482
|
+
interface RoomReconnectInfo {
|
|
483
|
+
attempt: number;
|
|
484
|
+
}
|
|
485
|
+
type RoomMediaKind = 'audio' | 'video' | 'screen';
|
|
486
|
+
interface RoomMediaTrack {
|
|
487
|
+
kind: RoomMediaKind;
|
|
488
|
+
trackId?: string;
|
|
489
|
+
deviceId?: string;
|
|
490
|
+
muted: boolean;
|
|
491
|
+
publishedAt?: number;
|
|
492
|
+
adminDisabled?: boolean;
|
|
493
|
+
}
|
|
494
|
+
interface RoomMemberMediaKindState {
|
|
495
|
+
published: boolean;
|
|
496
|
+
muted: boolean;
|
|
497
|
+
trackId?: string;
|
|
498
|
+
deviceId?: string;
|
|
499
|
+
publishedAt?: number;
|
|
500
|
+
adminDisabled?: boolean;
|
|
501
|
+
}
|
|
502
|
+
interface RoomMemberMediaState {
|
|
503
|
+
audio?: RoomMemberMediaKindState;
|
|
504
|
+
video?: RoomMemberMediaKindState;
|
|
505
|
+
screen?: RoomMemberMediaKindState;
|
|
506
|
+
}
|
|
507
|
+
interface RoomMediaMember {
|
|
508
|
+
member: RoomMember;
|
|
509
|
+
state: RoomMemberMediaState;
|
|
510
|
+
tracks: RoomMediaTrack[];
|
|
511
|
+
}
|
|
512
|
+
interface RoomMediaDeviceChange {
|
|
513
|
+
kind: RoomMediaKind;
|
|
514
|
+
deviceId: string;
|
|
515
|
+
}
|
|
516
|
+
declare class RoomClient {
|
|
517
|
+
private baseUrl;
|
|
518
|
+
private tokenManager;
|
|
519
|
+
private options;
|
|
520
|
+
/** Room namespace (e.g. 'game', 'chat') */
|
|
521
|
+
readonly namespace: string;
|
|
522
|
+
/** Room instance ID within the namespace */
|
|
523
|
+
readonly roomId: string;
|
|
524
|
+
private _sharedState;
|
|
525
|
+
private _sharedVersion;
|
|
526
|
+
private _playerState;
|
|
527
|
+
private _playerVersion;
|
|
528
|
+
private _members;
|
|
529
|
+
private _mediaMembers;
|
|
530
|
+
private ws;
|
|
531
|
+
private reconnectAttempts;
|
|
532
|
+
private connected;
|
|
533
|
+
private authenticated;
|
|
534
|
+
private joined;
|
|
535
|
+
private currentUserId;
|
|
536
|
+
private currentConnectionId;
|
|
537
|
+
private connectionState;
|
|
538
|
+
private reconnectInfo;
|
|
539
|
+
private connectingPromise;
|
|
540
|
+
private heartbeatTimer;
|
|
541
|
+
private intentionallyLeft;
|
|
542
|
+
private waitingForAuth;
|
|
543
|
+
private joinRequested;
|
|
544
|
+
private unsubAuthState;
|
|
545
|
+
private pendingRequests;
|
|
546
|
+
private pendingSignalRequests;
|
|
547
|
+
private pendingAdminRequests;
|
|
548
|
+
private pendingMemberStateRequests;
|
|
549
|
+
private pendingMediaRequests;
|
|
550
|
+
private sharedStateHandlers;
|
|
551
|
+
private playerStateHandlers;
|
|
552
|
+
private messageHandlers;
|
|
553
|
+
private allMessageHandlers;
|
|
554
|
+
private errorHandlers;
|
|
555
|
+
private kickedHandlers;
|
|
556
|
+
private memberSyncHandlers;
|
|
557
|
+
private memberJoinHandlers;
|
|
558
|
+
private memberLeaveHandlers;
|
|
559
|
+
private memberStateHandlers;
|
|
560
|
+
private signalHandlers;
|
|
561
|
+
private anySignalHandlers;
|
|
562
|
+
private mediaTrackHandlers;
|
|
563
|
+
private mediaTrackRemovedHandlers;
|
|
564
|
+
private mediaStateHandlers;
|
|
565
|
+
private mediaDeviceHandlers;
|
|
566
|
+
private reconnectHandlers;
|
|
567
|
+
private connectionStateHandlers;
|
|
568
|
+
readonly state: {
|
|
569
|
+
getShared: () => Record<string, unknown>;
|
|
570
|
+
getMine: () => Record<string, unknown>;
|
|
571
|
+
onSharedChange: (handler: SharedStateHandler) => Subscription;
|
|
572
|
+
onMineChange: (handler: PlayerStateHandler) => Subscription;
|
|
573
|
+
send: (actionType: string, payload?: unknown) => Promise<unknown>;
|
|
574
|
+
};
|
|
575
|
+
readonly meta: {
|
|
576
|
+
get: () => Promise<Record<string, unknown>>;
|
|
577
|
+
};
|
|
578
|
+
readonly signals: {
|
|
579
|
+
send: (event: string, payload?: unknown, options?: {
|
|
580
|
+
includeSelf?: boolean;
|
|
581
|
+
}) => Promise<void>;
|
|
582
|
+
sendTo: (memberId: string, event: string, payload?: unknown) => Promise<void>;
|
|
583
|
+
on: (event: string, handler: (payload: unknown, meta: RoomSignalMeta) => void) => Subscription;
|
|
584
|
+
onAny: (handler: (event: string, payload: unknown, meta: RoomSignalMeta) => void) => Subscription;
|
|
585
|
+
};
|
|
586
|
+
readonly members: {
|
|
587
|
+
list: () => RoomMember[];
|
|
588
|
+
onSync: (handler: (members: RoomMember[]) => void) => Subscription;
|
|
589
|
+
onJoin: (handler: (member: RoomMember) => void) => Subscription;
|
|
590
|
+
onLeave: (handler: (member: RoomMember, reason: RoomMemberLeaveReason) => void) => Subscription;
|
|
591
|
+
setState: (state: Record<string, unknown>) => Promise<void>;
|
|
592
|
+
clearState: () => Promise<void>;
|
|
593
|
+
onStateChange: (handler: (member: RoomMember, state: Record<string, unknown>) => void) => Subscription;
|
|
594
|
+
};
|
|
595
|
+
readonly admin: {
|
|
596
|
+
kick: (memberId: string) => Promise<void>;
|
|
597
|
+
mute: (memberId: string) => Promise<void>;
|
|
598
|
+
block: (memberId: string) => Promise<void>;
|
|
599
|
+
setRole: (memberId: string, role: string) => Promise<void>;
|
|
600
|
+
disableVideo: (memberId: string) => Promise<void>;
|
|
601
|
+
stopScreenShare: (memberId: string) => Promise<void>;
|
|
602
|
+
};
|
|
603
|
+
readonly media: {
|
|
604
|
+
list: () => RoomMediaMember[];
|
|
605
|
+
audio: {
|
|
606
|
+
enable: (payload?: {
|
|
607
|
+
trackId?: string;
|
|
608
|
+
deviceId?: string;
|
|
609
|
+
}) => Promise<void>;
|
|
610
|
+
disable: () => Promise<void>;
|
|
611
|
+
setMuted: (muted: boolean) => Promise<void>;
|
|
612
|
+
};
|
|
613
|
+
video: {
|
|
614
|
+
enable: (payload?: {
|
|
615
|
+
trackId?: string;
|
|
616
|
+
deviceId?: string;
|
|
617
|
+
}) => Promise<void>;
|
|
618
|
+
disable: () => Promise<void>;
|
|
619
|
+
setMuted: (muted: boolean) => Promise<void>;
|
|
620
|
+
};
|
|
621
|
+
screen: {
|
|
622
|
+
start: (payload?: {
|
|
623
|
+
trackId?: string;
|
|
624
|
+
deviceId?: string;
|
|
625
|
+
}) => Promise<void>;
|
|
626
|
+
stop: () => Promise<void>;
|
|
627
|
+
};
|
|
628
|
+
devices: {
|
|
629
|
+
switch: (payload: {
|
|
630
|
+
audioInputId?: string;
|
|
631
|
+
videoInputId?: string;
|
|
632
|
+
screenInputId?: string;
|
|
633
|
+
}) => Promise<void>;
|
|
634
|
+
};
|
|
635
|
+
onTrack: (handler: (track: RoomMediaTrack, member: RoomMember) => void) => Subscription;
|
|
636
|
+
onTrackRemoved: (handler: (track: RoomMediaTrack, member: RoomMember) => void) => Subscription;
|
|
637
|
+
onStateChange: (handler: (member: RoomMember, state: RoomMemberMediaState) => void) => Subscription;
|
|
638
|
+
onDeviceChange: (handler: (member: RoomMember, change: RoomMediaDeviceChange) => void) => Subscription;
|
|
639
|
+
};
|
|
640
|
+
readonly session: {
|
|
641
|
+
onError: (handler: ErrorHandler) => Subscription;
|
|
642
|
+
onKicked: (handler: KickedHandler) => Subscription;
|
|
643
|
+
onReconnect: (handler: (info: RoomReconnectInfo) => void) => Subscription;
|
|
644
|
+
onConnectionStateChange: (handler: (state: RoomConnectionState) => void) => Subscription;
|
|
645
|
+
};
|
|
646
|
+
constructor(baseUrl: string, namespace: string, roomId: string, tokenManager: TokenManager, options?: RoomOptions);
|
|
647
|
+
/** Get current shared state (read-only snapshot) */
|
|
648
|
+
getSharedState(): Record<string, unknown>;
|
|
649
|
+
/** Get current player state (read-only snapshot) */
|
|
650
|
+
getPlayerState(): Record<string, unknown>;
|
|
651
|
+
/**
|
|
652
|
+
* Get room metadata without joining (HTTP GET).
|
|
653
|
+
* Returns developer-defined metadata set by room.setMetadata() on the server.
|
|
654
|
+
*/
|
|
655
|
+
getMetadata(): Promise<Record<string, unknown>>;
|
|
656
|
+
/**
|
|
657
|
+
* Static: Get room metadata without creating a RoomClient instance.
|
|
658
|
+
* Useful for lobby screens where you need room info before joining.
|
|
659
|
+
*/
|
|
660
|
+
static getMetadata(baseUrl: string, namespace: string, roomId: string): Promise<Record<string, unknown>>;
|
|
661
|
+
/** Connect to the room, authenticate, and join */
|
|
662
|
+
join(): Promise<void>;
|
|
663
|
+
/** Leave the room and disconnect. Cleans up all pending requests. */
|
|
664
|
+
leave(): void;
|
|
665
|
+
/**
|
|
666
|
+
* Send an action to the server.
|
|
667
|
+
* Returns a Promise that resolves with the action result from the server.
|
|
668
|
+
*
|
|
669
|
+
* @example
|
|
670
|
+
* const result = await room.send('SET_SCORE', { score: 42 });
|
|
671
|
+
*/
|
|
672
|
+
send(actionType: string, payload?: unknown): Promise<unknown>;
|
|
673
|
+
/**
|
|
674
|
+
* Subscribe to shared state changes.
|
|
675
|
+
* Called on full sync and on each shared_delta.
|
|
676
|
+
*
|
|
677
|
+
* @returns Subscription with unsubscribe()
|
|
678
|
+
*/
|
|
679
|
+
onSharedState(handler: SharedStateHandler): Subscription;
|
|
680
|
+
/**
|
|
681
|
+
* Subscribe to player state changes.
|
|
682
|
+
* Called on full sync and on each player_delta.
|
|
683
|
+
*
|
|
684
|
+
* @returns Subscription with unsubscribe()
|
|
685
|
+
*/
|
|
686
|
+
onPlayerState(handler: PlayerStateHandler): Subscription;
|
|
687
|
+
/**
|
|
688
|
+
* Subscribe to messages of a specific type sent by room.sendMessage().
|
|
689
|
+
*
|
|
690
|
+
* @example
|
|
691
|
+
* room.onMessage('game_over', (data) => { console.log(data.winner); });
|
|
692
|
+
*
|
|
693
|
+
* @returns Subscription with unsubscribe()
|
|
694
|
+
*/
|
|
695
|
+
onMessage(messageType: string, handler: MessageHandler): Subscription;
|
|
696
|
+
/**
|
|
697
|
+
* Subscribe to ALL messages regardless of type.
|
|
698
|
+
*
|
|
699
|
+
* @returns Subscription with unsubscribe()
|
|
700
|
+
*/
|
|
701
|
+
onAnyMessage(handler: (messageType: string, data: unknown) => void): Subscription;
|
|
702
|
+
/** Subscribe to errors */
|
|
703
|
+
onError(handler: ErrorHandler): Subscription;
|
|
704
|
+
/** Subscribe to kick events */
|
|
705
|
+
onKicked(handler: KickedHandler): Subscription;
|
|
706
|
+
private onSignal;
|
|
707
|
+
private onAnySignal;
|
|
708
|
+
private onMembersSync;
|
|
709
|
+
private onMemberJoin;
|
|
710
|
+
private onMemberLeave;
|
|
711
|
+
private onMemberStateChange;
|
|
712
|
+
private onReconnect;
|
|
713
|
+
private onConnectionStateChange;
|
|
714
|
+
private onMediaTrack;
|
|
715
|
+
private onMediaTrackRemoved;
|
|
716
|
+
private onMediaStateChange;
|
|
717
|
+
private onMediaDeviceChange;
|
|
718
|
+
private sendSignal;
|
|
719
|
+
private sendMemberState;
|
|
720
|
+
private clearMemberState;
|
|
721
|
+
private sendMemberStateRequest;
|
|
722
|
+
private sendAdmin;
|
|
723
|
+
private sendMedia;
|
|
724
|
+
private switchMediaDevices;
|
|
725
|
+
private establishConnection;
|
|
726
|
+
private ensureConnection;
|
|
727
|
+
private authenticate;
|
|
728
|
+
private handleMessage;
|
|
729
|
+
private handleSync;
|
|
730
|
+
private handleSharedDelta;
|
|
731
|
+
private handlePlayerDelta;
|
|
732
|
+
private handleActionResult;
|
|
733
|
+
private handleActionError;
|
|
734
|
+
private handleAuthAck;
|
|
735
|
+
private handleServerMessage;
|
|
736
|
+
private handleSignalFrame;
|
|
737
|
+
private handleSignalSent;
|
|
738
|
+
private handleSignalError;
|
|
739
|
+
private handleMembersSync;
|
|
740
|
+
private handleMediaSync;
|
|
741
|
+
private handleMemberJoinFrame;
|
|
742
|
+
private handleMemberLeaveFrame;
|
|
743
|
+
private handleMemberStateFrame;
|
|
744
|
+
private handleMemberStateError;
|
|
745
|
+
private handleMediaTrackFrame;
|
|
746
|
+
private handleMediaTrackRemovedFrame;
|
|
747
|
+
private handleMediaStateFrame;
|
|
748
|
+
private handleMediaDeviceFrame;
|
|
749
|
+
private handleMediaResult;
|
|
750
|
+
private handleMediaError;
|
|
751
|
+
private handleAdminResult;
|
|
752
|
+
private handleAdminError;
|
|
753
|
+
private handleKicked;
|
|
754
|
+
private handleError;
|
|
755
|
+
private refreshAuth;
|
|
756
|
+
private handleAuthStateChange;
|
|
757
|
+
private handleAuthenticationFailure;
|
|
758
|
+
private normalizeMembers;
|
|
759
|
+
private normalizeMediaMembers;
|
|
760
|
+
private normalizeMember;
|
|
761
|
+
private normalizeState;
|
|
762
|
+
private normalizeMediaMember;
|
|
763
|
+
private normalizeMediaState;
|
|
764
|
+
private normalizeMediaKindState;
|
|
765
|
+
private normalizeMediaTracks;
|
|
766
|
+
private normalizeMediaTrack;
|
|
767
|
+
private normalizeMediaKind;
|
|
768
|
+
private normalizeSignalMeta;
|
|
769
|
+
private normalizeLeaveReason;
|
|
770
|
+
private upsertMember;
|
|
771
|
+
private removeMember;
|
|
772
|
+
private syncMediaMemberInfo;
|
|
773
|
+
private ensureMediaMember;
|
|
774
|
+
private removeMediaMember;
|
|
775
|
+
private upsertMediaTrack;
|
|
776
|
+
private removeMediaTrack;
|
|
777
|
+
private mergeMediaState;
|
|
778
|
+
private rejectPendingVoidRequests;
|
|
779
|
+
private setConnectionState;
|
|
780
|
+
private sendRaw;
|
|
781
|
+
private buildWsUrl;
|
|
782
|
+
private scheduleReconnect;
|
|
783
|
+
private startHeartbeat;
|
|
784
|
+
private stopHeartbeat;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* React Native Push Notification client.
|
|
789
|
+
*
|
|
790
|
+
* Platform support (FCM 일원화):
|
|
791
|
+
* - iOS: FCM token via Firebase iOS SDK bridge (tokenProvider callback)
|
|
792
|
+
* - Android: FCM token via FirebaseMessaging bridge (tokenProvider callback)
|
|
793
|
+
*
|
|
794
|
+
* Zero-parameter register() design: tokenProvider closure is set by native
|
|
795
|
+
* app code (AppDelegate / Application) before register() is called.
|
|
796
|
+
* SDK never calls native APIs directly — avoids hard dependency on firebase-messaging.
|
|
797
|
+
*
|
|
798
|
+
* @example
|
|
799
|
+
* // In AppDelegate (iOS) or Application (Android):
|
|
800
|
+
* client.push.setTokenProvider(async () => {
|
|
801
|
+
* const token = await messaging().getToken(); // FCM or APNs
|
|
802
|
+
* return { token, platform: 'android' };
|
|
803
|
+
* });
|
|
804
|
+
* // Then anywhere:
|
|
805
|
+
* await client.push.register();
|
|
806
|
+
*/
|
|
807
|
+
|
|
808
|
+
type PushPlatform = 'ios' | 'android' | 'web';
|
|
809
|
+
interface PushTokenProvider {
|
|
810
|
+
(): Promise<{
|
|
811
|
+
token: string;
|
|
812
|
+
platform: PushPlatform;
|
|
813
|
+
}>;
|
|
814
|
+
}
|
|
815
|
+
type PushPermissionStatus = 'granted' | 'denied' | 'not-determined' | 'provisional';
|
|
816
|
+
interface PushPermissionProvider {
|
|
817
|
+
getPermissionStatus(): Promise<PushPermissionStatus>;
|
|
818
|
+
requestPermission(): Promise<PushPermissionStatus>;
|
|
819
|
+
}
|
|
820
|
+
interface PushMessage {
|
|
821
|
+
title?: string;
|
|
822
|
+
body?: string;
|
|
823
|
+
data?: Record<string, unknown>;
|
|
824
|
+
}
|
|
825
|
+
type PushMessageHandler = (message: PushMessage) => void;
|
|
826
|
+
interface PushTopicProvider {
|
|
827
|
+
subscribeTopic(topic: string): Promise<void>;
|
|
828
|
+
unsubscribeTopic(topic: string): Promise<void>;
|
|
829
|
+
}
|
|
830
|
+
declare class PushClient {
|
|
831
|
+
private http;
|
|
832
|
+
private storage;
|
|
833
|
+
private core?;
|
|
834
|
+
private tokenProvider;
|
|
835
|
+
private permissionProvider;
|
|
836
|
+
private topicProvider;
|
|
837
|
+
private messageListeners;
|
|
838
|
+
private openedAppListeners;
|
|
839
|
+
constructor(http: HttpClient, storage: AsyncStorageAdapter, core?: GeneratedDbApi | undefined);
|
|
840
|
+
/**
|
|
841
|
+
* Set the native token provider.
|
|
842
|
+
* Must be called before register() — typically in App.tsx or native bootstrapping.
|
|
843
|
+
*
|
|
844
|
+
* @example (Firebase Messaging)
|
|
845
|
+
* client.push.setTokenProvider(async () => ({
|
|
846
|
+
* token: await messaging().getToken(),
|
|
847
|
+
* platform: 'android',
|
|
848
|
+
* }));
|
|
849
|
+
*
|
|
850
|
+
* @example (APNs via native bridge)
|
|
851
|
+
* client.push.setTokenProvider(async () => ({
|
|
852
|
+
* token: nativeBridge.getAPNsToken(),
|
|
853
|
+
* platform: 'ios',
|
|
854
|
+
* }));
|
|
855
|
+
*/
|
|
856
|
+
setTokenProvider(provider: PushTokenProvider): void;
|
|
857
|
+
/**
|
|
858
|
+
* Set the native permission provider.
|
|
859
|
+
* Call this with your FCM / @notifee/react-native permission handler.
|
|
860
|
+
*
|
|
861
|
+
* @example (Firebase Messaging)
|
|
862
|
+
* client.push.setPermissionProvider({
|
|
863
|
+
* getPermissionStatus: async () => {
|
|
864
|
+
* const status = await messaging().hasPermission();
|
|
865
|
+
* if (status === messaging.AuthorizationStatus.AUTHORIZED) return 'granted';
|
|
866
|
+
* if (status === messaging.AuthorizationStatus.PROVISIONAL) return 'provisional';
|
|
867
|
+
* if (status === messaging.AuthorizationStatus.DENIED) return 'denied';
|
|
868
|
+
* return 'not-determined';
|
|
869
|
+
* },
|
|
870
|
+
* requestPermission: async () => {
|
|
871
|
+
* const status = await messaging().requestPermission();
|
|
872
|
+
* if (status === messaging.AuthorizationStatus.AUTHORIZED) return 'granted';
|
|
873
|
+
* if (status === messaging.AuthorizationStatus.PROVISIONAL) return 'provisional';
|
|
874
|
+
* return 'denied';
|
|
875
|
+
* },
|
|
876
|
+
* });
|
|
877
|
+
*/
|
|
878
|
+
setPermissionProvider(provider: PushPermissionProvider): void;
|
|
879
|
+
/**
|
|
880
|
+
* Get current push notification permission status.
|
|
881
|
+
* Uses custom provider if set via setPermissionProvider(), otherwise uses
|
|
882
|
+
* built-in platform defaults (PermissionsAndroid on Android, auto-grant on iOS).
|
|
883
|
+
*/
|
|
884
|
+
getPermissionStatus(): Promise<PushPermissionStatus>;
|
|
885
|
+
/**
|
|
886
|
+
* Request push notification permission from the user.
|
|
887
|
+
* Uses custom provider if set via setPermissionProvider(), otherwise uses
|
|
888
|
+
* built-in platform defaults (PermissionsAndroid on Android, auto-grant on iOS).
|
|
889
|
+
*/
|
|
890
|
+
requestPermission(): Promise<PushPermissionStatus>;
|
|
891
|
+
/**
|
|
892
|
+
* Register for push notifications.
|
|
893
|
+
* Zero-parameter — token is acquired via setTokenProvider().
|
|
894
|
+
* Token is cached; network request only fires if token changes.
|
|
895
|
+
*/
|
|
896
|
+
register(options?: {
|
|
897
|
+
metadata?: Record<string, unknown>;
|
|
898
|
+
}): Promise<void>;
|
|
899
|
+
/**
|
|
900
|
+
* Unregister the current device from push notifications.
|
|
901
|
+
* Called automatically on signOut.
|
|
902
|
+
*/
|
|
903
|
+
unregister(deviceId?: string): Promise<void>;
|
|
904
|
+
/** Listen for push messages while app is in foreground. */
|
|
905
|
+
onMessage(callback: PushMessageHandler): () => void;
|
|
906
|
+
/** Listen for notification taps that opened the app. */
|
|
907
|
+
onMessageOpenedApp(callback: PushMessageHandler): () => void;
|
|
908
|
+
/**
|
|
909
|
+
* Dispatch a foreground message to all onMessage listeners.
|
|
910
|
+
* Call this from your native FCM/APNs foreground handler.
|
|
911
|
+
*
|
|
912
|
+
* @example (Firebase Messaging)
|
|
913
|
+
* messaging().onMessage(async (remoteMessage) => {
|
|
914
|
+
* client.push._dispatchForegroundMessage({
|
|
915
|
+
* title: remoteMessage.notification?.title,
|
|
916
|
+
* body: remoteMessage.notification?.body,
|
|
917
|
+
* data: remoteMessage.data,
|
|
918
|
+
* });
|
|
919
|
+
* });
|
|
920
|
+
*/
|
|
921
|
+
_dispatchForegroundMessage(message: PushMessage): void;
|
|
922
|
+
/**
|
|
923
|
+
* Dispatch an opened-app notification to all onMessageOpenedApp listeners.
|
|
924
|
+
* Call this from your notification tap handler.
|
|
925
|
+
*/
|
|
926
|
+
_dispatchOpenedAppMessage(message: PushMessage): void;
|
|
927
|
+
/**
|
|
928
|
+
* Set topic subscription provider.
|
|
929
|
+
* Inject your Firebase RN SDK's topic subscription handlers.
|
|
930
|
+
*
|
|
931
|
+
* @example
|
|
932
|
+
* client.push.setTopicProvider({
|
|
933
|
+
* subscribeTopic: (topic) => messaging().subscribeToTopic(topic),
|
|
934
|
+
* unsubscribeTopic: (topic) => messaging().unsubscribeFromTopic(topic),
|
|
935
|
+
* });
|
|
936
|
+
*/
|
|
937
|
+
setTopicProvider(provider: PushTopicProvider): void;
|
|
938
|
+
/**
|
|
939
|
+
* Subscribe to a push notification topic.
|
|
940
|
+
* Delegates to the topic provider set via setTopicProvider().
|
|
941
|
+
*/
|
|
942
|
+
subscribeTopic(topic: string): Promise<void>;
|
|
943
|
+
/**
|
|
944
|
+
* Unsubscribe from a push notification topic.
|
|
945
|
+
* Delegates to the topic provider set via setTopicProvider().
|
|
946
|
+
*/
|
|
947
|
+
unsubscribeTopic(topic: string): Promise<void>;
|
|
948
|
+
private _defaultGetPermissionStatus;
|
|
949
|
+
private _defaultRequestPermission;
|
|
950
|
+
private getOrCreateDeviceId;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* AppState-based lifecycle management for React Native.
|
|
955
|
+
*
|
|
956
|
+
* Responsibility:
|
|
957
|
+
* - Foreground: check if access token is about to expire → pre-emptive refresh
|
|
958
|
+
* - Foreground: reconnect WebSocket if disconnected during background
|
|
959
|
+
* - Background: disconnect WebSocket to avoid battery drain and server-side timeout
|
|
960
|
+
*
|
|
961
|
+
* Usage:
|
|
962
|
+
* const lifecycle = new LifecycleManager(tokenManager, databaseLive, AppState);
|
|
963
|
+
* lifecycle.start();
|
|
964
|
+
* // on unmount or destroy:
|
|
965
|
+
* lifecycle.stop();
|
|
966
|
+
*/
|
|
967
|
+
|
|
968
|
+
interface AppStateAdapter {
|
|
969
|
+
currentState: string;
|
|
970
|
+
addEventListener(type: 'change', handler: (state: string) => void): {
|
|
971
|
+
remove: () => void;
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
interface DatabaseLiveClientAdapter {
|
|
975
|
+
disconnect(): void;
|
|
976
|
+
reconnect?(): void;
|
|
977
|
+
}
|
|
978
|
+
declare class LifecycleManager {
|
|
979
|
+
private tokenManager;
|
|
980
|
+
private databaseLive;
|
|
981
|
+
private appState;
|
|
982
|
+
/** Optional: function to trigger token refresh (e.g. doRefresh from HttpClient) */
|
|
983
|
+
private doRefresh?;
|
|
984
|
+
private subscription;
|
|
985
|
+
private previousState;
|
|
986
|
+
constructor(tokenManager: TokenManager, databaseLive: DatabaseLiveClientAdapter | null, appState: AppStateAdapter,
|
|
987
|
+
/** Optional: function to trigger token refresh (e.g. doRefresh from HttpClient) */
|
|
988
|
+
doRefresh?: ((refreshToken: string) => Promise<{
|
|
989
|
+
accessToken: string;
|
|
990
|
+
refreshToken: string;
|
|
991
|
+
}>) | undefined);
|
|
992
|
+
/** Start listening to AppState changes. */
|
|
993
|
+
start(): void;
|
|
994
|
+
/** Stop listening to AppState changes and clean up. */
|
|
995
|
+
stop(): void;
|
|
996
|
+
private handleStateChange;
|
|
997
|
+
private onForeground;
|
|
998
|
+
private onBackground;
|
|
999
|
+
}
|
|
1000
|
+
interface UseLifecycleOptions {
|
|
1001
|
+
tokenManager: TokenManager;
|
|
1002
|
+
databaseLive: DatabaseLiveClientAdapter | null;
|
|
1003
|
+
appState: AppStateAdapter;
|
|
1004
|
+
doRefresh?: (refreshToken: string) => Promise<{
|
|
1005
|
+
accessToken: string;
|
|
1006
|
+
refreshToken: string;
|
|
1007
|
+
}>;
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* React hook that manages lifecycle automatically.
|
|
1011
|
+
* Starts on mount, stops on unmount.
|
|
1012
|
+
*
|
|
1013
|
+
* @example
|
|
1014
|
+
* function App() {
|
|
1015
|
+
* const { AppState } = require('react-native');
|
|
1016
|
+
* useLifecycle({ tokenManager: client._tokenManager, databaseLive: client._databaseLive, appState: AppState });
|
|
1017
|
+
* }
|
|
1018
|
+
*/
|
|
1019
|
+
declare function useLifecycle({ tokenManager, databaseLive, appState, doRefresh, }: UseLifecycleOptions): void;
|
|
1020
|
+
|
|
1021
|
+
type AnalyticsProperties = Record<string, string | number | boolean>;
|
|
1022
|
+
interface AnalyticsEvent {
|
|
1023
|
+
name: string;
|
|
1024
|
+
properties?: AnalyticsProperties;
|
|
1025
|
+
timestamp?: number;
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* React Native analytics helper.
|
|
1029
|
+
*
|
|
1030
|
+
* Unlike the browser client, RN sends events immediately because there is no
|
|
1031
|
+
* sendBeacon/page-unload behavior to coordinate against.
|
|
1032
|
+
*/
|
|
1033
|
+
declare class ClientAnalytics {
|
|
1034
|
+
private core;
|
|
1035
|
+
constructor(core: GeneratedDbApi);
|
|
1036
|
+
track(name: string, properties?: AnalyticsProperties): Promise<void>;
|
|
1037
|
+
trackBatch(events: AnalyticsEvent[]): Promise<void>;
|
|
1038
|
+
flush(): Promise<void>;
|
|
1039
|
+
destroy(): void;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* @edge-base/react-native — Full-featured client.
|
|
1044
|
+
* All APIs: auth, database-live, storage, push, room, captcha (turnstile), lifecycle
|
|
1045
|
+
*
|
|
1046
|
+
* @example
|
|
1047
|
+
* import { createClient } from '@edge-base/react-native';
|
|
1048
|
+
* import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
1049
|
+
* import { Linking, AppState } from 'react-native';
|
|
1050
|
+
*
|
|
1051
|
+
* const client = createClient('https://my-app.edgebase.fun', {
|
|
1052
|
+
* storage: AsyncStorage,
|
|
1053
|
+
* linking: Linking,
|
|
1054
|
+
* appState: AppState,
|
|
1055
|
+
* });
|
|
1056
|
+
*/
|
|
1057
|
+
|
|
1058
|
+
interface JuneClientOptions {
|
|
1059
|
+
/**
|
|
1060
|
+
* AsyncStorage adapter.
|
|
1061
|
+
* Pass `require('@react-native-async-storage/async-storage').default`
|
|
1062
|
+
*/
|
|
1063
|
+
storage: AsyncStorageAdapter;
|
|
1064
|
+
/**
|
|
1065
|
+
* Linking adapter — pass `require('react-native').Linking`
|
|
1066
|
+
* Required for OAuth sign-in.
|
|
1067
|
+
*/
|
|
1068
|
+
linking?: LinkingAdapter;
|
|
1069
|
+
/**
|
|
1070
|
+
* AppState adapter — pass `require('react-native').AppState`
|
|
1071
|
+
* Enables auto lifecycle management:
|
|
1072
|
+
* background → WebSocket disconnect
|
|
1073
|
+
* foreground → reconnect + token refresh
|
|
1074
|
+
*/
|
|
1075
|
+
appState?: AppStateAdapter;
|
|
1076
|
+
/** Database live subscription options (auto-reconnect, delays, etc.) */
|
|
1077
|
+
databaseLive?: DatabaseLiveOptions;
|
|
1078
|
+
/** Schema from typegen */
|
|
1079
|
+
schema?: Record<string, unknown>;
|
|
1080
|
+
}
|
|
1081
|
+
declare class ClientEdgeBase {
|
|
1082
|
+
readonly auth: AuthClient;
|
|
1083
|
+
readonly storage: StorageClient;
|
|
1084
|
+
readonly push: PushClient;
|
|
1085
|
+
readonly functions: FunctionsClient;
|
|
1086
|
+
readonly analytics: ClientAnalytics;
|
|
1087
|
+
private databaseLive;
|
|
1088
|
+
/** @internal exposed for advanced use (e.g. setDatabaseLive, testing) */
|
|
1089
|
+
readonly _tokenManager: TokenManager;
|
|
1090
|
+
/** @internal */
|
|
1091
|
+
readonly _httpClient: HttpClient;
|
|
1092
|
+
private lifecycleManager;
|
|
1093
|
+
private contextManager;
|
|
1094
|
+
private baseUrl;
|
|
1095
|
+
private core;
|
|
1096
|
+
constructor(url: string, options: JuneClientOptions);
|
|
1097
|
+
/**
|
|
1098
|
+
* Select a DB block by namespace and optional instance ID (#133 §2).
|
|
1099
|
+
*
|
|
1100
|
+
* @example
|
|
1101
|
+
* const posts = await client.db('shared').table('posts').where('status', '==', 'published').get();
|
|
1102
|
+
* client.db('shared').table('posts').onSnapshot((change) => { ... });
|
|
1103
|
+
*/
|
|
1104
|
+
db(namespace: string, instanceId?: string): DbRef;
|
|
1105
|
+
/**
|
|
1106
|
+
* Get a Room client for ephemeral stateful real-time sessions.
|
|
1107
|
+
*
|
|
1108
|
+
* @param namespace - The room namespace (e.g. 'game', 'chat')
|
|
1109
|
+
* @param roomId - The room instance ID within the namespace
|
|
1110
|
+
* @param options - Connection options
|
|
1111
|
+
*
|
|
1112
|
+
* @example
|
|
1113
|
+
* const room = client.room('game', 'room-123');
|
|
1114
|
+
* await room.join();
|
|
1115
|
+
* const result = await room.send('SET_SCORE', { score: 42 });
|
|
1116
|
+
*/
|
|
1117
|
+
room(namespace: string, roomId: string, options?: RoomOptions): RoomClient;
|
|
1118
|
+
/** Set legacy isolateBy context state. HTTP DB routing uses db(namespace, id). */
|
|
1119
|
+
setContext(context: ContextValue): void;
|
|
1120
|
+
/** Set locale for auth email i18n and Accept-Language headers. */
|
|
1121
|
+
setLocale(locale: string | undefined): void;
|
|
1122
|
+
/** Get the currently configured locale override. */
|
|
1123
|
+
getLocale(): string | undefined;
|
|
1124
|
+
/** Get the currently configured legacy isolateBy context state. */
|
|
1125
|
+
getContext(): ContextValue;
|
|
1126
|
+
/** Clean up all connections and listeners. */
|
|
1127
|
+
destroy(): void;
|
|
1128
|
+
}
|
|
1129
|
+
/** Create a React Native EdgeBase client. */
|
|
1130
|
+
declare function createClient(url: string, options: JuneClientOptions): ClientEdgeBase;
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* Client-side filter matching for database-live subscriptions.
|
|
1134
|
+
*
|
|
1135
|
+
* React Native shares the same query/filter tuple semantics as the web SDK,
|
|
1136
|
+
* so the matching logic stays identical across both clients.
|
|
1137
|
+
*/
|
|
1138
|
+
type FilterOperator = '==' | '!=' | '<' | '>' | '<=' | '>=' | 'contains' | 'contains-any' | 'in' | 'not in';
|
|
1139
|
+
interface FilterEntry {
|
|
1140
|
+
field: string;
|
|
1141
|
+
operator: FilterOperator;
|
|
1142
|
+
value: unknown;
|
|
1143
|
+
}
|
|
1144
|
+
declare function matchesFilter(data: Record<string, unknown>, filters: Record<string, unknown> | [string, FilterOperator, unknown][]): boolean;
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Turnstile CAPTCHA widget for React Native — WebView based.
|
|
1148
|
+
*
|
|
1149
|
+
* Supports all platforms:
|
|
1150
|
+
* - iOS: WKWebView via react-native-webview
|
|
1151
|
+
* - Android: android.webkit.WebView via react-native-webview
|
|
1152
|
+
* (uses window.ReactNativeWebView.postMessage instead of window.postMessage)
|
|
1153
|
+
* - Web (React Native Web): Falls back to direct script injection
|
|
1154
|
+
*
|
|
1155
|
+
* Usage:
|
|
1156
|
+
* <TurnstileWebView
|
|
1157
|
+
* siteKey="your-site-key"
|
|
1158
|
+
* action="signup"
|
|
1159
|
+
* onToken={(token) => handleToken(token)}
|
|
1160
|
+
* onError={(err) => handleError(err)}
|
|
1161
|
+
* />
|
|
1162
|
+
*
|
|
1163
|
+
* Or use the helper hook:
|
|
1164
|
+
* const { token, isLoading, error, reset } = useTurnstile({ baseUrl, action });
|
|
1165
|
+
*/
|
|
1166
|
+
|
|
1167
|
+
interface StyleProp {
|
|
1168
|
+
[key: string]: unknown;
|
|
1169
|
+
}
|
|
1170
|
+
interface WebViewMessage {
|
|
1171
|
+
nativeEvent: {
|
|
1172
|
+
data: string;
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
interface WebViewProps {
|
|
1176
|
+
source: {
|
|
1177
|
+
html: string;
|
|
1178
|
+
};
|
|
1179
|
+
style?: StyleProp;
|
|
1180
|
+
onMessage: (event: WebViewMessage) => void;
|
|
1181
|
+
testID?: string;
|
|
1182
|
+
javaScriptEnabled?: boolean;
|
|
1183
|
+
originWhitelist?: string[];
|
|
1184
|
+
scrollEnabled?: boolean;
|
|
1185
|
+
showsHorizontalScrollIndicator?: boolean;
|
|
1186
|
+
showsVerticalScrollIndicator?: boolean;
|
|
1187
|
+
}
|
|
1188
|
+
type TurnstileAppearance = 'always' | 'execute' | 'interaction-only';
|
|
1189
|
+
type TurnstileSize = 'normal' | 'compact' | 'flexible';
|
|
1190
|
+
interface TurnstileWebViewProps {
|
|
1191
|
+
siteKey: string;
|
|
1192
|
+
action?: string;
|
|
1193
|
+
/** Called when Turnstile successfully issues a token */
|
|
1194
|
+
onToken: (token: string) => void;
|
|
1195
|
+
/** Called when Turnstile fails or times out */
|
|
1196
|
+
onError?: (error: string) => void;
|
|
1197
|
+
/** Called when an interactive challenge appears (show the WebView) */
|
|
1198
|
+
onInteractive?: () => void;
|
|
1199
|
+
/** Turnstile appearance mode */
|
|
1200
|
+
appearance?: TurnstileAppearance;
|
|
1201
|
+
/** Turnstile widget size */
|
|
1202
|
+
size?: TurnstileSize;
|
|
1203
|
+
/** Test identifier forwarded to the underlying WebView shell */
|
|
1204
|
+
testID?: string;
|
|
1205
|
+
/** Style for the WebView container */
|
|
1206
|
+
style?: StyleProp;
|
|
1207
|
+
/** WebView component — inject from react-native-webview */
|
|
1208
|
+
WebViewComponent: React.ComponentType<WebViewProps>;
|
|
1209
|
+
}
|
|
1210
|
+
declare function TurnstileWebView({ siteKey, action, onToken, onError, onInteractive, appearance, size, testID, style, WebViewComponent, }: TurnstileWebViewProps): React.ReactElement;
|
|
1211
|
+
interface UseTurnstileOptions {
|
|
1212
|
+
baseUrl: string;
|
|
1213
|
+
action?: string;
|
|
1214
|
+
/** Inject WebView component — pass require('react-native-webview').WebView */
|
|
1215
|
+
WebViewComponent?: React.ComponentType<WebViewProps>;
|
|
1216
|
+
}
|
|
1217
|
+
interface UseTurnstileResult {
|
|
1218
|
+
/** Current captcha token (null until resolved) */
|
|
1219
|
+
token: string | null;
|
|
1220
|
+
/** True while waiting for Turnstile to issue a token */
|
|
1221
|
+
isLoading: boolean;
|
|
1222
|
+
/** Error message if Turnstile failed */
|
|
1223
|
+
error: string | null;
|
|
1224
|
+
/** True if interactive challenge is needed (show the WebView) */
|
|
1225
|
+
needsInteraction: boolean;
|
|
1226
|
+
/** The siteKey fetched from server (null if captcha not configured) */
|
|
1227
|
+
siteKey: string | null;
|
|
1228
|
+
/** Reset state — useful to retry after error */
|
|
1229
|
+
reset: () => void;
|
|
1230
|
+
/** Manually set the token (for manual override flow) */
|
|
1231
|
+
setToken: (token: string) => void;
|
|
1232
|
+
/** Pass to TurnstileWebView.onToken for stateful integration */
|
|
1233
|
+
onToken: (token: string) => void;
|
|
1234
|
+
/** Pass to TurnstileWebView.onError for stateful integration */
|
|
1235
|
+
onError: (error: string) => void;
|
|
1236
|
+
/** Pass to TurnstileWebView.onInteractive for stateful integration */
|
|
1237
|
+
onInteractive: () => void;
|
|
1238
|
+
}
|
|
1239
|
+
declare function useTurnstile({ baseUrl, action, }: UseTurnstileOptions): UseTurnstileResult;
|
|
1240
|
+
/**
|
|
1241
|
+
* Detect if we're running on React Native Web (browser) vs native.
|
|
1242
|
+
* Used internally to skip WebView when running on web platform.
|
|
1243
|
+
*/
|
|
1244
|
+
declare function isPlatformWeb(): boolean;
|
|
1245
|
+
|
|
1246
|
+
export { type AppStateAdapter, type AsyncStorageAdapter, AuthClient, type AuthResult, type AuthStateChangeHandler, ClientAnalytics, ClientEdgeBase, DatabaseLiveClient, type DatabaseLiveClientAdapter, type DatabaseLiveOptions, type FilterEntry, type FilterOperator, type JuneClientOptions, LifecycleManager, type LinkingAdapter, type PasskeysAuthOptions, PushClient, type PushMessage, type PushMessageHandler, type PushPermissionProvider, type PushPermissionStatus, type PushPlatform, type PushTokenProvider, RoomClient, type RoomConnectionState, type RoomMediaDeviceChange, type RoomMediaKind, type RoomMediaMember, type RoomMediaTrack, type RoomMember, type RoomMemberLeaveReason, type RoomMemberMediaKindState, type RoomMemberMediaState, type RoomOptions, type RoomReconnectInfo, type RoomSignalMeta, type Session, type SignInOptions, type SignUpOptions, type Subscription, TokenManager, type TokenPair, type TokenUser, TurnstileWebView, type TurnstileWebViewProps, type UpdateProfileOptions, type UseLifecycleOptions, type UseTurnstileOptions, type UseTurnstileResult, createClient, isPlatformWeb, matchesFilter, useLifecycle, useTurnstile };
|