@sdcorejs/angular 21.0.8 → 21.0.10
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/README.md +270 -140
- package/assets/scss/core/utilities/_grid.scss +25 -19
- package/fesm2022/sdcorejs-angular-components-anchor.mjs +15 -18
- package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-avatar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-badge.mjs +0 -1
- package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-button.mjs +6 -7
- package/fesm2022/sdcorejs-angular-components-button.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-chart.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-ckeditor-styles.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-code-editor.mjs +1 -1
- package/fesm2022/sdcorejs-angular-components-code-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs +17 -26
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-editor.mjs +7 -2
- package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs +712 -427
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs +35 -18
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-inform.mjs +0 -1
- package/fesm2022/sdcorejs-angular-components-inform.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal.mjs +21 -14
- package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-operator.mjs +1 -1
- package/fesm2022/sdcorejs-angular-components-operator.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-org-chart.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-preview.mjs +33 -29
- package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs +15 -28
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs +2 -3
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-quick-action.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-section.mjs +0 -1
- package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +3 -3
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-splitter.mjs +4 -4
- package/fesm2022/sdcorejs-angular-components-splitter.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-stepper.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab-router.mjs +31 -31
- package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-table.mjs +800 -855
- package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs +6 -6
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-view.mjs +3 -9
- package/fesm2022/sdcorejs-angular-components-view.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-configurations.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-directives.mjs +2 -7
- package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +0 -3
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs +11 -5
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date.mjs +2 -4
- package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs +2 -6
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs +4 -4
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-label.mjs +0 -1
- package/fesm2022/sdcorejs-angular-forms-label.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-radio.mjs +12 -2
- package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-select.mjs +45 -14
- package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-handlers.mjs +3 -6
- package/fesm2022/sdcorejs-angular-handlers.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-i18n.mjs +11 -7
- package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-interceptors.mjs +0 -2
- package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-auth.mjs +14 -38
- package/fesm2022/sdcorejs-angular-modules-auth.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-authom.mjs +14 -14
- package/fesm2022/sdcorejs-angular-modules-authom.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-keycloak.mjs +22 -11
- package/fesm2022/sdcorejs-angular-modules-keycloak.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-layout.mjs +30 -27
- package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-permission.mjs +5 -12
- package/fesm2022/sdcorejs-angular-modules-permission.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-pipes.mjs +71 -17
- package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-api.mjs +0 -2
- package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-cache.mjs +5 -11
- package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-confirm.mjs +16 -21
- package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-docx.mjs +2 -8
- package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-excel.mjs +2 -2
- package/fesm2022/sdcorejs-angular-services-excel.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-license.mjs +5 -12
- package/fesm2022/sdcorejs-angular-services-license.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-notify.mjs +70 -87
- package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-storage.mjs +6 -15
- package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-utilities-data-state.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-utilities-extensions.mjs +47 -5
- package/fesm2022/sdcorejs-angular-utilities-extensions.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-utilities-models.mjs +18 -3
- package/fesm2022/sdcorejs-angular-utilities-models.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular.mjs.map +1 -1
- package/package.json +2 -2
- package/types/sdcorejs-angular-components-document-builder.d.ts +2 -2
- package/types/sdcorejs-angular-components-form-generic.d.ts +45 -23
- package/types/sdcorejs-angular-components-import-excel.d.ts +2 -2
- package/types/sdcorejs-angular-components-mini-editor.d.ts +4 -4
- package/types/sdcorejs-angular-components-modal.d.ts +1 -1
- package/types/sdcorejs-angular-components-query-builder.d.ts +4 -4
- package/types/sdcorejs-angular-components-tab-router.d.ts +3 -3
- package/types/sdcorejs-angular-components-table.d.ts +8 -11
- package/types/sdcorejs-angular-components-view.d.ts +1 -1
- package/types/sdcorejs-angular-forms-chip-calendar.d.ts +2 -2
- package/types/sdcorejs-angular-forms-chip.d.ts +2 -2
- package/types/sdcorejs-angular-forms-date-range.d.ts +2 -0
- package/types/sdcorejs-angular-forms-input-color.d.ts +2 -1
- package/types/sdcorejs-angular-forms-select.d.ts +10 -2
- package/types/sdcorejs-angular-modules-auth.d.ts +7 -9
- package/types/sdcorejs-angular-modules-permission.d.ts +5 -6
- package/types/sdcorejs-angular-pipes.d.ts +22 -4
- package/types/sdcorejs-angular-services-confirm.d.ts +3 -3
- package/types/sdcorejs-angular-services-license.d.ts +2 -3
- package/types/sdcorejs-angular-services-notify.d.ts +4 -5
- package/types/sdcorejs-angular-services-storage.d.ts +2 -3
- package/types/sdcorejs-angular-utilities-extensions.d.ts +9 -1
|
@@ -98,7 +98,6 @@ class SdNoInternetInterceptor {
|
|
|
98
98
|
#startPolling = () => {
|
|
99
99
|
this.#stopPolling(); // Clear cũ nếu có
|
|
100
100
|
// @i18n-ignore — dev console log
|
|
101
|
-
console.log('--- Bắt đầu chế độ theo dõi mạng ---');
|
|
102
101
|
this.#pollInterval = setInterval(() => {
|
|
103
102
|
if (!this.#http) {
|
|
104
103
|
this.#http = this.injector.get(HttpClient);
|
|
@@ -108,7 +107,6 @@ class SdNoInternetInterceptor {
|
|
|
108
107
|
next: () => {
|
|
109
108
|
// --> ĐÃ CÓ MẠNG LẠI
|
|
110
109
|
// @i18n-ignore — dev console log
|
|
111
|
-
console.log('--> Kết nối đã được khôi phục!');
|
|
112
110
|
this.#stopPolling();
|
|
113
111
|
this.#isOffline = false;
|
|
114
112
|
// Thông báo thành công (tự tắt sau 5s)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdcorejs-angular-interceptors.mjs","sources":["../../../projects/sdcorejs-angular/interceptors/no-internet/no-internet.interceptor.ts","../../../projects/sdcorejs-angular/interceptors/unauthorized/unauthorized.interceptor.ts","../../../projects/sdcorejs-angular/interceptors/sdcorejs-angular-interceptors.ts"],"sourcesContent":["import { HttpClient, HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';\nimport { inject, Injectable, Injector } from '@angular/core';\nimport { MatSnackBar, MatSnackBarRef } from '@angular/material/snack-bar';\nimport { I18nService } from '@sdcorejs/angular/i18n';\nimport { Observable, throwError } from 'rxjs';\nimport { catchError, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class SdNoInternetInterceptor implements HttpInterceptor {\n readonly #i18n = inject(I18nService);\n\n // Trạng thái offline để tránh spam request check hoặc hiển thị nhiều snackbar\n #isOffline = false;\n\n // Giữ tham chiếu để có thể đóng/thao tác snackbar\n #snackBarRef: MatSnackBarRef<any> | null = null;\n\n // Giữ tham chiếu interval để clear khi có mạng lại\n #pollInterval: ReturnType<typeof setInterval> | null = null;\n\n // Lazy load HttpClient\n #http: HttpClient | null = null;\n\n // Endpoint kiểm tra mạng (Nên dùng 1 file tĩnh nhẹ hoặc API ping public uy tín)\n readonly #healthCheckUrl = 'https://jsonplaceholder.typicode.com/todos/1';\n\n // Thời gian lặp lại việc kiểm tra mạng (ms)\n readonly #checkIntervalDuration = 3000;\n\n constructor(\n private snackBar: MatSnackBar,\n private injector: Injector\n ) {}\n\n intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {\n return next.handle(request).pipe(\n catchError((error: HttpErrorResponse) => {\n // --- TRƯỜNG HỢP 1: Lỗi mất kết nối (Status 0) ---\n if (error.status === 0 && !this.#isOffline) {\n this.#isOffline = true;\n\n // Lazy load HttpClient để tránh lỗi Circular Dependency\n if (!this.#http) {\n this.#http = this.injector.get(HttpClient);\n }\n\n // Gọi thử một API public để xác minh xem là Mất mạng thật hay do CORS/Server chặn\n return this.#http.get(this.#healthCheckUrl).pipe(\n // 1. Ưu tiên bắt lỗi kết nối trước (Đây là logic MẤT MẠNG THẬT)\n catchError(_checkError => {\n // Hiển thị thông báo và bắt đầu polling chờ mạng\n this.#showReloadSnackbar(this.#i18n.t('core.interceptor.no-internet.offline'), { isSticky: true });\n this.#startPolling();\n\n // Ném lại lỗi gốc để Component biết request thất bại\n return throwError(() => error);\n }),\n\n // 2. Nếu không vào catchError ở trên -> Check thành công -> CÓ MẠNG\n // Lỗi status 0 ban đầu là do CORS, SSL, hoặc Server chặn connection\n switchMap(() => {\n this.#isOffline = false; // Reset cờ\n\n this.snackBar.open(this.#i18n.t('core.interceptor.no-internet.cors-error'), this.#i18n.t('core.common.close'), {\n duration: 5000,\n horizontalPosition: 'center',\n verticalPosition: 'top',\n });\n\n // Ném lại lỗi gốc\n return throwError(() => error);\n })\n );\n }\n\n // --- TRƯỜNG HỢP 2: Server bảo trì (503) ---\n else if (error.status === 503) {\n this.snackBar.open(this.#i18n.t('core.interceptor.no-internet.maintenance'), this.#i18n.t('core.common.close'), {\n horizontalPosition: 'center',\n verticalPosition: 'top',\n duration: 5000,\n });\n }\n\n // --- TRƯỜNG HỢP KHÁC: 401, 404, 500... ---\n // Ném lỗi ra để component xử lý bình thường\n return throwError(() => error);\n })\n );\n }\n\n /**\n * Hiển thị Snackbar thông báo trạng thái mạng\n */\n #showReloadSnackbar = (message: string, options?: { duration?: number; isSticky?: boolean }): void => {\n // Đóng snackbar cũ nếu đang hiện\n if (this.#snackBarRef) {\n this.#snackBarRef.dismiss();\n }\n\n this.#snackBarRef = this.snackBar.open(message, this.#i18n.t('core.common.reload'), {\n horizontalPosition: 'center',\n verticalPosition: 'top',\n // Nếu isSticky = true (mất mạng) -> Không tự tắt. Nếu có duration -> tự tắt.\n duration: options?.duration,\n panelClass: options?.isSticky ? ['offline-snackbar'] : undefined, // Class CSS tùy chọn\n });\n\n // Xử lý sự kiện bấm nút \"Tải lại trang\"\n this.#snackBarRef.onAction().subscribe(() => {\n window.location.reload();\n });\n };\n\n /**\n * Bắt đầu vòng lặp kiểm tra kết nối mạng\n */\n #startPolling = (): void => {\n this.#stopPolling(); // Clear cũ nếu có\n\n // @i18n-ignore — dev console log\n console.log('--- Bắt đầu chế độ theo dõi mạng ---');\n\n this.#pollInterval = setInterval(() => {\n if (!this.#http) {\n this.#http = this.injector.get(HttpClient);\n }\n\n // Check nhẹ\n this.#http.get(this.#healthCheckUrl).subscribe({\n next: () => {\n // --> ĐÃ CÓ MẠNG LẠI\n // @i18n-ignore — dev console log\n console.log('--> Kết nối đã được khôi phục!');\n\n this.#stopPolling();\n this.#isOffline = false;\n\n // Thông báo thành công (tự tắt sau 5s)\n this.#showReloadSnackbar(this.#i18n.t('core.interceptor.no-internet.restored'), { duration: 5000, isSticky: false });\n },\n error: () => {\n // --> VẪN MẤT MẠNG: Không làm gì cả, chờ lần check tiếp theo\n },\n });\n }, this.#checkIntervalDuration);\n };\n\n /**\n * Dừng vòng lặp kiểm tra\n */\n #stopPolling = (): void => {\n if (this.#pollInterval) {\n clearInterval(this.#pollInterval);\n this.#pollInterval = null;\n }\n };\n}\n","import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { SdAuthService } from '@sdcorejs/angular/modules';\nimport { Observable, throwError } from 'rxjs';\nimport { catchError } from 'rxjs/operators';\n\n@Injectable()\nexport class SdUnauthorizedInterceptor implements HttpInterceptor {\n #unauthorizedHandled = false;\n\n constructor(private authService: SdAuthService) {}\n\n intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {\n return next.handle(request).pipe(\n catchError((error: HttpErrorResponse) => {\n if (error.status === 401 && !this.#unauthorizedHandled) {\n this.#unauthorizedHandled = true;\n this.authService.signout();\n }\n\n return throwError(() => error);\n })\n );\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["i1"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sdcorejs-angular-interceptors.mjs","sources":["../../../projects/sdcorejs-angular/interceptors/no-internet/no-internet.interceptor.ts","../../../projects/sdcorejs-angular/interceptors/unauthorized/unauthorized.interceptor.ts","../../../projects/sdcorejs-angular/interceptors/sdcorejs-angular-interceptors.ts"],"sourcesContent":["import { HttpClient, HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';\nimport { inject, Injectable, Injector } from '@angular/core';\nimport { MatSnackBar, MatSnackBarRef } from '@angular/material/snack-bar';\nimport { I18nService } from '@sdcorejs/angular/i18n';\nimport { Observable, throwError } from 'rxjs';\nimport { catchError, switchMap } from 'rxjs/operators';\n\n@Injectable()\nexport class SdNoInternetInterceptor implements HttpInterceptor {\n readonly #i18n = inject(I18nService);\n\n // Trạng thái offline để tránh spam request check hoặc hiển thị nhiều snackbar\n #isOffline = false;\n\n // Giữ tham chiếu để có thể đóng/thao tác snackbar\n #snackBarRef: MatSnackBarRef<any> | null = null;\n\n // Giữ tham chiếu interval để clear khi có mạng lại\n #pollInterval: ReturnType<typeof setInterval> | null = null;\n\n // Lazy load HttpClient\n #http: HttpClient | null = null;\n\n // Endpoint kiểm tra mạng (Nên dùng 1 file tĩnh nhẹ hoặc API ping public uy tín)\n readonly #healthCheckUrl = 'https://jsonplaceholder.typicode.com/todos/1';\n\n // Thời gian lặp lại việc kiểm tra mạng (ms)\n readonly #checkIntervalDuration = 3000;\n\n constructor(\n private snackBar: MatSnackBar,\n private injector: Injector\n ) {}\n\n intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {\n return next.handle(request).pipe(\n catchError((error: HttpErrorResponse) => {\n // --- TRƯỜNG HỢP 1: Lỗi mất kết nối (Status 0) ---\n if (error.status === 0 && !this.#isOffline) {\n this.#isOffline = true;\n\n // Lazy load HttpClient để tránh lỗi Circular Dependency\n if (!this.#http) {\n this.#http = this.injector.get(HttpClient);\n }\n\n // Gọi thử một API public để xác minh xem là Mất mạng thật hay do CORS/Server chặn\n return this.#http.get(this.#healthCheckUrl).pipe(\n // 1. Ưu tiên bắt lỗi kết nối trước (Đây là logic MẤT MẠNG THẬT)\n catchError(_checkError => {\n // Hiển thị thông báo và bắt đầu polling chờ mạng\n this.#showReloadSnackbar(this.#i18n.t('core.interceptor.no-internet.offline'), { isSticky: true });\n this.#startPolling();\n\n // Ném lại lỗi gốc để Component biết request thất bại\n return throwError(() => error);\n }),\n\n // 2. Nếu không vào catchError ở trên -> Check thành công -> CÓ MẠNG\n // Lỗi status 0 ban đầu là do CORS, SSL, hoặc Server chặn connection\n switchMap(() => {\n this.#isOffline = false; // Reset cờ\n\n this.snackBar.open(this.#i18n.t('core.interceptor.no-internet.cors-error'), this.#i18n.t('core.common.close'), {\n duration: 5000,\n horizontalPosition: 'center',\n verticalPosition: 'top',\n });\n\n // Ném lại lỗi gốc\n return throwError(() => error);\n })\n );\n }\n\n // --- TRƯỜNG HỢP 2: Server bảo trì (503) ---\n else if (error.status === 503) {\n this.snackBar.open(this.#i18n.t('core.interceptor.no-internet.maintenance'), this.#i18n.t('core.common.close'), {\n horizontalPosition: 'center',\n verticalPosition: 'top',\n duration: 5000,\n });\n }\n\n // --- TRƯỜNG HỢP KHÁC: 401, 404, 500... ---\n // Ném lỗi ra để component xử lý bình thường\n return throwError(() => error);\n })\n );\n }\n\n /**\n * Hiển thị Snackbar thông báo trạng thái mạng\n */\n #showReloadSnackbar = (message: string, options?: { duration?: number; isSticky?: boolean }): void => {\n // Đóng snackbar cũ nếu đang hiện\n if (this.#snackBarRef) {\n this.#snackBarRef.dismiss();\n }\n\n this.#snackBarRef = this.snackBar.open(message, this.#i18n.t('core.common.reload'), {\n horizontalPosition: 'center',\n verticalPosition: 'top',\n // Nếu isSticky = true (mất mạng) -> Không tự tắt. Nếu có duration -> tự tắt.\n duration: options?.duration,\n panelClass: options?.isSticky ? ['offline-snackbar'] : undefined, // Class CSS tùy chọn\n });\n\n // Xử lý sự kiện bấm nút \"Tải lại trang\"\n this.#snackBarRef.onAction().subscribe(() => {\n window.location.reload();\n });\n };\n\n /**\n * Bắt đầu vòng lặp kiểm tra kết nối mạng\n */\n #startPolling = (): void => {\n this.#stopPolling(); // Clear cũ nếu có\n\n // @i18n-ignore — dev console log\n\n this.#pollInterval = setInterval(() => {\n if (!this.#http) {\n this.#http = this.injector.get(HttpClient);\n }\n\n // Check nhẹ\n this.#http.get(this.#healthCheckUrl).subscribe({\n next: () => {\n // --> ĐÃ CÓ MẠNG LẠI\n // @i18n-ignore — dev console log\n\n this.#stopPolling();\n this.#isOffline = false;\n\n // Thông báo thành công (tự tắt sau 5s)\n this.#showReloadSnackbar(this.#i18n.t('core.interceptor.no-internet.restored'), { duration: 5000, isSticky: false });\n },\n error: () => {\n // --> VẪN MẤT MẠNG: Không làm gì cả, chờ lần check tiếp theo\n },\n });\n }, this.#checkIntervalDuration);\n };\n\n /**\n * Dừng vòng lặp kiểm tra\n */\n #stopPolling = (): void => {\n if (this.#pollInterval) {\n clearInterval(this.#pollInterval);\n this.#pollInterval = null;\n }\n };\n}\n","import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';\nimport { Injectable } from '@angular/core';\nimport { SdAuthService } from '@sdcorejs/angular/modules';\nimport { Observable, throwError } from 'rxjs';\nimport { catchError } from 'rxjs/operators';\n\n@Injectable()\nexport class SdUnauthorizedInterceptor implements HttpInterceptor {\n #unauthorizedHandled = false;\n\n constructor(private authService: SdAuthService) {}\n\n intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {\n return next.handle(request).pipe(\n catchError((error: HttpErrorResponse) => {\n if (error.status === 401 && !this.#unauthorizedHandled) {\n this.#unauthorizedHandled = true;\n this.authService.signout();\n }\n\n return throwError(() => error);\n })\n );\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["i1"],"mappings":"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"}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken,
|
|
2
|
+
import { InjectionToken, inject, signal, Injectable } from '@angular/core';
|
|
3
3
|
import { Subject } from 'rxjs';
|
|
4
4
|
import { normalizeAsync, resolveMaybeAsync } from '@sdcorejs/utils/models';
|
|
5
5
|
import { toSignal } from '@angular/core/rxjs-interop';
|
|
6
|
-
import
|
|
6
|
+
import { Router } from '@angular/router';
|
|
7
7
|
|
|
8
8
|
const SD_AUTH_CONFIGURATION = new InjectionToken('sd.auth.configuration');
|
|
9
9
|
|
|
10
10
|
class SdAuthService {
|
|
11
|
-
authConfiguration;
|
|
12
11
|
// Đăng nhập
|
|
13
12
|
// #signin = new Subject<void>();
|
|
14
13
|
// signin$?: Observable<void>;
|
|
@@ -19,8 +18,8 @@ class SdAuthService {
|
|
|
19
18
|
// Thay đổi mật khẩu
|
|
20
19
|
#changePassword = new Subject();
|
|
21
20
|
changePassword$;
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
authConfiguration = inject(SD_AUTH_CONFIGURATION, { optional: true });
|
|
22
|
+
constructor() {
|
|
24
23
|
// this.#handleSignin();
|
|
25
24
|
this.#handleSignout();
|
|
26
25
|
this.#handleChangePassword();
|
|
@@ -62,26 +61,17 @@ class SdAuthService {
|
|
|
62
61
|
});
|
|
63
62
|
}
|
|
64
63
|
};
|
|
65
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdAuthService, deps: [
|
|
64
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
66
65
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdAuthService, providedIn: 'root' });
|
|
67
66
|
}
|
|
68
67
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdAuthService, decorators: [{
|
|
69
68
|
type: Injectable,
|
|
70
69
|
args: [{ providedIn: 'root' }]
|
|
71
|
-
}], ctorParameters: () => [
|
|
72
|
-
type: Optional
|
|
73
|
-
}, {
|
|
74
|
-
type: Inject,
|
|
75
|
-
args: [SD_AUTH_CONFIGURATION]
|
|
76
|
-
}] }] });
|
|
70
|
+
}], ctorParameters: () => [] });
|
|
77
71
|
|
|
78
72
|
class SdAuthGuard {
|
|
79
|
-
authConfiguration;
|
|
80
|
-
router;
|
|
81
|
-
constructor(authConfiguration, router) {
|
|
82
|
-
this.authConfiguration = authConfiguration;
|
|
83
|
-
this.router = router;
|
|
84
|
-
}
|
|
73
|
+
authConfiguration = inject(SD_AUTH_CONFIGURATION, { optional: true });
|
|
74
|
+
router = inject(Router);
|
|
85
75
|
canActivate = (route, state) => {
|
|
86
76
|
if (this.authConfiguration?.guard?.auth) {
|
|
87
77
|
return this.authConfiguration.guard.auth(route, state);
|
|
@@ -89,26 +79,17 @@ class SdAuthGuard {
|
|
|
89
79
|
// Nếu chưa tích hợp Auth Guard thì pass canActive
|
|
90
80
|
return true;
|
|
91
81
|
};
|
|
92
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdAuthGuard, deps: [
|
|
82
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdAuthGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
93
83
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdAuthGuard, providedIn: 'root' });
|
|
94
84
|
}
|
|
95
85
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdAuthGuard, decorators: [{
|
|
96
86
|
type: Injectable,
|
|
97
87
|
args: [{ providedIn: 'root' }]
|
|
98
|
-
}]
|
|
99
|
-
type: Optional
|
|
100
|
-
}, {
|
|
101
|
-
type: Inject,
|
|
102
|
-
args: [SD_AUTH_CONFIGURATION]
|
|
103
|
-
}] }, { type: i1.Router }] });
|
|
88
|
+
}] });
|
|
104
89
|
|
|
105
90
|
class SdPortalGuard {
|
|
106
|
-
authConfiguration;
|
|
107
|
-
router;
|
|
108
|
-
constructor(authConfiguration, router) {
|
|
109
|
-
this.authConfiguration = authConfiguration;
|
|
110
|
-
this.router = router;
|
|
111
|
-
}
|
|
91
|
+
authConfiguration = inject(SD_AUTH_CONFIGURATION, { optional: true });
|
|
92
|
+
router = inject(Router);
|
|
112
93
|
canActivate = (route, state) => {
|
|
113
94
|
if (this.authConfiguration?.guard?.portal) {
|
|
114
95
|
return this.authConfiguration.guard.portal(route, state);
|
|
@@ -116,18 +97,13 @@ class SdPortalGuard {
|
|
|
116
97
|
// Nếu chưa tích hợp Portal Guard thì pass canActive
|
|
117
98
|
return true;
|
|
118
99
|
};
|
|
119
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdPortalGuard, deps: [
|
|
100
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdPortalGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
120
101
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdPortalGuard, providedIn: 'root' });
|
|
121
102
|
}
|
|
122
103
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdPortalGuard, decorators: [{
|
|
123
104
|
type: Injectable,
|
|
124
105
|
args: [{ providedIn: 'root' }]
|
|
125
|
-
}]
|
|
126
|
-
type: Optional
|
|
127
|
-
}, {
|
|
128
|
-
type: Inject,
|
|
129
|
-
args: [SD_AUTH_CONFIGURATION]
|
|
130
|
-
}] }, { type: i1.Router }] });
|
|
106
|
+
}] });
|
|
131
107
|
|
|
132
108
|
/**
|
|
133
109
|
* Generated bundle index. Do not edit.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdcorejs-angular-modules-auth.mjs","sources":["../../../projects/sdcorejs-angular/modules/auth/configurations/auth.configuration.ts","../../../projects/sdcorejs-angular/modules/auth/services/auth.service.ts","../../../projects/sdcorejs-angular/modules/auth/guards/auth.guard.ts","../../../projects/sdcorejs-angular/modules/auth/guards/portal.guard.ts","../../../projects/sdcorejs-angular/modules/auth/sdcorejs-angular-modules-auth.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { CanActivate } from '@angular/router';\nimport { MaybeAsync } from '@sdcorejs/utils/models';\nimport { SdAuthInfo } from '../services';\n\nexport interface ISdAuthConfiguration {\n action?: IAuthConfigurationAction;\n guard?: IAuthConfigurationGuard;\n}\n\nexport const SD_AUTH_CONFIGURATION = new InjectionToken<ISdAuthConfiguration>('sd.auth.configuration');\n\ninterface IAuthConfigurationAction {\n signout: () => MaybeAsync<void>;\n changePassword?: () => MaybeAsync<void>;\n}\n\ninterface IAuthConfigurationGuard {\n auth?: CanActivate['canActivate'];\n portal?: CanActivate['canActivate'];\n authInfo: () => MaybeAsync<SdAuthInfo>;\n}\n","import {
|
|
1
|
+
{"version":3,"file":"sdcorejs-angular-modules-auth.mjs","sources":["../../../projects/sdcorejs-angular/modules/auth/configurations/auth.configuration.ts","../../../projects/sdcorejs-angular/modules/auth/services/auth.service.ts","../../../projects/sdcorejs-angular/modules/auth/guards/auth.guard.ts","../../../projects/sdcorejs-angular/modules/auth/guards/portal.guard.ts","../../../projects/sdcorejs-angular/modules/auth/sdcorejs-angular-modules-auth.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { CanActivate } from '@angular/router';\nimport { MaybeAsync } from '@sdcorejs/utils/models';\nimport { SdAuthInfo } from '../services';\n\nexport interface ISdAuthConfiguration {\n action?: IAuthConfigurationAction;\n guard?: IAuthConfigurationGuard;\n}\n\nexport const SD_AUTH_CONFIGURATION = new InjectionToken<ISdAuthConfiguration>('sd.auth.configuration');\n\ninterface IAuthConfigurationAction {\n signout: () => MaybeAsync<void>;\n changePassword?: () => MaybeAsync<void>;\n}\n\ninterface IAuthConfigurationGuard {\n auth?: CanActivate['canActivate'];\n portal?: CanActivate['canActivate'];\n authInfo: () => MaybeAsync<SdAuthInfo>;\n}\n","import { inject, Injectable, signal, Signal } from '@angular/core';\nimport { Observable, Subject } from 'rxjs';\nimport { ISdAuthConfiguration, SD_AUTH_CONFIGURATION } from '../configurations';\nimport { normalizeAsync, resolveMaybeAsync } from '@sdcorejs/utils/models';\nimport { SdAuthInfo } from './auth.model';\nimport { toSignal } from '@angular/core/rxjs-interop';\n\n@Injectable({ providedIn: 'root' })\nexport class SdAuthService {\n // Đăng nhập\n // #signin = new Subject<void>();\n // signin$?: Observable<void>;\n getAuthInfo?: Signal<SdAuthInfo | undefined>;\n\n // Đăng xuất\n #signout = new Subject<void>();\n signout$?: Observable<void>;\n\n // Thay đổi mật khẩu\n #changePassword = new Subject<void>();\n changePassword$?: Observable<void>;\n private readonly authConfiguration: ISdAuthConfiguration | null = inject(SD_AUTH_CONFIGURATION, { optional: true });\n\n constructor() {\n // this.#handleSignin();\n this.#handleSignout();\n this.#handleChangePassword();\n const defaultUser = {\n id: 'guest-id',\n username: 'guest',\n firstName: 'Guest',\n email: 'guest@gmail.com',\n };\n if (this.authConfiguration?.guard?.authInfo) {\n this.getAuthInfo = toSignal(normalizeAsync(this.authConfiguration.guard?.authInfo()), {\n initialValue: defaultUser,\n });\n } else {\n this.getAuthInfo = signal(defaultUser);\n }\n }\n\n // #handleSignin = () => {\n // this.signin$ = this.#signin.asObservable();\n // };\n\n #handleSignout = () => {\n this.signout$ = this.#signout.asObservable();\n };\n\n #handleChangePassword = () => {\n this.changePassword$ = this.#changePassword.asObservable();\n };\n signout = () => {\n if (this.authConfiguration?.action?.signout) {\n resolveMaybeAsync(this.authConfiguration.action.signout()).then(() => {\n this.#signout.next();\n });\n }\n };\n\n changePassword = () => {\n if (this.authConfiguration?.action?.changePassword) {\n resolveMaybeAsync(this.authConfiguration.action.changePassword()).then(() => {\n this.#changePassword.next();\n });\n }\n };\n}\n","import { inject, Injectable } from '@angular/core';\nimport { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';\nimport { ISdAuthConfiguration, SD_AUTH_CONFIGURATION } from '../configurations';\n\n@Injectable({ providedIn: 'root' })\nexport class SdAuthGuard implements CanActivate {\n private readonly authConfiguration: ISdAuthConfiguration | null = inject(SD_AUTH_CONFIGURATION, { optional: true });\n protected readonly router = inject(Router);\n\n canActivate = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {\n if (this.authConfiguration?.guard?.auth) {\n return this.authConfiguration.guard.auth(route, state);\n }\n // Nếu chưa tích hợp Auth Guard thì pass canActive\n return true;\n };\n}\n","import { inject, Injectable } from '@angular/core';\nimport { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';\nimport { ISdAuthConfiguration, SD_AUTH_CONFIGURATION } from '../configurations';\n\n@Injectable({ providedIn: 'root' })\nexport class SdPortalGuard implements CanActivate {\n private readonly authConfiguration: ISdAuthConfiguration | null = inject(SD_AUTH_CONFIGURATION, { optional: true });\n protected readonly router = inject(Router);\n\n canActivate = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {\n if (this.authConfiguration?.guard?.portal) {\n return this.authConfiguration.guard.portal(route, state);\n }\n // Nếu chưa tích hợp Portal Guard thì pass canActive\n return true;\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -24,8 +24,8 @@ class SdAuthOmService {
|
|
|
24
24
|
}
|
|
25
25
|
base64UrlEncode(bytes) {
|
|
26
26
|
let binary = '';
|
|
27
|
-
for (
|
|
28
|
-
binary += String.fromCharCode(
|
|
27
|
+
for (const byte of bytes)
|
|
28
|
+
binary += String.fromCharCode(byte);
|
|
29
29
|
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
30
30
|
}
|
|
31
31
|
generateCodeVerifier() {
|
|
@@ -79,7 +79,7 @@ class SdAuthOmService {
|
|
|
79
79
|
const state = crypto.randomUUID();
|
|
80
80
|
const codeVerifier = this.generateCodeVerifier();
|
|
81
81
|
const codeChallenge = await this.generateCodeChallenge(codeVerifier);
|
|
82
|
-
const returnTo = options?.returnTo ||
|
|
82
|
+
const returnTo = options?.returnTo || window.location.pathname + window.location.search;
|
|
83
83
|
sessionStorage.setItem(STORAGE_KEY_STATE, state);
|
|
84
84
|
sessionStorage.setItem(STORAGE_KEY_CODE_VERIFIER, codeVerifier);
|
|
85
85
|
sessionStorage.setItem(STORAGE_KEY_RETURN_TO, returnTo);
|
|
@@ -121,7 +121,7 @@ class SdAuthOmService {
|
|
|
121
121
|
});
|
|
122
122
|
if (!res.ok)
|
|
123
123
|
return false;
|
|
124
|
-
const data = await res.json();
|
|
124
|
+
const data = (await res.json());
|
|
125
125
|
if (!data.access_token)
|
|
126
126
|
return false;
|
|
127
127
|
this.accessToken.set(data.access_token);
|
|
@@ -154,15 +154,15 @@ class SdAuthOmService {
|
|
|
154
154
|
this.silentRefresh();
|
|
155
155
|
}, delayMs);
|
|
156
156
|
}
|
|
157
|
-
silentRefresh() {
|
|
157
|
+
async silentRefresh() {
|
|
158
158
|
if (!this.isBrowser)
|
|
159
|
-
return
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
159
|
+
return false;
|
|
160
|
+
const state = crypto.randomUUID();
|
|
161
|
+
const codeVerifier = this.generateCodeVerifier();
|
|
162
|
+
const codeChallenge = await this.generateCodeChallenge(codeVerifier);
|
|
163
|
+
const redirectUri = this.getSilentRedirectUri();
|
|
164
|
+
const timeoutMs = (this.config.authorizeTimeoutInSeconds ?? DEFAULT_AUTHORIZE_TIMEOUT_SECONDS) * 1000;
|
|
165
|
+
return new Promise(resolve => {
|
|
166
166
|
const iframe = document.createElement('iframe');
|
|
167
167
|
iframe.style.display = 'none';
|
|
168
168
|
let done = false;
|
|
@@ -286,7 +286,7 @@ function provideSdAuthOm(options) {
|
|
|
286
286
|
providers.push(provideAppInitializer(() => {
|
|
287
287
|
const configLoader = inject(SD_AUTHOM_CONFIGURATION);
|
|
288
288
|
const authom = inject(SdAuthOmService);
|
|
289
|
-
return configLoader.loadTenantConfig().then(
|
|
289
|
+
return configLoader.loadTenantConfig().then(config => authom.init(config));
|
|
290
290
|
}));
|
|
291
291
|
return makeEnvironmentProviders(providers);
|
|
292
292
|
}
|
|
@@ -305,7 +305,7 @@ class SdAuthOmModule {
|
|
|
305
305
|
useFactory: () => {
|
|
306
306
|
const configLoader = inject(SD_AUTHOM_CONFIGURATION);
|
|
307
307
|
const authom = inject(SdAuthOmService);
|
|
308
|
-
return () => configLoader.loadTenantConfig().then(
|
|
308
|
+
return () => configLoader.loadTenantConfig().then(config => authom.init(config));
|
|
309
309
|
},
|
|
310
310
|
},
|
|
311
311
|
],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdcorejs-angular-modules-authom.mjs","sources":["../../../projects/sdcorejs-angular/modules/authom/authom.configuration.ts","../../../projects/sdcorejs-angular/modules/authom/authom.service.ts","../../../projects/sdcorejs-angular/modules/authom/authom.interceptor.ts","../../../projects/sdcorejs-angular/modules/authom/authom.module.ts","../../../projects/sdcorejs-angular/modules/authom/index.ts","../../../projects/sdcorejs-angular/modules/authom/sdcorejs-angular-modules-authom.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport interface SdAuthOmTenantConfig {\n domain: string;\n clientId: string;\n\n redirectUri?: string;\n audience?: string;\n organization?: string;\n scope?: string;\n\n secureRoutes?: string[];\n\n silentRefreshRedirectUri?: string;\n refreshThresholdSeconds?: number;\n authorizeTimeoutInSeconds?: number;\n}\n\nexport interface ISdAuthOmConfiguration {\n loadTenantConfig: () => Promise<SdAuthOmTenantConfig>;\n}\n\nexport const SD_AUTHOM_CONFIGURATION = new InjectionToken<ISdAuthOmConfiguration>('sd-authom.configuration');\n","import { Injectable, PLATFORM_ID, computed, inject, signal } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { SdAuthOmTenantConfig } from './authom.configuration';\nimport { StringUtilities } from '@sdcorejs/utils/fns';\n\nconst STORAGE_KEY_STATE = 'authom_state';\nconst STORAGE_KEY_CODE_VERIFIER = 'authom_code_verifier';\nconst STORAGE_KEY_RETURN_TO = 'authom_return_to';\n\nconst DEFAULT_SCOPE = 'openid profile email';\nconst DEFAULT_REFRESH_THRESHOLD_SECONDS = 30;\nconst DEFAULT_AUTHORIZE_TIMEOUT_SECONDS = 5;\n\n@Injectable({ providedIn: 'root' })\nexport class SdAuthOmService {\n private readonly platformId = inject(PLATFORM_ID);\n private readonly isBrowser = isPlatformBrowser(this.platformId);\n\n readonly accessToken = signal<string | null>(null);\n readonly idTokenClaims = signal<Record<string, unknown> | null>(null);\n readonly isAuthenticated = computed(() => this.accessToken() !== null);\n\n config!: SdAuthOmTenantConfig;\n private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n\n getAccessToken(): string | null {\n return this.accessToken();\n }\n\n private base64UrlEncode(bytes: Uint8Array): string {\n let binary = '';\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n }\n\n private generateCodeVerifier(): string {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return this.base64UrlEncode(bytes);\n }\n\n private async generateCodeChallenge(verifier: string): Promise<string> {\n return StringUtilities.sha256(verifier);\n }\n\n private buildAuthorizeUrl(params: {\n state: string;\n codeChallenge: string;\n redirectUri: string;\n prompt?: 'none';\n }): string {\n const search = new URLSearchParams({\n response_type: 'code',\n client_id: this.config.clientId,\n redirect_uri: params.redirectUri,\n state: params.state,\n code_challenge: params.codeChallenge,\n code_challenge_method: 'S256',\n scope: this.config.scope || DEFAULT_SCOPE,\n });\n if (this.config.audience) search.set('audience', this.config.audience);\n if (this.config.organization) search.set('organization', this.config.organization);\n if (params.prompt) search.set('prompt', params.prompt);\n return `https://${this.config.domain}/authorize?${search.toString()}`;\n }\n\n private getDefaultRedirectUri(): string {\n return this.config.redirectUri || window.location.origin;\n }\n\n private getSilentRedirectUri(): string {\n return this.config.silentRefreshRedirectUri || `${window.location.origin}/silent-authom.html`;\n }\n\n private decodeJwtPayload(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n const padded = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n const json = atob(padded + '==='.slice((padded.length + 3) % 4));\n return JSON.parse(json);\n } catch {\n return null;\n }\n }\n\n async login(options?: { returnTo?: string }): Promise<void> {\n if (!this.isBrowser) return;\n\n const state = crypto.randomUUID();\n const codeVerifier = this.generateCodeVerifier();\n const codeChallenge = await this.generateCodeChallenge(codeVerifier);\n const returnTo = options?.returnTo || (window.location.pathname + window.location.search);\n\n sessionStorage.setItem(STORAGE_KEY_STATE, state);\n sessionStorage.setItem(STORAGE_KEY_CODE_VERIFIER, codeVerifier);\n sessionStorage.setItem(STORAGE_KEY_RETURN_TO, returnTo);\n\n window.location.href = this.buildAuthorizeUrl({\n state,\n codeChallenge,\n redirectUri: this.getDefaultRedirectUri(),\n });\n }\n\n logout(options?: { returnTo?: string }): void {\n if (!this.isBrowser) return;\n\n this.accessToken.set(null);\n this.idTokenClaims.set(null);\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n\n const returnTo = options?.returnTo || window.location.origin;\n const search = new URLSearchParams({\n client_id: this.config.clientId,\n returnTo,\n });\n window.location.href = `https://${this.config.domain}/v2/logout?${search.toString()}`;\n }\n\n private async exchangeCode(code: string, codeVerifier: string, redirectUri: string): Promise<boolean> {\n try {\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n code_verifier: codeVerifier,\n redirect_uri: redirectUri,\n client_id: this.config.clientId,\n });\n const res = await fetch(`https://${this.config.domain}/oauth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n if (!res.ok) return false;\n const data = await res.json() as { access_token?: string; id_token?: string };\n if (!data.access_token) return false;\n\n this.accessToken.set(data.access_token);\n if (data.id_token) {\n this.idTokenClaims.set(this.decodeJwtPayload(data.id_token));\n }\n this.scheduleRefresh(data.access_token);\n return true;\n } catch {\n return false;\n }\n }\n\n private scheduleRefresh(token: string): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n const claims = this.decodeJwtPayload(token);\n const exp = claims?.['exp'];\n if (typeof exp !== 'number') {\n // @i18n-ignore — dev console warning\n console.warn('[SdAuthOmService] Token không có exp — bỏ qua auto-refresh');\n return;\n }\n const threshold = this.config.refreshThresholdSeconds ?? DEFAULT_REFRESH_THRESHOLD_SECONDS;\n const nowSeconds = Math.floor(Date.now() / 1000);\n const delayMs = Math.max(0, (exp - nowSeconds - threshold) * 1000);\n this.refreshTimer = setTimeout(() => {\n this.silentRefresh();\n }, delayMs);\n }\n\n silentRefresh(): Promise<boolean> {\n if (!this.isBrowser) return Promise.resolve(false);\n\n return new Promise(async (resolve) => {\n const state = crypto.randomUUID();\n const codeVerifier = this.generateCodeVerifier();\n const codeChallenge = await this.generateCodeChallenge(codeVerifier);\n const redirectUri = this.getSilentRedirectUri();\n const timeoutMs = (this.config.authorizeTimeoutInSeconds ?? DEFAULT_AUTHORIZE_TIMEOUT_SECONDS) * 1000;\n\n const iframe = document.createElement('iframe');\n iframe.style.display = 'none';\n\n let done = false;\n const finish = (success: boolean) => {\n if (done) return;\n done = true;\n clearTimeout(timer);\n iframe.remove();\n window.removeEventListener('message', onMessage);\n resolve(success);\n };\n\n const timer = setTimeout(() => finish(false), timeoutMs);\n\n const onMessage = async (event: MessageEvent) => {\n if (event.origin !== window.location.origin) return;\n const data = event.data as { type?: string; code?: string; state?: string } | null;\n if (!data || typeof data !== 'object') return;\n\n if (data.type === 'AUTHOM_SILENT_SUCCESS' && data.code && data.state === state) {\n const ok = await this.exchangeCode(data.code, codeVerifier, redirectUri);\n if (!ok) {\n this.accessToken.set(null);\n this.idTokenClaims.set(null);\n }\n finish(ok);\n } else if (data.type === 'AUTHOM_SILENT_ERROR') {\n this.accessToken.set(null);\n this.idTokenClaims.set(null);\n finish(false);\n }\n };\n\n window.addEventListener('message', onMessage);\n\n iframe.src = this.buildAuthorizeUrl({\n state,\n codeChallenge,\n redirectUri,\n prompt: 'none',\n });\n document.body.appendChild(iframe);\n });\n }\n\n async handleCallback(): Promise<boolean> {\n if (!this.isBrowser) return false;\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get('code');\n const state = params.get('state');\n if (!code) return false;\n\n const savedState = sessionStorage.getItem(STORAGE_KEY_STATE);\n const codeVerifier = sessionStorage.getItem(STORAGE_KEY_CODE_VERIFIER);\n const returnTo = sessionStorage.getItem(STORAGE_KEY_RETURN_TO);\n sessionStorage.removeItem(STORAGE_KEY_STATE);\n sessionStorage.removeItem(STORAGE_KEY_CODE_VERIFIER);\n\n if (state !== savedState || !codeVerifier) {\n window.history.replaceState({}, '', window.location.pathname);\n sessionStorage.removeItem(STORAGE_KEY_RETURN_TO);\n return false;\n }\n\n const redirectUri = this.getDefaultRedirectUri();\n const ok = await this.exchangeCode(code, codeVerifier, redirectUri);\n\n if (returnTo) {\n sessionStorage.removeItem(STORAGE_KEY_RETURN_TO);\n window.history.replaceState({}, '', returnTo);\n } else {\n window.history.replaceState({}, '', window.location.pathname);\n }\n\n return ok;\n }\n\n async init(config: SdAuthOmTenantConfig): Promise<boolean> {\n this.config = config;\n if (!this.isBrowser) return false;\n\n const hasCode = new URLSearchParams(window.location.search).has('code');\n if (hasCode) {\n const ok = await this.handleCallback();\n if (ok) return true;\n }\n\n return this.silentRefresh();\n }\n}\n","import { HttpInterceptorFn } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { SdAuthOmService } from './authom.service';\n\nconst escapeRegExp = (str: string): string => str.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const matchGlob = (pattern: string, url: string): boolean => {\n const regexBody = pattern.split('*').map(escapeRegExp).join('.*');\n return new RegExp(`^${regexBody}$`).test(url);\n};\n\nexport const SdAuthOmInterceptor: HttpInterceptorFn = (req, next) => {\n const authom = inject(SdAuthOmService);\n const token = authom.getAccessToken();\n const config = authom.config;\n\n if (!token || !config) return next(req);\n\n const isSecure = config.secureRoutes?.some(pattern => matchGlob(pattern, req.url));\n if (!isSecure) return next(req);\n\n const authReq = req.clone({\n headers: req.headers.set('Authorization', `Bearer ${token}`),\n });\n return next(authReq);\n};\n","import {\n ModuleWithProviders,\n NgModule,\n Provider,\n Type,\n EnvironmentProviders,\n makeEnvironmentProviders,\n provideAppInitializer,\n inject,\n APP_INITIALIZER,\n} from '@angular/core';\nimport { ISdAuthOmConfiguration, SD_AUTHOM_CONFIGURATION } from './authom.configuration';\nimport { SdAuthOmService } from './authom.service';\n\ninterface ProvideOptions {\n useClass?: Type<ISdAuthOmConfiguration>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useFactory?: (...args: any[]) => ISdAuthOmConfiguration;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n deps?: any[];\n}\n\nexport function provideSdAuthOm(options: ProvideOptions): EnvironmentProviders {\n const providers: Array<Provider | EnvironmentProviders> = [SdAuthOmService];\n\n if (options.useFactory) {\n providers.push({ provide: SD_AUTHOM_CONFIGURATION, useFactory: options.useFactory, deps: options.deps || [] });\n } else if (options.useClass) {\n providers.push({ provide: SD_AUTHOM_CONFIGURATION, useClass: options.useClass });\n }\n\n providers.push(\n provideAppInitializer(() => {\n const configLoader = inject(SD_AUTHOM_CONFIGURATION);\n const authom = inject(SdAuthOmService);\n return configLoader.loadTenantConfig().then((config) => authom.init(config));\n })\n );\n\n return makeEnvironmentProviders(providers);\n}\n\n@NgModule({})\nexport class SdAuthOmModule {\n static forRoot(options: ProvideOptions): ModuleWithProviders<SdAuthOmModule> {\n return {\n ngModule: SdAuthOmModule,\n providers: [\n SdAuthOmService,\n ...(options.useFactory\n ? [{ provide: SD_AUTHOM_CONFIGURATION, useFactory: options.useFactory, deps: options.deps || [] }]\n : [{ provide: SD_AUTHOM_CONFIGURATION, useClass: options.useClass! }]\n ),\n {\n provide: APP_INITIALIZER,\n multi: true,\n useFactory: () => {\n const configLoader = inject(SD_AUTHOM_CONFIGURATION);\n const authom = inject(SdAuthOmService);\n return () => configLoader.loadTenantConfig().then((config) => authom.init(config));\n },\n },\n ],\n };\n }\n}\n","/**\n * Module `authom` — OAuth 2.0 + PKCE authentication client cho AuthOM (Auth0-based).\n *\n * Cách dùng (standalone):\n * ```ts\n * import { provideHttpClient, withInterceptors } from '@angular/common/http';\n * import { provideSdAuthOm, SdAuthOmInterceptor } from '@sdcorejs/angular/modules/authom';\n *\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideHttpClient(withInterceptors([SdAuthOmInterceptor])),\n * provideSdAuthOm({\n * useFactory: () => ({\n * loadTenantConfig: () => Promise.resolve({\n * domain: 'login.example.com',\n * clientId: 'YOUR_CLIENT_ID',\n * audience: 'https://api.example.com',\n * organization: 'org_xxx',\n * scope: 'openid profile email offline_access',\n * secureRoutes: ['https://api.example.com/*'],\n * }),\n * }),\n * }),\n * ],\n * };\n * ```\n *\n * Yêu cầu setup thêm:\n * 1. Copy file `silent-authom.html` từ source module này vào thư mục `public/` của app.\n * 2. App phải chạy trên HTTPS (hoặc localhost) — Web Crypto API yêu cầu secure context.\n */\nexport * from './authom.configuration';\nexport * from './authom.service';\nexport * from './authom.interceptor';\nexport * from './authom.module';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sdcorejs-angular-modules-authom.mjs","sources":["../../../projects/sdcorejs-angular/modules/authom/authom.configuration.ts","../../../projects/sdcorejs-angular/modules/authom/authom.service.ts","../../../projects/sdcorejs-angular/modules/authom/authom.interceptor.ts","../../../projects/sdcorejs-angular/modules/authom/authom.module.ts","../../../projects/sdcorejs-angular/modules/authom/index.ts","../../../projects/sdcorejs-angular/modules/authom/sdcorejs-angular-modules-authom.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport interface SdAuthOmTenantConfig {\n domain: string;\n clientId: string;\n\n redirectUri?: string;\n audience?: string;\n organization?: string;\n scope?: string;\n\n secureRoutes?: string[];\n\n silentRefreshRedirectUri?: string;\n refreshThresholdSeconds?: number;\n authorizeTimeoutInSeconds?: number;\n}\n\nexport interface ISdAuthOmConfiguration {\n loadTenantConfig: () => Promise<SdAuthOmTenantConfig>;\n}\n\nexport const SD_AUTHOM_CONFIGURATION = new InjectionToken<ISdAuthOmConfiguration>('sd-authom.configuration');\n","import { Injectable, PLATFORM_ID, computed, inject, signal } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { SdAuthOmTenantConfig } from './authom.configuration';\nimport { StringUtilities } from '@sdcorejs/utils/fns';\n\nconst STORAGE_KEY_STATE = 'authom_state';\nconst STORAGE_KEY_CODE_VERIFIER = 'authom_code_verifier';\nconst STORAGE_KEY_RETURN_TO = 'authom_return_to';\n\nconst DEFAULT_SCOPE = 'openid profile email';\nconst DEFAULT_REFRESH_THRESHOLD_SECONDS = 30;\nconst DEFAULT_AUTHORIZE_TIMEOUT_SECONDS = 5;\n\n@Injectable({ providedIn: 'root' })\nexport class SdAuthOmService {\n private readonly platformId = inject(PLATFORM_ID);\n private readonly isBrowser = isPlatformBrowser(this.platformId);\n\n readonly accessToken = signal<string | null>(null);\n readonly idTokenClaims = signal<Record<string, unknown> | null>(null);\n readonly isAuthenticated = computed(() => this.accessToken() !== null);\n\n config!: SdAuthOmTenantConfig;\n private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n\n getAccessToken(): string | null {\n return this.accessToken();\n }\n\n private base64UrlEncode(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n }\n\n private generateCodeVerifier(): string {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return this.base64UrlEncode(bytes);\n }\n\n private async generateCodeChallenge(verifier: string): Promise<string> {\n return StringUtilities.sha256(verifier);\n }\n\n private buildAuthorizeUrl(params: { state: string; codeChallenge: string; redirectUri: string; prompt?: 'none' }): string {\n const search = new URLSearchParams({\n response_type: 'code',\n client_id: this.config.clientId,\n redirect_uri: params.redirectUri,\n state: params.state,\n code_challenge: params.codeChallenge,\n code_challenge_method: 'S256',\n scope: this.config.scope || DEFAULT_SCOPE,\n });\n if (this.config.audience) search.set('audience', this.config.audience);\n if (this.config.organization) search.set('organization', this.config.organization);\n if (params.prompt) search.set('prompt', params.prompt);\n return `https://${this.config.domain}/authorize?${search.toString()}`;\n }\n\n private getDefaultRedirectUri(): string {\n return this.config.redirectUri || window.location.origin;\n }\n\n private getSilentRedirectUri(): string {\n return this.config.silentRefreshRedirectUri || `${window.location.origin}/silent-authom.html`;\n }\n\n private decodeJwtPayload(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n const padded = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n const json = atob(padded + '==='.slice((padded.length + 3) % 4));\n return JSON.parse(json);\n } catch {\n return null;\n }\n }\n\n async login(options?: { returnTo?: string }): Promise<void> {\n if (!this.isBrowser) return;\n\n const state = crypto.randomUUID();\n const codeVerifier = this.generateCodeVerifier();\n const codeChallenge = await this.generateCodeChallenge(codeVerifier);\n const returnTo = options?.returnTo || window.location.pathname + window.location.search;\n\n sessionStorage.setItem(STORAGE_KEY_STATE, state);\n sessionStorage.setItem(STORAGE_KEY_CODE_VERIFIER, codeVerifier);\n sessionStorage.setItem(STORAGE_KEY_RETURN_TO, returnTo);\n\n window.location.href = this.buildAuthorizeUrl({\n state,\n codeChallenge,\n redirectUri: this.getDefaultRedirectUri(),\n });\n }\n\n logout(options?: { returnTo?: string }): void {\n if (!this.isBrowser) return;\n\n this.accessToken.set(null);\n this.idTokenClaims.set(null);\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n\n const returnTo = options?.returnTo || window.location.origin;\n const search = new URLSearchParams({\n client_id: this.config.clientId,\n returnTo,\n });\n window.location.href = `https://${this.config.domain}/v2/logout?${search.toString()}`;\n }\n\n private async exchangeCode(code: string, codeVerifier: string, redirectUri: string): Promise<boolean> {\n try {\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n code_verifier: codeVerifier,\n redirect_uri: redirectUri,\n client_id: this.config.clientId,\n });\n const res = await fetch(`https://${this.config.domain}/oauth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n if (!res.ok) return false;\n const data = (await res.json()) as { access_token?: string; id_token?: string };\n if (!data.access_token) return false;\n\n this.accessToken.set(data.access_token);\n if (data.id_token) {\n this.idTokenClaims.set(this.decodeJwtPayload(data.id_token));\n }\n this.scheduleRefresh(data.access_token);\n return true;\n } catch {\n return false;\n }\n }\n\n private scheduleRefresh(token: string): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n const claims = this.decodeJwtPayload(token);\n const exp = claims?.['exp'];\n if (typeof exp !== 'number') {\n // @i18n-ignore — dev console warning\n console.warn('[SdAuthOmService] Token không có exp — bỏ qua auto-refresh');\n return;\n }\n const threshold = this.config.refreshThresholdSeconds ?? DEFAULT_REFRESH_THRESHOLD_SECONDS;\n const nowSeconds = Math.floor(Date.now() / 1000);\n const delayMs = Math.max(0, (exp - nowSeconds - threshold) * 1000);\n this.refreshTimer = setTimeout(() => {\n this.silentRefresh();\n }, delayMs);\n }\n\n async silentRefresh(): Promise<boolean> {\n if (!this.isBrowser) return false;\n\n const state = crypto.randomUUID();\n const codeVerifier = this.generateCodeVerifier();\n const codeChallenge = await this.generateCodeChallenge(codeVerifier);\n const redirectUri = this.getSilentRedirectUri();\n const timeoutMs = (this.config.authorizeTimeoutInSeconds ?? DEFAULT_AUTHORIZE_TIMEOUT_SECONDS) * 1000;\n\n return new Promise(resolve => {\n const iframe = document.createElement('iframe');\n iframe.style.display = 'none';\n\n let done = false;\n const finish = (success: boolean) => {\n if (done) return;\n done = true;\n clearTimeout(timer);\n iframe.remove();\n window.removeEventListener('message', onMessage);\n resolve(success);\n };\n\n const timer = setTimeout(() => finish(false), timeoutMs);\n\n const onMessage = async (event: MessageEvent) => {\n if (event.origin !== window.location.origin) return;\n const data = event.data as { type?: string; code?: string; state?: string } | null;\n if (!data || typeof data !== 'object') return;\n\n if (data.type === 'AUTHOM_SILENT_SUCCESS' && data.code && data.state === state) {\n const ok = await this.exchangeCode(data.code, codeVerifier, redirectUri);\n if (!ok) {\n this.accessToken.set(null);\n this.idTokenClaims.set(null);\n }\n finish(ok);\n } else if (data.type === 'AUTHOM_SILENT_ERROR') {\n this.accessToken.set(null);\n this.idTokenClaims.set(null);\n finish(false);\n }\n };\n\n window.addEventListener('message', onMessage);\n\n iframe.src = this.buildAuthorizeUrl({\n state,\n codeChallenge,\n redirectUri,\n prompt: 'none',\n });\n document.body.appendChild(iframe);\n });\n }\n\n async handleCallback(): Promise<boolean> {\n if (!this.isBrowser) return false;\n\n const params = new URLSearchParams(window.location.search);\n const code = params.get('code');\n const state = params.get('state');\n if (!code) return false;\n\n const savedState = sessionStorage.getItem(STORAGE_KEY_STATE);\n const codeVerifier = sessionStorage.getItem(STORAGE_KEY_CODE_VERIFIER);\n const returnTo = sessionStorage.getItem(STORAGE_KEY_RETURN_TO);\n sessionStorage.removeItem(STORAGE_KEY_STATE);\n sessionStorage.removeItem(STORAGE_KEY_CODE_VERIFIER);\n\n if (state !== savedState || !codeVerifier) {\n window.history.replaceState({}, '', window.location.pathname);\n sessionStorage.removeItem(STORAGE_KEY_RETURN_TO);\n return false;\n }\n\n const redirectUri = this.getDefaultRedirectUri();\n const ok = await this.exchangeCode(code, codeVerifier, redirectUri);\n\n if (returnTo) {\n sessionStorage.removeItem(STORAGE_KEY_RETURN_TO);\n window.history.replaceState({}, '', returnTo);\n } else {\n window.history.replaceState({}, '', window.location.pathname);\n }\n\n return ok;\n }\n\n async init(config: SdAuthOmTenantConfig): Promise<boolean> {\n this.config = config;\n if (!this.isBrowser) return false;\n\n const hasCode = new URLSearchParams(window.location.search).has('code');\n if (hasCode) {\n const ok = await this.handleCallback();\n if (ok) return true;\n }\n\n return this.silentRefresh();\n }\n}\n","import { HttpInterceptorFn } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { SdAuthOmService } from './authom.service';\n\nconst escapeRegExp = (str: string): string => str.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nexport const matchGlob = (pattern: string, url: string): boolean => {\n const regexBody = pattern.split('*').map(escapeRegExp).join('.*');\n return new RegExp(`^${regexBody}$`).test(url);\n};\n\nexport const SdAuthOmInterceptor: HttpInterceptorFn = (req, next) => {\n const authom = inject(SdAuthOmService);\n const token = authom.getAccessToken();\n const config = authom.config;\n\n if (!token || !config) return next(req);\n\n const isSecure = config.secureRoutes?.some(pattern => matchGlob(pattern, req.url));\n if (!isSecure) return next(req);\n\n const authReq = req.clone({\n headers: req.headers.set('Authorization', `Bearer ${token}`),\n });\n return next(authReq);\n};\n","import {\n ModuleWithProviders,\n NgModule,\n Provider,\n Type,\n EnvironmentProviders,\n makeEnvironmentProviders,\n provideAppInitializer,\n inject,\n APP_INITIALIZER,\n} from '@angular/core';\nimport { ISdAuthOmConfiguration, SD_AUTHOM_CONFIGURATION } from './authom.configuration';\nimport { SdAuthOmService } from './authom.service';\n\ninterface ProvideOptions {\n useClass?: Type<ISdAuthOmConfiguration>;\n\n useFactory?: (...args: any[]) => ISdAuthOmConfiguration;\n\n deps?: any[];\n}\n\nexport function provideSdAuthOm(options: ProvideOptions): EnvironmentProviders {\n const providers: (Provider | EnvironmentProviders)[] = [SdAuthOmService];\n\n if (options.useFactory) {\n providers.push({ provide: SD_AUTHOM_CONFIGURATION, useFactory: options.useFactory, deps: options.deps || [] });\n } else if (options.useClass) {\n providers.push({ provide: SD_AUTHOM_CONFIGURATION, useClass: options.useClass });\n }\n\n providers.push(\n provideAppInitializer(() => {\n const configLoader = inject(SD_AUTHOM_CONFIGURATION);\n const authom = inject(SdAuthOmService);\n return configLoader.loadTenantConfig().then(config => authom.init(config));\n })\n );\n\n return makeEnvironmentProviders(providers);\n}\n\n@NgModule({})\nexport class SdAuthOmModule {\n static forRoot(options: ProvideOptions): ModuleWithProviders<SdAuthOmModule> {\n return {\n ngModule: SdAuthOmModule,\n providers: [\n SdAuthOmService,\n ...(options.useFactory\n ? [{ provide: SD_AUTHOM_CONFIGURATION, useFactory: options.useFactory, deps: options.deps || [] }]\n : [{ provide: SD_AUTHOM_CONFIGURATION, useClass: options.useClass! }]),\n {\n provide: APP_INITIALIZER,\n multi: true,\n useFactory: () => {\n const configLoader = inject(SD_AUTHOM_CONFIGURATION);\n const authom = inject(SdAuthOmService);\n return () => configLoader.loadTenantConfig().then(config => authom.init(config));\n },\n },\n ],\n };\n }\n}\n","/**\n * Module `authom` — OAuth 2.0 + PKCE authentication client cho AuthOM (Auth0-based).\n *\n * Cách dùng (standalone):\n * ```ts\n * import { provideHttpClient, withInterceptors } from '@angular/common/http';\n * import { provideSdAuthOm, SdAuthOmInterceptor } from '@sdcorejs/angular/modules/authom';\n *\n * export const appConfig: ApplicationConfig = {\n * providers: [\n * provideHttpClient(withInterceptors([SdAuthOmInterceptor])),\n * provideSdAuthOm({\n * useFactory: () => ({\n * loadTenantConfig: () => Promise.resolve({\n * domain: 'login.example.com',\n * clientId: 'YOUR_CLIENT_ID',\n * audience: 'https://api.example.com',\n * organization: 'org_xxx',\n * scope: 'openid profile email offline_access',\n * secureRoutes: ['https://api.example.com/*'],\n * }),\n * }),\n * }),\n * ],\n * };\n * ```\n *\n * Yêu cầu setup thêm:\n * 1. Copy file `silent-authom.html` từ source module này vào thư mục `public/` của app.\n * 2. App phải chạy trên HTTPS (hoặc localhost) — Web Crypto API yêu cầu secure context.\n */\nexport * from './authom.configuration';\nexport * from './authom.service';\nexport * from './authom.interceptor';\nexport * from './authom.module';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -51,14 +51,25 @@ class SdKeycloakService {
|
|
|
51
51
|
* `'silent-renew'` | `'/silent-renew'` | `'silent-renew.html'` → `${origin}/silent-renew.html`.
|
|
52
52
|
*/
|
|
53
53
|
toPublicHtmlUrl(name) {
|
|
54
|
-
const base = name
|
|
54
|
+
const base = name
|
|
55
|
+
.trim()
|
|
56
|
+
.replace(/^\/+/, '')
|
|
57
|
+
.replace(/\.html$/i, '');
|
|
55
58
|
return `${window.location.origin}/${base}.html`;
|
|
56
59
|
}
|
|
57
60
|
// Tiện ích nhanh cho Dev sử dụng
|
|
58
|
-
login() {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
login() {
|
|
62
|
+
return this.keycloak.login();
|
|
63
|
+
}
|
|
64
|
+
logout() {
|
|
65
|
+
return this.keycloak.logout({ redirectUri: window.location.origin });
|
|
66
|
+
}
|
|
67
|
+
getToken() {
|
|
68
|
+
return this.keycloak.token;
|
|
69
|
+
}
|
|
70
|
+
getIsAuthenticated() {
|
|
71
|
+
return this.keycloak.authenticated;
|
|
72
|
+
}
|
|
62
73
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdKeycloakService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
63
74
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdKeycloakService, providedIn: 'root' });
|
|
64
75
|
}
|
|
@@ -82,7 +93,7 @@ const SdKeycloakInterceptor = (req, next) => {
|
|
|
82
93
|
// Đảm bảo token luôn hợp lệ (cập nhật nếu token sẽ hết hạn trong 30s tới)
|
|
83
94
|
return from(keycloak.updateToken(30)).pipe(switchMap(() => {
|
|
84
95
|
const authReq = req.clone({
|
|
85
|
-
headers: req.headers.set('Authorization', `Bearer ${keycloak.token}`)
|
|
96
|
+
headers: req.headers.set('Authorization', `Bearer ${keycloak.token}`),
|
|
86
97
|
});
|
|
87
98
|
return next(authReq);
|
|
88
99
|
}));
|
|
@@ -104,7 +115,7 @@ function provideSdKeycloak(options) {
|
|
|
104
115
|
providers.push(provideAppInitializer(() => {
|
|
105
116
|
const configLoader = inject(SD_KEYCLOAK_CONFIGURATION);
|
|
106
117
|
const keycloakService = inject(SdKeycloakService);
|
|
107
|
-
return configLoader.loadTenantConfig().then(
|
|
118
|
+
return configLoader.loadTenantConfig().then(config => keycloakService.init(config));
|
|
108
119
|
}));
|
|
109
120
|
return makeEnvironmentProviders(providers);
|
|
110
121
|
}
|
|
@@ -127,10 +138,10 @@ class SdKeycloakModule {
|
|
|
127
138
|
useFactory: () => {
|
|
128
139
|
const configLoader = inject(SD_KEYCLOAK_CONFIGURATION);
|
|
129
140
|
const keycloakService = inject(SdKeycloakService);
|
|
130
|
-
return () => configLoader.loadTenantConfig().then(
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
]
|
|
141
|
+
return () => configLoader.loadTenantConfig().then(config => keycloakService.init(config));
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
],
|
|
134
145
|
};
|
|
135
146
|
}
|
|
136
147
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdKeycloakModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdcorejs-angular-modules-keycloak.mjs","sources":["../../../projects/sdcorejs-angular/modules/keycloak/keycloak.configuration.ts","../../../projects/sdcorejs-angular/modules/keycloak/keycloak.service.ts","../../../projects/sdcorejs-angular/modules/keycloak/keycloak.interceptor.ts","../../../projects/sdcorejs-angular/modules/keycloak/keycloak.module.ts","../../../projects/sdcorejs-angular/modules/keycloak/sdcorejs-angular-modules-keycloak.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport interface SdKeycloakTenantConfig {\n url: string;\n realm: string;\n clientId: string;\n secureRoutes?: string[]; // Các API cần đính token (vd: ['/api/v1'])\n\n /**\n * Basename (KHÔNG kèm đuôi `.html`) của file Keycloak **silent SSO redirect** mà consumer\n * BẮT BUỘC đặt trong thư mục `public/`. Mặc định `'silent-renew'`.\n *\n * Service sẽ truyền `${origin}/<silentRenewUrl>.html` vào `silentCheckSsoRedirectUri`. Với giá trị\n * mặc định, Keycloak gọi `${origin}/silent-renew.html` (giống hành vi cũ — không phá vỡ project hiện có).\n *\n * Consumer cần tạo file tĩnh này (nội dung mẫu ở `modules/keycloak/htmls/silent-renew.html`); nếu THIẾU,\n * silent `check-sso` thất bại và `init()` trả về `false` không mong muốn (user bị coi là chưa đăng nhập).\n * Cho phép truyền kèm `/` đầu hoặc đuôi `.html` (tự chuẩn hoá), hoặc đặt sâu hơn:\n * `'assets/silent-renew'` → `${origin}/assets/silent-renew.html`.\n */\n silentRenewUrl?: string;\n\n /**\n * Basename (KHÔNG kèm đuôi `.html`) của **trang lỗi tĩnh** trong `public/`, dùng làm đích redirect khi\n * `keycloak.init()` NÉM LỖI (Keycloak sập / mạng lỗi / cấu hình realm sai). Mặc định `'auth-keycloak-error'`.\n *\n * Khi init thất bại, app điều hướng full-page tới `${origin}/<authErrorUrl>.html`. Vì là file tĩnh nên\n * trang lỗi vẫn hiển thị kể cả khi bundle Angular chưa khởi tạo được. Service cũng tự bỏ qua `init()` nếu\n * đang đứng sẵn ở trang lỗi này (chống vòng lặp redirect).\n *\n * Consumer nên tạo file (nội dung mẫu ở `modules/keycloak/htmls/auth-keycloak-error.html`) và viết lại\n * branding/nội dung. Nếu THIẾU, redirect rơi vào trang 404 của host. Chuẩn hoá giá trị giống `silentRenewUrl`.\n */\n authErrorUrl?: string;\n}\n\nexport interface ISdKeycloakConfiguration {\n loadTenantConfig: () => Promise<SdKeycloakTenantConfig>;\n}\n\nexport const SD_KEYCLOAK_CONFIGURATION = new InjectionToken<ISdKeycloakConfiguration>('sd-keycloak.configuration');\n","import { Injectable } from '@angular/core';\nimport Keycloak from 'keycloak-js'; // Import trực tiếp SDK gốc của Keycloak\nimport { SdKeycloakTenantConfig } from './keycloak.configuration';\n\n@Injectable({ providedIn: 'root' })\nexport class SdKeycloakService {\n public keycloak!: Keycloak;\n public config!: SdKeycloakTenantConfig;\n\n async init(config: SdKeycloakTenantConfig): Promise<boolean> {\n this.config = config;\n\n // Đường dẫn 2 file tĩnh trong public/ (consumer cấu hình qua silentRenewUrl / authErrorUrl).\n const silentRenewHref = this.toPublicHtmlUrl(config.silentRenewUrl || 'silent-renew');\n const authErrorHref = this.toPublicHtmlUrl(config.authErrorUrl || 'auth-keycloak-error');\n\n // Nếu đang đứng ở chính trang lỗi → KHÔNG init lại (chống vòng lặp redirect khi init liên tục lỗi).\n if (window.location.pathname === new URL(authErrorHref).pathname) {\n return false;\n }\n\n // 1. Khởi tạo instance\n this.keycloak = new Keycloak({\n url: config.url,\n realm: config.realm,\n clientId: config.clientId,\n });\n\n // 2. Lắng nghe sự kiện hết hạn token để tự động làm mới ngầm\n this.keycloak.onTokenExpired = () => {\n this.keycloak.updateToken(30).catch(() => {\n // @i18n-ignore — dev console warning\n console.warn('Token refresh failed. Re-authentication required.');\n this.keycloak.login();\n });\n };\n\n // 3. Thực thi quá trình boot Keycloak; nếu lỗi → điều hướng tới trang lỗi tĩnh trong public/.\n try {\n return await this.keycloak.init({\n onLoad: 'check-sso',\n silentCheckSsoRedirectUri: silentRenewHref,\n checkLoginIframe: false, // Tắt check Iframe để chống lỗi vòng lặp\n });\n } catch (error) {\n // @i18n-ignore — dev console error\n console.error('Keycloak init failed:', error);\n window.location.href = authErrorHref;\n return false;\n }\n }\n\n /**\n * Chuẩn hoá basename file tĩnh trong public/ thành URL tuyệt đối.\n * `'silent-renew'` | `'/silent-renew'` | `'silent-renew.html'` → `${origin}/silent-renew.html`.\n */\n private toPublicHtmlUrl(name: string): string {\n const base = name.trim().replace(/^\\/+/, '').replace(/\\.html$/i, '');\n return `${window.location.origin}/${base}.html`;\n }\n\n // Tiện ích nhanh cho Dev sử dụng\n login() { return this.keycloak.login(); }\n logout() { return this.keycloak.logout({ redirectUri: window.location.origin }); }\n getToken() { return this.keycloak.token; }\n getIsAuthenticated() { return this.keycloak.authenticated; }\n}\n","import { HttpInterceptorFn } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { from, switchMap } from 'rxjs';\nimport { SdKeycloakService } from './keycloak.service';\n\nexport const SdKeycloakInterceptor: HttpInterceptorFn = (req, next) => {\n const keycloakService = inject(SdKeycloakService);\n const { keycloak, config } = keycloakService;\n\n // Nếu chưa init xong hoặc chưa đăng nhập -> cho request đi qua bình thường\n if (!keycloak || !keycloak.authenticated || !config) {\n return next(req);\n }\n\n // Kiểm tra xem URL của request có nằm trong mảng secureRoutes không\n const isSecure = config.secureRoutes?.some(route => req.url.includes(route));\n if (!isSecure) {\n return next(req);\n }\n\n // Đảm bảo token luôn hợp lệ (cập nhật nếu token sẽ hết hạn trong 30s tới)\n return from(keycloak.updateToken(30)).pipe(\n switchMap(() => {\n const authReq = req.clone({\n headers: req.headers.set('Authorization', `Bearer ${keycloak.token}`)\n });\n return next(authReq);\n })\n );\n};","import { \n ModuleWithProviders, \n NgModule, \n Provider, \n Type, \n EnvironmentProviders, \n makeEnvironmentProviders, \n provideAppInitializer, \n inject,\n APP_INITIALIZER\n} from '@angular/core';\nimport { ISdKeycloakConfiguration, SD_KEYCLOAK_CONFIGURATION } from './keycloak.configuration';\nimport { SdKeycloakService } from './keycloak.service';\n\n// =======================================================================\n// CÁCH 1: Dùng cho Angular 19 Standalone (Gọi trong app.config.ts)\n// =======================================================================\nexport function provideSdKeycloak(options: {\n useClass?: Type<ISdKeycloakConfiguration>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useFactory?: (...args: any[]) => ISdKeycloakConfiguration;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n deps?: any[];\n}): EnvironmentProviders {\n \n // FIX 1: Khai báo mảng nhận cả Provider (cho Service/Token) lẫn EnvironmentProviders (cho AppInitializer)\n const providers: Array<Provider | EnvironmentProviders> = [SdKeycloakService];\n\n if (options.useFactory) {\n providers.push({ provide: SD_KEYCLOAK_CONFIGURATION, useFactory: options.useFactory, deps: options.deps || [] });\n } else if (options.useClass) {\n providers.push({ provide: SD_KEYCLOAK_CONFIGURATION, useClass: options.useClass });\n }\n\n // Standalone dùng được provideAppInitializer ngon lành\n providers.push(\n provideAppInitializer(() => {\n const configLoader = inject(SD_KEYCLOAK_CONFIGURATION);\n const keycloakService = inject(SdKeycloakService);\n return configLoader.loadTenantConfig().then((config) => keycloakService.init(config));\n })\n );\n\n return makeEnvironmentProviders(providers);\n}\n\n// =======================================================================\n// CÁCH 2: Dùng cho kiến trúc NgModule cũ (Backward Compatibility)\n// =======================================================================\n@NgModule({})\nexport class SdKeycloakModule {\n static forRoot(options: {\n useClass?: Type<ISdKeycloakConfiguration>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useFactory?: (...args: any[]) => ISdKeycloakConfiguration;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n deps?: any[];\n }): ModuleWithProviders<SdKeycloakModule> {\n \n return {\n ngModule: SdKeycloakModule,\n providers: [\n SdKeycloakService,\n ...(options.useFactory \n ? [{ provide: SD_KEYCLOAK_CONFIGURATION, useFactory: options.useFactory, deps: options.deps || [] }]\n : [{ provide: SD_KEYCLOAK_CONFIGURATION, useClass: options.useClass! }]\n ),\n \n // FIX 2: NgModule bắt buộc phải dùng APP_INITIALIZER (nhưng viết kiểu xịn của Angular 14+, dùng inject)\n {\n provide: APP_INITIALIZER,\n multi: true,\n useFactory: () => {\n const configLoader = inject(SD_KEYCLOAK_CONFIGURATION);\n const keycloakService = inject(SdKeycloakService);\n return () => configLoader.loadTenantConfig().then((config) => keycloakService.init(config));\n }\n }\n ]\n };\n }\n}","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sdcorejs-angular-modules-keycloak.mjs","sources":["../../../projects/sdcorejs-angular/modules/keycloak/keycloak.configuration.ts","../../../projects/sdcorejs-angular/modules/keycloak/keycloak.service.ts","../../../projects/sdcorejs-angular/modules/keycloak/keycloak.interceptor.ts","../../../projects/sdcorejs-angular/modules/keycloak/keycloak.module.ts","../../../projects/sdcorejs-angular/modules/keycloak/sdcorejs-angular-modules-keycloak.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport interface SdKeycloakTenantConfig {\n url: string;\n realm: string;\n clientId: string;\n secureRoutes?: string[]; // Các API cần đính token (vd: ['/api/v1'])\n\n /**\n * Basename (KHÔNG kèm đuôi `.html`) của file Keycloak **silent SSO redirect** mà consumer\n * BẮT BUỘC đặt trong thư mục `public/`. Mặc định `'silent-renew'`.\n *\n * Service sẽ truyền `${origin}/<silentRenewUrl>.html` vào `silentCheckSsoRedirectUri`. Với giá trị\n * mặc định, Keycloak gọi `${origin}/silent-renew.html` (giống hành vi cũ — không phá vỡ project hiện có).\n *\n * Consumer cần tạo file tĩnh này (nội dung mẫu ở `modules/keycloak/htmls/silent-renew.html`); nếu THIẾU,\n * silent `check-sso` thất bại và `init()` trả về `false` không mong muốn (user bị coi là chưa đăng nhập).\n * Cho phép truyền kèm `/` đầu hoặc đuôi `.html` (tự chuẩn hoá), hoặc đặt sâu hơn:\n * `'assets/silent-renew'` → `${origin}/assets/silent-renew.html`.\n */\n silentRenewUrl?: string;\n\n /**\n * Basename (KHÔNG kèm đuôi `.html`) của **trang lỗi tĩnh** trong `public/`, dùng làm đích redirect khi\n * `keycloak.init()` NÉM LỖI (Keycloak sập / mạng lỗi / cấu hình realm sai). Mặc định `'auth-keycloak-error'`.\n *\n * Khi init thất bại, app điều hướng full-page tới `${origin}/<authErrorUrl>.html`. Vì là file tĩnh nên\n * trang lỗi vẫn hiển thị kể cả khi bundle Angular chưa khởi tạo được. Service cũng tự bỏ qua `init()` nếu\n * đang đứng sẵn ở trang lỗi này (chống vòng lặp redirect).\n *\n * Consumer nên tạo file (nội dung mẫu ở `modules/keycloak/htmls/auth-keycloak-error.html`) và viết lại\n * branding/nội dung. Nếu THIẾU, redirect rơi vào trang 404 của host. Chuẩn hoá giá trị giống `silentRenewUrl`.\n */\n authErrorUrl?: string;\n}\n\nexport interface ISdKeycloakConfiguration {\n loadTenantConfig: () => Promise<SdKeycloakTenantConfig>;\n}\n\nexport const SD_KEYCLOAK_CONFIGURATION = new InjectionToken<ISdKeycloakConfiguration>('sd-keycloak.configuration');\n","import { Injectable } from '@angular/core';\nimport Keycloak from 'keycloak-js'; // Import trực tiếp SDK gốc của Keycloak\nimport { SdKeycloakTenantConfig } from './keycloak.configuration';\n\n@Injectable({ providedIn: 'root' })\nexport class SdKeycloakService {\n public keycloak!: Keycloak;\n public config!: SdKeycloakTenantConfig;\n\n async init(config: SdKeycloakTenantConfig): Promise<boolean> {\n this.config = config;\n\n // Đường dẫn 2 file tĩnh trong public/ (consumer cấu hình qua silentRenewUrl / authErrorUrl).\n const silentRenewHref = this.toPublicHtmlUrl(config.silentRenewUrl || 'silent-renew');\n const authErrorHref = this.toPublicHtmlUrl(config.authErrorUrl || 'auth-keycloak-error');\n\n // Nếu đang đứng ở chính trang lỗi → KHÔNG init lại (chống vòng lặp redirect khi init liên tục lỗi).\n if (window.location.pathname === new URL(authErrorHref).pathname) {\n return false;\n }\n\n // 1. Khởi tạo instance\n this.keycloak = new Keycloak({\n url: config.url,\n realm: config.realm,\n clientId: config.clientId,\n });\n\n // 2. Lắng nghe sự kiện hết hạn token để tự động làm mới ngầm\n this.keycloak.onTokenExpired = () => {\n this.keycloak.updateToken(30).catch(() => {\n // @i18n-ignore — dev console warning\n console.warn('Token refresh failed. Re-authentication required.');\n this.keycloak.login();\n });\n };\n\n // 3. Thực thi quá trình boot Keycloak; nếu lỗi → điều hướng tới trang lỗi tĩnh trong public/.\n try {\n return await this.keycloak.init({\n onLoad: 'check-sso',\n silentCheckSsoRedirectUri: silentRenewHref,\n checkLoginIframe: false, // Tắt check Iframe để chống lỗi vòng lặp\n });\n } catch (error) {\n // @i18n-ignore — dev console error\n console.error('Keycloak init failed:', error);\n window.location.href = authErrorHref;\n return false;\n }\n }\n\n /**\n * Chuẩn hoá basename file tĩnh trong public/ thành URL tuyệt đối.\n * `'silent-renew'` | `'/silent-renew'` | `'silent-renew.html'` → `${origin}/silent-renew.html`.\n */\n private toPublicHtmlUrl(name: string): string {\n const base = name\n .trim()\n .replace(/^\\/+/, '')\n .replace(/\\.html$/i, '');\n return `${window.location.origin}/${base}.html`;\n }\n\n // Tiện ích nhanh cho Dev sử dụng\n login() {\n return this.keycloak.login();\n }\n logout() {\n return this.keycloak.logout({ redirectUri: window.location.origin });\n }\n getToken() {\n return this.keycloak.token;\n }\n getIsAuthenticated() {\n return this.keycloak.authenticated;\n }\n}\n","import { HttpInterceptorFn } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { from, switchMap } from 'rxjs';\nimport { SdKeycloakService } from './keycloak.service';\n\nexport const SdKeycloakInterceptor: HttpInterceptorFn = (req, next) => {\n const keycloakService = inject(SdKeycloakService);\n const { keycloak, config } = keycloakService;\n\n // Nếu chưa init xong hoặc chưa đăng nhập -> cho request đi qua bình thường\n if (!keycloak || !keycloak.authenticated || !config) {\n return next(req);\n }\n\n // Kiểm tra xem URL của request có nằm trong mảng secureRoutes không\n const isSecure = config.secureRoutes?.some(route => req.url.includes(route));\n if (!isSecure) {\n return next(req);\n }\n\n // Đảm bảo token luôn hợp lệ (cập nhật nếu token sẽ hết hạn trong 30s tới)\n return from(keycloak.updateToken(30)).pipe(\n switchMap(() => {\n const authReq = req.clone({\n headers: req.headers.set('Authorization', `Bearer ${keycloak.token}`),\n });\n return next(authReq);\n })\n );\n};\n","import {\n ModuleWithProviders,\n NgModule,\n Provider,\n Type,\n EnvironmentProviders,\n makeEnvironmentProviders,\n provideAppInitializer,\n inject,\n APP_INITIALIZER,\n} from '@angular/core';\nimport { ISdKeycloakConfiguration, SD_KEYCLOAK_CONFIGURATION } from './keycloak.configuration';\nimport { SdKeycloakService } from './keycloak.service';\n\n// =======================================================================\n// CÁCH 1: Dùng cho Angular 19 Standalone (Gọi trong app.config.ts)\n// =======================================================================\nexport function provideSdKeycloak(options: {\n useClass?: Type<ISdKeycloakConfiguration>;\n\n useFactory?: (...args: any[]) => ISdKeycloakConfiguration;\n\n deps?: any[];\n}): EnvironmentProviders {\n // FIX 1: Khai báo mảng nhận cả Provider (cho Service/Token) lẫn EnvironmentProviders (cho AppInitializer)\n const providers: (Provider | EnvironmentProviders)[] = [SdKeycloakService];\n\n if (options.useFactory) {\n providers.push({ provide: SD_KEYCLOAK_CONFIGURATION, useFactory: options.useFactory, deps: options.deps || [] });\n } else if (options.useClass) {\n providers.push({ provide: SD_KEYCLOAK_CONFIGURATION, useClass: options.useClass });\n }\n\n // Standalone dùng được provideAppInitializer ngon lành\n providers.push(\n provideAppInitializer(() => {\n const configLoader = inject(SD_KEYCLOAK_CONFIGURATION);\n const keycloakService = inject(SdKeycloakService);\n return configLoader.loadTenantConfig().then(config => keycloakService.init(config));\n })\n );\n\n return makeEnvironmentProviders(providers);\n}\n\n// =======================================================================\n// CÁCH 2: Dùng cho kiến trúc NgModule cũ (Backward Compatibility)\n// =======================================================================\n@NgModule({})\nexport class SdKeycloakModule {\n static forRoot(options: {\n useClass?: Type<ISdKeycloakConfiguration>;\n\n useFactory?: (...args: any[]) => ISdKeycloakConfiguration;\n\n deps?: any[];\n }): ModuleWithProviders<SdKeycloakModule> {\n return {\n ngModule: SdKeycloakModule,\n providers: [\n SdKeycloakService,\n ...(options.useFactory\n ? [{ provide: SD_KEYCLOAK_CONFIGURATION, useFactory: options.useFactory, deps: options.deps || [] }]\n : [{ provide: SD_KEYCLOAK_CONFIGURATION, useClass: options.useClass! }]),\n\n // FIX 2: NgModule bắt buộc phải dùng APP_INITIALIZER (nhưng viết kiểu xịn của Angular 14+, dùng inject)\n {\n provide: APP_INITIALIZER,\n multi: true,\n useFactory: () => {\n const configLoader = inject(SD_KEYCLOAK_CONFIGURATION);\n const keycloakService = inject(SdKeycloakService);\n return () => configLoader.loadTenantConfig().then(config => keycloakService.init(config));\n },\n },\n ],\n };\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|