diff --git a/docs/en/UI/Angular/Multi-Tenancy.md b/docs/en/UI/Angular/Multi-Tenancy.md index 92f76435ca..493f6bf194 100644 --- a/docs/en/UI/Angular/Multi-Tenancy.md +++ b/docs/en/UI/Angular/Multi-Tenancy.md @@ -125,6 +125,37 @@ After this replacement, the app will use the following URLs: The app sends the `__tenant` header that contains the current tenant id on each request. +### Handling Domain-based Tenant not found + +When the application is initialized and a domain-based tenant is active, it makes a request for to validate the tenant. If it cannot be located, the Angular application calls the function. The function displays an error message, but the functionality can be customized by injecting the `TENANT_NOT_FOUND_BY_NAME` token into the function. + +Example: +```ts +import { TENANT_NOT_FOUND_BY_NAME } from '@abp/ng.core'; + +@NgModule({ + imports: [ + // removed for clarity + ], + providers: [ + // removed for clarity + { + provide: TENANT_NOT_FOUND_BY_NAME, + useFactory: function () { + return () => { + window.location.replace('https://abp.io'); + }; + }, + }, + ], + declarations: [AppComponent], + bootstrap: [AppComponent], +}) +export class AppModule {} + +``` + ## See Also - [Multi Tenancy in ABP](../../Multi-Tenancy.md) + diff --git a/npm/ng-packs/packages/core/src/lib/tokens/index.ts b/npm/ng-packs/packages/core/src/lib/tokens/index.ts index d01f652b98..12f4aae15a 100644 --- a/npm/ng-packs/packages/core/src/lib/tokens/index.ts +++ b/npm/ng-packs/packages/core/src/lib/tokens/index.ts @@ -12,4 +12,5 @@ export * from './pipe-to-login.token'; export * from './set-token-response-to-storage.token'; export * from './check-authentication-state'; export * from './http-context.token'; -export * from './others-group.token' \ No newline at end of file +export * from './others-group.token'; +export * from './tenant-not-found-by-name'; diff --git a/npm/ng-packs/packages/core/src/lib/tokens/tenant-not-found-by-name.ts b/npm/ng-packs/packages/core/src/lib/tokens/tenant-not-found-by-name.ts new file mode 100644 index 0000000000..639c1a3d1e --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tokens/tenant-not-found-by-name.ts @@ -0,0 +1,6 @@ +import { InjectionToken } from '@angular/core'; +import { HttpErrorResponse } from '@angular/common/http'; + +export const TENANT_NOT_FOUND_BY_NAME = new InjectionToken< + (HttpErrorResponse: HttpErrorResponse) => void +>('TENANT_NOT_FOUND_BY_NAME'); diff --git a/npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts index a0b5aa89c6..cd1b9fe50e 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/multi-tenancy-utils.ts @@ -1,13 +1,14 @@ import { Injector } from '@angular/core'; import clone from 'just-clone'; - -import { tap } from 'rxjs/operators'; import { Environment } from '../models/environment'; import { FindTenantResultDto } from '../proxy/volo/abp/asp-net-core/mvc/multi-tenancy/models'; import { EnvironmentService } from '../services/environment.service'; import { MultiTenancyService } from '../services/multi-tenancy.service'; import { createTokenParser } from './string-utils'; +import { firstValueFrom } from 'rxjs'; +import { TENANT_NOT_FOUND_BY_NAME } from '../tokens/tenant-not-found-by-name'; +import { HttpErrorResponse } from '@angular/common/http'; const tenancyPlaceholder = '{0}'; @@ -27,6 +28,7 @@ function getCurrentTenancyNameFromUrl(tenantKey: string) { export async function parseTenantFromUrl(injector: Injector) { const environmentService = injector.get(EnvironmentService); const multiTenancyService = injector.get(MultiTenancyService); + const tenantNotFoundHandler = injector.get(TENANT_NOT_FOUND_BY_NAME, null); const baseUrl = environmentService.getEnvironment()?.application?.baseUrl || ''; const tenancyName = getCurrentTenancyName(baseUrl); @@ -55,21 +57,33 @@ export async function parseTenantFromUrl(injector: Injector) { * Before this request takes place, we need to replace placeholders aka "{0}". */ replaceTenantNameWithinEnvironment(injector, tenancyName); - return multiTenancyService - .setTenantByName(tenancyName) - .pipe(tap(setEnvironmentWithDomainTenant)) - .toPromise(); - } else { - /** - * If there is no tenant, we still have to clean up {0}. from baseUrl to avoid incorrect http requests. - */ - replaceTenantNameWithinEnvironment(injector, '', tenancyPlaceholder + '.'); - const tenantIdFromQueryParams = getCurrentTenancyNameFromUrl(multiTenancyService.tenantKey); - if (tenantIdFromQueryParams) { - return multiTenancyService.setTenantById(tenantIdFromQueryParams).toPromise(); + const tenant$ = multiTenancyService.setTenantByName(tenancyName); + try { + const result = await firstValueFrom(tenant$); + setEnvironmentWithDomainTenant(result); + return Promise.resolve(result); + } catch (httpError: HttpErrorResponse | any) { + if ( + httpError instanceof HttpErrorResponse && + httpError.status === 404 && + tenantNotFoundHandler + ) { + tenantNotFoundHandler(httpError); + } + return Promise.reject(); } } + /** + * If there is no tenant, we still have to clean up {0}. from baseUrl to avoid incorrect http requests. + */ + replaceTenantNameWithinEnvironment(injector, '', tenancyPlaceholder + '.'); + + const tenantIdFromQueryParams = getCurrentTenancyNameFromUrl(multiTenancyService.tenantKey); + if (tenantIdFromQueryParams) { + const tenantById$ = multiTenancyService.setTenantById(tenantIdFromQueryParams); + return firstValueFrom(tenantById$); + } return Promise.resolve(); } @@ -97,14 +111,20 @@ function replaceTenantNameWithinEnvironment( ); } - if(!environment.oAuthConfig) { + if (!environment.oAuthConfig) { environment.oAuthConfig = {}; } - environment.oAuthConfig.issuer = (environment.oAuthConfig.issuer || '').replace(placeholder, tenancyName); + environment.oAuthConfig.issuer = (environment.oAuthConfig.issuer || '').replace( + placeholder, + tenancyName, + ); Object.keys(environment.apis).forEach(api => { Object.keys(environment.apis[api]).forEach(key => { - environment.apis[api][key] = (environment.apis[api][key] || '').replace(placeholder, tenancyName); + environment.apis[api][key] = (environment.apis[api][key] || '').replace( + placeholder, + tenancyName, + ); }); }); diff --git a/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts b/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts index e929ab7c79..8b10d39bf2 100644 --- a/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts +++ b/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts @@ -1,6 +1,6 @@ import { Injectable, Injector } from '@angular/core'; import { Params } from '@angular/router'; -import { from, Observable, lastValueFrom } from 'rxjs'; +import { from, Observable, lastValueFrom, EMPTY } from 'rxjs'; import { filter, map, switchMap, take, tap } from 'rxjs/operators'; import { AbpAuthResponse, IAuthService, LoginParams } from '@abp/ng.core'; import { AuthFlowStrategy } from '../strategies'; @@ -44,6 +44,10 @@ export class AbpOAuthService implements IAuthService { } logout(queryParams?: Params): Observable { + + if(!this.strategy){ + return EMPTY + } return this.strategy.logout(queryParams); } diff --git a/npm/ng-packs/packages/theme-shared/src/lib/providers/index.ts b/npm/ng-packs/packages/theme-shared/src/lib/providers/index.ts index 40efb9df39..076d6169ba 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/providers/index.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/providers/index.ts @@ -1,2 +1,3 @@ export * from './ng-bootstrap-config.provider'; export * from './route.provider'; +export * from './tenant-not-found.provider'; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/providers/tenant-not-found.provider.ts b/npm/ng-packs/packages/theme-shared/src/lib/providers/tenant-not-found.provider.ts new file mode 100644 index 0000000000..ac98f613b4 --- /dev/null +++ b/npm/ng-packs/packages/theme-shared/src/lib/providers/tenant-not-found.provider.ts @@ -0,0 +1,20 @@ +import { TENANT_NOT_FOUND_BY_NAME } from '@abp/ng.core'; +import { inject, Provider } from '@angular/core'; +import { ConfirmationService } from '../services'; +import { HttpErrorResponse } from '@angular/common/http'; + +export const tenantNotFoundProvider: Provider = { + provide: TENANT_NOT_FOUND_BY_NAME, + useFactory: function () { + const confirm = inject(ConfirmationService); + return (response: HttpErrorResponse) => { + const { error } = response.error; + // hide loading donut + const appRoot = document.querySelector('app-root div.donut'); + if (appRoot) { + appRoot.remove(); + } + confirm.error(error.details, error.message, { hideCancelBtn: true, hideYesBtn: true }); + }; + }, +}; diff --git a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts index 0fea3f166c..030418eadf 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/theme-shared.module.ts @@ -40,6 +40,7 @@ import { CardModule } from './components/card/card.module'; import { AbpVisibleDirective } from './directives'; import { FormInputComponent } from './components/form-input/form-input.component'; import { FormCheckboxComponent } from './components/checkbox/checkbox.component'; +import { tenantNotFoundProvider } from './providers/tenant-not-found.provider'; const declarationsWithExports = [ BreadcrumbComponent, @@ -58,7 +59,7 @@ const declarationsWithExports = [ ModalCloseDirective, AbpVisibleDirective, FormInputComponent, - FormCheckboxComponent + FormCheckboxComponent, ]; @NgModule({ @@ -69,7 +70,6 @@ const declarationsWithExports = [ NgbPaginationModule, EllipsisModule, CardModule, - ], declarations: [...declarationsWithExports, HttpErrorWrapperComponent], exports: [ @@ -77,11 +77,11 @@ const declarationsWithExports = [ EllipsisModule, NgxValidateCoreModule, CardModule, - ...declarationsWithExports + ...declarationsWithExports, ], providers: [DatePipe], }) -export class BaseThemeSharedModule { } +export class BaseThemeSharedModule {} @NgModule({ imports: [BaseThemeSharedModule], @@ -144,6 +144,7 @@ export class ThemeSharedModule { ...(confirmationIcons || {}), }, }, + tenantNotFoundProvider, ], }; }