# Changelog

All notable changes to `@aseansc-admin/sea-http` are documented here.

---

## [0.11.1] — 2026-08-14

### Fixed

- **`AscApiError.message`** — giờ chứa nội dung lỗi thật từ BE thay vì chuỗi debug `[authenType] API responded with status: X`. Tự dò các field message thường gặp trong `body.data` theo thứ tự `messageVn` → `message` → `msg` → `errorMessage` → `desc` → `description` → `messageEn` (ưu tiên tiếng Việt theo locale mặc định của app), dùng field đầu tiên khớp. Không tìm thấy field nào → giữ nguyên message debug cũ.
- **`AscApiError`/`AscApiResponse` — hỗ trợ BE trả lỗi ở field `error` riêng (dạng bos-api)**: một số BE không trả `body.data` khi lỗi mà để nội dung lỗi ở field `error` ngang hàng `body`/`header` (`{ code, desc, messageVn, messageEn }`). `AscApiResponse` giờ khai thêm `error?: AscApiResponseError`, `body.data` thành optional; `_send()` fallback sang `response.error` khi `body.data` rỗng để build `AscApiError`.

---

## [0.11.0] — 2026-08-07

### Added

- **`AscApiService.postFormDataBlob()`** — kết hợp `postFormData()` + `postBlob()`: dựng request dạng multipart `FormData` (envelope header + `userID` inject trực tiếp qua `ASC_HTTP_USER_ID_FN`, giống hệt `postFormData()`) nhưng nhận response dạng binary (`responseType: 'blob', observe: 'response'`, không unwrap, không throw `AscApiError`, giống hệt `postBlob()`). Dùng cho endpoint vừa upload file vừa trả response không đồng nhất kiểu (rỗng / Blob nhị phân / JSON envelope lỗi) — trước đây không method nào đáp ứng được case này (`postFormData()` luôn parse JSON và tự throw; `postBlob()` không hỗ trợ upload file). Dùng lại `AscPostFormDataOptions` có sẵn, không có type mới. Không đổi behavior của `postFormData()`/`postBlob()` hiện có.

---

## [0.10.2] — 2026-08-06

### Fixed

- **`ascErrorInterceptor` — 401 Unauthorized**: giờ thực sự xoá session (`AscLocalStorageAuthService`, `AscCookieAuthService`, `AscSessionStorageAuthService`) trước khi redirect `/login`. Trước đó comment ghi "xoá session" nhưng code chỉ redirect, không xoá gì — token cũ vẫn có thể bị `ascAuthInterceptor` đọc lại ở request sau. Case 403 không đổi (token vẫn hợp lệ, chỉ thiếu quyền).

---

## [0.10.1] — 2026-07-20

### Fixed

- **`ascErrorInterceptor` — 403 Forbidden**: giờ redirect về `/login` giống 401 thay vì rơi vào `errorHandler` mặc định.

---

## [0.10.0] — 2026-07-17

### Added

- **`AscApiService.postBlob()`** — gọi API envelope kèm response dạng binary (`Blob`), dùng cho download file. Tự build envelope `{ header, body }` giống hệt `post()` (đi qua `ascAuthInterceptor` bình thường vì body vẫn là JSON) nhưng request với `responseType: 'blob', observe: 'response'` và trả nguyên `HttpResponse<Blob>` — không unwrap `body.data`, không throw `AscApiError` vì response không phải JSON envelope. Consuming app không cần tự ghi đè envelope để gọi API download nữa. Export mới: `AscPostBlobOptions`.

---

## [0.9.0] — 2026-07-14

### Added

- **`AscApiService.postFormData()` — hỗ trợ `file: null` để xoá file trên BE**: `AscFormDataFile.file` nới kiểu thành `File | null`. Khi `null`, gửi field multipart rỗng (`''`) thay vì `Blob` — theo quy ước BE, field rỗng nghĩa là xoá file đó khỏi record hiện có. Không đưa field vào `files` nếu muốn giữ nguyên file cũ.

