@c80/core
v1.2.2
Published
Infra Angular compartida del ecosistema barman (StorageService SSR-safe, ImageService de Cloudflare R2, factory del interceptor de auth, la sesion canonica email/password con guards y el ErrorHandler global de monitoreo). Fuente unica consumida por kyron-
Downloads
634
Readme
@c80/core
Infra Angular compartida del ecosistema barman. Fuente unica de los servicios de infraestructura que
antes vivian duplicados (copias identicas tras la reconciliacion D21) en kyron-front y chillbox.
Peer de Angular ^21, publicada a npm igual que @c80/ui y @c80/types (los fronts son repos
separados, no un monorepo: npm es el canal de distribucion).
Alcance v1.0.0 — SOLO los 3 servicios de infra reconciliados a un canonico identico:
StorageService— acceso alocalStorageSSR-safe + try/catch.ImageService— construccion de URLs de Cloudflare R2 (avatares/productos).createAuthInterceptor(...)— factory del interceptor de auth (Bearer + reaccion ante 401).- Sesion email/password canonica (1.1.0) —
C80AuthSessionService(token + perfil signal + permisos),c80AuthGuard/c80PermissionGuard/c80ProfileResolveryc80AuthInterceptorlisto. La usan kyron y midas viaprovideC80Core({ image, auth: {...} }); chillbox NO (login Google propio, sigue concreateAuthInterceptor). - ErrorHandler global de monitoreo (1.2.0) —
C80GlobalErrorHandlerreporta los errores de runtime del front a un endpoint (POST client-error) con fetch directo, truncado y throttled, y es fail-silent. Registrar conprovideC80Core({ ..., monitoring: { clientErrorUrl } })+provideC80GlobalErrorHandler(). Lo usan los 3 fronts (extraido de chillbox).
NO incluye notification ni base-crud (son solo de kyron, sin duplicacion).
Que exporta el barrel (@c80/core)
| Export | Tipo | Notas |
|---|---|---|
| StorageService | @Injectable({providedIn:'root'}) | set<T> / get<T> / remove / clear / has. No requiere config. |
| ImageService | @Injectable({providedIn:'root'}) | getAvatarUrl / getProductImageUrl / isR2Url. Requiere C80_CORE_CONFIG.image. |
| createAuthInterceptor(config) | (AuthInterceptorConfig) => HttpInterceptorFn | Factory; el front arma su authInterceptor delegando aca. |
| AuthInterceptorConfig | interface | Estrategia de auth del front (ver abajo). |
| provideC80Core(config) | (C80CoreConfig) => Provider | Registra la config de instancia en el bootstrap. |
| C80_CORE_CONFIG | InjectionToken<C80CoreConfig> | Token de la config; usar provideC80Core en vez de proveerlo a mano. |
| C80CoreConfig / C80ImageConfig / C80AuthSessionConfig | interface | Forma de la config (auth es opcional). |
| C80AuthSessionService | @Injectable({providedIn:'root'}) | Sesion email/password: login / logout / initialize / user signal / hasPermission*. Requiere C80_CORE_CONFIG.auth. |
| c80AuthGuard / c80PermissionGuard | CanActivateFn | Guards de sesion y de permisos por route.data (C80PermissionGuardData). |
| c80ProfileResolver | ResolveFn | Garantiza el perfil cargado y lo entrega a la ruta. |
| c80AuthInterceptor | HttpInterceptorFn | Interceptor listo (Bearer salvo login, 401 transparente). |
| C80AuthCredentials / C80SessionUser / C80SessionRole / C80LoginResult | interface | Tipos estructurales minimos de la sesion (castear al contrato pleno en la app). |
Config de instancia (obligatoria para ImageService)
ImageService depende de la config de R2, que es config de instancia (el publicUrl del bucket
cambia por entorno) y NO puede hardcodearse en la lib. Cada front la provee con su environment.image:
// app.config.ts / main de cada front
import { provideC80Core } from '@c80/core';
import { environment } from '@environments/environment';
providers: [
provideC80Core({ image: environment.image }),
]environment.image de ambos fronts ya tiene la forma exacta de C80ImageConfig
({ publicUrl, avatarsFolder, productsFolder, imageFormat }), asi que se pasa directo.
StorageService no necesita config. Si un front usa solo StorageService, provideC80Core igual es
inofensivo (barato); pero si usa ImageService sin proveer la config, el inject(C80_CORE_CONFIG)
falla en runtime — proveerlo siempre.
Registro del interceptor de auth
La FORMA compartida es "Bearer salvo login + reaccion global ante 401". Los 3 puntos que divergen por
diseño se parametrizan (no se unifican): endpoint de login, fuente del token y handler del 401. Cada
front escribe un interceptor delgado que inyecta SUS deps y delega en createAuthInterceptor:
// chillbox — src/app/shared/interceptors/auth.interceptor.ts
import type { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { createAuthInterceptor, StorageService } from '@c80/core';
import { AuthService } from '../services/auth.service';
import { STORAGE_KEYS, API_ENDPOINTS } from '@shared/constants';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const storage = inject(StorageService);
const auth = inject(AuthService);
return createAuthInterceptor({
getToken: () => storage.get<string>(STORAGE_KEYS.TOKEN),
isLoginRequest: (r) => r.url === API_ENDPOINTS.auth.googleLogin,
onUnauthorized: () => auth.logout(),
})(req, next);
};// kyron — src/app/core/interceptors/auth.interceptor.ts
import type { HttpInterceptorFn } from '@angular/common/http';
import { createAuthInterceptor } from '@c80/core';
import { TokenStorage } from '@core/utils/token-storage.util';
// El 401 lo maneja notification.interceptor => NO se pasa onUnauthorized (interceptor transparente a
// la respuesta, mismo camino que hoy).
export const authInterceptor: HttpInterceptorFn = createAuthInterceptor({
getToken: () => TokenStorage.get(),
isLoginRequest: (r) => r.url.includes('/auth/login'),
skipBearerOnLoginRequest: true,
});AuthInterceptorConfig
getToken: () => string | null— de donde sale el token.isLoginRequest?: (req) => boolean— marca el request de login (default: ninguno). Se usa para no desloguear ante su 401 y, siskipBearerOnLoginRequest, para no adjuntarle el Bearer.skipBearerOnLoginRequest?: boolean— no adjunta Bearer al login (kyrontrue; chillbox omite =false).onUnauthorized?: () => void— accion ante un 401 que no sea del login (chillbox:logout). Si se omite, el interceptor no envuelve la respuesta (lo maneja otra pieza; caso kyron).
Publicacion
Mismo flujo que @c80/ui: pnpm publish:core (corre gate + bump + build del dist/core + publish).
La v1.0.0 se publico manualmente (sin bump) desde dist/core.
