@goat-bravos/shared-lib-client 1.0.4 → 1.0.5
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/dist/enums/error-code.enum.d.ts +2 -1
- package/dist/enums/error-code.enum.d.ts.map +1 -1
- package/dist/enums/error-code.enum.js +1 -0
- package/dist/interceptors/auth.interceptor.d.ts +7 -8
- package/dist/interceptors/auth.interceptor.d.ts.map +1 -1
- package/dist/interceptors/auth.interceptor.js +40 -24
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ export declare enum ErrorCode {
|
|
|
7
7
|
VALIDATION_ERROR = "validation.error",
|
|
8
8
|
CONFLICT = "conflict",
|
|
9
9
|
SERVICE_UNAVAILABLE = "service.unavailable",
|
|
10
|
-
REFRESH_TOKEN_INVALID = "auth.exception.refresh_token_invalid"
|
|
10
|
+
REFRESH_TOKEN_INVALID = "auth.exception.refresh_token_invalid",
|
|
11
|
+
ACCESS_TOKEN_EXPIRED = "auth.exception.access_token_expired"
|
|
11
12
|
}
|
|
12
13
|
//# sourceMappingURL=error-code.enum.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-code.enum.d.ts","sourceRoot":"","sources":["../../src/enums/error-code.enum.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS;IACnB,kBAAkB,uBAAuB;IACzC,YAAY,iBAAiB;IAC7B,SAAS,cAAc;IACvB,WAAW,gBAAgB;IAC3B,qBAAqB,0BAA0B;IAC/C,gBAAgB,qBAAqB;IACrC,QAAQ,aAAa;IACrB,mBAAmB,wBAAwB;IAC3C,qBAAqB,yCAAyC;
|
|
1
|
+
{"version":3,"file":"error-code.enum.d.ts","sourceRoot":"","sources":["../../src/enums/error-code.enum.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS;IACnB,kBAAkB,uBAAuB;IACzC,YAAY,iBAAiB;IAC7B,SAAS,cAAc;IACvB,WAAW,gBAAgB;IAC3B,qBAAqB,0BAA0B;IAC/C,gBAAgB,qBAAqB;IACrC,QAAQ,aAAa;IACrB,mBAAmB,wBAAwB;IAC3C,qBAAqB,yCAAyC;IAC9D,oBAAoB,wCAAwC;CAC7D"}
|
|
@@ -9,4 +9,5 @@ export var ErrorCode;
|
|
|
9
9
|
ErrorCode["CONFLICT"] = "conflict";
|
|
10
10
|
ErrorCode["SERVICE_UNAVAILABLE"] = "service.unavailable";
|
|
11
11
|
ErrorCode["REFRESH_TOKEN_INVALID"] = "auth.exception.refresh_token_invalid";
|
|
12
|
+
ErrorCode["ACCESS_TOKEN_EXPIRED"] = "auth.exception.access_token_expired";
|
|
12
13
|
})(ErrorCode || (ErrorCode = {}));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HttpInterceptorFn } from "@angular/common/http";
|
|
2
|
+
export declare const CANCEL_REFRESH_TOKEN = "CANCEL_REFRESH_TOKEN";
|
|
2
3
|
/**
|
|
3
4
|
* Cấu hình cho auth interceptor.
|
|
4
5
|
*/
|
|
@@ -31,11 +32,6 @@ export interface AuthInterceptorConfig {
|
|
|
31
32
|
export declare function configureAuthInterceptor(config: AuthInterceptorConfig): void;
|
|
32
33
|
/**
|
|
33
34
|
* Thêm các path vào danh sách loại trừ mà không ghi đè danh sách mặc định.
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* import { addExcludedPaths } from 'shared-lib-client';
|
|
37
|
-
*
|
|
38
|
-
* addExcludedPaths(['/public/products', '/public/categories']);
|
|
39
35
|
*/
|
|
40
36
|
export declare function addExcludedPaths(paths: string[]): void;
|
|
41
37
|
/**
|
|
@@ -47,9 +43,6 @@ export declare function getExcludedPaths(): string[];
|
|
|
47
43
|
* - Tự gắn header Authorization nếu có access token
|
|
48
44
|
* - Khi gặp lỗi 401 thì phát event để Shell/Auth xử lý refresh token
|
|
49
45
|
* - Các request đang chờ sẽ được đồng bộ qua `notifyTokenRefreshed`
|
|
50
|
-
*
|
|
51
|
-
* Để cấu hình danh sách path không gắn Bearer, gọi `configureAuthInterceptor()`
|
|
52
|
-
* hoặc `addExcludedPaths()` trước khi ứng dụng bắt đầu gửi request.
|
|
53
46
|
*/
|
|
54
47
|
export declare const authInterceptor: HttpInterceptorFn;
|
|
55
48
|
/**
|
|
@@ -57,4 +50,10 @@ export declare const authInterceptor: HttpInterceptorFn;
|
|
|
57
50
|
* để đánh thức các request đang chờ.
|
|
58
51
|
*/
|
|
59
52
|
export declare function notifyTokenRefreshed(newToken: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* Được Shell/Auth MFE gọi khi refresh token thất bại
|
|
55
|
+
* để giải phóng toàn bộ các request đang nằm chờ trong hàng đợi
|
|
56
|
+
* tránh rò rỉ bộ nhớ (memory leak).
|
|
57
|
+
*/
|
|
58
|
+
export declare function cancelTokenRefresh(): void;
|
|
60
59
|
//# sourceMappingURL=auth.interceptor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.interceptor.d.ts","sourceRoot":"","sources":["../../src/interceptors/auth.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,iBAAiB,EAClB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.interceptor.d.ts","sourceRoot":"","sources":["../../src/interceptors/auth.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,iBAAiB,EAClB,MAAM,sBAAsB,CAAC;AAa9B,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAM3D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAWD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAI5E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAGtD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAE3C;AAaD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,iBAuC7B,CAAC;AA8DF;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAG3D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC"}
|
|
@@ -2,22 +2,21 @@ import { throwError, BehaviorSubject } from "rxjs";
|
|
|
2
2
|
import { catchError, filter, take, switchMap, finalize } from "rxjs/operators";
|
|
3
3
|
import { StorageUtil } from "../utils/storage.util";
|
|
4
4
|
import { ErrorCode } from "../enums/error-code.enum";
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// CONFIGURATION & STATE
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Hằng số nội bộ dùng để nhận diện tín hiệu hủy làm mới token.
|
|
9
|
+
export const CANCEL_REFRESH_TOKEN = "CANCEL_REFRESH_TOKEN";
|
|
5
10
|
// Cờ và subject dùng để đồng bộ các request khi access token hết hạn.
|
|
6
11
|
let isRefreshing = false;
|
|
7
12
|
const refreshTokenSubject = new BehaviorSubject(null);
|
|
8
|
-
/**
|
|
9
|
-
* Danh sách path mặc định sẽ không gắn Bearer token.
|
|
10
|
-
*/
|
|
11
|
-
const DEFAULT_EXCLUDED_PATHS = [
|
|
12
|
-
"/login",
|
|
13
|
-
"/hrm/users/register",
|
|
14
|
-
"/password-reset",
|
|
15
|
-
"/refresh",
|
|
16
|
-
];
|
|
17
13
|
/**
|
|
18
14
|
* Danh sách path đang được sử dụng (có thể được cấu hình từ bên ngoài).
|
|
19
15
|
*/
|
|
20
|
-
let activeExcludedPaths = [
|
|
16
|
+
let activeExcludedPaths = [];
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// PUBLIC API (CONFIGURATION & UTILS)
|
|
19
|
+
// ============================================================================
|
|
21
20
|
/**
|
|
22
21
|
* Cấu hình auth interceptor từ bên ngoài (thường gọi ở Shell App).
|
|
23
22
|
*
|
|
@@ -42,11 +41,6 @@ export function configureAuthInterceptor(config) {
|
|
|
42
41
|
}
|
|
43
42
|
/**
|
|
44
43
|
* Thêm các path vào danh sách loại trừ mà không ghi đè danh sách mặc định.
|
|
45
|
-
*
|
|
46
|
-
* @example
|
|
47
|
-
* import { addExcludedPaths } from 'shared-lib-client';
|
|
48
|
-
*
|
|
49
|
-
* addExcludedPaths(['/public/products', '/public/categories']);
|
|
50
44
|
*/
|
|
51
45
|
export function addExcludedPaths(paths) {
|
|
52
46
|
const newPaths = paths.filter((p) => !activeExcludedPaths.includes(p));
|
|
@@ -64,14 +58,14 @@ export function getExcludedPaths() {
|
|
|
64
58
|
function isExcluded(url) {
|
|
65
59
|
return activeExcludedPaths.some((pattern) => url.includes(pattern));
|
|
66
60
|
}
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// INTERCEPTOR CORE
|
|
63
|
+
// ============================================================================
|
|
67
64
|
/**
|
|
68
65
|
* Auth interceptor dùng chung cho các micro-frontend.
|
|
69
66
|
* - Tự gắn header Authorization nếu có access token
|
|
70
67
|
* - Khi gặp lỗi 401 thì phát event để Shell/Auth xử lý refresh token
|
|
71
68
|
* - Các request đang chờ sẽ được đồng bộ qua `notifyTokenRefreshed`
|
|
72
|
-
*
|
|
73
|
-
* Để cấu hình danh sách path không gắn Bearer, gọi `configureAuthInterceptor()`
|
|
74
|
-
* hoặc `addExcludedPaths()` trước khi ứng dụng bắt đầu gửi request.
|
|
75
69
|
*/
|
|
76
70
|
export const authInterceptor = (req, next) => {
|
|
77
71
|
// 1. Lấy access token từ localStorage
|
|
@@ -90,19 +84,26 @@ export const authInterceptor = (req, next) => {
|
|
|
90
84
|
return next(authReq).pipe(catchError((error) => {
|
|
91
85
|
const responseBody = error.error;
|
|
92
86
|
const errorCode = responseBody?.status?.code;
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
// Nếu Refresh Token đã hỏng hoặc không hợp lệ -> Báo thẳng cho Shell App/Auth để Đăng xuất ngay
|
|
88
|
+
if (errorCode === ErrorCode.REFRESH_TOKEN_INVALID) {
|
|
89
|
+
window.dispatchEvent(new CustomEvent("FORCE_LOGOUT"));
|
|
90
|
+
return throwError(() => error);
|
|
91
|
+
}
|
|
92
|
+
// Token hết hạn (hoặc mã 401 chung)
|
|
93
|
+
const isAccessTokenExpired = error.status === 401 || errorCode === ErrorCode.ACCESS_TOKEN_EXPIRED;
|
|
94
|
+
// Chỉ xử lý refresh cho các request nghiệp vụ, không áp dụng cho request login/hệ thống đã cấu hình loại trừ.
|
|
95
|
+
if (isAccessTokenExpired && !isExcludedRequest) {
|
|
97
96
|
return handle401Error(authReq, next);
|
|
98
97
|
}
|
|
99
98
|
return throwError(() => error);
|
|
100
99
|
}));
|
|
101
100
|
};
|
|
101
|
+
// ============================================================================
|
|
102
|
+
// TOKEN REFRESH LOGIC
|
|
103
|
+
// ============================================================================
|
|
102
104
|
/**
|
|
103
105
|
* Khi access token hết hạn, thư viện không tự gọi API refresh.
|
|
104
|
-
* Thay vào đó nó phát event `AUTH_TOKEN_EXPIRED` để Shell/Auth MFE chủ động refresh
|
|
105
|
-
* sau đó đợi token mới được đẩy ngược lại qua `notifyTokenRefreshed`.
|
|
106
|
+
* Thay vào đó nó phát event `AUTH_TOKEN_EXPIRED` để Shell/Auth MFE chủ động refresh.
|
|
106
107
|
*/
|
|
107
108
|
function handle401Error(request, next) {
|
|
108
109
|
if (!isRefreshing) {
|
|
@@ -112,6 +113,9 @@ function handle401Error(request, next) {
|
|
|
112
113
|
window.dispatchEvent(new CustomEvent("AUTH_TOKEN_EXPIRED"));
|
|
113
114
|
// Đợi access token mới, sau đó phát lại request cũ.
|
|
114
115
|
return refreshTokenSubject.pipe(filter((token) => token !== null), take(1), switchMap((token) => {
|
|
116
|
+
if (token === CANCEL_REFRESH_TOKEN) {
|
|
117
|
+
return throwError(() => new Error("Refresh process was cancelled. Request dropped."));
|
|
118
|
+
}
|
|
115
119
|
return next(request.clone({
|
|
116
120
|
setHeaders: { Authorization: `Bearer ${token}` },
|
|
117
121
|
}));
|
|
@@ -122,6 +126,9 @@ function handle401Error(request, next) {
|
|
|
122
126
|
else {
|
|
123
127
|
// Các request đến sau sẽ chờ cùng một đợt refresh đang diễn ra.
|
|
124
128
|
return refreshTokenSubject.pipe(filter((token) => token !== null), take(1), switchMap((token) => {
|
|
129
|
+
if (token === CANCEL_REFRESH_TOKEN) {
|
|
130
|
+
return throwError(() => new Error("Refresh process was cancelled. Request dropped."));
|
|
131
|
+
}
|
|
125
132
|
return next(request.clone({
|
|
126
133
|
setHeaders: { Authorization: `Bearer ${token}` },
|
|
127
134
|
}));
|
|
@@ -136,3 +143,12 @@ export function notifyTokenRefreshed(newToken) {
|
|
|
136
143
|
refreshTokenSubject.next(newToken);
|
|
137
144
|
isRefreshing = false;
|
|
138
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Được Shell/Auth MFE gọi khi refresh token thất bại
|
|
148
|
+
* để giải phóng toàn bộ các request đang nằm chờ trong hàng đợi
|
|
149
|
+
* tránh rò rỉ bộ nhớ (memory leak).
|
|
150
|
+
*/
|
|
151
|
+
export function cancelTokenRefresh() {
|
|
152
|
+
refreshTokenSubject.next(CANCEL_REFRESH_TOKEN);
|
|
153
|
+
isRefreshing = false;
|
|
154
|
+
}
|