@solar-taro/http 1.0.0 → 1.0.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 1.0.1 (2025-01-22)
2
+
3
+ This was a version bump only for http to align it with other projects, there were no code changes.
@@ -0,0 +1,9 @@
1
+ import { HttpContextToken, HttpInterceptorFn } from '@ngify/http';
2
+ /** HTTP 令牌:允许拦截器处理 `Result#code` 不为 `0` 的错误 */
3
+ export declare const CATCH_RESULT_ERROR_TOKEN: HttpContextToken<boolean>;
4
+ /**
5
+ * 负责业务接口的拦截
6
+ * - 添加 `Api` 请求头
7
+ * - 捕获响应异常并提示
8
+ */
9
+ export declare function useApiInterceptor(): HttpInterceptorFn;
@@ -0,0 +1,35 @@
1
+ import { HttpContextToken, HttpResponse } from '@ngify/http';
2
+ import { WX_REQUSET_TOKEN } from '@ngify/http-wx';
3
+ import { overlay } from '@solar-taro/core';
4
+ import { tap } from 'rxjs';
5
+ /** HTTP 令牌:允许拦截器处理 `Result#code` 不为 `0` 的错误 */
6
+ export const CATCH_RESULT_ERROR_TOKEN = new HttpContextToken(() => true);
7
+ /**
8
+ * 负责业务接口的拦截
9
+ * - 添加 `Api` 请求头
10
+ * - 捕获响应异常并提示
11
+ */
12
+ export function useApiInterceptor() {
13
+ return (request, next) => {
14
+ request = request.clone({
15
+ headers: request.headers.set('Api', 'v1')
16
+ });
17
+ request.context.set(WX_REQUSET_TOKEN, {
18
+ ...request.context.get(WX_REQUSET_TOKEN),
19
+ enableHttp2: true,
20
+ // forceCellularNetwork: true
21
+ });
22
+ return next(request).pipe(tap((event) => {
23
+ if (event instanceof HttpResponse) {
24
+ const { body } = event;
25
+ if ((body === null || body === void 0 ? void 0 : body.code) !== 0 && request.context.get(CATCH_RESULT_ERROR_TOKEN)) {
26
+ // 延迟一下,确保不会被后面的 dismiss loading 给关掉 tips
27
+ setTimeout(() => {
28
+ overlay.tips('操作失败,原因:' + ((body === null || body === void 0 ? void 0 : body.msg) || '未知错误'));
29
+ });
30
+ }
31
+ }
32
+ }));
33
+ };
34
+ }
35
+ //# sourceMappingURL=api.interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.interceptor.js","sourceRoot":"","sources":["../../../packages/http/src/api.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAA4D,YAAY,EAAE,MAAM,aAAa,CAAC;AACvH,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAc,GAAG,EAAE,MAAM,MAAM,CAAC;AAEvC,+CAA+C;AAC/C,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,gBAAgB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAEzE;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,CAAC,OAA6B,EAAE,IAAmB,EAAkC,EAAE;QAC5F,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC;YACtB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC;SAC1C,CAAC,CAAC;QAEH,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE;YACpC,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;YACxC,WAAW,EAAE,IAAI;YACjB,6BAA6B;SAC9B,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CACvB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACZ,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;gBAClC,MAAM,EAAE,IAAI,EAAE,GAAG,KAA6B,CAAC;gBAC/C,IAAI,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,IAAI,MAAK,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,EAAE,CAAC;oBACtE,yCAAyC;oBACzC,UAAU,CAAC,GAAG,EAAE;wBACd,OAAO,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,GAAG,KAAI,MAAM,CAAC,CAAC,CAAC;oBACnD,CAAC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CACH,CAAC;IACJ,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { HttpContextToken, HttpInterceptorFn } from '@ngify/http';
2
+ export declare const AUTO_HOST_TOEKN: HttpContextToken<boolean>;
3
+ /**
4
+ * 自动为 / 开头的 URL 添加 host
5
+ * 可以使用 {@link AUTO_HOST_TOEKN} 来跳过该拦截器
6
+ * @param host
7
+ * @returns
8
+ */
9
+ export declare function useAutoHostInterceptor(host: string): HttpInterceptorFn;
@@ -0,0 +1,20 @@
1
+ import { HttpContextToken } from '@ngify/http';
2
+ export const AUTO_HOST_TOEKN = new HttpContextToken(() => true);
3
+ /**
4
+ * 自动为 / 开头的 URL 添加 host
5
+ * 可以使用 {@link AUTO_HOST_TOEKN} 来跳过该拦截器
6
+ * @param host
7
+ * @returns
8
+ */
9
+ export function useAutoHostInterceptor(host) {
10
+ return (request, next) => {
11
+ if (request.context.get(AUTO_HOST_TOEKN) &&
12
+ request.url.startsWith('/')) {
13
+ return next(request.clone({
14
+ url: host + request.url
15
+ }));
16
+ }
17
+ return next(request);
18
+ };
19
+ }
20
+ //# sourceMappingURL=auto-host.interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-host.interceptor.js","sourceRoot":"","sources":["../../../packages/http/src/auto-host.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAqB,MAAM,aAAa,CAAC;AAElE,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,gBAAgB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE;QACvB,IACE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAC3B,CAAC;YACD,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;gBACxB,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG;aACxB,CAAC,CAAC,CAAC;QACN,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { HttpContextToken, HttpRequest, HttpResponse } from '@ngify/http';
2
+ /**
3
+ * 缓存令牌,值可以为:
4
+ * - 布尔值,为 true 时,表示在一次会话中缓存永不过期,默认值为 false
5
+ * - 缓存时间,单位毫秒
6
+ */
7
+ export declare const CACHEABLE_TOKEN: HttpContextToken<number | boolean>;
8
+ /** 缓存条目 */
9
+ export interface CacheEntry {
10
+ url: string;
11
+ response: HttpResponse<unknown>;
12
+ expire: number;
13
+ }
14
+ export declare class CacheManager {
15
+ private readonly cacheMap;
16
+ /**
17
+ * 获取缓存
18
+ * @param request
19
+ */
20
+ get(request: HttpRequest<unknown>): HttpResponse<unknown> | null;
21
+ /**
22
+ * 缓存
23
+ * @param request
24
+ * @param response
25
+ */
26
+ put(request: HttpRequest<unknown>, response: HttpResponse<unknown>): void;
27
+ /**
28
+ * 清扫过期缓存
29
+ */
30
+ clear(): void;
31
+ /**
32
+ * 通过标记(字符串、正则)模糊查询以撤销缓存
33
+ * @param mark
34
+ * @param once 只撤销一个
35
+ */
36
+ revoke(mark: string | RegExp, once?: boolean): void;
37
+ /**
38
+ * 撤销所有缓存
39
+ */
40
+ revokeAll(): void;
41
+ }
42
+ export declare const cacheManager: CacheManager;
@@ -0,0 +1,72 @@
1
+ import { HttpContextToken } from '@ngify/http';
2
+ /**
3
+ * 缓存令牌,值可以为:
4
+ * - 布尔值,为 true 时,表示在一次会话中缓存永不过期,默认值为 false
5
+ * - 缓存时间,单位毫秒
6
+ */
7
+ export const CACHEABLE_TOKEN = new HttpContextToken(() => false);
8
+ export class CacheManager {
9
+ constructor() {
10
+ this.cacheMap = new Map();
11
+ }
12
+ /**
13
+ * 获取缓存
14
+ * @param request
15
+ */
16
+ get(request) {
17
+ // 判断当前请求是否已被缓存,若未缓存则返回null
18
+ const entry = this.cacheMap.get(request.urlWithParams);
19
+ if (!entry) {
20
+ return null;
21
+ }
22
+ // 若缓存命中,则判断缓存是否过期,若已过期则返回null。否则返回请求对应的响应对象
23
+ return Date.now() > entry.expire ? null : entry.response;
24
+ }
25
+ /**
26
+ * 缓存
27
+ * @param request
28
+ * @param response
29
+ */
30
+ put(request, response) {
31
+ const token = request.context.get(CACHEABLE_TOKEN);
32
+ const entry = {
33
+ url: request.urlWithParams,
34
+ response: response,
35
+ expire: Date.now() + (token === true ? Infinity : token)
36
+ };
37
+ this.cacheMap.set(request.urlWithParams, entry);
38
+ }
39
+ /**
40
+ * 清扫过期缓存
41
+ */
42
+ clear() {
43
+ for (const [key, entry] of this.cacheMap) {
44
+ if (Date.now() > entry.expire) {
45
+ this.cacheMap.delete(key);
46
+ }
47
+ }
48
+ }
49
+ /**
50
+ * 通过标记(字符串、正则)模糊查询以撤销缓存
51
+ * @param mark
52
+ * @param once 只撤销一个
53
+ */
54
+ revoke(mark, once) {
55
+ for (const key of this.cacheMap.keys()) {
56
+ if (mark instanceof RegExp ? mark.test(key) : key.includes(mark)) {
57
+ this.cacheMap.delete(key);
58
+ if (once) {
59
+ return;
60
+ }
61
+ }
62
+ }
63
+ }
64
+ /**
65
+ * 撤销所有缓存
66
+ */
67
+ revokeAll() {
68
+ this.cacheMap.clear();
69
+ }
70
+ }
71
+ export const cacheManager = new CacheManager();
72
+ //# sourceMappingURL=cache-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-manager.js","sourceRoot":"","sources":["../../../../packages/http/src/caching/cache-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAA6B,MAAM,aAAa,CAAC;AAE1E;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,gBAAgB,CAAmB,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;AASnF,MAAM,OAAO,YAAY;IAAzB;QACmB,aAAQ,GAAG,IAAI,GAAG,EAAsB,CAAC;IA+D5D,CAAC;IA7DC;;;OAGG;IACH,GAAG,CAAC,OAA6B;QAC/B,2BAA2B;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QAEvD,IAAI,CAAC,KAAK,EAAE,CAAC;YAAC,OAAO,IAAI,CAAC;QAAC,CAAC;QAC5B,4CAA4C;QAC5C,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC3D,CAAC;IAED;;;;OAIG;IACH,GAAG,CAAC,OAA6B,EAAE,QAA+B;QAChE,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAkB,CAAC;QACpE,MAAM,KAAK,GAAe;YACxB,GAAG,EAAE,OAAO,CAAC,aAAa;YAC1B,QAAQ,EAAE,QAAQ;YAClB,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;SACzD,CAAC;QAEF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,KAAK;QACH,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACzC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC9B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,IAAqB,EAAE,IAAc;QAC1C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;YACvC,IAAI,IAAI,YAAY,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBACjE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC1B,IAAI,IAAI,EAAE,CAAC;oBAAC,OAAO;gBAAC,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACH,SAAS;QACP,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;CAEF;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,YAAY,EAAE,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { HttpInterceptorFn } from '@ngify/http';
2
+ /**
3
+ * 负责接口的缓存拦截
4
+ * - 请求需要携带 {@link CACHEABLE_TOKEN}
5
+ */
6
+ export declare function useCacheInterceptor(): HttpInterceptorFn;
@@ -0,0 +1,46 @@
1
+ import { HttpResponse } from '@ngify/http';
2
+ import { finalize, of, share, tap } from 'rxjs';
3
+ import { CACHEABLE_TOKEN, cacheManager } from './cache-manager.js';
4
+ /**
5
+ * 判断请求是否可缓存
6
+ * 一般只有 GET 请求才需要缓存
7
+ * @param request
8
+ */
9
+ function isCachable(request) {
10
+ const value = request.context.get(CACHEABLE_TOKEN);
11
+ return value === true || typeof value === 'number';
12
+ }
13
+ /**
14
+ * 负责接口的缓存拦截
15
+ * - 请求需要携带 {@link CACHEABLE_TOKEN}
16
+ */
17
+ export function useCacheInterceptor() {
18
+ const requestingMap = new Map();
19
+ return (request, next) => {
20
+ // 判断当前请求是否可缓存
21
+ if (!isCachable(request)) {
22
+ return next(request);
23
+ }
24
+ // 缓存命中则直接返回
25
+ const response = cacheManager.get(request);
26
+ if (response) {
27
+ return of(response);
28
+ }
29
+ // 如果可缓存的请求已经有正在请求的,就不重复发了
30
+ const requesting = requestingMap.get(request.urlWithParams);
31
+ if (requesting) {
32
+ return requesting;
33
+ }
34
+ // 发送请求,成功后缓存
35
+ const obs = next(request).pipe(tap(event => {
36
+ cacheManager.clear();
37
+ if (event instanceof HttpResponse) {
38
+ cacheManager.put(request, event);
39
+ }
40
+ }), finalize(() => requestingMap.delete(request.urlWithParams)), share());
41
+ // 存入请求中
42
+ requestingMap.set(request.urlWithParams, obs);
43
+ return obs;
44
+ };
45
+ }
46
+ //# sourceMappingURL=caching.interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caching.interceptor.js","sourceRoot":"","sources":["../../../../packages/http/src/caching/caching.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4D,YAAY,EAAE,MAAM,aAAa,CAAC;AACrG,OAAO,EAAc,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEhE;;;;GAIG;AACH,SAAS,UAAU,CAAC,OAA6B;IAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACnD,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC;AACrD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,aAAa,GAAG,IAAI,GAAG,EAA0C,CAAC;IACxE,OAAO,CAAC,OAA6B,EAAE,IAAmB,EAAkC,EAAE;QAC5F,cAAc;QACd,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC;QAED,YAAY;QACZ,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3C,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC;QACtB,CAAC;QAED,0BAA0B;QAC1B,MAAM,UAAU,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QAC5D,IAAI,UAAU,EAAE,CAAC;YACf,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,aAAa;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAC5B,GAAG,CAAC,KAAK,CAAC,EAAE;YACV,YAAY,CAAC,KAAK,EAAE,CAAC;YACrB,IAAI,KAAK,YAAY,YAAY,EAAE,CAAC;gBAClC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACnC,CAAC;QACH,CAAC,CAAC,EACF,QAAQ,CAAC,GAAG,EAAE,CACZ,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAC5C,EACD,KAAK,EAAE,CACR,CAAC;QAEF,QAAQ;QACR,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAE9C,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './cache-manager.js';
2
+ export * from './caching.interceptor.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/http/src/caching/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { HttpContextToken, HttpInterceptorFn } from '@ngify/http';
2
+ /** 是否捕获 HTTP 请求错误(!2XX),默认为 true */
3
+ export declare const CATCH_HTTP_ERROR_TOKEN: HttpContextToken<boolean>;
4
+ /**
5
+ * 一个公共拦截器
6
+ * - 捕获非 200 的 HTTP 请求错误并提示(除了 401)
7
+ * - 使用 {@link CATCH_HTTP_ERROR_TOKEN} 跳过该拦截器
8
+ * @returns
9
+ */
10
+ export declare function useCommonInterceptor(): HttpInterceptorFn;
@@ -0,0 +1,35 @@
1
+ import { HttpContextToken, HttpStatusCode } from '@ngify/http';
2
+ import { overlay, vibrator } from '@solar-taro/core';
3
+ import { catchError } from 'rxjs';
4
+ /** 是否捕获 HTTP 请求错误(!2XX),默认为 true */
5
+ export const CATCH_HTTP_ERROR_TOKEN = new HttpContextToken(() => true);
6
+ /**
7
+ * 一个公共拦截器
8
+ * - 捕获非 200 的 HTTP 请求错误并提示(除了 401)
9
+ * - 使用 {@link CATCH_HTTP_ERROR_TOKEN} 跳过该拦截器
10
+ * @returns
11
+ */
12
+ export function useCommonInterceptor() {
13
+ return (request, next) => {
14
+ return next(request).pipe(catchError((error) => {
15
+ if (request.context.get(CATCH_HTTP_ERROR_TOKEN) && error.status !== HttpStatusCode.Unauthorized) {
16
+ vibrator.short();
17
+ setTimeout(() => {
18
+ var _a;
19
+ const message = (_a = error.error) === null || _a === void 0 ? void 0 : _a.msg;
20
+ if (message) {
21
+ overlay.tips('请求失败,原因:' + message);
22
+ }
23
+ else if (error.status >= 0) {
24
+ overlay.tips('请求失败,状态码:' + error.status);
25
+ }
26
+ else {
27
+ overlay.tips('请求失败,原因:未知错误');
28
+ }
29
+ });
30
+ }
31
+ throw error;
32
+ }));
33
+ };
34
+ }
35
+ //# sourceMappingURL=common.interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"common.interceptor.js","sourceRoot":"","sources":["../../../packages/http/src/common.interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAA+E,cAAc,EAAE,MAAM,aAAa,CAAC;AAC5I,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAc,MAAM,MAAM,CAAC;AAE9C,oCAAoC;AACpC,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,gBAAgB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,CAAC,OAA6B,EAAE,IAAmB,EAAkC,EAAE;QAC5F,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CACvB,UAAU,CAAC,CAAC,KAAwB,EAAE,EAAE;YACtC,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,cAAc,CAAC,YAAY,EAAE,CAAC;gBAChG,QAAQ,CAAC,KAAK,EAAE,CAAC;gBACjB,UAAU,CAAC,GAAG,EAAE;;oBACd,MAAM,OAAO,GAAG,MAAA,KAAK,CAAC,KAAK,0CAAE,GAAG,CAAC;oBAEjC,IAAI,OAAO,EAAE,CAAC;wBACZ,OAAO,CAAC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,CAAC;oBACrC,CAAC;yBAAM,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;wBAC7B,OAAO,CAAC,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;oBAC3C,CAAC;yBAAM,CAAC;wBACN,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;oBAC/B,CAAC;gBACH,CAAC,CAAC,CAAA;YACJ,CAAC;YAED,MAAM,KAAK,CAAC;QACd,CAAC,CAAC,CACH,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
package/headers.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export declare const enum WxHeader {
2
+ App = "X-WX-APP",
3
+ Code = "X-WX-Code",
4
+ EncryptedData = "X-WX-Encrypted-Data",
5
+ Iv = "X-WX-IV",
6
+ OpenId = "X-WX-OPEN-ID",
7
+ UnionId = "X-WX-UNION-ID"
8
+ }
package/headers.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=headers.js.map
package/headers.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../../../packages/http/src/headers.ts"],"names":[],"mappings":""}
package/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from './api.interceptor.js';
2
+ export * from './auto-host.interceptor.js';
3
+ export * from './caching/index.js';
4
+ export * from './common.interceptor.js';
5
+ export * from './headers.js';
6
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../packages/http/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,WAAW,CAAC;AAC1B,cAAc,sBAAsB,CAAC;AACrC,cAAc,WAAW,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solar-taro/http",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "sideEffects": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -12,5 +12,9 @@
12
12
  "@ngify/http": "^2.0.0",
