valtech-components 2.0.724 → 2.0.726
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/esm2022/lib/components/templates/auth-background/auth-background.component.mjs +5 -5
- package/esm2022/lib/services/auth/handoff.service.mjs +152 -0
- package/esm2022/lib/services/auth/index.mjs +5 -1
- package/esm2022/lib/services/auth/org-switch.service.mjs +137 -0
- package/esm2022/lib/services/firebase/firestore-collection.mjs +10 -2
- package/esm2022/lib/services/firebase/firestore.service.mjs +22 -1
- package/esm2022/lib/services/firebase/shared-config.mjs +63 -30
- package/esm2022/lib/version.mjs +2 -2
- package/fesm2022/valtech-components.mjs +374 -36
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/services/auth/handoff.service.d.ts +149 -0
- package/lib/services/auth/index.d.ts +4 -0
- package/lib/services/auth/org-switch.service.d.ts +127 -0
- package/lib/services/firebase/firestore-collection.d.ts +12 -1
- package/lib/services/firebase/firestore.service.d.ts +18 -0
- package/lib/services/firebase/shared-config.d.ts +61 -28
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { HttpClient } from '@angular/common/http';
|
|
2
|
+
import { Router } from '@angular/router';
|
|
3
|
+
import { Observable } from 'rxjs';
|
|
4
|
+
import { AuthService } from './auth.service';
|
|
5
|
+
import { ValtechAuthConfig } from './types';
|
|
6
|
+
import * as i0 from "@angular/core";
|
|
7
|
+
/**
|
|
8
|
+
* Name of the query param that carries the handoff token. Apps may override
|
|
9
|
+
* via `detectAndExchangeHandoff({ tokenParam })` but the default keeps the
|
|
10
|
+
* convention consistent across the factory.
|
|
11
|
+
*/
|
|
12
|
+
export declare const HANDOFF_TOKEN_PARAM = "handoff";
|
|
13
|
+
/**
|
|
14
|
+
* Name of the query param that carries the post-exchange route.
|
|
15
|
+
*/
|
|
16
|
+
export declare const HANDOFF_ROUTE_PARAM = "route";
|
|
17
|
+
/**
|
|
18
|
+
* Options for `HandoffService.detectAndExchangeHandoff`.
|
|
19
|
+
*/
|
|
20
|
+
export interface DetectAndExchangeOptions {
|
|
21
|
+
/** Override the query param name. Default: `'handoff'`. */
|
|
22
|
+
tokenParam?: string;
|
|
23
|
+
/** Override the route param name. Default: `'route'`. */
|
|
24
|
+
routeParam?: string;
|
|
25
|
+
/** Where to navigate if no route param is present. Default: `'/'`. */
|
|
26
|
+
defaultRoute?: string;
|
|
27
|
+
/** Where to navigate on exchange error. Default: app's configured `loginRoute`. */
|
|
28
|
+
errorRoute?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Request body for `POST /v2/auth/handoff`.
|
|
32
|
+
*
|
|
33
|
+
* Both fields are optional and stored for audit only — the exchange step
|
|
34
|
+
* does not enforce that the redeeming app matches `targetAppId`.
|
|
35
|
+
*/
|
|
36
|
+
export interface HandoffCreateRequest {
|
|
37
|
+
/** Target app the handoff is intended for (e.g. `"myvaltech"`). Audit-only. */
|
|
38
|
+
targetAppId?: string;
|
|
39
|
+
/** Route the target app should navigate to after exchange. Echoed back. */
|
|
40
|
+
route?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Response body from `POST /v2/auth/handoff`.
|
|
44
|
+
*/
|
|
45
|
+
export interface HandoffCreateResponse {
|
|
46
|
+
operationId: string;
|
|
47
|
+
/** Raw token to embed in the redirect URL as `?handoff=<token>`. 30s TTL, single-use. */
|
|
48
|
+
token: string;
|
|
49
|
+
/** RFC3339 expiration timestamp. */
|
|
50
|
+
expiresAt: string;
|
|
51
|
+
/** Echoed `route` from the request, when provided. */
|
|
52
|
+
route?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Response body from `POST /v2/auth/handoff/exchange`.
|
|
56
|
+
*
|
|
57
|
+
* Same shape as `SigninResponse` — installable via `AuthService.setExternalAuth`.
|
|
58
|
+
*/
|
|
59
|
+
export interface HandoffExchangeResponse {
|
|
60
|
+
operationId: string;
|
|
61
|
+
accessToken: string;
|
|
62
|
+
refreshToken: string;
|
|
63
|
+
firebaseToken?: string;
|
|
64
|
+
expiresIn: number;
|
|
65
|
+
tokenType: string;
|
|
66
|
+
userId: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* HandoffService — cross-app session transfer.
|
|
70
|
+
*
|
|
71
|
+
* Implements the OAuth Authorization Code pattern, applied internally between
|
|
72
|
+
* apps that share the same backend.
|
|
73
|
+
*
|
|
74
|
+
* **Origin app (user is authenticated):**
|
|
75
|
+
*
|
|
76
|
+
* ```typescript
|
|
77
|
+
* const { token, route } = await firstValueFrom(
|
|
78
|
+
* handoff.createHandoff({ targetAppId: 'myvaltech', route: '/app/dashboard' })
|
|
79
|
+
* );
|
|
80
|
+
* window.location.href = `https://myvaltech.app/?handoff=${token}&route=${encodeURIComponent(route ?? '/')}`;
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* **Target app (boot):** call `exchangeHandoff(token)`. On success, the session
|
|
84
|
+
* is installed (`AuthService.setExternalAuth`) and the user is authenticated.
|
|
85
|
+
* See `detectAndExchangeHandoff` helper for the typical bootstrap pattern.
|
|
86
|
+
*
|
|
87
|
+
* Security notes:
|
|
88
|
+
* - Token is single-use and short-lived (30s) — enforced server-side.
|
|
89
|
+
* - Token in URL must be removed from history after exchange to avoid log leakage.
|
|
90
|
+
* - The exchange endpoint is public; do NOT add auth header. The
|
|
91
|
+
* `HttpClient` request below avoids triggering the auth interceptor since the
|
|
92
|
+
* user isn't logged in yet on the target app.
|
|
93
|
+
*/
|
|
94
|
+
export declare class HandoffService {
|
|
95
|
+
private config;
|
|
96
|
+
private http;
|
|
97
|
+
private auth;
|
|
98
|
+
private router;
|
|
99
|
+
private detected;
|
|
100
|
+
constructor(config: ValtechAuthConfig, http: HttpClient, auth: AuthService, router: Router);
|
|
101
|
+
/**
|
|
102
|
+
* Create a handoff token. Caller must be authenticated.
|
|
103
|
+
*
|
|
104
|
+
* @param request Optional metadata: target app id and intended route.
|
|
105
|
+
* @returns Observable emitting `{ token, expiresAt, route? }`.
|
|
106
|
+
*/
|
|
107
|
+
createHandoff(request?: HandoffCreateRequest): Observable<HandoffCreateResponse>;
|
|
108
|
+
/**
|
|
109
|
+
* Exchange a handoff token for a session and install it.
|
|
110
|
+
*
|
|
111
|
+
* On success, the response is piped through `AuthService.setExternalAuth`
|
|
112
|
+
* so the user becomes authenticated. Subsequent navigation can proceed.
|
|
113
|
+
*
|
|
114
|
+
* On failure, the observable errors. The caller is responsible for
|
|
115
|
+
* showing an error and routing to `/login`.
|
|
116
|
+
*
|
|
117
|
+
* @param token Raw handoff token read from the URL.
|
|
118
|
+
*/
|
|
119
|
+
exchangeHandoff(token: string): Observable<HandoffExchangeResponse>;
|
|
120
|
+
/**
|
|
121
|
+
* Bootstrap helper — reads the handoff token from the current URL, exchanges
|
|
122
|
+
* it for a session, and navigates to the intended route with the token
|
|
123
|
+
* stripped from history.
|
|
124
|
+
*
|
|
125
|
+
* Idempotent: subsequent calls are no-ops. Wire from an
|
|
126
|
+
* `APP_INITIALIZER`/`provideAppInitializer` factory in `main.ts`.
|
|
127
|
+
*
|
|
128
|
+
* ```typescript
|
|
129
|
+
* // main.ts
|
|
130
|
+
* provideAppInitializer(() => inject(HandoffService).detectAndExchangeHandoff())
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* On error (expired/used/invalid token), redirects to `errorRoute` (default:
|
|
134
|
+
* the app's configured `loginRoute`). The URL is always cleaned, even on
|
|
135
|
+
* error, so the token cannot be retried by refreshing the page.
|
|
136
|
+
*
|
|
137
|
+
* @returns `true` if a handoff was detected and processed (success or fail).
|
|
138
|
+
* `false` if no token was present (cold boot, normal flow).
|
|
139
|
+
*/
|
|
140
|
+
detectAndExchangeHandoff(options?: DetectAndExchangeOptions): Promise<boolean>;
|
|
141
|
+
/**
|
|
142
|
+
* Persist the session in `AuthService`. Mirrors the install flow used by
|
|
143
|
+
* the normal signin path so timers, Firebase, and tab-sync all kick in.
|
|
144
|
+
*/
|
|
145
|
+
private installSession;
|
|
146
|
+
private get baseUrl();
|
|
147
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<HandoffService, never>;
|
|
148
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<HandoffService>;
|
|
149
|
+
}
|
|
@@ -81,3 +81,7 @@ export { DeviceService } from './device.service';
|
|
|
81
81
|
export { SessionService } from './session.service';
|
|
82
82
|
export { OAuthService } from './oauth.service';
|
|
83
83
|
export { OAuthCallbackComponent } from './oauth-callback.component';
|
|
84
|
+
export { HandoffService, HANDOFF_TOKEN_PARAM, HANDOFF_ROUTE_PARAM } from './handoff.service';
|
|
85
|
+
export type { HandoffCreateRequest, HandoffCreateResponse, HandoffExchangeResponse, DetectAndExchangeOptions, } from './handoff.service';
|
|
86
|
+
export { OrgSwitchService } from './org-switch.service';
|
|
87
|
+
export type { OrgChangedEvent, SwitchOrgOptions } from './org-switch.service';
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Observable } from 'rxjs';
|
|
2
|
+
import { AuthService } from './auth.service';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* Event emitted when the active organization changes.
|
|
6
|
+
*/
|
|
7
|
+
export interface OrgChangedEvent {
|
|
8
|
+
/** Org the user was on before the switch. May be empty on first sign-in. */
|
|
9
|
+
previousOrg: string;
|
|
10
|
+
/** Org the user just switched to. */
|
|
11
|
+
newOrg: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Options for `OrgSwitchService.switchTo`.
|
|
15
|
+
*/
|
|
16
|
+
export interface SwitchOrgOptions {
|
|
17
|
+
/**
|
|
18
|
+
* If `true`, after the switch succeeds the page is fully reloaded via
|
|
19
|
+
* `window.location.reload()`. Useful for apps with significant in-memory
|
|
20
|
+
* state tied to the previous org that's hard to invalidate piece by piece.
|
|
21
|
+
*
|
|
22
|
+
* Trade-off: reload loses all in-memory state (forms, scroll position).
|
|
23
|
+
* Default: `false` — relies on `orgChanged$` and `auth.user()` signal
|
|
24
|
+
* propagation for components to react.
|
|
25
|
+
*/
|
|
26
|
+
reload?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* OrgSwitchService — orchestrates active organization changes across the app.
|
|
30
|
+
*
|
|
31
|
+
* Built on top of `AuthService.switchOrg`, which already:
|
|
32
|
+
* - Hits `POST /v2/auth/switch-org` and receives a new Firebase custom token.
|
|
33
|
+
* - Re-authenticates Firebase Auth with the new token (RBAC claims update).
|
|
34
|
+
* - Broadcasts `ORG_SWITCH` to other tabs via `AuthSyncService` (multi-tab sync).
|
|
35
|
+
*
|
|
36
|
+
* What this service adds on top:
|
|
37
|
+
* - A `switching` signal so the UI can show a loading indicator (1-2s switch).
|
|
38
|
+
* - An `orgChanged$` Observable that components subscribe to in order to
|
|
39
|
+
* invalidate their org-scoped caches (e.g. drop old query results, reset
|
|
40
|
+
* page state) without a full page reload.
|
|
41
|
+
* - Optional `reload: true` for apps where invalidating in-memory state piece
|
|
42
|
+
* by piece is impractical.
|
|
43
|
+
*
|
|
44
|
+
* **What this service does NOT do automatically:**
|
|
45
|
+
*
|
|
46
|
+
* 1. **Teardown Firestore listeners.** Listeners are owned by their subscribing
|
|
47
|
+
* components (typically via `takeUntilDestroyed` or async pipe). When the
|
|
48
|
+
* component re-renders or unsubscribes, the listener disposes. If a
|
|
49
|
+
* component does NOT unsubscribe on org change, its listener will keep
|
|
50
|
+
* pointing at the previous org's path and may start failing rules. The fix
|
|
51
|
+
* is component-level: subscribe to `orgChanged$` and reset state, or use
|
|
52
|
+
* the `reload: true` option.
|
|
53
|
+
*
|
|
54
|
+
* 2. **Re-instantiate routed components.** Angular keeps mounted components
|
|
55
|
+
* alive across navigations. If you need fresh state, either subscribe to
|
|
56
|
+
* `orgChanged$` in the component, or use `reload: true`.
|
|
57
|
+
*
|
|
58
|
+
* @example Basic switch with loading state
|
|
59
|
+
* ```typescript
|
|
60
|
+
* private orgSwitch = inject(OrgSwitchService);
|
|
61
|
+
*
|
|
62
|
+
* async onSwitchOrg(orgId: string) {
|
|
63
|
+
* await this.orgSwitch.switchTo(orgId);
|
|
64
|
+
* // Components subscribed to orgChanged$ have already reset their state.
|
|
65
|
+
* }
|
|
66
|
+
*
|
|
67
|
+
* // In template:
|
|
68
|
+
* @if (orgSwitch.switching()) { <val-loading /> }
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @example Component reacting to org change
|
|
72
|
+
* ```typescript
|
|
73
|
+
* private orgSwitch = inject(OrgSwitchService);
|
|
74
|
+
*
|
|
75
|
+
* constructor() {
|
|
76
|
+
* this.orgSwitch.orgChanged$
|
|
77
|
+
* .pipe(takeUntilDestroyed())
|
|
78
|
+
* .subscribe(() => this.resetState());
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @example Brutal reload mode
|
|
83
|
+
* ```typescript
|
|
84
|
+
* await this.orgSwitch.switchTo(orgId, { reload: true });
|
|
85
|
+
* // window.location.reload() — clean slate, loses scroll position
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare class OrgSwitchService {
|
|
89
|
+
private auth;
|
|
90
|
+
private readonly _switching;
|
|
91
|
+
private readonly _orgChanged;
|
|
92
|
+
/**
|
|
93
|
+
* `true` while a switch is in flight. UI should disable interactions
|
|
94
|
+
* with org-scoped data and show a loading indicator.
|
|
95
|
+
*/
|
|
96
|
+
readonly switching: import("@angular/core").Signal<boolean>;
|
|
97
|
+
/**
|
|
98
|
+
* Fires after a successful switch, with the previous and new org ids.
|
|
99
|
+
* Components subscribe to invalidate caches / reset state.
|
|
100
|
+
*
|
|
101
|
+
* Fires AFTER the Firebase re-auth completes — listeners attached here
|
|
102
|
+
* see the updated Firebase user / activeOrg claim.
|
|
103
|
+
*/
|
|
104
|
+
readonly orgChanged$: Observable<OrgChangedEvent>;
|
|
105
|
+
constructor(auth: AuthService);
|
|
106
|
+
/**
|
|
107
|
+
* Switch the user's active organization.
|
|
108
|
+
*
|
|
109
|
+
* Re-entrant safe: while a switch is in flight, additional calls are
|
|
110
|
+
* rejected silently (returns immediately). Inspect `switching()` to gate UI.
|
|
111
|
+
*
|
|
112
|
+
* @param orgId Target organization id. Must be one the user has a role in
|
|
113
|
+
* — backend rejects otherwise with `PERMISSION_DENIED`.
|
|
114
|
+
* @param options See `SwitchOrgOptions`.
|
|
115
|
+
*
|
|
116
|
+
* @throws The error from `auth.switchOrg` if the backend call fails.
|
|
117
|
+
* `switching` returns to `false` before the error propagates.
|
|
118
|
+
*/
|
|
119
|
+
switchTo(orgId: string, options?: SwitchOrgOptions): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Read the current `activeOrg` from the auth user signal.
|
|
122
|
+
* Falls back to empty string if the user isn't loaded yet.
|
|
123
|
+
*/
|
|
124
|
+
private currentActiveOrg;
|
|
125
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<OrgSwitchService, never>;
|
|
126
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<OrgSwitchService>;
|
|
127
|
+
}
|
|
@@ -16,6 +16,17 @@ export interface CollectionOptions {
|
|
|
16
16
|
* Default: true
|
|
17
17
|
*/
|
|
18
18
|
timestamps?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Si true, el path se trata como global y NO se prefija con `apps/{appId}/`.
|
|
21
|
+
*
|
|
22
|
+
* Usar para colecciones cross-app a nivel user/global, p.ej.
|
|
23
|
+
* `users/{uid}/notifications` (inbox global), `profiles/{uid}` o `public/...`.
|
|
24
|
+
*
|
|
25
|
+
* Las reglas de Firestore deben permitir el path absoluto correspondiente.
|
|
26
|
+
*
|
|
27
|
+
* Default: false (path prefijado a `apps/{appId}/...`).
|
|
28
|
+
*/
|
|
29
|
+
skipAppPrefix?: boolean;
|
|
19
30
|
}
|
|
20
31
|
/**
|
|
21
32
|
* Referencia a una sub-colección tipada.
|
|
@@ -73,8 +84,8 @@ export declare class FirestoreCollectionFactory {
|
|
|
73
84
|
*/
|
|
74
85
|
export declare class TypedCollection<T extends FirestoreDocument> {
|
|
75
86
|
private firestore;
|
|
76
|
-
private collectionPath;
|
|
77
87
|
private readonly options;
|
|
88
|
+
private readonly collectionPath;
|
|
78
89
|
constructor(firestore: FirestoreService, collectionPath: string, options?: CollectionOptions);
|
|
79
90
|
/**
|
|
80
91
|
* Obtiene un documento por ID.
|
|
@@ -2,6 +2,19 @@ import { Firestore, FieldValue } from '@angular/fire/firestore';
|
|
|
2
2
|
import { Observable } from 'rxjs';
|
|
3
3
|
import { FirestoreDocument, PaginatedResult, QueryOptions } from './types';
|
|
4
4
|
import * as i0 from "@angular/core";
|
|
5
|
+
/**
|
|
6
|
+
* Internal sentinel used to mark a collection path as global cross-app —
|
|
7
|
+
* i.e. it should NOT be prefixed with `apps/{appId}/`.
|
|
8
|
+
*
|
|
9
|
+
* Apps consume this via `CollectionOptions.skipAppPrefix`; the sentinel
|
|
10
|
+
* stays in `TypedCollection` and is stripped here before Firestore sees it.
|
|
11
|
+
*
|
|
12
|
+
* Format keeps it impossible to collide with a real Firestore path (`:` is
|
|
13
|
+
* not allowed in collection segments).
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export declare const ABS_PATH_SENTINEL = "__abs__:";
|
|
5
18
|
/**
|
|
6
19
|
* Servicio para operaciones CRUD en Firestore.
|
|
7
20
|
*
|
|
@@ -43,6 +56,11 @@ export declare class FirestoreService {
|
|
|
43
56
|
* Prefija el path de colección con el appId si está configurado.
|
|
44
57
|
* Si no hay appId, retorna el path sin modificar (backward compatible).
|
|
45
58
|
*
|
|
59
|
+
* Si el path empieza con `ABS_PATH_SENTINEL` (`__abs__:`), se asume que el
|
|
60
|
+
* caller quiere un path global cross-app (ej. `users/{uid}/notifications` —
|
|
61
|
+
* el inbox global de notificaciones). El sentinel se strippea y el resto
|
|
62
|
+
* del path se pasa verbatim, sin prefijo `apps/{appId}/`.
|
|
63
|
+
*
|
|
46
64
|
* @internal
|
|
47
65
|
*/
|
|
48
66
|
private prefixCollectionPath;
|
|
@@ -9,54 +9,87 @@ import { AppId, EmulatorConfig, FirebaseConfig, ValtechFirebaseConfig } from './
|
|
|
9
9
|
import type { AnalyticsConfig } from './analytics-types';
|
|
10
10
|
export type { AppId } from './types';
|
|
11
11
|
/**
|
|
12
|
-
* Genera paths de Storage con
|
|
12
|
+
* Genera paths de Storage alineados con storage.rules.
|
|
13
|
+
*
|
|
14
|
+
* Convenciones (ver frontend/firebase/storage.rules):
|
|
15
|
+
* - Avatar global del user (cross-app): /users/{uid}/avatar.jpg
|
|
16
|
+
* - Files privados del user (cross-app): /users/{uid}/files/{path}
|
|
17
|
+
* - Avatar per-app del user: /apps/{appId}/users/{uid}/avatar.jpg
|
|
18
|
+
* - Files per-app del user: /apps/{appId}/users/{uid}/files/{path}
|
|
19
|
+
* - Public per-app: /apps/{appId}/public/{path}
|
|
20
|
+
* - Shared per-app (auth-only): /apps/{appId}/shared/{path}
|
|
21
|
+
* - Public global: /public/{path}
|
|
13
22
|
*
|
|
14
23
|
* @example
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
24
|
+
* storagePaths.forApp('showcase', 'public', 'banner.jpg')
|
|
25
|
+
* // => 'apps/showcase/public/banner.jpg'
|
|
26
|
+
*
|
|
27
|
+
* storagePaths.userAvatar('user123')
|
|
28
|
+
* // => 'users/user123/avatar.jpg'
|
|
18
29
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* // => 'profile-photos/user123/avatar.jpg'
|
|
30
|
+
* storagePaths.appUserFile('showcase', 'user123', 'doc.pdf')
|
|
31
|
+
* // => 'apps/showcase/users/user123/files/doc.pdf'
|
|
22
32
|
*/
|
|
23
33
|
export declare const storagePaths: {
|
|
24
|
-
/**
|
|
34
|
+
/** Path per-app: apps/{appId}/{...paths} */
|
|
25
35
|
forApp: (appId: AppId, ...paths: string[]) => string;
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
/** Avatar global del user (cross-app) */
|
|
37
|
+
userAvatar: (userId: string) => string;
|
|
38
|
+
/** Thumbnail global del user (cross-app) */
|
|
39
|
+
userThumb: (userId: string) => string;
|
|
40
|
+
/** File privado global del user (cross-app) */
|
|
41
|
+
userFile: (userId: string, ...paths: string[]) => string;
|
|
42
|
+
/** Avatar per-app del user */
|
|
43
|
+
appUserAvatar: (appId: AppId, userId: string) => string;
|
|
44
|
+
/** File per-app del user */
|
|
45
|
+
appUserFile: (appId: AppId, userId: string, ...paths: string[]) => string;
|
|
46
|
+
/** Public global (acceso sin auth, write admin-only) */
|
|
47
|
+
public: (...paths: string[]) => string;
|
|
33
48
|
};
|
|
34
49
|
/**
|
|
35
|
-
* Genera paths de colecciones con
|
|
50
|
+
* Genera paths de colecciones alineados con firestore.rules.
|
|
36
51
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
52
|
+
* Convenciones (ver frontend/firebase/firestore.rules):
|
|
53
|
+
* - Cross-app shared:
|
|
54
|
+
* /users/{uid} (privado), /profiles/{uid} (público global),
|
|
55
|
+
* /users/{uid}/notifications (INBOX GLOBAL cross-app cross-org)
|
|
56
|
+
* - Per-app:
|
|
57
|
+
* /apps/{appId}/{collection}, /apps/{appId}/profiles/{uid}
|
|
58
|
+
* - Per-app per-user (notifications NO viven aquí):
|
|
59
|
+
* /apps/{appId}/users/{uid}/{collection}
|
|
60
|
+
* - Per-app per-org:
|
|
61
|
+
* /apps/{appId}/orgs/{orgId}/{collection}
|
|
39
62
|
*
|
|
40
63
|
* @example
|
|
41
|
-
* // Colección específica de la app
|
|
42
64
|
* collections.forApp('showcase', 'items')
|
|
43
65
|
* // => 'apps/showcase/items'
|
|
44
66
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
67
|
+
* collections.appUser('showcase', 'user123', 'drafts')
|
|
68
|
+
* // => 'apps/showcase/users/user123/drafts'
|
|
69
|
+
*
|
|
70
|
+
* collections.appOrg('showcase', 'org_abc', 'posts')
|
|
71
|
+
* // => 'apps/showcase/orgs/org_abc/posts'
|
|
72
|
+
*
|
|
73
|
+
* collections.userNotifications('user123')
|
|
74
|
+
* // => 'users/user123/notifications'
|
|
48
75
|
*/
|
|
49
76
|
export declare const collections: {
|
|
50
|
-
/**
|
|
77
|
+
/** Per-app cross-user: apps/{appId}/{collection} */
|
|
51
78
|
forApp: (appId: AppId, collectionName: string) => string;
|
|
52
|
-
/**
|
|
79
|
+
/** Per-app per-user: apps/{appId}/users/{uid}/{collection} */
|
|
80
|
+
appUser: (appId: AppId, userId: string, collectionName: string) => string;
|
|
81
|
+
/** Per-app per-org: apps/{appId}/orgs/{orgId}/{collection} */
|
|
82
|
+
appOrg: (appId: AppId, orgId: string, collectionName: string) => string;
|
|
83
|
+
/** Per-app perfil público del user: apps/{appId}/profiles/{uid} */
|
|
84
|
+
appProfile: (appId: AppId, userId: string) => string;
|
|
85
|
+
/** Inbox global del user (cross-app cross-org) */
|
|
86
|
+
userNotifications: (userId: string) => string;
|
|
87
|
+
/** Cross-app shared (sin namespace, nivel raíz) */
|
|
53
88
|
shared: {
|
|
54
|
-
/**
|
|
89
|
+
/** Doc privado del user — /users/{uid} */
|
|
55
90
|
users: string;
|
|
56
|
-
/** Perfiles públicos */
|
|
91
|
+
/** Perfiles públicos globales — /profiles/{uid} */
|
|
57
92
|
profiles: string;
|
|
58
|
-
/** Notificaciones de usuarios */
|
|
59
|
-
notifications: string;
|
|
60
93
|
};
|
|
61
94
|
};
|
|
62
95
|
/**
|
package/lib/version.d.ts
CHANGED