---

## [0.8.0] — 2026-07-14

### ⚠ BREAKING CHANGE

- **`AscApiService.postFormData()` — đổi tên field multipart chứa envelope JSON từ `'data'` sang `'request'`**: tránh trùng khái niệm với `files[].field` và với `AscPostFormDataOptions.data` (payload nghiệp vụ). BE nào đã tích hợp theo field `'data'` (từ bản `0.7.0`) cần đổi sang đọc field `'request'`.

---

## [0.7.0] — 2026-07-14

### Added

- **`AscApiService.postFormData()`** — gọi API envelope kèm file upload (`multipart/form-data`). Dùng chung `_send()` với `post()` nên unwrap `body.data`/throw `AscApiError` giống hệt. Envelope `{ header, body: { authenType, data } }` đóng gói thành 1 field multipart `'data'` (`JSON.stringify`), mỗi file trong `files[]` append riêng theo `field`. Không phụ thuộc `ascAuthInterceptor` để inject `userID` (interceptor chỉ mutate được JSON body, không "nhìn" được vào `FormData`) — tự lấy `userID` thật qua `ASC_HTTP_USER_ID_FN` ngay lúc build header. Export mới: `AscPostFormDataOptions`, `AscFormDataFile`.

---

## [0.6.0] — 2026-07-07

### Added

- **`AscSessionStorageAuthService`** — auth storage backed by `sessionStorage` (cleared on tab/browser close), implementing `AscAuthStorageService` alongside the existing localStorage/cookie services.

### Changed

- **`ascAuthInterceptor`**, **`AscSseService`**, **`AscWebSocketService`** — token resolution now falls back through `localStorage → cookie → sessionStorage` (previously localStorage/cookie only), so `AscSessionStorageAuthService` works consistently across HTTP, SSE, and WebSocket.

### Breaking

- **`LoginResponseData`/`LoginPermission` removed.** Login response shape is no longer hardcoded — `AscApiService.login<TResponse>()` and `AscAuthStorageService.getUser<T>()` are now generic over a new minimal base type `AscAuthUser` (`{ token: string; [key: string]: unknown }`). Define your own interface extending `AscAuthUser` and pass it as the type argument.
- **`AscLoginOptions` flattened** — no longer wraps the payload in a `data` property. `login({ data: {...}, endpoint })` → `login({ username, password, authenType, type, endpoint })`.

---

## [0.5.0] — 2026-07-02

### Added

- **`AscApiService.request()`** — gọi endpoint chuẩn HTTP (GET/POST/PUT/PATCH/DELETE), không bọc envelope `{ header, body }`. Dùng cho BE mới trả thẳng REST, chạy song song với `post()`/`login()` (envelope), đi qua **cùng interceptor chain**: Bearer token vẫn tự inject, loading indicator vẫn tự đếm, lỗi vẫn tự xử lý qua `ASC_HTTP_ERROR_HANDLER`. Không throw `AscApiError` — lỗi luôn là `HttpErrorResponse` chuẩn.
- **`AscRestOptions`**, **`AscHttpMethod`** — export types mới cho `request()`.

### Changed

- **`ascErrorInterceptor`** ⚠️ **breaking-ish**: trước đây chỉ gọi `ASC_HTTP_ERROR_HANDLER` cho status `403`/`0`/`5xx`, các status khác (`400`, `404`...) bị bỏ qua âm thầm. Giờ **mọi status khác `401`** đều gọi error handler — đảm bảo `request()` (REST thuần) có cùng error flow như envelope (vốn luôn gọi handler cho mọi business error bất kể status). `401` vẫn chỉ redirect `/login`, không gọi handler. App nào đang phụ thuộc việc `404`/`400` bị im lặng bỏ qua cần review lại `ASC_HTTP_ERROR_HANDLER`.

---

## [0.4.2] — 2026-06-24

### Added

