@sd-angular/core 19.0.39 → 19.0.41
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/components/table/src/components/desktop-cell/desktop-cell.component.d.ts +1 -1
- package/components/table/src/services/field-value.util.d.ts +14 -0
- package/fesm2022/sd-angular-core-components-table.mjs +105 -11
- package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-upload-file.mjs +5 -0
- package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-auth.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-permission.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-api.mjs +211 -8
- package/fesm2022/sd-angular-core-services-api.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-excel.mjs +5 -0
- package/fesm2022/sd-angular-core-services-excel.mjs.map +1 -1
- package/fesm2022/sd-angular-core-utilities-models.mjs.map +1 -1
- package/modules/auth/configurations/auth.configuration.d.ts +4 -3
- package/modules/layout/configurations/layout.configuration.d.ts +3 -2
- package/modules/permission/src/configurations/permission.configuration.d.ts +3 -2
- package/package.json +10 -10
- package/services/api/index.d.ts +1 -0
- package/services/api/src/api-handler-registry.d.ts +49 -0
- package/services/api/src/api-handler-selection.d.ts +10 -0
- package/services/api/src/api-route-configurations.d.ts +20 -0
- package/services/api/src/api.model.d.ts +7 -0
- package/utilities/models/src/maybe-async.model.d.ts +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sd-angular-core-modules-permission.mjs","sources":["../../../projects/sd-angular/modules/permission/src/configurations/permission.configuration.ts","../../../projects/sd-angular/modules/permission/src/services/permission.service.ts","../../../projects/sd-angular/modules/permission/src/directives/permission.directive.ts","../../../projects/sd-angular/modules/permission/src/guards/permission.guard.ts","../../../projects/sd-angular/modules/permission/sd-angular-core-modules-permission.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { MaybeAsync } from '@sdcorejs/utils/models';\n\n/**\n * Cấu hình trung tâm cho module permission.\n *\n * Luồng hoạt động chính:\n * 1. `loadPermissions(key?)` ở service được gọi để lấy danh sách mã quyền theo từng `key` cấu hình.\n * 2. Guard/service đối chiếu mã quyền theo route metadata.\n * 3. Khi không đủ quyền, callback `onForbiden()` sẽ được gọi (nếu có).\n *\n * Lưu ý:\n * - `SD_PERMISSION_CONFIGURATION` hỗ trợ cả cấu hình đơn và mảng cấu hình (`multi: true`).\n * - Nếu cần tạm bỏ qua kiểm tra quyền (POC/UAT cục bộ), đặt `disabled = true`.\n */\nexport interface ISdPermissionConfiguration {\n /**\n * Khóa định danh cấu hình.\n * Dùng để phân biệt khi hệ thống mở rộng theo nhiều profile permission.\n *\n * Lưu ý: `undefined` cũng được xem là một key hợp lệ (cấu hình mặc định).\n */\n key?: string;\n\n /**\n * Bật/tắt kiểm tra permission toàn cục.\n * - `true`: bỏ qua kiểm tra quyền.\n * - `false | undefined`: kiểm tra quyền theo cấu hình route.\n */\n disabled?: boolean;\n\n /**\n * Trả về danh sách mã quyền của user hiện tại.\n * Có thể đồng bộ hoặc bất đồng bộ.\n *\n * Ví dụ giá trị trả về:\n * - `['PRODUCT_C_EMPLOYEE_VIEW', 'PRODUCT_C_EMPLOYEE_UPDATE']`\n */\n loadPermissions: () => MaybeAsync<string[]>;\n\n /**\n * Callback xử lý khi user không có quyền truy cập URL hiện tại.\n * Thường dùng để điều hướng sang trang forbidden hoặc hiển thị thông báo.\n *\n * Giữ nguyên tên `onForbiden` để tương thích API hiện tại.\n */\n onForbiden?: () => void;\n\n /**\n * Cung cấp access token hiện tại cho các tác vụ liên quan permission.\n * Hỗ trợ trả về đồng bộ, Promise hoặc Observable.\n */\n getToken?: () => MaybeAsync<string | undefined | null>;\n}\n\n/**\n * InjectionToken cho cấu hình permission.\n *\n * Ví dụ provider:\n * {\n * provide: SD_PERMISSION_CONFIGURATION,\n * useValue: {\n * disabled: false,\n * loadPermissions: () => ['SAMPLE_C_EMPLOYEE_VIEW'],\n * onForbiden: () => router.navigateByUrl('/layout/forbidden')\n * }\n * }\n */\nexport const SD_PERMISSION_CONFIGURATION =\n new InjectionToken<ISdPermissionConfiguration | ISdPermissionConfiguration[]>('sd-permission.configuration');\n","import { inject, Injectable } from '@angular/core';\nimport { SdCache, SdCacheService } from '@sd-angular/core/services/cache';\nimport { ArrayUtilities } from '@sd-angular/core/utilities/extensions';\nimport { ISdPermissionConfiguration, SD_PERMISSION_CONFIGURATION } from '../configurations';\nimport { MaybeAsync, resolveMaybeAsync } from '@sdcorejs/utils/models';\n\n@Injectable({ providedIn: 'root' })\nexport class SdPermissionService {\n #permissionMapByKey: Record<string, Record<string, boolean>> = {};\n readonly #configuration = inject<ISdPermissionConfiguration | ISdPermissionConfiguration[]>(SD_PERMISSION_CONFIGURATION);\n readonly #cacheService = inject(SdCacheService);\n #permissionsByKey: SdCache<Record<string, string[]>> = this.#cacheService.create<Record<string, string[]>>('212a51fa-38d5-43b2-bd46-922d85950ba3', {\n type: 'session',\n default: {},\n });\n readonly #loadedKeys = new Set<string>();\n\n constructor() {\n this.#validateDuplicateConfigKeys();\n }\n\n #getConfigurations = (): ISdPermissionConfiguration[] => {\n const config = this.#configuration;\n if (!config) {\n return [];\n }\n return Array.isArray(config) ? config : [config];\n };\n\n #normalizeKey = (key?: string): string => {\n return key === undefined ? '__undefined__' : key;\n };\n\n #validateDuplicateConfigKeys = (): void => {\n const seen = new Set<string>();\n for (const config of this.#getConfigurations()) {\n const normalizedKey = this.#normalizeKey(config.key);\n if (seen.has(normalizedKey)) {\n const keyLabel = config.key === undefined ? 'undefined' : config.key;\n throw new Error(`[Permission] Duplicate permission configuration key: ${keyLabel}`);\n }\n seen.add(normalizedKey);\n }\n };\n\n #getConfigurationByKey = (key?: string): ISdPermissionConfiguration | undefined => {\n return this.#getConfigurations().find(config => config.key === key);\n };\n\n #getEffectivePermissionKey = (key?: string): string | undefined => {\n if (this.#getConfigurationByKey(key)) {\n return key;\n }\n\n // Portal-level config uses key = undefined and acts as default fallback\n if (key !== undefined && this.#getConfigurationByKey(undefined)) {\n return undefined;\n }\n\n return key;\n };\n\n #setPermissionsForKey = (normalizedKey: string, permissions: string[]): void => {\n const distinctPermissions = ArrayUtilities.distinct(permissions || []);\n const current = this.#permissionsByKey.get() || {};\n this.#permissionsByKey.set({\n ...current,\n [normalizedKey]: distinctPermissions,\n });\n\n const permissionMap: Record<string, boolean> = {};\n distinctPermissions.forEach(permission => {\n permissionMap[permission] = true;\n });\n this.#permissionMapByKey[normalizedKey] = permissionMap;\n };\n\n loadPermissions = async (key?: string): Promise<string[]> => {\n const effectiveKey = this.#getEffectivePermissionKey(key);\n const normalizedKey = this.#normalizeKey(effectiveKey);\n if (this.#loadedKeys.has(normalizedKey)) {\n return this.#permissionsByKey.get()?.[normalizedKey] || [];\n }\n\n const configuration = this.#getConfigurationByKey(effectiveKey);\n if (!configuration) {\n this.#setPermissionsForKey(normalizedKey, []);\n this.#loadedKeys.add(normalizedKey);\n return [];\n }\n\n try {\n const permissions: string[] = await resolveMaybeAsync(configuration.loadPermissions());\n this.#setPermissionsForKey(normalizedKey, permissions || []);\n } catch (err) {\n console.error(err);\n this.#setPermissionsForKey(normalizedKey, []);\n } finally {\n this.#loadedKeys.add(normalizedKey);\n }\n\n return this.#permissionsByKey.get()?.[normalizedKey] || [];\n };\n\n loadAllPermissions = async (): Promise<void> => {\n const configurations = this.#getConfigurations();\n if (!configurations.length) {\n await this.loadPermissions(undefined);\n return;\n }\n\n await Promise.all(configurations.map(config => this.loadPermissions(config.key)));\n };\n\n hasPermission = (permission: string | string[], key?: string) => {\n if (!permission?.toString()) {\n return true;\n }\n\n const effectiveKey = this.#getEffectivePermissionKey(key);\n const configuration = this.#getConfigurationByKey(effectiveKey);\n if (configuration?.disabled) {\n return true;\n }\n\n const normalizedKey = this.#normalizeKey(effectiveKey);\n const permissionMap = this.#permissionMapByKey[normalizedKey] || {};\n const permissions = Array.isArray(permission) ? permission : [permission];\n return permissions.some(val => permissionMap[val]);\n };\n\n getToken = async (key?: string) => {\n const effectiveKey = this.#getEffectivePermissionKey(key);\n const getToken = this.#getConfigurationByKey(effectiveKey)?.getToken as (() => MaybeAsync<string | undefined | null>) | undefined;\n if (!getToken) {\n throw new Error('[Permission] Method getToken');\n }\n\n const token = await resolveMaybeAsync(getToken());\n if (token === '') {\n return undefined;\n }\n return token;\n };\n\n decodeToken = async <T>(key?: string): Promise<T | null> => {\n const token = await this.getToken(key);\n if (!token) {\n return null;\n }\n try {\n const payload = token.split('.')[1];\n const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split('')\n .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join('')\n );\n return JSON.parse(jsonPayload);\n } catch (error) {\n console.error('Invalid token', error);\n return null;\n }\n };\n}\n","import { Directive, effect, inject, input, TemplateRef, ViewContainerRef } from '@angular/core';\nimport { SdPermissionService } from '../services';\n\n@Directive({\n selector: '[sdPermission]',\n})\nexport class SdPermissionDirective {\n readonly #templateRef: TemplateRef<Record<string, never>> = inject(TemplateRef);\n readonly #viewContainerRef = inject(ViewContainerRef);\n readonly #permissionService = inject(SdPermissionService);\n\n // Nếu là mảng thì chỉ cần có 1 permission trong mảng đó xem như có quyền\n readonly sdPermission = input<string | string[] | undefined | null>(undefined);\n readonly sdPermissionKey = input<string | undefined>(undefined);\n\n constructor() {\n effect(() => {\n const permission = this.sdPermission();\n const permissionKey = this.sdPermissionKey();\n\n this.#viewContainerRef.clear();\n\n // Nếu không gắn permission thì render\n if (!permission?.toString()) {\n this.#viewContainerRef.createEmbeddedView(this.#templateRef);\n return;\n }\n\n // Kiểm tra permission theo key (nếu có)\n if (this.#permissionService.hasPermission(permission, permissionKey)) {\n this.#viewContainerRef.createEmbeddedView(this.#templateRef);\n }\n });\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { ActivatedRouteSnapshot, CanActivate, CanActivateChild, RouterStateSnapshot } from '@angular/router';\nimport { ISdPermissionConfiguration, SD_PERMISSION_CONFIGURATION } from '../configurations';\nimport { SdPermissionService } from '../services';\n\n@Injectable({ providedIn: 'root' })\nexport class SdPermissionGuard implements CanActivate, CanActivateChild {\n private readonly configuration: ISdPermissionConfiguration | ISdPermissionConfiguration[] = inject(SD_PERMISSION_CONFIGURATION);\n private readonly permissionService = inject(SdPermissionService);\n\n #getConfigurations = (): ISdPermissionConfiguration[] => {\n const config = this.configuration;\n if (!config) {\n return [];\n }\n return Array.isArray(config) ? config : [config];\n };\n\n canActivate = async (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {\n // Guard ở layer portal: preload toàn bộ permission theo tất cả key đã cấu hình\n await this.permissionService.loadAllPermissions().catch(console.error);\n return true;\n };\n\n canActivateChild = async (activatedRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {\n const permission = activatedRoute.data['permission'];\n const permissionKey = activatedRoute.data?.['permissionKey'] as string | undefined;\n if (this.permissionService.hasPermission(permission, permissionKey)) {\n return true;\n }\n\n const configurations = this.#getConfigurations();\n const onForbiden = configurations\n .filter(config => config.key === permissionKey || (permissionKey !== undefined && config.key === undefined))\n .map(config => config.onForbiden)\n .find(val => !!val);\n onForbiden?.();\n return false;\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":"sd-angular-core-modules-permission.mjs","sources":["../../../projects/sd-angular/modules/permission/src/configurations/permission.configuration.ts","../../../projects/sd-angular/modules/permission/src/services/permission.service.ts","../../../projects/sd-angular/modules/permission/src/directives/permission.directive.ts","../../../projects/sd-angular/modules/permission/src/guards/permission.guard.ts","../../../projects/sd-angular/modules/permission/sd-angular-core-modules-permission.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { MaybeAsync } from '@sdcorejs/utils/models';\nimport { Observable } from 'rxjs';\n\n/**\n * Cấu hình trung tâm cho module permission.\n *\n * Luồng hoạt động chính:\n * 1. `loadPermissions(key?)` ở service được gọi để lấy danh sách mã quyền theo từng `key` cấu hình.\n * 2. Guard/service đối chiếu mã quyền theo route metadata.\n * 3. Khi không đủ quyền, callback `onForbiden()` sẽ được gọi (nếu có).\n *\n * Lưu ý:\n * - `SD_PERMISSION_CONFIGURATION` hỗ trợ cả cấu hình đơn và mảng cấu hình (`multi: true`).\n * - Nếu cần tạm bỏ qua kiểm tra quyền (POC/UAT cục bộ), đặt `disabled = true`.\n */\nexport interface ISdPermissionConfiguration {\n /**\n * Khóa định danh cấu hình.\n * Dùng để phân biệt khi hệ thống mở rộng theo nhiều profile permission.\n *\n * Lưu ý: `undefined` cũng được xem là một key hợp lệ (cấu hình mặc định).\n */\n key?: string;\n\n /**\n * Bật/tắt kiểm tra permission toàn cục.\n * - `true`: bỏ qua kiểm tra quyền.\n * - `false | undefined`: kiểm tra quyền theo cấu hình route.\n */\n disabled?: boolean;\n\n /**\n * Trả về danh sách mã quyền của user hiện tại.\n * Có thể đồng bộ hoặc bất đồng bộ.\n *\n * Ví dụ giá trị trả về:\n * - `['PRODUCT_C_EMPLOYEE_VIEW', 'PRODUCT_C_EMPLOYEE_UPDATE']`\n */\n loadPermissions: () => MaybeAsync<string[]> | Observable<string[]>;\n\n /**\n * Callback xử lý khi user không có quyền truy cập URL hiện tại.\n * Thường dùng để điều hướng sang trang forbidden hoặc hiển thị thông báo.\n *\n * Giữ nguyên tên `onForbiden` để tương thích API hiện tại.\n */\n onForbiden?: () => void;\n\n /**\n * Cung cấp access token hiện tại cho các tác vụ liên quan permission.\n * Hỗ trợ trả về đồng bộ, Promise hoặc Observable.\n */\n // why: `MaybeAsync` của `@sdcorejs/utils` 1.1.x là `T | Promise<T> | Observable<T>` (RxJS). Từ 1.2 nó dùng\n // `SubscribableLike<T>` cấu trúc, và kiểu này KHÔNG nhận `Observable` của kiểu hẹp hơn (vd `Observable<string>`\n // cho slot `string | null | undefined`, hay Observable của một subtype). Giữ `Observable<T>` tường minh để\n // consumer đang truyền Observable/BehaviorSubject vẫn compile như trước.\n getToken?: () => MaybeAsync<string | undefined | null> | Observable<string | undefined | null>;\n}\n\n/**\n * InjectionToken cho cấu hình permission.\n *\n * Ví dụ provider:\n * {\n * provide: SD_PERMISSION_CONFIGURATION,\n * useValue: {\n * disabled: false,\n * loadPermissions: () => ['SAMPLE_C_EMPLOYEE_VIEW'],\n * onForbiden: () => router.navigateByUrl('/layout/forbidden')\n * }\n * }\n */\nexport const SD_PERMISSION_CONFIGURATION =\n new InjectionToken<ISdPermissionConfiguration | ISdPermissionConfiguration[]>('sd-permission.configuration');\n","import { inject, Injectable } from '@angular/core';\nimport { SdCache, SdCacheService } from '@sd-angular/core/services/cache';\nimport { ArrayUtilities } from '@sd-angular/core/utilities/extensions';\nimport { ISdPermissionConfiguration, SD_PERMISSION_CONFIGURATION } from '../configurations';\nimport { resolveMaybeAsync } from '@sdcorejs/utils/models';\n\n@Injectable({ providedIn: 'root' })\nexport class SdPermissionService {\n #permissionMapByKey: Record<string, Record<string, boolean>> = {};\n readonly #configuration = inject<ISdPermissionConfiguration | ISdPermissionConfiguration[]>(SD_PERMISSION_CONFIGURATION);\n readonly #cacheService = inject(SdCacheService);\n #permissionsByKey: SdCache<Record<string, string[]>> = this.#cacheService.create<Record<string, string[]>>('212a51fa-38d5-43b2-bd46-922d85950ba3', {\n type: 'session',\n default: {},\n });\n readonly #loadedKeys = new Set<string>();\n\n constructor() {\n this.#validateDuplicateConfigKeys();\n }\n\n #getConfigurations = (): ISdPermissionConfiguration[] => {\n const config = this.#configuration;\n if (!config) {\n return [];\n }\n return Array.isArray(config) ? config : [config];\n };\n\n #normalizeKey = (key?: string): string => {\n return key === undefined ? '__undefined__' : key;\n };\n\n #validateDuplicateConfigKeys = (): void => {\n const seen = new Set<string>();\n for (const config of this.#getConfigurations()) {\n const normalizedKey = this.#normalizeKey(config.key);\n if (seen.has(normalizedKey)) {\n const keyLabel = config.key === undefined ? 'undefined' : config.key;\n throw new Error(`[Permission] Duplicate permission configuration key: ${keyLabel}`);\n }\n seen.add(normalizedKey);\n }\n };\n\n #getConfigurationByKey = (key?: string): ISdPermissionConfiguration | undefined => {\n return this.#getConfigurations().find(config => config.key === key);\n };\n\n #getEffectivePermissionKey = (key?: string): string | undefined => {\n if (this.#getConfigurationByKey(key)) {\n return key;\n }\n\n // Portal-level config uses key = undefined and acts as default fallback\n if (key !== undefined && this.#getConfigurationByKey(undefined)) {\n return undefined;\n }\n\n return key;\n };\n\n #setPermissionsForKey = (normalizedKey: string, permissions: string[]): void => {\n const distinctPermissions = ArrayUtilities.distinct(permissions || []);\n const current = this.#permissionsByKey.get() || {};\n this.#permissionsByKey.set({\n ...current,\n [normalizedKey]: distinctPermissions,\n });\n\n const permissionMap: Record<string, boolean> = {};\n distinctPermissions.forEach(permission => {\n permissionMap[permission] = true;\n });\n this.#permissionMapByKey[normalizedKey] = permissionMap;\n };\n\n loadPermissions = async (key?: string): Promise<string[]> => {\n const effectiveKey = this.#getEffectivePermissionKey(key);\n const normalizedKey = this.#normalizeKey(effectiveKey);\n if (this.#loadedKeys.has(normalizedKey)) {\n return this.#permissionsByKey.get()?.[normalizedKey] || [];\n }\n\n const configuration = this.#getConfigurationByKey(effectiveKey);\n if (!configuration) {\n this.#setPermissionsForKey(normalizedKey, []);\n this.#loadedKeys.add(normalizedKey);\n return [];\n }\n\n try {\n const permissions: string[] = await resolveMaybeAsync<string[]>(configuration.loadPermissions());\n this.#setPermissionsForKey(normalizedKey, permissions || []);\n } catch (err) {\n console.error(err);\n this.#setPermissionsForKey(normalizedKey, []);\n } finally {\n this.#loadedKeys.add(normalizedKey);\n }\n\n return this.#permissionsByKey.get()?.[normalizedKey] || [];\n };\n\n loadAllPermissions = async (): Promise<void> => {\n const configurations = this.#getConfigurations();\n if (!configurations.length) {\n await this.loadPermissions(undefined);\n return;\n }\n\n await Promise.all(configurations.map(config => this.loadPermissions(config.key)));\n };\n\n hasPermission = (permission: string | string[], key?: string) => {\n if (!permission?.toString()) {\n return true;\n }\n\n const effectiveKey = this.#getEffectivePermissionKey(key);\n const configuration = this.#getConfigurationByKey(effectiveKey);\n if (configuration?.disabled) {\n return true;\n }\n\n const normalizedKey = this.#normalizeKey(effectiveKey);\n const permissionMap = this.#permissionMapByKey[normalizedKey] || {};\n const permissions = Array.isArray(permission) ? permission : [permission];\n return permissions.some(val => permissionMap[val]);\n };\n\n getToken = async (key?: string) => {\n const effectiveKey = this.#getEffectivePermissionKey(key);\n const getToken = this.#getConfigurationByKey(effectiveKey)?.getToken;\n if (!getToken) {\n throw new Error('[Permission] Method getToken');\n }\n\n const token = await resolveMaybeAsync<string | undefined | null>(getToken());\n if (token === '') {\n return undefined;\n }\n return token;\n };\n\n decodeToken = async <T>(key?: string): Promise<T | null> => {\n const token = await this.getToken(key);\n if (!token) {\n return null;\n }\n try {\n const payload = token.split('.')[1];\n const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split('')\n .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join('')\n );\n return JSON.parse(jsonPayload);\n } catch (error) {\n console.error('Invalid token', error);\n return null;\n }\n };\n}\n","import { Directive, effect, inject, input, TemplateRef, ViewContainerRef } from '@angular/core';\nimport { SdPermissionService } from '../services';\n\n@Directive({\n selector: '[sdPermission]',\n})\nexport class SdPermissionDirective {\n readonly #templateRef: TemplateRef<Record<string, never>> = inject(TemplateRef);\n readonly #viewContainerRef = inject(ViewContainerRef);\n readonly #permissionService = inject(SdPermissionService);\n\n // Nếu là mảng thì chỉ cần có 1 permission trong mảng đó xem như có quyền\n readonly sdPermission = input<string | string[] | undefined | null>(undefined);\n readonly sdPermissionKey = input<string | undefined>(undefined);\n\n constructor() {\n effect(() => {\n const permission = this.sdPermission();\n const permissionKey = this.sdPermissionKey();\n\n this.#viewContainerRef.clear();\n\n // Nếu không gắn permission thì render\n if (!permission?.toString()) {\n this.#viewContainerRef.createEmbeddedView(this.#templateRef);\n return;\n }\n\n // Kiểm tra permission theo key (nếu có)\n if (this.#permissionService.hasPermission(permission, permissionKey)) {\n this.#viewContainerRef.createEmbeddedView(this.#templateRef);\n }\n });\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { ActivatedRouteSnapshot, CanActivate, CanActivateChild, RouterStateSnapshot } from '@angular/router';\nimport { ISdPermissionConfiguration, SD_PERMISSION_CONFIGURATION } from '../configurations';\nimport { SdPermissionService } from '../services';\n\n@Injectable({ providedIn: 'root' })\nexport class SdPermissionGuard implements CanActivate, CanActivateChild {\n private readonly configuration: ISdPermissionConfiguration | ISdPermissionConfiguration[] = inject(SD_PERMISSION_CONFIGURATION);\n private readonly permissionService = inject(SdPermissionService);\n\n #getConfigurations = (): ISdPermissionConfiguration[] => {\n const config = this.configuration;\n if (!config) {\n return [];\n }\n return Array.isArray(config) ? config : [config];\n };\n\n canActivate = async (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {\n // Guard ở layer portal: preload toàn bộ permission theo tất cả key đã cấu hình\n await this.permissionService.loadAllPermissions().catch(console.error);\n return true;\n };\n\n canActivateChild = async (activatedRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {\n const permission = activatedRoute.data['permission'];\n const permissionKey = activatedRoute.data?.['permissionKey'] as string | undefined;\n if (this.permissionService.hasPermission(permission, permissionKey)) {\n return true;\n }\n\n const configurations = this.#getConfigurations();\n const onForbiden = configurations\n .filter(config => config.key === permissionKey || (permissionKey !== undefined && config.key === undefined))\n .map(config => config.onForbiden)\n .find(val => !!val);\n onForbiden?.();\n return false;\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,203 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, Injectable, NgModule } from '@angular/core';
|
|
2
|
+
import { InjectionToken, DestroyRef, EnvironmentInjector, signal, computed, inject, ErrorHandler, Injectable, makeEnvironmentProviders, provideEnvironmentInitializer, isDevMode, NgModule } from '@angular/core';
|
|
3
|
+
import { RoutesRecognized, ROUTES, Router } from '@angular/router';
|
|
3
4
|
import { HttpClient, HttpResponse, HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
|
4
5
|
import { SdCacheService } from '@sd-angular/core/services/cache';
|
|
5
6
|
import { BrowserUtilities, Utilities } from '@sdcorejs/utils/fns';
|
|
7
|
+
import { FilePickerCancelledError } from '@sdcorejs/utils/errors';
|
|
6
8
|
import { lastValueFrom, from, throwError } from 'rxjs';
|
|
7
9
|
import { timeout, map, catchError, shareReplay, switchMap } from 'rxjs/operators';
|
|
8
10
|
|
|
9
11
|
const SD_API_CONFIG = new InjectionToken('sd-api.configuration');
|
|
10
|
-
|
|
12
|
+
/**
|
|
13
|
+
* @deprecated Use `SD_API_CONFIG`. This is the same injection token.
|
|
14
|
+
*
|
|
15
|
+
* why: trước đây đây là một InjectionToken RIÊNG (cùng description) và không nơi nào đọc nó, nên
|
|
16
|
+
* consumer theo tên này nhận danh sách handler rỗng mà không có cảnh báo. Giờ là alias của
|
|
17
|
+
* `SD_API_CONFIG`, cùng chiều với `@sdcorejs/angular`.
|
|
18
|
+
*/
|
|
19
|
+
const SD_API_CONFIGURATION = SD_API_CONFIG;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Nhặt `SD_API_CONFIG` khai trong injector của lazy route và đăng ký vào registry ở root.
|
|
23
|
+
*
|
|
24
|
+
* why: interceptor sống ở root nên không thấy `{ provide: SD_API_CONFIG, multi: true }` nằm trong
|
|
25
|
+
* `Route.providers` hay NgModule nạp bằng `loadChildren`. Library không biết shell mount nó eager
|
|
26
|
+
* hay lazy, nên Core phải tự tìm các injector đó thay vì bắt library đổi cách khai.
|
|
27
|
+
*
|
|
28
|
+
* Duyệt cây route của Router lúc registry được tạo và ở mỗi `RoutesRecognized` — lúc recognize vừa
|
|
29
|
+
* tạo xong mọi injector của navigation, trước guard (`canActivate`), resolver và component. Mỗi
|
|
30
|
+
* injector được đọc một lần và gỡ khi nó bị huỷ, nên handler sống đúng bằng injector của route.
|
|
31
|
+
*/
|
|
32
|
+
function sdTrackRouteApiConfigurations(router, sink, errorHandler, destroyRef) {
|
|
33
|
+
const tracked = new WeakSet();
|
|
34
|
+
const register = (injector) => {
|
|
35
|
+
if (tracked.has(injector))
|
|
36
|
+
return;
|
|
37
|
+
tracked.add(injector);
|
|
38
|
+
try {
|
|
39
|
+
// why: `self` — chỉ provider của chính scope này; root và scope cha đã được tính ở chỗ khác.
|
|
40
|
+
const configurations = injector.get(SD_API_CONFIG, null, { self: true, optional: true }) ?? [];
|
|
41
|
+
if (!configurations.length)
|
|
42
|
+
return;
|
|
43
|
+
const unregisters = configurations.map(configuration => sink.register(configuration));
|
|
44
|
+
injector.get(DestroyRef).onDestroy(() => unregisters.forEach(unregister => unregister()));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
errorHandler.handleError(error);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const collect = (routes) => {
|
|
51
|
+
for (const route of routes) {
|
|
52
|
+
for (const injector of [route._injector, route._loadedInjector]) {
|
|
53
|
+
if (injector instanceof EnvironmentInjector)
|
|
54
|
+
register(injector);
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(route.children))
|
|
57
|
+
collect(route.children);
|
|
58
|
+
if (Array.isArray(route._loadedRoutes))
|
|
59
|
+
collect(route._loadedRoutes);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
// why: registry có thể được tạo giữa chừng — sau navigation đầu, hay trong guard của chính lazy
|
|
63
|
+
// route — nên đọc cả những injector Router đã dựng trước đó.
|
|
64
|
+
collect(router.config);
|
|
65
|
+
const subscription = router.events.subscribe(event => {
|
|
66
|
+
if (event instanceof RoutesRecognized)
|
|
67
|
+
collect(router.config);
|
|
68
|
+
});
|
|
69
|
+
destroyRef.onDestroy(() => subscription.unsubscribe());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Handler API đăng ký lúc runtime, sống ở root injector.
|
|
74
|
+
*
|
|
75
|
+
* why: `SdHttpInterceptor` được đăng ký qua `HTTP_INTERCEPTORS` ở ROOT, nên
|
|
76
|
+
* `inject(SD_API_CONFIG)` của nó chỉ thấy provider ở root. Library nào khai
|
|
77
|
+
* `{ provide: SD_API_CONFIG, multi: true }` trong NgModule mà shell nạp bằng
|
|
78
|
+
* `loadChildren` thì provider nằm ở child injector của lazy route — interceptor không
|
|
79
|
+
* bao giờ thấy, nên mất `intercept` / `beforeRemote` / `afterRemote` trong im lặng.
|
|
80
|
+
* Registry này là điểm hẹn ở root mà mọi scope (root, eager, lazy) đều ghi vào được.
|
|
81
|
+
*
|
|
82
|
+
* Registry tự nhặt `SD_API_CONFIG` khai trong injector của lazy route (`Route.providers`,
|
|
83
|
+
* NgModule của `loadChildren`) trong chính navigation Router tạo injector đó — trước guard,
|
|
84
|
+
* resolver và component — và gỡ khi injector bị huỷ;
|
|
85
|
+
* library giữ nguyên cách khai multi provider, không cần biết shell mount nó eager hay lazy.
|
|
86
|
+
*/
|
|
87
|
+
class SdApiHandlerRegistry {
|
|
88
|
+
// why: bọc từng lần đăng ký vào entry riêng để gỡ đúng lần đó, kể cả khi cùng một object
|
|
89
|
+
// configuration được đăng ký ở hai scope.
|
|
90
|
+
#entries = signal([]);
|
|
91
|
+
/** Các configuration đang được đăng ký, theo thứ tự đăng ký. */
|
|
92
|
+
configurations = computed(() => this.#entries().map(entry => entry.configuration));
|
|
93
|
+
constructor() {
|
|
94
|
+
// why: chỉ theo dõi khi app có Router — inject `Router` ở app không cấu hình route sẽ dựng
|
|
95
|
+
// một Router thừa.
|
|
96
|
+
if (inject(ROUTES, { optional: true })) {
|
|
97
|
+
sdTrackRouteApiConfigurations(inject(Router), this, inject(ErrorHandler), inject(DestroyRef));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Đăng ký một configuration. Trả về hàm gỡ đăng ký (gọi nhiều lần vẫn an toàn); dùng
|
|
102
|
+
* {@link provideSdApiConfiguration} để việc gỡ gắn với vòng đời của scope.
|
|
103
|
+
*/
|
|
104
|
+
register(configuration) {
|
|
105
|
+
const entry = { configuration };
|
|
106
|
+
this.#entries.update(entries => [...entries, entry]);
|
|
107
|
+
let registered = true;
|
|
108
|
+
return () => {
|
|
109
|
+
if (!registered)
|
|
110
|
+
return;
|
|
111
|
+
registered = false;
|
|
112
|
+
this.#entries.update(entries => entries.filter(candidate => candidate !== entry));
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdApiHandlerRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
116
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdApiHandlerRegistry, providedIn: 'root' });
|
|
117
|
+
}
|
|
118
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdApiHandlerRegistry, decorators: [{
|
|
119
|
+
type: Injectable,
|
|
120
|
+
args: [{ providedIn: 'root' }]
|
|
121
|
+
}], ctorParameters: () => [] });
|
|
122
|
+
/**
|
|
123
|
+
* Đăng ký handler API cho scope hiện tại: root (`bootstrapApplication` / `AppModule`),
|
|
124
|
+
* NgModule nạp eager, hay `Route.providers` / NgModule của lazy route.
|
|
125
|
+
*
|
|
126
|
+
* Configuration được đăng ký vào {@link SdApiHandlerRegistry} ngay khi injector của scope
|
|
127
|
+
* được tạo — tức trước mọi request scope đó phát ra — và gỡ khi injector bị huỷ. Truyền
|
|
128
|
+
* class thì class được provide trong chính scope đó, nên nó inject được dependency của scope.
|
|
129
|
+
*
|
|
130
|
+
* ```ts
|
|
131
|
+
* // library
|
|
132
|
+
* @NgModule({ providers: [provideSdApiConfiguration(ApiConfiguration)] })
|
|
133
|
+
* export class OrdersModule {}
|
|
134
|
+
*
|
|
135
|
+
* // hoặc route
|
|
136
|
+
* { path: 'orders', providers: [provideSdApiConfiguration(ApiConfiguration)], loadComponent: ... }
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
function provideSdApiConfiguration(configuration) {
|
|
140
|
+
const configurationClass = typeof configuration === 'function' ? configuration : undefined;
|
|
141
|
+
return makeEnvironmentProviders([
|
|
142
|
+
...(configurationClass ? [configurationClass] : []),
|
|
143
|
+
provideEnvironmentInitializer(() => {
|
|
144
|
+
const instance = configurationClass ? inject(configurationClass) : configuration;
|
|
145
|
+
const unregister = inject(SdApiHandlerRegistry).register(instance);
|
|
146
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
147
|
+
}),
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Handler đã cảnh báo host rỗng — chỉ cảnh báo một lần cho mỗi handler. */
|
|
152
|
+
const warnedHandlers = new WeakSet();
|
|
153
|
+
const configurationName = (configuration) => {
|
|
154
|
+
const name = configuration?.constructor?.name;
|
|
155
|
+
return name && name !== 'Object' ? name : 'an ISdApiConfiguration value';
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Host dùng được của handler: bỏ phần tử không phải chuỗi, rỗng hoặc chỉ khoảng trắng.
|
|
159
|
+
*
|
|
160
|
+
* why: `''` prefix-khớp MỌI URL, nên một handler `hosts: ['']` nuốt hết request của các lib
|
|
161
|
+
* khác. Host rỗng lại là trạng thái hợp lệ — shell forward nó cho service chưa deploy ở môi
|
|
162
|
+
* trường đó — nên BỎ QUA chứ không ném (ném sẽ giết bootstrap).
|
|
163
|
+
*/
|
|
164
|
+
const usableHosts = (handler, configuration) => {
|
|
165
|
+
const hosts = (Array.isArray(handler.hosts) ? handler.hosts : [])
|
|
166
|
+
.filter((host) => typeof host === 'string')
|
|
167
|
+
.map(host => host.trim())
|
|
168
|
+
.filter(Boolean);
|
|
169
|
+
if (!hosts.length && isDevMode() && !warnedHandlers.has(handler)) {
|
|
170
|
+
warnedHandlers.add(handler);
|
|
171
|
+
console.warn(`[sd-api] A handler in ${configurationName(configuration)} has no non-blank host and is skipped. ` +
|
|
172
|
+
'This is expected for a service that is not deployed in this environment; otherwise check its `hosts`.');
|
|
173
|
+
}
|
|
174
|
+
return hosts;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Handler chịu trách nhiệm cho `url`: host khớp prefix DÀI NHẤT thắng; hai host dài bằng nhau
|
|
178
|
+
* thì handler đăng ký trước thắng.
|
|
179
|
+
*
|
|
180
|
+
* why: trước đây là `handlers.find(...)` — handler đầu tiên khớp thắng, nên khi nhiều lib nằm
|
|
181
|
+
* sau cùng một gateway (phân biệt bằng context path) thì thứ tự đăng ký quyết định ai nhận
|
|
182
|
+
* request. Giờ `https://gw.example/bpm` thắng `https://gw.example` bất kể thứ tự.
|
|
183
|
+
*/
|
|
184
|
+
function sdResolveApiHandler(url, configurations) {
|
|
185
|
+
let best;
|
|
186
|
+
let bestLength = -1;
|
|
187
|
+
for (const configuration of configurations) {
|
|
188
|
+
for (const handler of configuration?.handlers ?? []) {
|
|
189
|
+
if (!handler)
|
|
190
|
+
continue;
|
|
191
|
+
for (const host of usableHosts(handler, configuration)) {
|
|
192
|
+
if (host.length > bestLength && url.startsWith(host)) {
|
|
193
|
+
best = handler;
|
|
194
|
+
bestLength = host.length;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return best;
|
|
200
|
+
}
|
|
11
201
|
|
|
12
202
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
13
203
|
class SdApiService {
|
|
@@ -15,6 +205,7 @@ class SdApiService {
|
|
|
15
205
|
#dedupCacheDuration = 1000; // 1s (Deduplication cache duration)
|
|
16
206
|
#httpClient = inject(HttpClient);
|
|
17
207
|
#configurations = inject(SD_API_CONFIG, { optional: true }) || [];
|
|
208
|
+
#registry = inject(SdApiHandlerRegistry);
|
|
18
209
|
#cacheService = inject(SdCacheService);
|
|
19
210
|
// Thay đổi cấu trúc Cache: Lưu Observable thay vì Subject phức tạp
|
|
20
211
|
// Key: hash string -> Value: { stream$: Observable, expiry: number }
|
|
@@ -41,7 +232,17 @@ class SdApiService {
|
|
|
41
232
|
};
|
|
42
233
|
// Upload file logic giữ nguyên nhưng refactor nhẹ
|
|
43
234
|
upload = async (url, option) => {
|
|
44
|
-
|
|
235
|
+
let file;
|
|
236
|
+
try {
|
|
237
|
+
file = await BrowserUtilities.upload(option);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
// why: từ `@sdcorejs/utils` 1.2, huỷ/timeout hộp chọn file reject `FilePickerCancelledError` thay vì
|
|
241
|
+
// resolve rỗng. Hợp đồng của `upload()` là huỷ thì trả `undefined` và không gửi request.
|
|
242
|
+
if (error instanceof FilePickerCancelledError)
|
|
243
|
+
return undefined;
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
45
246
|
if (!Array.isArray(file) && file) {
|
|
46
247
|
return this.uploadFile(url, file);
|
|
47
248
|
}
|
|
@@ -127,8 +328,8 @@ class SdApiService {
|
|
|
127
328
|
};
|
|
128
329
|
// --- HELPERS ---
|
|
129
330
|
#getHandler = (url) => {
|
|
130
|
-
|
|
131
|
-
return
|
|
331
|
+
// why: cùng quy tắc chọn handler với SdHttpInterceptor (prefix dài nhất, gồm cả handler lazy).
|
|
332
|
+
return sdResolveApiHandler(url, [...this.#configurations, ...this.#registry.configurations()]);
|
|
132
333
|
};
|
|
133
334
|
#generateKey = (url, method, body, option) => {
|
|
134
335
|
// FormData không hash được nội dung file, luôn generate key mới
|
|
@@ -164,14 +365,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
|
|
|
164
365
|
|
|
165
366
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
166
367
|
class SdHttpInterceptor {
|
|
368
|
+
// why: interceptor sống ở root; handler của lazy scope chỉ đến qua registry, nên resolve MỖI request
|
|
369
|
+
// (hợp nhất list tĩnh với registry) thay vì cache một lần lúc construct.
|
|
167
370
|
#configurations = inject(SD_API_CONFIG, { optional: true }) || [];
|
|
371
|
+
#registry = inject(SdApiHandlerRegistry);
|
|
168
372
|
intercept(request, next) {
|
|
169
373
|
const url = request.url;
|
|
170
374
|
if (!url) {
|
|
171
375
|
throw new Error(`Invalid URL`);
|
|
172
376
|
}
|
|
173
|
-
const
|
|
174
|
-
const handler = handlers?.find(e => e.hosts.some(host => url.startsWith(host)));
|
|
377
|
+
const handler = sdResolveApiHandler(url, [...this.#configurations, ...this.#registry.configurations()]);
|
|
175
378
|
const intercept = handler?.intercept;
|
|
176
379
|
if (intercept) {
|
|
177
380
|
request = request.clone(intercept(request));
|
|
@@ -230,5 +433,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
|
|
|
230
433
|
* Generated bundle index. Do not edit.
|
|
231
434
|
*/
|
|
232
435
|
|
|
233
|
-
export { SD_API_CONFIG, SD_API_CONFIGURATION, SdApiModule, SdApiService, SdHttpInterceptor };
|
|
436
|
+
export { SD_API_CONFIG, SD_API_CONFIGURATION, SdApiHandlerRegistry, SdApiModule, SdApiService, SdHttpInterceptor, provideSdApiConfiguration };
|
|
234
437
|
//# sourceMappingURL=sd-angular-core-services-api.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sd-angular-core-services-api.mjs","sources":["../../../projects/sd-angular/services/api/src/api.model.ts","../../../projects/sd-angular/services/api/src/api.service.ts","../../../projects/sd-angular/services/api/src/interceptors/api.interceptor.ts","../../../projects/sd-angular/services/api/src/api.module.ts","../../../projects/sd-angular/services/api/sd-angular-core-services-api.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { HttpClient, HttpErrorResponse, HttpRequest, HttpResponse } from '@angular/common/http';\nimport { InjectionToken } from '@angular/core';\nimport { SdCacheOption } from '@sd-angular/core/services/cache';\n// import hash from 'object-hash';\n\nexport interface SdApiOption {\n cacheOption?: SdCacheOption;\n timeout?: number; // Default: 30000 (30s)\n autoCache?: boolean; // Default: true\n}\ntype HttpGetOption = Parameters<HttpClient['get']>[1];\nexport type SdGetOption = HttpGetOption & SdApiOption;\n\ntype HttpPostOption = Parameters<HttpClient['post']>[2];\nexport type SdPostOption = HttpPostOption & SdApiOption;\n\ntype HttpPutOption = Parameters<HttpClient['put']>[2];\nexport type SdPutOption = HttpPutOption & SdApiOption;\n\ntype HttpDeleteOption = Parameters<HttpClient['delete']>[1];\nexport type SdDeleteOption = HttpDeleteOption & SdApiOption;\n\nexport interface SdApiHandler {\n /** Danh sách host URL mà handler này sẽ xử lý */\n hosts: string[];\n /** Can thiệp request: gắn header, token, transform body... */\n intercept?: (request: HttpRequest<any>) => HttpRequest<any>;\n /** Hook chạy TRƯỚC khi gửi request (dùng để log, tracking...) */\n beforeRemote?: (request: HttpRequest<any>) => void | Promise<void>;\n /** Hook chạy SAU khi nhận response (xử lý lỗi, notify...) */\n afterRemote?: (\n response: HttpResponse<any> | HttpErrorResponse | Error\n ) => void | Promise<void>;\n /** Transform response body thành kiểu dữ liệu mong muốn */\n mapResponse?: <Tres = any, Tdata = any>(response: Tres) => Tdata;\n /** Timeout tính bằng milliseconds. Mặc định: 30000 (30 giây) */\n timeout?: number;\n}\n\nexport interface ISdApiConfiguration {\n handlers: SdApiHandler[];\n}\n\nexport const SD_API_CONFIG = new InjectionToken<ISdApiConfiguration>('sd-api.configuration');\n\nexport const SD_API_CONFIGURATION = new InjectionToken<ISdApiConfiguration>('sd-api.configuration');","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { HttpClient } from '@angular/common/http';\nimport { inject, Injectable } from '@angular/core';\nimport { SdCacheService } from '@sd-angular/core/services/cache';\nimport { BrowserUtilities, Utilities } from '@sdcorejs/utils/fns';\nimport { lastValueFrom, Observable } from 'rxjs';\nimport { catchError, map, shareReplay, timeout } from 'rxjs/operators';\nimport { ISdApiConfiguration, SD_API_CONFIG, SdApiHandler, SdDeleteOption, SdGetOption, SdPostOption, SdPutOption } from './api.model';\n\n// Gom nhóm các Option lại cho gọn\ntype SdHttpOptions = SdGetOption & SdPostOption & SdPutOption & SdDeleteOption;\ntype HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdApiService {\n readonly #defaultTimeout = 60000; // 60s\n readonly #dedupCacheDuration = 1000; // 1s (Deduplication cache duration)\n #httpClient = inject(HttpClient);\n #configurations = inject<ISdApiConfiguration[]>(SD_API_CONFIG, { optional: true }) || [];\n #cacheService = inject(SdCacheService);\n\n // Thay đổi cấu trúc Cache: Lưu Observable thay vì Subject phức tạp\n // Key: hash string -> Value: { stream$: Observable, expiry: number }\n #inFlightRequests: Map<string, { stream$: Observable<any>; expiry: number }> = new Map();\n\n constructor() {\n // Optional: Cơ chế dọn dẹp cache định kỳ (mỗi 1 phút dọn dẹp các key hết hạn)\n setInterval(() => this.#cleanupCache(), 60000);\n }\n\n get http() {\n return this.#httpClient;\n }\n\n // --- PUBLIC METHODS ---\n\n get = <T = any>(url: string, option?: SdGetOption): Promise<T> => {\n return this.#executeWithLayeredCache<T>(url, 'GET', undefined, option);\n };\n\n post = <T = any>(url: string, body?: any, option?: SdPostOption): Promise<T> => {\n return this.#executeWithLayeredCache<T>(url, 'POST', body, option);\n };\n\n put = <T = any>(url: string, body?: any, option?: SdPutOption): Promise<T> => {\n return this.#executeWithLayeredCache<T>(url, 'PUT', body, option);\n };\n\n delete = <T = any>(url: string, option?: SdDeleteOption): Promise<T> => {\n return this.#executeWithLayeredCache<T>(url, 'DELETE', undefined, option);\n };\n\n // Upload file logic giữ nguyên nhưng refactor nhẹ\n upload = async (url: string, option?: { extensions?: string[]; maxSizeInMb?: number }): Promise<any> => {\n const file = await BrowserUtilities.upload(option);\n if (!Array.isArray(file) && file) {\n return this.uploadFile(url, file);\n }\n };\n\n uploadFile = async (url: string, file: File): Promise<any> => {\n if (!file) return null;\n if (file.name.lastIndexOf('.') === -1) throw new Error('Invalid file extension');\n\n const formData = new FormData();\n formData.append('file', file, file.name);\n // Upload thường không cần deduplication cache, set autoCache: false\n return await this.post(url, formData, { autoCache: false });\n };\n\n // --- PRIVATE CORE LOGIC ---\n\n /**\n * Hàm trung gian xử lý Layer Cache (SdCacheService) trước khi gọi API thực tế\n */\n #executeWithLayeredCache = async <T>(url: string, method: HttpMethod, body?: any, option?: SdHttpOptions): Promise<T> => {\n // Layer 1: Persistent Cache (SdCacheService)\n if (option?.cacheOption) {\n const key = this.#generateKey(url, method, body, option);\n const { get, set, has } = this.#cacheService.create(key, option.cacheOption);\n\n if (has()) {\n return get();\n }\n\n const result = await this.#request<T>(url, method, body, option);\n set(result);\n return result;\n }\n\n // Không có cache dài hạn thì gọi trực tiếp logic deduplication\n return this.#request<T>(url, method, body, option);\n };\n\n /**\n * Core Request: Xử lý Deduplication, Timeout, Mapping Response\n */\n #request = <T>(url: string, method: HttpMethod, body: any, option?: SdHttpOptions): Promise<T> => {\n const key = this.#generateKey(url, method, body, option);\n\n // Layer 2: Deduplication Cache (In-Flight Request / Short-term cache)\n const now = Date.now();\n const cachedItem = this.#inFlightRequests.get(key);\n\n // Nếu đã có request đang chạy hoặc mới chạy xong trong vòng 1s -> Trả về stream đó luôn\n if (cachedItem && cachedItem.expiry > now) {\n return lastValueFrom(cachedItem.stream$);\n }\n\n // Setup request mới\n const handler = this.#getHandler(url);\n const apiTimeout = option?.timeout ?? handler?.timeout ?? this.#defaultTimeout;\n\n // Tạo Observable call API\n const request$ = this.#httpClient\n .request(method, url, {\n body,\n headers: option?.headers,\n params: option?.params,\n observe: 'response', // Lấy full response để check status\n responseType: option?.responseType,\n })\n .pipe(\n timeout(apiTimeout),\n map(res => {\n // Normalize Response Logic\n const bodyRes = res.body as any;\n // Logic check response cũ của bạn\n if (bodyRes && typeof bodyRes === 'object' && 'ok' in bodyRes && !bodyRes.ok) {\n throw bodyRes; // Giả sử structure trả về { ok: false, ... } là lỗi\n }\n\n if (handler?.mapResponse) {\n return handler.mapResponse(bodyRes);\n }\n\n return bodyRes;\n }),\n catchError(err => {\n // Xóa cache ngay lập tức nếu lỗi để user có thể retry\n this.#inFlightRequests.delete(key);\n throw err;\n }),\n // QUAN TRỌNG: shareReplay(1) giúp share kết quả cho các subscriber đến sau (trong 1s)\n shareReplay(1)\n );\n\n // Lưu vào Map\n this.#inFlightRequests.set(key, {\n stream$: request$,\n expiry: now + this.#dedupCacheDuration,\n });\n\n // Chuyển đổi sang Promise cho đúng return type của bạn\n return lastValueFrom(request$);\n };\n\n // --- HELPERS ---\n\n #getHandler = (url: string): SdApiHandler | undefined => {\n const handlers = this.#configurations.flatMap(b => b?.handlers || []);\n return handlers.find(e => e.hosts.some(host => url.startsWith(host)));\n };\n\n #generateKey = (url: string, method: HttpMethod, body: any, option?: SdHttpOptions): string => {\n // FormData không hash được nội dung file, luôn generate key mới\n if (body instanceof FormData || option?.autoCache === false) {\n return Utilities.generateUuid();\n }\n return Utilities.hash({\n url,\n method,\n params: option?.params,\n headers: option?.headers,\n body,\n });\n };\n\n // Dọn dẹp memory leak\n #cleanupCache() {\n const now = Date.now();\n this.#inFlightRequests.forEach((value, key) => {\n if (value.expiry < now) {\n this.#inFlightRequests.delete(key);\n }\n });\n }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';\nimport { inject, Injectable } from '@angular/core';\nimport { from, Observable, throwError } from 'rxjs';\nimport { catchError, map, switchMap } from 'rxjs/operators';\nimport { ISdApiConfiguration, SD_API_CONFIG } from '../api.model';\n\n@Injectable()\nexport class SdHttpInterceptor implements HttpInterceptor {\n #configurations = inject<ISdApiConfiguration[]>(SD_API_CONFIG, { optional: true }) || [];\n\n intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {\n const url = request.url;\n if (!url) {\n throw new Error(`Invalid URL`);\n }\n const handlers = this.#configurations.flatMap(configuration => configuration?.handlers || []);\n const handler = handlers?.find(e => e.hosts.some(host => url.startsWith(host)));\n const intercept = handler?.intercept;\n if (intercept) {\n request = request.clone(intercept(request));\n }\n if (request.body instanceof FormData) {\n request = request.clone({\n headers: request.headers.delete('Content-Type'),\n });\n }\n const beforeRemoteHandler = handler?.beforeRemote;\n const afterRemoteHandler = handler?.afterRemote;\n const beforeRemote = beforeRemoteHandler?.(request);\n if (beforeRemote instanceof Promise) {\n return from(beforeRemote).pipe(\n switchMap(() => next.handle(request)),\n map((event: HttpEvent<any>) => {\n if (event instanceof HttpResponse) {\n afterRemoteHandler?.(event);\n }\n return event;\n }),\n catchError((error: HttpErrorResponse) => {\n afterRemoteHandler?.(error);\n return throwError(() => error);\n })\n );\n }\n return next.handle(request).pipe(\n map((event: HttpEvent<any>) => {\n if (event instanceof HttpResponse) {\n afterRemoteHandler?.(event);\n }\n return event;\n }),\n catchError((error: HttpErrorResponse) => {\n afterRemoteHandler?.(error);\n return throwError(() => error);\n })\n );\n }\n}\n","import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';\nimport { NgModule } from '@angular/core';\nimport { SdHttpInterceptor } from './interceptors/api.interceptor';\n\n@NgModule({\n imports: [],\n exports: [],\n providers: [provideHttpClient(withInterceptorsFromDi()), { provide: HTTP_INTERCEPTORS, useClass: SdHttpInterceptor, multi: true }],\n})\nexport class SdApiModule {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sd-angular-core-services-api.mjs","sources":["../../../projects/sd-angular/services/api/src/api.model.ts","../../../projects/sd-angular/services/api/src/api-route-configurations.ts","../../../projects/sd-angular/services/api/src/api-handler-registry.ts","../../../projects/sd-angular/services/api/src/api-handler-selection.ts","../../../projects/sd-angular/services/api/src/api.service.ts","../../../projects/sd-angular/services/api/src/interceptors/api.interceptor.ts","../../../projects/sd-angular/services/api/src/api.module.ts","../../../projects/sd-angular/services/api/sd-angular-core-services-api.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { HttpClient, HttpErrorResponse, HttpRequest, HttpResponse } from '@angular/common/http';\nimport { InjectionToken } from '@angular/core';\nimport { SdCacheOption } from '@sd-angular/core/services/cache';\n// import hash from 'object-hash';\n\nexport interface SdApiOption {\n cacheOption?: SdCacheOption;\n timeout?: number; // Default: 30000 (30s)\n autoCache?: boolean; // Default: true\n}\ntype HttpGetOption = Parameters<HttpClient['get']>[1];\nexport type SdGetOption = HttpGetOption & SdApiOption;\n\ntype HttpPostOption = Parameters<HttpClient['post']>[2];\nexport type SdPostOption = HttpPostOption & SdApiOption;\n\ntype HttpPutOption = Parameters<HttpClient['put']>[2];\nexport type SdPutOption = HttpPutOption & SdApiOption;\n\ntype HttpDeleteOption = Parameters<HttpClient['delete']>[1];\nexport type SdDeleteOption = HttpDeleteOption & SdApiOption;\n\nexport interface SdApiHandler {\n /** Danh sách host URL mà handler này sẽ xử lý */\n hosts: string[];\n /** Can thiệp request: gắn header, token, transform body... */\n intercept?: (request: HttpRequest<any>) => HttpRequest<any>;\n /** Hook chạy TRƯỚC khi gửi request (dùng để log, tracking...) */\n beforeRemote?: (request: HttpRequest<any>) => void | Promise<void>;\n /** Hook chạy SAU khi nhận response (xử lý lỗi, notify...) */\n afterRemote?: (\n response: HttpResponse<any> | HttpErrorResponse | Error\n ) => void | Promise<void>;\n /** Transform response body thành kiểu dữ liệu mong muốn */\n mapResponse?: <Tres = any, Tdata = any>(response: Tres) => Tdata;\n /** Timeout tính bằng milliseconds. Mặc định: 30000 (30 giây) */\n timeout?: number;\n}\n\nexport interface ISdApiConfiguration {\n handlers: SdApiHandler[];\n}\n\nexport const SD_API_CONFIG = new InjectionToken<ISdApiConfiguration>('sd-api.configuration');\n\n/**\n * @deprecated Use `SD_API_CONFIG`. This is the same injection token.\n *\n * why: trước đây đây là một InjectionToken RIÊNG (cùng description) và không nơi nào đọc nó, nên\n * consumer theo tên này nhận danh sách handler rỗng mà không có cảnh báo. Giờ là alias của\n * `SD_API_CONFIG`, cùng chiều với `@sdcorejs/angular`.\n */\nexport const SD_API_CONFIGURATION = SD_API_CONFIG;\n","import { DestroyRef, EnvironmentInjector, ErrorHandler } from '@angular/core';\nimport { Route, Router, RoutesRecognized } from '@angular/router';\nimport { ISdApiConfiguration, SD_API_CONFIG } from './api.model';\n\n/** Nơi nhận configuration nhặt được từ route; {@link SdApiHandlerRegistry} là hiện thực. */\ninterface SdApiConfigurationSink {\n register(configuration: ISdApiConfiguration): () => void;\n}\n\n/**\n * Field Router gắn lên route: `_injector` (dựng từ `Route.providers`), `_loadedInjector` (NgModule\n * của `loadChildren`) và `_loadedRoutes` (route con đã nạp).\n *\n * why: Router không có API public trả injector của route. Các field này có mặt ở mọi bản Angular\n * 19–22 (chính Router đọc chúng trong `getClosestRouteInjector`); code chỉ nhận giá trị đúng kiểu,\n * nên bản Angular nào đổi tên field thì chỉ mất tính năng, không vỡ.\n */\ntype SdRouterRoute = Route & { _injector?: unknown; _loadedInjector?: unknown; _loadedRoutes?: unknown };\n\n/**\n * Nhặt `SD_API_CONFIG` khai trong injector của lazy route và đăng ký vào registry ở root.\n *\n * why: interceptor sống ở root nên không thấy `{ provide: SD_API_CONFIG, multi: true }` nằm trong\n * `Route.providers` hay NgModule nạp bằng `loadChildren`. Library không biết shell mount nó eager\n * hay lazy, nên Core phải tự tìm các injector đó thay vì bắt library đổi cách khai.\n *\n * Duyệt cây route của Router lúc registry được tạo và ở mỗi `RoutesRecognized` — lúc recognize vừa\n * tạo xong mọi injector của navigation, trước guard (`canActivate`), resolver và component. Mỗi\n * injector được đọc một lần và gỡ khi nó bị huỷ, nên handler sống đúng bằng injector của route.\n */\nexport function sdTrackRouteApiConfigurations(\n router: Router,\n sink: SdApiConfigurationSink,\n errorHandler: ErrorHandler,\n destroyRef: DestroyRef\n): void {\n const tracked = new WeakSet<EnvironmentInjector>();\n\n const register = (injector: EnvironmentInjector): void => {\n if (tracked.has(injector)) return;\n tracked.add(injector);\n try {\n // why: `self` — chỉ provider của chính scope này; root và scope cha đã được tính ở chỗ khác.\n const configurations = injector.get<ISdApiConfiguration[] | null>(SD_API_CONFIG, null, { self: true, optional: true }) ?? [];\n if (!configurations.length) return;\n const unregisters = configurations.map(configuration => sink.register(configuration));\n injector.get(DestroyRef).onDestroy(() => unregisters.forEach(unregister => unregister()));\n } catch (error) {\n errorHandler.handleError(error);\n }\n };\n\n const collect = (routes: readonly SdRouterRoute[]): void => {\n for (const route of routes) {\n for (const injector of [route._injector, route._loadedInjector]) {\n if (injector instanceof EnvironmentInjector) register(injector);\n }\n if (Array.isArray(route.children)) collect(route.children);\n if (Array.isArray(route._loadedRoutes)) collect(route._loadedRoutes);\n }\n };\n\n // why: registry có thể được tạo giữa chừng — sau navigation đầu, hay trong guard của chính lazy\n // route — nên đọc cả những injector Router đã dựng trước đó.\n collect(router.config);\n const subscription = router.events.subscribe(event => {\n if (event instanceof RoutesRecognized) collect(router.config);\n });\n destroyRef.onDestroy(() => subscription.unsubscribe());\n}\n","import {\n computed,\n DestroyRef,\n EnvironmentProviders,\n ErrorHandler,\n inject,\n Injectable,\n makeEnvironmentProviders,\n provideEnvironmentInitializer,\n signal,\n Type,\n} from '@angular/core';\nimport { Router, ROUTES } from '@angular/router';\nimport { ISdApiConfiguration } from './api.model';\nimport { sdTrackRouteApiConfigurations } from './api-route-configurations';\n\n/**\n * Handler API đăng ký lúc runtime, sống ở root injector.\n *\n * why: `SdHttpInterceptor` được đăng ký qua `HTTP_INTERCEPTORS` ở ROOT, nên\n * `inject(SD_API_CONFIG)` của nó chỉ thấy provider ở root. Library nào khai\n * `{ provide: SD_API_CONFIG, multi: true }` trong NgModule mà shell nạp bằng\n * `loadChildren` thì provider nằm ở child injector của lazy route — interceptor không\n * bao giờ thấy, nên mất `intercept` / `beforeRemote` / `afterRemote` trong im lặng.\n * Registry này là điểm hẹn ở root mà mọi scope (root, eager, lazy) đều ghi vào được.\n *\n * Registry tự nhặt `SD_API_CONFIG` khai trong injector của lazy route (`Route.providers`,\n * NgModule của `loadChildren`) trong chính navigation Router tạo injector đó — trước guard,\n * resolver và component — và gỡ khi injector bị huỷ;\n * library giữ nguyên cách khai multi provider, không cần biết shell mount nó eager hay lazy.\n */\n@Injectable({ providedIn: 'root' })\nexport class SdApiHandlerRegistry {\n // why: bọc từng lần đăng ký vào entry riêng để gỡ đúng lần đó, kể cả khi cùng một object\n // configuration được đăng ký ở hai scope.\n readonly #entries = signal<readonly { readonly configuration: ISdApiConfiguration }[]>([]);\n\n /** Các configuration đang được đăng ký, theo thứ tự đăng ký. */\n readonly configurations = computed(() => this.#entries().map(entry => entry.configuration));\n\n constructor() {\n // why: chỉ theo dõi khi app có Router — inject `Router` ở app không cấu hình route sẽ dựng\n // một Router thừa.\n if (inject(ROUTES, { optional: true })) {\n sdTrackRouteApiConfigurations(inject(Router), this, inject(ErrorHandler), inject(DestroyRef));\n }\n }\n\n /**\n * Đăng ký một configuration. Trả về hàm gỡ đăng ký (gọi nhiều lần vẫn an toàn); dùng\n * {@link provideSdApiConfiguration} để việc gỡ gắn với vòng đời của scope.\n */\n register(configuration: ISdApiConfiguration): () => void {\n const entry = { configuration };\n this.#entries.update(entries => [...entries, entry]);\n let registered = true;\n return () => {\n if (!registered) return;\n registered = false;\n this.#entries.update(entries => entries.filter(candidate => candidate !== entry));\n };\n }\n}\n\n/**\n * Đăng ký handler API cho scope hiện tại: root (`bootstrapApplication` / `AppModule`),\n * NgModule nạp eager, hay `Route.providers` / NgModule của lazy route.\n *\n * Configuration được đăng ký vào {@link SdApiHandlerRegistry} ngay khi injector của scope\n * được tạo — tức trước mọi request scope đó phát ra — và gỡ khi injector bị huỷ. Truyền\n * class thì class được provide trong chính scope đó, nên nó inject được dependency của scope.\n *\n * ```ts\n * // library\n * @NgModule({ providers: [provideSdApiConfiguration(ApiConfiguration)] })\n * export class OrdersModule {}\n *\n * // hoặc route\n * { path: 'orders', providers: [provideSdApiConfiguration(ApiConfiguration)], loadComponent: ... }\n * ```\n */\nexport function provideSdApiConfiguration(configuration: Type<ISdApiConfiguration> | ISdApiConfiguration): EnvironmentProviders {\n const configurationClass = typeof configuration === 'function' ? configuration : undefined;\n return makeEnvironmentProviders([\n ...(configurationClass ? [configurationClass] : []),\n provideEnvironmentInitializer(() => {\n const instance = configurationClass ? inject(configurationClass) : (configuration as ISdApiConfiguration);\n const unregister = inject(SdApiHandlerRegistry).register(instance);\n inject(DestroyRef).onDestroy(unregister);\n }),\n ]);\n}\n","import { isDevMode } from '@angular/core';\nimport { ISdApiConfiguration, SdApiHandler } from './api.model';\n\n/** Handler đã cảnh báo host rỗng — chỉ cảnh báo một lần cho mỗi handler. */\nconst warnedHandlers = new WeakSet<SdApiHandler>();\n\nconst configurationName = (configuration: ISdApiConfiguration | null | undefined): string => {\n const name = configuration?.constructor?.name;\n return name && name !== 'Object' ? name : 'an ISdApiConfiguration value';\n};\n\n/**\n * Host dùng được của handler: bỏ phần tử không phải chuỗi, rỗng hoặc chỉ khoảng trắng.\n *\n * why: `''` prefix-khớp MỌI URL, nên một handler `hosts: ['']` nuốt hết request của các lib\n * khác. Host rỗng lại là trạng thái hợp lệ — shell forward nó cho service chưa deploy ở môi\n * trường đó — nên BỎ QUA chứ không ném (ném sẽ giết bootstrap).\n */\nconst usableHosts = (handler: SdApiHandler, configuration: ISdApiConfiguration | null | undefined): string[] => {\n const hosts = (Array.isArray(handler.hosts) ? handler.hosts : [])\n .filter((host): host is string => typeof host === 'string')\n .map(host => host.trim())\n .filter(Boolean);\n if (!hosts.length && isDevMode() && !warnedHandlers.has(handler)) {\n warnedHandlers.add(handler);\n console.warn(\n `[sd-api] A handler in ${configurationName(configuration)} has no non-blank host and is skipped. ` +\n 'This is expected for a service that is not deployed in this environment; otherwise check its `hosts`.'\n );\n }\n return hosts;\n};\n\n/**\n * Handler chịu trách nhiệm cho `url`: host khớp prefix DÀI NHẤT thắng; hai host dài bằng nhau\n * thì handler đăng ký trước thắng.\n *\n * why: trước đây là `handlers.find(...)` — handler đầu tiên khớp thắng, nên khi nhiều lib nằm\n * sau cùng một gateway (phân biệt bằng context path) thì thứ tự đăng ký quyết định ai nhận\n * request. Giờ `https://gw.example/bpm` thắng `https://gw.example` bất kể thứ tự.\n */\nexport function sdResolveApiHandler(\n url: string,\n configurations: readonly (ISdApiConfiguration | null | undefined)[]\n): SdApiHandler | undefined {\n let best: SdApiHandler | undefined;\n let bestLength = -1;\n for (const configuration of configurations) {\n for (const handler of configuration?.handlers ?? []) {\n if (!handler) continue;\n for (const host of usableHosts(handler, configuration)) {\n if (host.length > bestLength && url.startsWith(host)) {\n best = handler;\n bestLength = host.length;\n }\n }\n }\n }\n return best;\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { HttpClient } from '@angular/common/http';\nimport { inject, Injectable } from '@angular/core';\nimport { SdCacheService } from '@sd-angular/core/services/cache';\nimport { BrowserUtilities, Utilities } from '@sdcorejs/utils/fns';\nimport { FilePickerCancelledError } from '@sdcorejs/utils/errors';\nimport { lastValueFrom, Observable } from 'rxjs';\nimport { catchError, map, shareReplay, timeout } from 'rxjs/operators';\nimport { SdApiHandlerRegistry } from './api-handler-registry';\nimport { sdResolveApiHandler } from './api-handler-selection';\nimport { ISdApiConfiguration, SD_API_CONFIG, SdApiHandler, SdDeleteOption, SdGetOption, SdPostOption, SdPutOption } from './api.model';\n\n// Gom nhóm các Option lại cho gọn\ntype SdHttpOptions = SdGetOption & SdPostOption & SdPutOption & SdDeleteOption;\ntype HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdApiService {\n readonly #defaultTimeout = 60000; // 60s\n readonly #dedupCacheDuration = 1000; // 1s (Deduplication cache duration)\n #httpClient = inject(HttpClient);\n #configurations = inject<ISdApiConfiguration[]>(SD_API_CONFIG, { optional: true }) || [];\n #registry = inject(SdApiHandlerRegistry);\n #cacheService = inject(SdCacheService);\n\n // Thay đổi cấu trúc Cache: Lưu Observable thay vì Subject phức tạp\n // Key: hash string -> Value: { stream$: Observable, expiry: number }\n #inFlightRequests: Map<string, { stream$: Observable<any>; expiry: number }> = new Map();\n\n constructor() {\n // Optional: Cơ chế dọn dẹp cache định kỳ (mỗi 1 phút dọn dẹp các key hết hạn)\n setInterval(() => this.#cleanupCache(), 60000);\n }\n\n get http() {\n return this.#httpClient;\n }\n\n // --- PUBLIC METHODS ---\n\n get = <T = any>(url: string, option?: SdGetOption): Promise<T> => {\n return this.#executeWithLayeredCache<T>(url, 'GET', undefined, option);\n };\n\n post = <T = any>(url: string, body?: any, option?: SdPostOption): Promise<T> => {\n return this.#executeWithLayeredCache<T>(url, 'POST', body, option);\n };\n\n put = <T = any>(url: string, body?: any, option?: SdPutOption): Promise<T> => {\n return this.#executeWithLayeredCache<T>(url, 'PUT', body, option);\n };\n\n delete = <T = any>(url: string, option?: SdDeleteOption): Promise<T> => {\n return this.#executeWithLayeredCache<T>(url, 'DELETE', undefined, option);\n };\n\n // Upload file logic giữ nguyên nhưng refactor nhẹ\n upload = async (url: string, option?: { extensions?: string[]; maxSizeInMb?: number }): Promise<any> => {\n let file: File | File[];\n try {\n file = await BrowserUtilities.upload(option);\n } catch (error) {\n // why: từ `@sdcorejs/utils` 1.2, huỷ/timeout hộp chọn file reject `FilePickerCancelledError` thay vì\n // resolve rỗng. Hợp đồng của `upload()` là huỷ thì trả `undefined` và không gửi request.\n if (error instanceof FilePickerCancelledError) return undefined;\n throw error;\n }\n if (!Array.isArray(file) && file) {\n return this.uploadFile(url, file);\n }\n };\n\n uploadFile = async (url: string, file: File): Promise<any> => {\n if (!file) return null;\n if (file.name.lastIndexOf('.') === -1) throw new Error('Invalid file extension');\n\n const formData = new FormData();\n formData.append('file', file, file.name);\n // Upload thường không cần deduplication cache, set autoCache: false\n return await this.post(url, formData, { autoCache: false });\n };\n\n // --- PRIVATE CORE LOGIC ---\n\n /**\n * Hàm trung gian xử lý Layer Cache (SdCacheService) trước khi gọi API thực tế\n */\n #executeWithLayeredCache = async <T>(url: string, method: HttpMethod, body?: any, option?: SdHttpOptions): Promise<T> => {\n // Layer 1: Persistent Cache (SdCacheService)\n if (option?.cacheOption) {\n const key = this.#generateKey(url, method, body, option);\n const { get, set, has } = this.#cacheService.create(key, option.cacheOption);\n\n if (has()) {\n return get();\n }\n\n const result = await this.#request<T>(url, method, body, option);\n set(result);\n return result;\n }\n\n // Không có cache dài hạn thì gọi trực tiếp logic deduplication\n return this.#request<T>(url, method, body, option);\n };\n\n /**\n * Core Request: Xử lý Deduplication, Timeout, Mapping Response\n */\n #request = <T>(url: string, method: HttpMethod, body: any, option?: SdHttpOptions): Promise<T> => {\n const key = this.#generateKey(url, method, body, option);\n\n // Layer 2: Deduplication Cache (In-Flight Request / Short-term cache)\n const now = Date.now();\n const cachedItem = this.#inFlightRequests.get(key);\n\n // Nếu đã có request đang chạy hoặc mới chạy xong trong vòng 1s -> Trả về stream đó luôn\n if (cachedItem && cachedItem.expiry > now) {\n return lastValueFrom(cachedItem.stream$);\n }\n\n // Setup request mới\n const handler = this.#getHandler(url);\n const apiTimeout = option?.timeout ?? handler?.timeout ?? this.#defaultTimeout;\n\n // Tạo Observable call API\n const request$ = this.#httpClient\n .request(method, url, {\n body,\n headers: option?.headers,\n params: option?.params,\n observe: 'response', // Lấy full response để check status\n responseType: option?.responseType,\n })\n .pipe(\n timeout(apiTimeout),\n map(res => {\n // Normalize Response Logic\n const bodyRes = res.body as any;\n // Logic check response cũ của bạn\n if (bodyRes && typeof bodyRes === 'object' && 'ok' in bodyRes && !bodyRes.ok) {\n throw bodyRes; // Giả sử structure trả về { ok: false, ... } là lỗi\n }\n\n if (handler?.mapResponse) {\n return handler.mapResponse(bodyRes);\n }\n\n return bodyRes;\n }),\n catchError(err => {\n // Xóa cache ngay lập tức nếu lỗi để user có thể retry\n this.#inFlightRequests.delete(key);\n throw err;\n }),\n // QUAN TRỌNG: shareReplay(1) giúp share kết quả cho các subscriber đến sau (trong 1s)\n shareReplay(1)\n );\n\n // Lưu vào Map\n this.#inFlightRequests.set(key, {\n stream$: request$,\n expiry: now + this.#dedupCacheDuration,\n });\n\n // Chuyển đổi sang Promise cho đúng return type của bạn\n return lastValueFrom(request$);\n };\n\n // --- HELPERS ---\n\n #getHandler = (url: string): SdApiHandler | undefined => {\n // why: cùng quy tắc chọn handler với SdHttpInterceptor (prefix dài nhất, gồm cả handler lazy).\n return sdResolveApiHandler(url, [...this.#configurations, ...this.#registry.configurations()]);\n };\n\n #generateKey = (url: string, method: HttpMethod, body: any, option?: SdHttpOptions): string => {\n // FormData không hash được nội dung file, luôn generate key mới\n if (body instanceof FormData || option?.autoCache === false) {\n return Utilities.generateUuid();\n }\n return Utilities.hash({\n url,\n method,\n params: option?.params,\n headers: option?.headers,\n body,\n });\n };\n\n // Dọn dẹp memory leak\n #cleanupCache() {\n const now = Date.now();\n this.#inFlightRequests.forEach((value, key) => {\n if (value.expiry < now) {\n this.#inFlightRequests.delete(key);\n }\n });\n }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';\nimport { inject, Injectable } from '@angular/core';\nimport { from, Observable, throwError } from 'rxjs';\nimport { catchError, map, switchMap } from 'rxjs/operators';\nimport { SdApiHandlerRegistry } from '../api-handler-registry';\nimport { sdResolveApiHandler } from '../api-handler-selection';\nimport { ISdApiConfiguration, SD_API_CONFIG, SdApiHandler } from '../api.model';\n\n@Injectable()\nexport class SdHttpInterceptor implements HttpInterceptor {\n // why: interceptor sống ở root; handler của lazy scope chỉ đến qua registry, nên resolve MỖI request\n // (hợp nhất list tĩnh với registry) thay vì cache một lần lúc construct.\n #configurations = inject<ISdApiConfiguration[]>(SD_API_CONFIG, { optional: true }) || [];\n #registry = inject(SdApiHandlerRegistry);\n\n intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {\n const url = request.url;\n if (!url) {\n throw new Error(`Invalid URL`);\n }\n const handler: SdApiHandler | undefined = sdResolveApiHandler(url, [...this.#configurations, ...this.#registry.configurations()]);\n const intercept = handler?.intercept;\n if (intercept) {\n request = request.clone(intercept(request));\n }\n if (request.body instanceof FormData) {\n request = request.clone({\n headers: request.headers.delete('Content-Type'),\n });\n }\n const beforeRemoteHandler = handler?.beforeRemote;\n const afterRemoteHandler = handler?.afterRemote;\n const beforeRemote = beforeRemoteHandler?.(request);\n if (beforeRemote instanceof Promise) {\n return from(beforeRemote).pipe(\n switchMap(() => next.handle(request)),\n map((event: HttpEvent<any>) => {\n if (event instanceof HttpResponse) {\n afterRemoteHandler?.(event);\n }\n return event;\n }),\n catchError((error: HttpErrorResponse) => {\n afterRemoteHandler?.(error);\n return throwError(() => error);\n })\n );\n }\n return next.handle(request).pipe(\n map((event: HttpEvent<any>) => {\n if (event instanceof HttpResponse) {\n afterRemoteHandler?.(event);\n }\n return event;\n }),\n catchError((error: HttpErrorResponse) => {\n afterRemoteHandler?.(error);\n return throwError(() => error);\n })\n );\n }\n}\n","import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';\nimport { NgModule } from '@angular/core';\nimport { SdHttpInterceptor } from './interceptors/api.interceptor';\n\n@NgModule({\n imports: [],\n exports: [],\n providers: [provideHttpClient(withInterceptorsFromDi()), { provide: HTTP_INTERCEPTORS, useClass: SdHttpInterceptor, multi: true }],\n})\nexport class SdApiModule {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { inject, Injectable } from '@angular/core';
|
|
3
3
|
import { DateUtilities } from '@sd-angular/core/utilities/extensions';
|
|
4
4
|
import { BrowserUtilities } from '@sdcorejs/utils/fns';
|
|
5
|
+
import { FilePickerCancelledError } from '@sdcorejs/utils/errors';
|
|
5
6
|
import { I18nService } from '@sd-angular/core/i18n';
|
|
6
7
|
|
|
7
8
|
const DANGEROUS_SPREADSHEET_FORMULA_PREFIX = /^[=+\-@\t\r\n]/;
|
|
@@ -334,6 +335,10 @@ class SdExcelService {
|
|
|
334
335
|
return await this.parse(file);
|
|
335
336
|
}
|
|
336
337
|
catch (error) {
|
|
338
|
+
// why: từ `@sdcorejs/utils` 1.2, huỷ/timeout hộp chọn file reject `FilePickerCancelledError` thay vì
|
|
339
|
+
// resolve `null`. Huỷ không phải lỗi — giữ nguyên kết quả rỗng như 1.1.x.
|
|
340
|
+
if (error instanceof FilePickerCancelledError)
|
|
341
|
+
return { items: [], file: null };
|
|
337
342
|
console.error('Upload error:', error);
|
|
338
343
|
throw error; // Hoặc return { items: [], file: null } tùy logic nghiệp vụ
|
|
339
344
|
}
|