13
13
  "@ngify/http-wx": "^2.0.0",
14
14
  "rxjs": "^7.8.0"
15
- }
15
+ },
16
+ "types": "./index.d.ts",
17
+ "module": "./index.js",
18
+ "type": "module",
19
+ "main": "./index.js"
16
20
  }
package/eslint.config.cjs DELETED
@@ -1,22 +0,0 @@
1
- const baseConfig = require('../../eslint.config.cjs');
2
-
3
- module.exports = [
4
- ...baseConfig,
5
- {
6
- files: ['**/*.json'],
7
- rules: {
8
- '@nx/dependency-checks': [
9
- 'error',
10
- {
11
- ignoredFiles: [
12
- '{projectRoot}/eslint.config.{js,cjs,mjs}',
13
- '{projectRoot}/vite.config.{js,ts,mjs,mts}',
14
- ],
15
- },
16
- ],
17
- },
18
- languageOptions: {
19
- parser: require('jsonc-eslint-parser'),
20
- },
21
- },
22
- ];
package/project.json DELETED
@@ -1,42 +0,0 @@
1
- {
2
- "name": "http",
3
- "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
- "sourceRoot": "packages/http/src",
5
- "projectType": "library",
6
- "tags": [],
7
- "targets": {
8
- "build": {
9
- "executor": "@nx/js:tsc",
10
- "outputs": [
11
- "{options.outputPath}"
12
- ],
13
- "options": {
14
- "outputPath": "dist/http",
15
- "main": "packages/http/src/index.ts",
16
- "tsConfig": "packages/http/tsconfig.lib.json",
17
- "assets": [
18
- "packages/http/*.md"
19
- ]
20
- }
21
- },
22
- "postbuild": {
23
- "executor": "nx:run-commands",
24
- "dependsOn": [
25
- "build"
26
- ],
27
- "options": {
28
- "commands": [
29
- "node scripts/postbuild.js http"
30
- ]
31
- }
32
- },
33
- "release:dev": {
34
- "executor": "nx:run-commands",
35
- "options": {
36
- "commands": [
37
- "yalc publish dist/http"
38
- ]
39
- }
40
- }
41
- }
42
- }
@@ -1,41 +0,0 @@
1
- import { HttpContextToken, HttpEvent, HttpHandlerFn, HttpInterceptorFn, HttpRequest, HttpResponse } from '@ngify/http';
2
- import { WX_REQUSET_TOKEN } from '@ngify/http-wx';
3
- import { Result } from '@solar-kit/planets/sun';
4
- import { overlay } from '@solar-taro/core';
5
- import { Observable, tap } from 'rxjs';
6
-
7
- /** HTTP 令牌:允许拦截器处理 `Result#code` 不为 `0` 的错误 */
8
- export const CATCH_RESULT_ERROR_TOKEN = new HttpContextToken(() => true);
9
-
10
- /**
11
- * 负责业务接口的拦截
12
- * - 添加 `Api` 请求头
13
- * - 捕获响应异常并提示
14
- */
15
- export function useApiInterceptor(): HttpInterceptorFn {
16
- return (request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {
17
- request = request.clone({
18
- headers: request.headers.set('Api', 'v1')
19
- });
20
-
21
- request.context.set(WX_REQUSET_TOKEN, {
22
- ...request.context.get(WX_REQUSET_TOKEN),
23
- enableHttp2: true,
24
- // forceCellularNetwork: true
25
- });
26
-
27
- return next(request).pipe(
28
- tap((event) => {
29
- if (event instanceof HttpResponse) {
30
- const { body } = event as HttpResponse<Result>;
31
- if (body?.code !== 0 && request.context.get(CATCH_RESULT_ERROR_TOKEN)) {
32
- // 延迟一下,确保不会被后面的 dismiss loading 给关掉 tips
33
- setTimeout(() => {
34
- overlay.tips('操作失败,原因:' + (body?.msg || '未知错误'));
35
- });
36
- }
37
- }
38
- })
39
- );
40
- }
41
- }
@@ -1,23 +0,0 @@
1
- import { HttpContextToken, HttpInterceptorFn } from '@ngify/http';
2
-
3
- export const AUTO_HOST_TOEKN = new HttpContextToken(() => true);
4
-
5
- /**
6
- * 自动为 / 开头的 URL 添加 host
7
- * 可以使用 {@link AUTO_HOST_TOEKN} 来跳过该拦截器
8
- * @param host
9
- * @returns
10
- */
11
- export function useAutoHostInterceptor(host: string): HttpInterceptorFn {
12
- return (request, next) => {
13
- if (
14
- request.context.get(AUTO_HOST_TOEKN) &&
15
- request.url.startsWith('/')
16
- ) {
17
- return next(request.clone({
18
- url: host + request.url
19
- }));
20
- }
21
- return next(request);
22
- };
23
- }
@@ -1,83 +0,0 @@
1
- import { HttpContextToken, HttpRequest, HttpResponse } from '@ngify/http';
2
-
3
- /**
4
- * 缓存令牌,值可以为:
5
- * - 布尔值,为 true 时,表示在一次会话中缓存永不过期,默认值为 false
6
- * - 缓存时间,单位毫秒
7
- */
8
- export const CACHEABLE_TOKEN = new HttpContextToken<boolean | number>(() => false);
9
-
10
- /** 缓存条目 */
11
- export interface CacheEntry {
12
- url: string;
13
- response: HttpResponse<unknown>;
14
- expire: number;
15
- }
16
-
17
- export class CacheManager {
18
- private readonly cacheMap = new Map<string, CacheEntry>();
19
-
20
- /**
21
- * 获取缓存
22
- * @param request
23
- */
24
- get(request: HttpRequest<unknown>): HttpResponse<unknown> | null {
25
- // 判断当前请求是否已被缓存,若未缓存则返回null
26
- const entry = this.cacheMap.get(request.urlWithParams);
27
-
28
- if (!entry) { return null; }
29
- // 若缓存命中,则判断缓存是否过期,若已过期则返回null。否则返回请求对应的响应对象
30
- return Date.now() > entry.expire ? null : entry.response;
31
- }
32
-
33
- /**
34
- * 缓存
35
- * @param request
36
- * @param response
37
- */
38
- put(request: HttpRequest<unknown>, response: HttpResponse<unknown>): void {
39
- const token = request.context.get(CACHEABLE_TOKEN) as true | number;
40
- const entry: CacheEntry = {
41
- url: request.urlWithParams,
42
- response: response,
43
- expire: Date.now() + (token === true ? Infinity : token)
44
- };
45
-
46
- this.cacheMap.set(request.urlWithParams, entry);
47
- }
48
-
49
- /**
50
- * 清扫过期缓存
51
- */
52
- clear() {
53
- for (const [key, entry] of this.cacheMap) {
54
- if (Date.now() > entry.expire) {
55
- this.cacheMap.delete(key);
56
- }
57
- }
58
- }
59
-
60
- /**
61
- * 通过标记(字符串、正则)模糊查询以撤销缓存
62
- * @param mark
63
- * @param once 只撤销一个
64
- */
65
- revoke(mark: string | RegExp, once?: boolean) {
66
- for (const key of this.cacheMap.keys()) {
67
- if (mark instanceof RegExp ? mark.test(key) : key.includes(mark)) {
68
- this.cacheMap.delete(key);
69
- if (once) { return; }
70
- }
71
- }
72
- }
73
-
74
- /**
75
- * 撤销所有缓存
76
- */
77
- revokeAll() {
78
- this.cacheMap.clear();
79
- }
80
-
81
- }
82
-
83
- export const cacheManager = new CacheManager();
@@ -1,58 +0,0 @@
1
- import { HttpEvent, HttpHandlerFn, HttpInterceptorFn, HttpRequest, HttpResponse } from '@ngify/http';
2
- import { Observable, finalize, of, share, tap } from 'rxjs';
3
- import { CACHEABLE_TOKEN, cacheManager } from './cache-manager';
4
-
5
- /**
6
- * 判断请求是否可缓存
7
- * 一般只有 GET 请求才需要缓存
8
- * @param request
9
- */
10
- function isCachable(request: HttpRequest<unknown>): boolean {
11
- const value = request.context.get(CACHEABLE_TOKEN);
12
- return value === true || typeof value === 'number';
13
- }
14
-
15
- /**
16
- * 负责接口的缓存拦截
17
- * - 请求需要携带 {@link CACHEABLE_TOKEN}
18
- */
19
- export function useCacheInterceptor(): HttpInterceptorFn {
20
- const requestingMap = new Map<string, Observable<HttpEvent<unknown>>>();
21
- return (request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {
22
- // 判断当前请求是否可缓存
23
- if (!isCachable(request)) {
24
- return next(request);
25
- }
26
-
27
- // 缓存命中则直接返回
28
- const response = cacheManager.get(request);
29
- if (response) {
30
- return of(response);
31
- }
32
-
33
- // 如果可缓存的请求已经有正在请求的,就不重复发了
34
- const requesting = requestingMap.get(request.urlWithParams);
35
- if (requesting) {
36
- return requesting;
37
- }
38
-
39
- // 发送请求,成功后缓存
40
- const obs = next(request).pipe(
41
- tap(event => {
42
- cacheManager.clear();
43
- if (event instanceof HttpResponse) {
44
- cacheManager.put(request, event);
45
- }
46
- }),
47
- finalize(() =>
48
- requestingMap.delete(request.urlWithParams)
49
- ),
50
- share()
51
- );
52
-
53
- // 存入请求中
54
- requestingMap.set(request.urlWithParams, obs);
55
-
56
- return obs;
57
- };
58
- }
@@ -1,37 +0,0 @@
1
- import { HttpContextToken, HttpErrorResponse, HttpEvent, HttpHandlerFn, HttpInterceptorFn, HttpRequest, HttpStatusCode } from '@ngify/http';
2
- import { overlay, vibrator } from '@solar-taro/core';
3
- import { catchError, Observable } from 'rxjs';
4
-
5
- /** 是否捕获 HTTP 请求错误(!2XX),默认为 true */
6
- export const CATCH_HTTP_ERROR_TOKEN = new HttpContextToken(() => true);
7
-
8
- /**
9
- * 一个公共拦截器
10
- * - 捕获非 200 的 HTTP 请求错误并提示(除了 401)
11
- * - 使用 {@link CATCH_HTTP_ERROR_TOKEN} 跳过该拦截器
12
- * @returns
13
- */
14
- export function useCommonInterceptor(): HttpInterceptorFn {
15
- return (request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {
16
- return next(request).pipe(
17
- catchError((error: HttpErrorResponse) => {
18
- if (request.context.get(CATCH_HTTP_ERROR_TOKEN) && error.status !== HttpStatusCode.Unauthorized) {
19
- vibrator.short();
20
- setTimeout(() => {
21
- const message = error.error?.msg;
22
-
23
- if (message) {
24
- overlay.tips('请求失败,原因:' + message);
25
- } else if (error.status >= 0) {
26
- overlay.tips('请求失败,状态码:' + error.status);
27
- } else {
28
- overlay.tips('请求失败,原因:未知错误');
29
- }
30
- })
31
- }
32
-
33
- throw error;
34
- })
35
- );
36
- };
37
- }
package/src/headers.ts DELETED
@@ -1,8 +0,0 @@
1
- export const enum WxHeader {
2
- App = 'X-WX-APP',
3
- Code = 'X-WX-Code',
4
- EncryptedData = 'X-WX-Encrypted-Data',
5
- Iv = 'X-WX-IV',
6
- OpenId = 'X-WX-OPEN-ID',
7
- UnionId = 'X-WX-UNION-ID',
8
- }
package/tsconfig.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "forceConsistentCasingInFileNames": true,
5
- "strict": true,
6
- "noImplicitOverride": true,
7
- "noImplicitReturns": true,
8
- "noFallthroughCasesInSwitch": true,
9
- "noPropertyAccessFromIndexSignature": true
10
- },
11
- "files": [],
12
- "include": [],
13
- "references": [
14
- {
15
- "path": "./tsconfig.lib.json"
16
- },
17
- {
18
- "path": "./tsconfig.spec.json"
19
- }
20
- ]
21
- }
package/tsconfig.lib.json DELETED
@@ -1,24 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../dist/out-tsc",
5
- "declaration": true
6
- },
7
- "include": [
8
- "src/**/*.ts"
9
- ],
10
- "exclude": [
11
- "vite.config.ts",
12
- "vite.config.mts",
13
- "vitest.config.ts",
14
- "vitest.config.mts",
15
- "src/**/*.test.ts",
16
- "src/**/*.spec.ts",
17
- "src/**/*.test.tsx",
18
- "src/**/*.spec.tsx",
19
- "src/**/*.test.js",
20
- "src/**/*.spec.js",
21
- "src/**/*.test.jsx",
22
- "src/**/*.spec.jsx"
23
- ]
24
- }
@@ -1,28 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../dist/out-tsc",
5
- "types": [
6
- "vitest/globals",
7
- "vitest/importMeta",
8
- "vite/client",
9
- "node",
10
- "vitest"
11
- ]
12
- },
13
- "include": [
14
- "vite.config.ts",
15
- "vite.config.mts",
16
- "vitest.config.ts",
17
- "vitest.config.mts",
18
- "src/**/*.test.ts",
19
- "src/**/*.spec.ts",
20
- "src/**/*.test.tsx",
21
- "src/**/*.spec.tsx",
22
- "src/**/*.test.js",
23
- "src/**/*.spec.js",
24
- "src/**/*.test.jsx",
25
- "src/**/*.spec.jsx",
26
- "src/**/*.d.ts"
27
- ]
28
- }
package/typedoc.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "$schema": "https://typedoc.org/schema.json",
3
- "entryPoints": [
4
- "src/index.ts"
5
- ]
6
- }
package/vite.config.ts DELETED
@@ -1,24 +0,0 @@
1
- import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
2
- import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
3
- import { defineConfig } from 'vite';
4
-
5
- export default defineConfig({
6
- root: __dirname,
7
- cacheDir: '../../node_modules/.vite/http',
8
- plugins: [nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
9
- // Uncomment this if you are using workers.
10
- // worker: {
11
- // plugins: [ nxViteTsPaths() ],
12
- // },
13
- test: {
14
- watch: false,
15
- globals: true,
16
- environment: 'node',
17
- include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
18
- reporters: ['default'],
19
- coverage: {
20
- reportsDirectory: '../../coverage/http',
21
- provider: 'v8',
22
- },
23
- },
24
- });
File without changes
File without changes