- **`SKIP_LOADING`** — `HttpContextToken<boolean>` (default `false`) cho phép tắt loading indicator cho từng request riêng lẻ. Export từ barrel: `import { SKIP_LOADING } from '@aseansc-admin/sea-http'`.
- **`AscPostOptions.skipLoading`** — input `skipLoading?: boolean` trong `AscApiService.post()`. Khi `true`, set `SKIP_LOADING = true` trên `HttpContext` của request → `ascLoadingInterceptor` bỏ qua request đó, không tăng bộ đếm loading. Hữu ích cho background polling, auto-refresh, silent call.

### Changed

- **`ascLoadingInterceptor`** — kiểm tra `req.context.get(SKIP_LOADING)` trước khi tăng bộ đếm. Default vẫn là bật loading cho mọi request.
- README — cập nhật section "Global loading indicator" với hướng dẫn dùng `AscLoadingBarComponent` từ `@aseansc-admin/ui`, thêm section "Tắt loading cho request cụ thể" với ví dụ `skipLoading` và `SKIP_LOADING`.

---

## [0.4.1] — 2026-06-23

### Changed
- **`msw` peer dependency** — pin từ `>=2.0.0` xuống `^2.14.6` (version đã test). README install command cập nhật: `npm install msw@2.14.6 --save-dev`.

---

## [0.4.0] — 2026-06-23

### Added
- **MSW testing helpers** — secondary entry point `@aseansc-admin/sea-http/testing` (requires `msw >= 2` as optional peer dependency):
  - `createAscHandler(endpoint, mocks)` — MSW POST handler tự route theo `authenType`/`command` trong envelope body
  - `ascOperation(key, resolver, options?)` — khai báo mock cho một operation
  - `ascOk(data)` / `ascFail(status, data?)` — tạo envelope response thủ công
  - Option `errorStatus` — giả lập lỗi nghiệp vụ (sea-http ném `AscApiError`)
  - Option `httpStatus` — giả lập HTTP error 401/403/500/... (bypass envelope)
  - Option `networkError` — giả lập mất kết nối mạng (`HttpErrorResponse.status = 0`)
  - Option `delayMs` — giả lập độ trễ mạng để test loading state
- Export types `AscMockResolver`, `AscMockOperationOptions`, `AscMock`

---

## [0.3.1] — 2026-06-04

### Changed
- `ascErrorInterceptor`: retry logic cho 500 chỉ áp dụng đúng cho `status === 500`, các lỗi khác không bị retry.

---

## [0.3.0] — 2026-06-01

### Added
- **SSE** — `AscSseService`: kết nối Server-Sent Events, tự append JWT token vào query param, hỗ trợ named events qua `.on()`, auto-reconnect.
- **WebSocket** — `AscWebSocketService` + `AscWsConnection`: envelope-based WebSocket, filter message theo `authenType` qua `.on<T>()`, auto-reconnect với linear back-off.
- `ascErrorInterceptor`: retry tự động tối đa 3 lần (delay 1s/2s/3s) khi server trả `500`.
- Export types `AscSseOptions`, `AscSseEvent`, `AscWsOptions`, `AscWsMessage`, `AscWsHeader`, `AscWsStatus`.

---

## [0.2.0] — 2026-05-21

### Breaking Changes
- **`AscApiService.post()`** — đổi từ positional params sang options object:
  ```ts
  // trước
  this.api.post<T>('authenType', data, 'endpoint')
  // sau
  this.api.post<T>({ authenType: 'authenType', data, endpoint: 'endpoint' })
  ```
- **`AscApiService.login()`** — đổi từ positional params sang options object:
  ```ts
  // trước
  this.api.login(data, 'endpoint')
  // sau
  this.api.login({ data, endpoint: 'endpoint' })
  ```

### Added
- Export types `AscPostOptions<TBody>` và `AscLoginOptions` để consumer có thể type options khi cần.

---

## [0.1.0] — Initial release
