@spacelr/sdk 0.1.7 → 0.1.9
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.d.mts +94 -7
- package/dist/index.d.ts +94 -7
- package/dist/index.js +116 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +116 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -58,8 +58,10 @@ interface RegisterResponse {
|
|
|
58
58
|
username: string;
|
|
59
59
|
displayName?: string;
|
|
60
60
|
};
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
/** Omitted by the server when `emailVerificationRequired` is true. */
|
|
62
|
+
access_token?: string;
|
|
63
|
+
/** Omitted by the server when `emailVerificationRequired` is true. */
|
|
64
|
+
refresh_token?: string;
|
|
63
65
|
emailVerificationRequired?: boolean;
|
|
64
66
|
}
|
|
65
67
|
interface TokenResponse {
|
|
@@ -430,11 +432,58 @@ declare class RealtimeClient {
|
|
|
430
432
|
private resubscribeAll;
|
|
431
433
|
}
|
|
432
434
|
|
|
435
|
+
type AuthState = 'authenticated' | 'unauthenticated';
|
|
436
|
+
type AuthStateListener = (state: AuthState) => void | Promise<void>;
|
|
433
437
|
declare class AuthModule {
|
|
434
438
|
private http;
|
|
435
439
|
private tokenManager;
|
|
436
440
|
private config;
|
|
441
|
+
private stateListeners;
|
|
442
|
+
private unsubscribeAuthLost;
|
|
443
|
+
private lastEmittedState;
|
|
437
444
|
constructor(http: HttpClient, tokenManager: TokenManager, config: SpacelrClientConfig);
|
|
445
|
+
/**
|
|
446
|
+
* Returns true if a non-expired access token is currently in storage.
|
|
447
|
+
* Does NOT make a network request — safe for route guards and other
|
|
448
|
+
* hot paths that run on every navigation.
|
|
449
|
+
*
|
|
450
|
+
* A token within the refresh buffer (about to expire) still counts as
|
|
451
|
+
* authenticated because the next protected request will auto-refresh it.
|
|
452
|
+
*
|
|
453
|
+
* Any error from the underlying TokenStorage (corrupt JSON, quota, etc.)
|
|
454
|
+
* is treated as "not authenticated" rather than propagated, so route
|
|
455
|
+
* guards can't be crashed by a misbehaving storage backend.
|
|
456
|
+
*/
|
|
457
|
+
isAuthenticated(): Promise<boolean>;
|
|
458
|
+
/**
|
|
459
|
+
* Subscribe to auth-state transitions. The callback fires:
|
|
460
|
+
* - 'authenticated' after a successful login/register/exchange/2FA-verify
|
|
461
|
+
* - 'unauthenticated' after logout or when a token refresh fails
|
|
462
|
+
*
|
|
463
|
+
* Only fires for transitions that happen after the subscription. If the
|
|
464
|
+
* user is already logged in at subscribe time (e.g. tokens restored from
|
|
465
|
+
* storage on app boot), no 'authenticated' event is emitted — call
|
|
466
|
+
* `isAuthenticated()` once up-front for the initial state.
|
|
467
|
+
*
|
|
468
|
+
* Silent token refreshes do NOT produce an event (auth state is
|
|
469
|
+
* unchanged). Subscribe to `spacelrClient.onTokenRefreshed(...)` if you
|
|
470
|
+
* need to observe successful refreshes.
|
|
471
|
+
*
|
|
472
|
+
* Listener may return `void` or `Promise<void>`. Rejections are swallowed
|
|
473
|
+
* so one broken subscriber can't poison others or the auth flow. The
|
|
474
|
+
* dispatch is fire-and-forget: `logout()` / `login()` resolve as soon as
|
|
475
|
+
* the dispatch loop returns, without awaiting async listeners.
|
|
476
|
+
*
|
|
477
|
+
* Returns an unsubscribe function.
|
|
478
|
+
*/
|
|
479
|
+
onAuthStateChange(listener: AuthStateListener): () => void;
|
|
480
|
+
/**
|
|
481
|
+
* Detach this AuthModule from the TokenManager. Call when discarding the
|
|
482
|
+
* client (tests, HMR, multi-client setups) to avoid leaking the internal
|
|
483
|
+
* onAuthLost subscription. Idempotent — safe to call more than once.
|
|
484
|
+
*/
|
|
485
|
+
dispose(): void;
|
|
486
|
+
private emitState;
|
|
438
487
|
login(params: LoginParams): Promise<LoginResponse>;
|
|
439
488
|
register(params: RegisterParams): Promise<RegisterResponse>;
|
|
440
489
|
refresh(refreshToken: string): Promise<TokenResponse>;
|
|
@@ -544,6 +593,21 @@ interface DeleteResult {
|
|
|
544
593
|
interface FindByIdOptions {
|
|
545
594
|
populate?: PopulateOption[];
|
|
546
595
|
}
|
|
596
|
+
/**
|
|
597
|
+
* Options for a server-side substring search.
|
|
598
|
+
*
|
|
599
|
+
* Limits: `query` 1–200 chars, `fields` 1–10 entries (each matching
|
|
600
|
+
* `/^[a-zA-Z0-9_.]+$/`, max 64 chars), `limit` max 100.
|
|
601
|
+
*/
|
|
602
|
+
interface SearchOptions {
|
|
603
|
+
query: string;
|
|
604
|
+
fields: string[];
|
|
605
|
+
filter?: Record<string, unknown>;
|
|
606
|
+
sort?: Record<string, 1 | -1>;
|
|
607
|
+
limit?: number;
|
|
608
|
+
offset?: number;
|
|
609
|
+
select?: string[];
|
|
610
|
+
}
|
|
547
611
|
interface SubscribeHandlers<T = Record<string, unknown>> {
|
|
548
612
|
where?: Record<string, string | number | boolean>;
|
|
549
613
|
onInsert?: (doc: T & {
|
|
@@ -586,6 +650,18 @@ declare class CollectionRef<T = Record<string, unknown>> {
|
|
|
586
650
|
_id?: string;
|
|
587
651
|
})[]): Promise<InsertResult>;
|
|
588
652
|
find(filter?: Record<string, unknown>): QueryBuilder<T>;
|
|
653
|
+
/**
|
|
654
|
+
* Server-side substring search across the specified fields.
|
|
655
|
+
*
|
|
656
|
+
* The query is regex-escaped server-side and matched case-insensitively via
|
|
657
|
+
* MongoDB `$regex`. Performance note: unanchored case-insensitive regex
|
|
658
|
+
* cannot use a standard B-tree index — on very large collections consider
|
|
659
|
+
* narrowing with `filter` to scope the scan.
|
|
660
|
+
*
|
|
661
|
+
* Limits: `query` 1–200 chars, `fields` 1–10 entries (each matching
|
|
662
|
+
* `/^[a-zA-Z0-9_.]+$/`, max 64 chars), `limit` max 100.
|
|
663
|
+
*/
|
|
664
|
+
search(opts: SearchOptions): Promise<FindResult<T>>;
|
|
589
665
|
findById(id: string, options?: FindByIdOptions): Promise<T & {
|
|
590
666
|
_id: string;
|
|
591
667
|
}>;
|
|
@@ -665,7 +741,14 @@ declare class NotificationsModule {
|
|
|
665
741
|
}
|
|
666
742
|
|
|
667
743
|
interface FunctionInvokeOptions {
|
|
668
|
-
secret
|
|
744
|
+
/** Webhook secret — used for `webhook` and `hybrid` invokeMode. */
|
|
745
|
+
secret?: string;
|
|
746
|
+
/**
|
|
747
|
+
* If true, the SDK attaches the currently signed-in user's bearer token
|
|
748
|
+
* via `Authorization` (managed by the SDK's TokenManager). Use for
|
|
749
|
+
* `authenticated`, `hybrid`, or `public` invokeMode.
|
|
750
|
+
*/
|
|
751
|
+
authenticated?: boolean;
|
|
669
752
|
payload?: Record<string, unknown>;
|
|
670
753
|
}
|
|
671
754
|
interface FunctionInvokeResult {
|
|
@@ -677,11 +760,15 @@ declare class FunctionsModule {
|
|
|
677
760
|
private http;
|
|
678
761
|
constructor(http: HttpClient);
|
|
679
762
|
/**
|
|
680
|
-
* Invoke a function
|
|
763
|
+
* Invoke a function.
|
|
681
764
|
* Calls POST /api/v1/functions/:projectId/:functionId/invoke
|
|
682
|
-
*
|
|
765
|
+
*
|
|
766
|
+
* Provide `secret` for webhook/hybrid functions, `authenticated: true` for
|
|
767
|
+
* JWT-based invocation, or both for hybrid (JWT wins). Public mode needs
|
|
768
|
+
* neither, though `authenticated: true` still populates `event.auth` inside
|
|
769
|
+
* the function.
|
|
683
770
|
*/
|
|
684
|
-
invoke(projectId: string, functionId: string, options
|
|
771
|
+
invoke(projectId: string, functionId: string, options?: FunctionInvokeOptions): Promise<FunctionInvokeResult>;
|
|
685
772
|
}
|
|
686
773
|
|
|
687
774
|
interface SpacelrClient {
|
|
@@ -741,4 +828,4 @@ interface SpacelrClient {
|
|
|
741
828
|
}
|
|
742
829
|
declare function createClient(config: SpacelrClientConfig): SpacelrClient;
|
|
743
830
|
|
|
744
|
-
export { type ApiResponse, type AuthLostReason, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, type FunctionInvokeOptions, type FunctionInvokeResult, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type SubscribeHandlers, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, type VapidKeyResponse, createClient, generatePKCEChallenge };
|
|
831
|
+
export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, type FunctionInvokeOptions, type FunctionInvokeResult, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type SearchOptions, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type SubscribeHandlers, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, type VapidKeyResponse, createClient, generatePKCEChallenge };
|
package/dist/index.d.ts
CHANGED
|
@@ -58,8 +58,10 @@ interface RegisterResponse {
|
|
|
58
58
|
username: string;
|
|
59
59
|
displayName?: string;
|
|
60
60
|
};
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
/** Omitted by the server when `emailVerificationRequired` is true. */
|
|
62
|
+
access_token?: string;
|
|
63
|
+
/** Omitted by the server when `emailVerificationRequired` is true. */
|
|
64
|
+
refresh_token?: string;
|
|
63
65
|
emailVerificationRequired?: boolean;
|
|
64
66
|
}
|
|
65
67
|
interface TokenResponse {
|
|
@@ -430,11 +432,58 @@ declare class RealtimeClient {
|
|
|
430
432
|
private resubscribeAll;
|
|
431
433
|
}
|
|
432
434
|
|
|
435
|
+
type AuthState = 'authenticated' | 'unauthenticated';
|
|
436
|
+
type AuthStateListener = (state: AuthState) => void | Promise<void>;
|
|
433
437
|
declare class AuthModule {
|
|
434
438
|
private http;
|
|
435
439
|
private tokenManager;
|
|
436
440
|
private config;
|
|
441
|
+
private stateListeners;
|
|
442
|
+
private unsubscribeAuthLost;
|
|
443
|
+
private lastEmittedState;
|
|
437
444
|
constructor(http: HttpClient, tokenManager: TokenManager, config: SpacelrClientConfig);
|
|
445
|
+
/**
|
|
446
|
+
* Returns true if a non-expired access token is currently in storage.
|
|
447
|
+
* Does NOT make a network request — safe for route guards and other
|
|
448
|
+
* hot paths that run on every navigation.
|
|
449
|
+
*
|
|
450
|
+
* A token within the refresh buffer (about to expire) still counts as
|
|
451
|
+
* authenticated because the next protected request will auto-refresh it.
|
|
452
|
+
*
|
|
453
|
+
* Any error from the underlying TokenStorage (corrupt JSON, quota, etc.)
|
|
454
|
+
* is treated as "not authenticated" rather than propagated, so route
|
|
455
|
+
* guards can't be crashed by a misbehaving storage backend.
|
|
456
|
+
*/
|
|
457
|
+
isAuthenticated(): Promise<boolean>;
|
|
458
|
+
/**
|
|
459
|
+
* Subscribe to auth-state transitions. The callback fires:
|
|
460
|
+
* - 'authenticated' after a successful login/register/exchange/2FA-verify
|
|
461
|
+
* - 'unauthenticated' after logout or when a token refresh fails
|
|
462
|
+
*
|
|
463
|
+
* Only fires for transitions that happen after the subscription. If the
|
|
464
|
+
* user is already logged in at subscribe time (e.g. tokens restored from
|
|
465
|
+
* storage on app boot), no 'authenticated' event is emitted — call
|
|
466
|
+
* `isAuthenticated()` once up-front for the initial state.
|
|
467
|
+
*
|
|
468
|
+
* Silent token refreshes do NOT produce an event (auth state is
|
|
469
|
+
* unchanged). Subscribe to `spacelrClient.onTokenRefreshed(...)` if you
|
|
470
|
+
* need to observe successful refreshes.
|
|
471
|
+
*
|
|
472
|
+
* Listener may return `void` or `Promise<void>`. Rejections are swallowed
|
|
473
|
+
* so one broken subscriber can't poison others or the auth flow. The
|
|
474
|
+
* dispatch is fire-and-forget: `logout()` / `login()` resolve as soon as
|
|
475
|
+
* the dispatch loop returns, without awaiting async listeners.
|
|
476
|
+
*
|
|
477
|
+
* Returns an unsubscribe function.
|
|
478
|
+
*/
|
|
479
|
+
onAuthStateChange(listener: AuthStateListener): () => void;
|
|
480
|
+
/**
|
|
481
|
+
* Detach this AuthModule from the TokenManager. Call when discarding the
|
|
482
|
+
* client (tests, HMR, multi-client setups) to avoid leaking the internal
|
|
483
|
+
* onAuthLost subscription. Idempotent — safe to call more than once.
|
|
484
|
+
*/
|
|
485
|
+
dispose(): void;
|
|
486
|
+
private emitState;
|
|
438
487
|
login(params: LoginParams): Promise<LoginResponse>;
|
|
439
488
|
register(params: RegisterParams): Promise<RegisterResponse>;
|
|
440
489
|
refresh(refreshToken: string): Promise<TokenResponse>;
|
|
@@ -544,6 +593,21 @@ interface DeleteResult {
|
|
|
544
593
|
interface FindByIdOptions {
|
|
545
594
|
populate?: PopulateOption[];
|
|
546
595
|
}
|
|
596
|
+
/**
|
|
597
|
+
* Options for a server-side substring search.
|
|
598
|
+
*
|
|
599
|
+
* Limits: `query` 1–200 chars, `fields` 1–10 entries (each matching
|
|
600
|
+
* `/^[a-zA-Z0-9_.]+$/`, max 64 chars), `limit` max 100.
|
|
601
|
+
*/
|
|
602
|
+
interface SearchOptions {
|
|
603
|
+
query: string;
|
|
604
|
+
fields: string[];
|
|
605
|
+
filter?: Record<string, unknown>;
|
|
606
|
+
sort?: Record<string, 1 | -1>;
|
|
607
|
+
limit?: number;
|
|
608
|
+
offset?: number;
|
|
609
|
+
select?: string[];
|
|
610
|
+
}
|
|
547
611
|
interface SubscribeHandlers<T = Record<string, unknown>> {
|
|
548
612
|
where?: Record<string, string | number | boolean>;
|
|
549
613
|
onInsert?: (doc: T & {
|
|
@@ -586,6 +650,18 @@ declare class CollectionRef<T = Record<string, unknown>> {
|
|
|
586
650
|
_id?: string;
|
|
587
651
|
})[]): Promise<InsertResult>;
|
|
588
652
|
find(filter?: Record<string, unknown>): QueryBuilder<T>;
|
|
653
|
+
/**
|
|
654
|
+
* Server-side substring search across the specified fields.
|
|
655
|
+
*
|
|
656
|
+
* The query is regex-escaped server-side and matched case-insensitively via
|
|
657
|
+
* MongoDB `$regex`. Performance note: unanchored case-insensitive regex
|
|
658
|
+
* cannot use a standard B-tree index — on very large collections consider
|
|
659
|
+
* narrowing with `filter` to scope the scan.
|
|
660
|
+
*
|
|
661
|
+
* Limits: `query` 1–200 chars, `fields` 1–10 entries (each matching
|
|
662
|
+
* `/^[a-zA-Z0-9_.]+$/`, max 64 chars), `limit` max 100.
|
|
663
|
+
*/
|
|
664
|
+
search(opts: SearchOptions): Promise<FindResult<T>>;
|
|
589
665
|
findById(id: string, options?: FindByIdOptions): Promise<T & {
|
|
590
666
|
_id: string;
|
|
591
667
|
}>;
|
|
@@ -665,7 +741,14 @@ declare class NotificationsModule {
|
|
|
665
741
|
}
|
|
666
742
|
|
|
667
743
|
interface FunctionInvokeOptions {
|
|
668
|
-
secret
|
|
744
|
+
/** Webhook secret — used for `webhook` and `hybrid` invokeMode. */
|
|
745
|
+
secret?: string;
|
|
746
|
+
/**
|
|
747
|
+
* If true, the SDK attaches the currently signed-in user's bearer token
|
|
748
|
+
* via `Authorization` (managed by the SDK's TokenManager). Use for
|
|
749
|
+
* `authenticated`, `hybrid`, or `public` invokeMode.
|
|
750
|
+
*/
|
|
751
|
+
authenticated?: boolean;
|
|
669
752
|
payload?: Record<string, unknown>;
|
|
670
753
|
}
|
|
671
754
|
interface FunctionInvokeResult {
|
|
@@ -677,11 +760,15 @@ declare class FunctionsModule {
|
|
|
677
760
|
private http;
|
|
678
761
|
constructor(http: HttpClient);
|
|
679
762
|
/**
|
|
680
|
-
* Invoke a function
|
|
763
|
+
* Invoke a function.
|
|
681
764
|
* Calls POST /api/v1/functions/:projectId/:functionId/invoke
|
|
682
|
-
*
|
|
765
|
+
*
|
|
766
|
+
* Provide `secret` for webhook/hybrid functions, `authenticated: true` for
|
|
767
|
+
* JWT-based invocation, or both for hybrid (JWT wins). Public mode needs
|
|
768
|
+
* neither, though `authenticated: true` still populates `event.auth` inside
|
|
769
|
+
* the function.
|
|
683
770
|
*/
|
|
684
|
-
invoke(projectId: string, functionId: string, options
|
|
771
|
+
invoke(projectId: string, functionId: string, options?: FunctionInvokeOptions): Promise<FunctionInvokeResult>;
|
|
685
772
|
}
|
|
686
773
|
|
|
687
774
|
interface SpacelrClient {
|
|
@@ -741,4 +828,4 @@ interface SpacelrClient {
|
|
|
741
828
|
}
|
|
742
829
|
declare function createClient(config: SpacelrClientConfig): SpacelrClient;
|
|
743
830
|
|
|
744
|
-
export { type ApiResponse, type AuthLostReason, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, type FunctionInvokeOptions, type FunctionInvokeResult, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type SubscribeHandlers, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, type VapidKeyResponse, createClient, generatePKCEChallenge };
|
|
831
|
+
export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, type FunctionInvokeOptions, type FunctionInvokeResult, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type SearchOptions, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type SubscribeHandlers, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, type VapidKeyResponse, createClient, generatePKCEChallenge };
|
package/dist/index.js
CHANGED
|
@@ -901,6 +901,8 @@ var RealtimeClient = class {
|
|
|
901
901
|
// libs/sdk/src/modules/auth.module.ts
|
|
902
902
|
var AuthModule = class {
|
|
903
903
|
constructor(http, tokenManager, config) {
|
|
904
|
+
this.stateListeners = /* @__PURE__ */ new Set();
|
|
905
|
+
this.lastEmittedState = null;
|
|
904
906
|
this.http = http;
|
|
905
907
|
this.tokenManager = tokenManager;
|
|
906
908
|
this.config = config;
|
|
@@ -913,6 +915,84 @@ var AuthModule = class {
|
|
|
913
915
|
expiresAt
|
|
914
916
|
};
|
|
915
917
|
});
|
|
918
|
+
this.unsubscribeAuthLost = this.tokenManager.onAuthLost(
|
|
919
|
+
() => this.emitState("unauthenticated")
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Returns true if a non-expired access token is currently in storage.
|
|
924
|
+
* Does NOT make a network request — safe for route guards and other
|
|
925
|
+
* hot paths that run on every navigation.
|
|
926
|
+
*
|
|
927
|
+
* A token within the refresh buffer (about to expire) still counts as
|
|
928
|
+
* authenticated because the next protected request will auto-refresh it.
|
|
929
|
+
*
|
|
930
|
+
* Any error from the underlying TokenStorage (corrupt JSON, quota, etc.)
|
|
931
|
+
* is treated as "not authenticated" rather than propagated, so route
|
|
932
|
+
* guards can't be crashed by a misbehaving storage backend.
|
|
933
|
+
*/
|
|
934
|
+
async isAuthenticated() {
|
|
935
|
+
try {
|
|
936
|
+
const tokens = await this.tokenManager.getStoredTokens();
|
|
937
|
+
if (!tokens?.accessToken) return false;
|
|
938
|
+
if (tokens.expiresAt && tokens.expiresAt * 1e3 <= Date.now()) return false;
|
|
939
|
+
return true;
|
|
940
|
+
} catch {
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Subscribe to auth-state transitions. The callback fires:
|
|
946
|
+
* - 'authenticated' after a successful login/register/exchange/2FA-verify
|
|
947
|
+
* - 'unauthenticated' after logout or when a token refresh fails
|
|
948
|
+
*
|
|
949
|
+
* Only fires for transitions that happen after the subscription. If the
|
|
950
|
+
* user is already logged in at subscribe time (e.g. tokens restored from
|
|
951
|
+
* storage on app boot), no 'authenticated' event is emitted — call
|
|
952
|
+
* `isAuthenticated()` once up-front for the initial state.
|
|
953
|
+
*
|
|
954
|
+
* Silent token refreshes do NOT produce an event (auth state is
|
|
955
|
+
* unchanged). Subscribe to `spacelrClient.onTokenRefreshed(...)` if you
|
|
956
|
+
* need to observe successful refreshes.
|
|
957
|
+
*
|
|
958
|
+
* Listener may return `void` or `Promise<void>`. Rejections are swallowed
|
|
959
|
+
* so one broken subscriber can't poison others or the auth flow. The
|
|
960
|
+
* dispatch is fire-and-forget: `logout()` / `login()` resolve as soon as
|
|
961
|
+
* the dispatch loop returns, without awaiting async listeners.
|
|
962
|
+
*
|
|
963
|
+
* Returns an unsubscribe function.
|
|
964
|
+
*/
|
|
965
|
+
onAuthStateChange(listener) {
|
|
966
|
+
this.stateListeners.add(listener);
|
|
967
|
+
return () => {
|
|
968
|
+
this.stateListeners.delete(listener);
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Detach this AuthModule from the TokenManager. Call when discarding the
|
|
973
|
+
* client (tests, HMR, multi-client setups) to avoid leaking the internal
|
|
974
|
+
* onAuthLost subscription. Idempotent — safe to call more than once.
|
|
975
|
+
*/
|
|
976
|
+
dispose() {
|
|
977
|
+
this.unsubscribeAuthLost();
|
|
978
|
+
this.unsubscribeAuthLost = () => {
|
|
979
|
+
};
|
|
980
|
+
this.stateListeners.clear();
|
|
981
|
+
this.lastEmittedState = null;
|
|
982
|
+
}
|
|
983
|
+
emitState(state) {
|
|
984
|
+
if (state === this.lastEmittedState) return;
|
|
985
|
+
this.lastEmittedState = state;
|
|
986
|
+
for (const listener of this.stateListeners) {
|
|
987
|
+
try {
|
|
988
|
+
const result = listener(state);
|
|
989
|
+
if (result && typeof result.then === "function") {
|
|
990
|
+
result.then(void 0, () => {
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
} catch {
|
|
994
|
+
}
|
|
995
|
+
}
|
|
916
996
|
}
|
|
917
997
|
async login(params) {
|
|
918
998
|
const response = await this.http.request({
|
|
@@ -958,6 +1038,7 @@ var AuthModule = class {
|
|
|
958
1038
|
} catch {
|
|
959
1039
|
}
|
|
960
1040
|
await this.tokenManager.clearTokens();
|
|
1041
|
+
this.emitState("unauthenticated");
|
|
961
1042
|
}
|
|
962
1043
|
async verifyEmail(token) {
|
|
963
1044
|
return this.http.request({
|
|
@@ -1028,6 +1109,7 @@ var AuthModule = class {
|
|
|
1028
1109
|
refreshToken: response.refresh_token,
|
|
1029
1110
|
expiresAt
|
|
1030
1111
|
});
|
|
1112
|
+
this.emitState("authenticated");
|
|
1031
1113
|
return response;
|
|
1032
1114
|
}
|
|
1033
1115
|
async generatePKCE() {
|
|
@@ -1110,6 +1192,7 @@ var AuthModule = class {
|
|
|
1110
1192
|
refreshToken: response.refresh_token,
|
|
1111
1193
|
expiresAt
|
|
1112
1194
|
});
|
|
1195
|
+
this.emitState("authenticated");
|
|
1113
1196
|
}
|
|
1114
1197
|
async storeTokensFromRegister(response) {
|
|
1115
1198
|
if (!response.access_token) return;
|
|
@@ -1117,6 +1200,7 @@ var AuthModule = class {
|
|
|
1117
1200
|
accessToken: response.access_token,
|
|
1118
1201
|
refreshToken: response.refresh_token
|
|
1119
1202
|
});
|
|
1203
|
+
this.emitState("authenticated");
|
|
1120
1204
|
}
|
|
1121
1205
|
};
|
|
1122
1206
|
|
|
@@ -1413,6 +1497,25 @@ var CollectionRef = class {
|
|
|
1413
1497
|
find(filter) {
|
|
1414
1498
|
return new QueryBuilder(this.http, this.basePath, filter);
|
|
1415
1499
|
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Server-side substring search across the specified fields.
|
|
1502
|
+
*
|
|
1503
|
+
* The query is regex-escaped server-side and matched case-insensitively via
|
|
1504
|
+
* MongoDB `$regex`. Performance note: unanchored case-insensitive regex
|
|
1505
|
+
* cannot use a standard B-tree index — on very large collections consider
|
|
1506
|
+
* narrowing with `filter` to scope the scan.
|
|
1507
|
+
*
|
|
1508
|
+
* Limits: `query` 1–200 chars, `fields` 1–10 entries (each matching
|
|
1509
|
+
* `/^[a-zA-Z0-9_.]+$/`, max 64 chars), `limit` max 100.
|
|
1510
|
+
*/
|
|
1511
|
+
async search(opts) {
|
|
1512
|
+
return this.http.request({
|
|
1513
|
+
method: "POST",
|
|
1514
|
+
path: `${this.basePath}/search`,
|
|
1515
|
+
body: opts,
|
|
1516
|
+
authenticated: true
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1416
1519
|
async findById(id, options) {
|
|
1417
1520
|
const query = {};
|
|
1418
1521
|
if (options?.populate?.length) {
|
|
@@ -1669,19 +1772,25 @@ var FunctionsModule = class {
|
|
|
1669
1772
|
this.http = http;
|
|
1670
1773
|
}
|
|
1671
1774
|
/**
|
|
1672
|
-
* Invoke a function
|
|
1775
|
+
* Invoke a function.
|
|
1673
1776
|
* Calls POST /api/v1/functions/:projectId/:functionId/invoke
|
|
1674
|
-
*
|
|
1777
|
+
*
|
|
1778
|
+
* Provide `secret` for webhook/hybrid functions, `authenticated: true` for
|
|
1779
|
+
* JWT-based invocation, or both for hybrid (JWT wins). Public mode needs
|
|
1780
|
+
* neither, though `authenticated: true` still populates `event.auth` inside
|
|
1781
|
+
* the function.
|
|
1675
1782
|
*/
|
|
1676
|
-
async invoke(projectId, functionId, options) {
|
|
1783
|
+
async invoke(projectId, functionId, options = {}) {
|
|
1784
|
+
const headers = {};
|
|
1785
|
+
if (options.secret) {
|
|
1786
|
+
headers["X-Webhook-Secret"] = options.secret;
|
|
1787
|
+
}
|
|
1677
1788
|
return this.http.request({
|
|
1678
1789
|
method: "POST",
|
|
1679
1790
|
path: `/api/v1/functions/${encodeURIComponent(projectId)}/${encodeURIComponent(functionId)}/invoke`,
|
|
1680
|
-
headers
|
|
1681
|
-
"X-Webhook-Secret": options.secret
|
|
1682
|
-
},
|
|
1791
|
+
headers,
|
|
1683
1792
|
body: options.payload ?? {},
|
|
1684
|
-
authenticated: false
|
|
1793
|
+
authenticated: options.authenticated ?? false
|
|
1685
1794
|
});
|
|
1686
1795
|
}
|
|
1687
1796
|
};
|