import { computed, inject, Injectable, signal } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { firstValueFrom } from 'rxjs'; import { NGXLogger } from 'ngx-logger'; import { BASE_HREF } from '../../utils'; import { Router } from '@angular/router'; export interface IBaseTenant { readonly tenantId: string; readonly displayName: string; readonly guid: string; } @Injectable({ providedIn: 'root' }) export class TenantsService { readonly tenants = signal(undefined); readonly hasMultiple = computed(() => { const tenants = this.tenants(); return tenants ? tenants.length > 1 : false; }); readonly #http = inject(HttpClient); readonly #router = inject(Router); readonly #logger = inject(NGXLogger); readonly #baseHref = inject(BASE_HREF); readonly #storageReference: Storage = { length: localStorage.length, clear: localStorage.clear.bind(localStorage), getItem: localStorage.getItem.bind(localStorage), setItem: localStorage.setItem.bind(localStorage), removeItem: localStorage.removeItem.bind(localStorage), key: localStorage.key.bind(localStorage), }; readonly #activeTenantId = signal(''); get currentTenant() { return this.#activeTenantId(); } async loadTenants() { this.#logger.info('[TENANTS] Loading tenants...'); const tenants = await firstValueFrom(this.#http.get('/tenants/simple')); this.tenants.set(tenants); const tenant = this.getTenantFromRoute(); if (tenant) { this.#logger.info('[TENANTS] Tenant from route: ', tenant); if (await this.selectTenant(tenant)) { await this.#executeMainResolverAndRedirect(tenant); } return; } this.#logger.info('[TENANTS] No tenant in route'); if (!this.hasMultiple) { this.#logger.info('[TENANTS] Only one tenant loaded, auto-select it and redirect to login page'); const tenant = tenants[0].tenantId; if (await this.selectTenant(tenant)) { await this.#executeMainResolverAndRedirect(tenant); } } } getTenantFromRoute() { const path = window.location.pathname; const nextSlash = path.indexOf('/', this.#baseHref.length + 1); return path.substring(this.#baseHref.length + 1, nextSlash >= 0 ? nextSlash : path.length); } async selectTenant(tenantId: string) { const tenants = this.tenants(); if (!tenants) { throw new Error('Tenants not loaded!'); } const unknownTenant = !tenants.map(t => t.tenantId).includes(tenantId); if (unknownTenant) { this.#logger.info('[TENANTS] Unknown tenant, redirecting to select tenant page'); await this.#router.navigate(['/']); return false; } this.#mutateStorage(tenantId); this.#setCurrentTenantId(tenantId); return true; } #setCurrentTenantId(tenantId: string) { this.#logger.info('[TENANTS] Set current tenant id: ', tenantId); this.#activeTenantId.set(tenantId); } #mutateStorage(tenant: string) { localStorage.getItem = (key: string) => { return this.#storageReference.getItem(tenant + ':' + key); }; localStorage.setItem = (key: string, value: string) => { this.#storageReference.setItem(tenant + ':' + key, value); }; localStorage.removeItem = (key: string) => { this.#storageReference.removeItem(tenant + ':' + key); }; } async #executeMainResolverAndRedirect(tenant: string) { const intendedPath = window.location.pathname.replace(this.#baseHref, ''); // TODO: Load roles in child routes canLoad functions so we can redirect straight to wanted URL await this.#router.navigate([tenant], { skipLocationChange: true }); await this.#router.navigate([intendedPath]); } }