|
| 1 | +import { isPlatformServer } from '@angular/common'; |
| 2 | +import { Inject, Injectable, Optional, PLATFORM_ID } from '@angular/core'; |
| 3 | +import { HttpClient, HttpResponse } from '@angular/common/http'; |
| 4 | +import { catchError } from 'rxjs/operators'; |
| 5 | +import { firstValueFrom, of as observableOf } from 'rxjs'; |
| 6 | +import { HTML_SUFFIX, STATIC_FILES_PROJECT_PATH } from '../static-page/static-page-routing-paths'; |
| 7 | +import { isEmpty } from './empty.util'; |
| 8 | +import { LocaleService } from '../core/locale/locale.service'; |
| 9 | +import { APP_CONFIG, AppConfig } from '../../config/app-config.interface'; |
| 10 | +import { REQUEST } from '@nguniversal/express-engine/tokens'; |
| 11 | + |
| 12 | +/** |
| 13 | + * Service for loading static `.html` files stored in the `/static-files` folder. |
| 14 | + */ |
| 15 | +@Injectable() |
| 16 | +export class HtmlContentService { |
| 17 | + constructor(private http: HttpClient, |
| 18 | + private localeService: LocaleService, |
| 19 | + @Inject(APP_CONFIG) protected appConfig?: AppConfig, |
| 20 | + @Inject(PLATFORM_ID) private platformId?: object, |
| 21 | + @Optional() @Inject(REQUEST) private request?: any, |
| 22 | + ) {} |
| 23 | + |
| 24 | + private getNamespacePrefix(): string { |
| 25 | + const nameSpace = this.appConfig?.ui?.nameSpace ?? '/'; |
| 26 | + if (nameSpace === '/') { |
| 27 | + return ''; |
| 28 | + } |
| 29 | + return nameSpace.endsWith('/') ? nameSpace.slice(0, -1) : nameSpace; |
| 30 | + } |
| 31 | + |
| 32 | + private composeNamespacedUrl(url: string): string { |
| 33 | + if (/^https?:\/\//i.test(url)) { |
| 34 | + return url; |
| 35 | + } |
| 36 | + |
| 37 | + const normalizedPath = url.startsWith('/') ? url : `/${url}`; |
| 38 | + const namespacePrefix = this.getNamespacePrefix(); |
| 39 | + |
| 40 | + if (namespacePrefix && normalizedPath.startsWith(`${namespacePrefix}/`)) { |
| 41 | + return normalizedPath; |
| 42 | + } |
| 43 | + |
| 44 | + return `${namespacePrefix}${normalizedPath}`; |
| 45 | + } |
| 46 | + |
| 47 | + private buildRuntimeUrl(path: string): string { |
| 48 | + if (!isPlatformServer(this.platformId) || !this.request) { |
| 49 | + return path; |
| 50 | + } |
| 51 | + |
| 52 | + const protocol = this.request.protocol; |
| 53 | + const host = this.request.get?.('host'); |
| 54 | + if (!protocol || !host) { |
| 55 | + return path; |
| 56 | + } |
| 57 | + |
| 58 | + return `${protocol}://${host}${path}`; |
| 59 | + } |
| 60 | + |
| 61 | + getHtmlContent(url: string) { |
| 62 | + const namespacedUrl = this.composeNamespacedUrl(url); |
| 63 | + const runtimeUrl = this.buildRuntimeUrl(namespacedUrl); |
| 64 | + return this.http.get(runtimeUrl, { responseType: 'text' }).pipe( |
| 65 | + catchError(() => observableOf(''))); |
| 66 | + } |
| 67 | + |
| 68 | + /** |
| 69 | + * Load `.html` file content and return the full response. |
| 70 | + * @param url file location |
| 71 | + */ |
| 72 | + fetchHtmlContent(url: string) { |
| 73 | + const namespacedUrl = this.composeNamespacedUrl(url); |
| 74 | + const runtimeUrl = this.buildRuntimeUrl(namespacedUrl); |
| 75 | + return this.http.get(runtimeUrl, { responseType: 'text', observe: 'response' }).pipe( |
| 76 | + catchError((error) => observableOf(new HttpResponse({ status: error.status || 0, body: '' })))); |
| 77 | + } |
| 78 | + |
| 79 | + /** |
| 80 | + * Load HTML content for a single URL attempt and handle cached 304 responses. |
| 81 | + * @param url file location |
| 82 | + */ |
| 83 | + private async loadHtmlContent(url: string): Promise<string | undefined> { |
| 84 | + const response = await firstValueFrom(this.fetchHtmlContent(url)); |
| 85 | + if (response.status === 200) { |
| 86 | + return response.body ?? ''; |
| 87 | + } |
| 88 | + if (response.status === 304) { |
| 89 | + return response.body ?? ''; |
| 90 | + } |
| 91 | + return undefined; |
| 92 | + } |
| 93 | + |
| 94 | + /** |
| 95 | + * Get the html file content as a string by the file name and the current locale. |
| 96 | + */ |
| 97 | + async getHmtlContentByPathAndLocale(fileName: string) { |
| 98 | + let url = ''; |
| 99 | + // Get current language |
| 100 | + let language = this.localeService.getCurrentLanguageCode(); |
| 101 | + // If language is default = `en` do not load static files from translated package e.g. `cs`. |
| 102 | + language = language === 'en' ? '' : language; |
| 103 | + |
| 104 | + // Try to find the html file in the translated package. `static-files/language_code/some_file.html` |
| 105 | + // Compose url |
| 106 | + url = STATIC_FILES_PROJECT_PATH; |
| 107 | + url += isEmpty(language) ? '/' + fileName : '/' + language + '/' + fileName; |
| 108 | + // Add `.html` suffix to get the current html file |
| 109 | + url = url.endsWith(HTML_SUFFIX) ? url : url + HTML_SUFFIX; |
| 110 | + let potentialContent = await this.loadHtmlContent(url); |
| 111 | + if (potentialContent !== undefined) { |
| 112 | + return potentialContent; |
| 113 | + } |
| 114 | + |
| 115 | + // If the file wasn't find, get the non-translated file from the default package. |
| 116 | + url = STATIC_FILES_PROJECT_PATH + '/' + fileName; |
| 117 | + // Add `.html` suffix to match localized request behavior |
| 118 | + url = url.endsWith(HTML_SUFFIX) ? url : url + HTML_SUFFIX; |
| 119 | + potentialContent = await this.loadHtmlContent(url); |
| 120 | + if (potentialContent !== undefined) { |
| 121 | + return potentialContent; |
| 122 | + } |
| 123 | + } |
| 124 | +} |
0 commit comments