@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.
@@ -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;CAC/D"}
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;AAY9B;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAiBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAI5E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAGtD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAE3C;AASD;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAE,iBAiC7B,CAAC;AAiDF;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAG3D"}
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 = [...DEFAULT_EXCLUDED_PATHS];
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
- // 401 trường hợp chuẩn; ngoài ra backend thể trả REFRESH_TOKEN_INVALID.
94
- const isAuthError = error.status === 401 || errorCode === ErrorCode.REFRESH_TOKEN_INVALID;
95
- // Chỉ xử lý refresh cho các request nghiệp vụ, không áp dụng cho login/refresh.
96
- if (isAuthError && !isExcludedRequest) {
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goat-bravos/shared-lib-client",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "type": "module",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "21.0.1",