@ningboyz/wxmp 0.0.0-stage → 1.0.0

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/.prettierrc ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "printWidth": 200,
3
+ "tabWidth": 2,
4
+ "useTabs": false,
5
+ "semi": true,
6
+ "singleQuote": false,
7
+ "trailingComma": "none",
8
+ "bracketSpacing": true,
9
+ "bracketSameLine": true,
10
+ "arrowParens": "always",
11
+ "htmlWhitespaceSensitivity": "ignore",
12
+ "vueIndentScriptAndStyle": false,
13
+ "endOfLine": "auto",
14
+ "singleAttributePerLine": false
15
+ }
package/package.json CHANGED
@@ -1,6 +1,28 @@
1
- {
2
- "name": "@ningboyz/wxmp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "@ningboyz/wxmp",
3
+ "version": "1.0.0",
4
+ "private": false,
5
+ "description": "宁波甬政微信小程序库",
6
+ "author": "nbyt-syq",
7
+ "license": "ISC",
8
+ "keywords": [],
9
+ "main": "src/index.ts",
10
+ "scripts": {
11
+ "build": "vite build",
12
+ "prettier": "prettier --config ./.prettierrc --write \"./**/*.{ts,tsx}\" "
13
+ },
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "registry": "https://registry.npmjs.org/"
17
+ },
18
+ "dependencies": {
19
+ "@ningboyz/types": "1.8.55",
20
+ "lodash": "4.17.21"
21
+ },
22
+ "devDependencies": {
23
+ "@types/lodash": "4.17.20",
24
+ "vite": "7.1.3",
25
+ "vite-plugin-dts": "4.5.4",
26
+ "vite-plugin-tsx-resolve-types": "0.0.4"
27
+ }
28
+ }
@@ -0,0 +1,30 @@
1
+ import { TCore } from "@ningboyz/types";
2
+ import { WxRequest, createWxRequest } from "../request/wxRequest";
3
+ import { IWxConfig, IWxLoginParams } from "../types";
4
+
5
+ class WxClient {
6
+ private wxRequest: WxRequest;
7
+
8
+ private constructor(config: IWxConfig) {
9
+ this.wxRequest = createWxRequest(config);
10
+ }
11
+
12
+ public static create = (config: IWxConfig): WxClient => {
13
+ return new WxClient(config);
14
+ };
15
+
16
+ /**
17
+ * 微信登录
18
+ * @param params
19
+ * @returns
20
+ */
21
+ public login(params: IWxLoginParams) {
22
+ return this.wxRequest.post<TCore.IUserResponse[]>("/gapi/core/tuser/verifyd2/weixin", undefined, params);
23
+ }
24
+ }
25
+
26
+ const createWxClient = (config: IWxConfig): WxClient => {
27
+ return WxClient.create(config);
28
+ };
29
+
30
+ export { type WxClient, createWxClient };
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { WxClient, createWxClient } from "./client/wxClient";
2
+ import { WxRequest, createWxRequest } from "./request/wxRequest";
3
+ import { IWxConfig, IWxInternalRequestConfig, IWxLoginParams, IWxQueryParams, IWxResponse, TWxConfig, TWxInterceptorsRequest, TWxInterceptorsResponse, TWxLoginParams } from "./types";
4
+
5
+ export {
6
+ type WxClient,
7
+ createWxClient,
8
+ type WxRequest,
9
+ createWxRequest,
10
+ type IWxConfig,
11
+ type IWxInternalRequestConfig,
12
+ type IWxLoginParams,
13
+ type IWxQueryParams,
14
+ type IWxResponse,
15
+ type TWxInterceptorsRequest,
16
+ type TWxInterceptorsResponse,
17
+ TWxConfig,
18
+ TWxLoginParams
19
+ };
@@ -0,0 +1,205 @@
1
+ import { TResp } from "@ningboyz/types";
2
+ import _ from "lodash";
3
+ import { IWxConfig, IWxInternalRequestConfig, IWxQueryParams, IWxResponse, REQUEST_TIMEOUT_MILLISECONDS } from "../types";
4
+
5
+ const HTTP_STATUS_MIN = 200;
6
+ const HTTP_STATUS_MAX = 300;
7
+ const CONTENT_TYPE_JSON = "application/json";
8
+ const CONTENT_TYPE_FORM = "application/x-www-form-urlencoded";
9
+
10
+ interface IWxRequestFail {
11
+ errMsg: string;
12
+ }
13
+
14
+ interface IWxRequestSuccess<T> {
15
+ data: T;
16
+ statusCode: number;
17
+ }
18
+
19
+ interface IWxRequestOption<T> {
20
+ url: string;
21
+ method: "GET" | "POST";
22
+ data?: object | string;
23
+ header: Record<string, string>;
24
+ timeout: number;
25
+ success: (result: IWxRequestSuccess<T>) => void;
26
+ fail: (error: IWxRequestFail) => void;
27
+ }
28
+
29
+ interface IMiniProgramWx {
30
+ request: <T>(option: IWxRequestOption<T>) => void;
31
+ }
32
+
33
+ class WxRequest {
34
+ private config: IWxConfig;
35
+
36
+ private constructor(config: IWxConfig) {
37
+ this.config = config;
38
+ }
39
+
40
+ public static create(config: IWxConfig): WxRequest {
41
+ return new WxRequest(config);
42
+ }
43
+
44
+ /**
45
+ * 表单提交。body 字段名 params,值是 JSON 字符串,对应 apis 里的 post。
46
+ */
47
+ public post<T>(url: string, querys?: IWxQueryParams, params?: object, headers?: Record<string, string>): Promise<TResp.IResponse<T>> {
48
+ const data = _.isNil(params) ? void 0 : { params: JSON.stringify(params) };
49
+ const header = this.createHeader(_.isNil(params) ? void 0 : CONTENT_TYPE_FORM, headers);
50
+ return this.send<T>("POST", url, querys, data, header);
51
+ }
52
+
53
+ /**
54
+ * JSON 提交,对应 apis 里的 postJson。国密加密接口请走此方法。
55
+ */
56
+ public postJson<T>(url: string, querys?: IWxQueryParams, params?: object, headers?: Record<string, string>): Promise<TResp.IResponse<T>> {
57
+ const header = this.createHeader(CONTENT_TYPE_JSON, headers);
58
+ return this.send<T>("POST", url, querys, params, header);
59
+ }
60
+
61
+ public get<T>(url: string, querys?: IWxQueryParams, headers?: Record<string, string>): Promise<TResp.IResponse<T>> {
62
+ const header = this.createHeader(void 0, headers);
63
+ return this.send<T>("GET", url, querys, void 0, header);
64
+ }
65
+
66
+ private send<T>(method: "GET" | "POST", url: string, querys: IWxQueryParams | undefined, data: object | undefined, header: Record<string, string>): Promise<TResp.IResponse<T>> {
67
+ return (async () => {
68
+ let requestConfig: IWxInternalRequestConfig = {
69
+ url,
70
+ method,
71
+ querys,
72
+ data,
73
+ header
74
+ };
75
+
76
+ if (!_.isNil(this.config.interceptorsRequest)) {
77
+ requestConfig = await this.config.interceptorsRequest(requestConfig);
78
+ }
79
+
80
+ const requestUrl = this.createRequestUrl(requestConfig.url, requestConfig.querys);
81
+ const timeout = this.config.timeout ?? REQUEST_TIMEOUT_MILLISECONDS;
82
+
83
+ return await new Promise<TResp.IResponse<T>>((resolve) => {
84
+ try {
85
+ this.getMiniProgramWx().request<TResp.IResponse<T>>({
86
+ url: requestUrl,
87
+ method: requestConfig.method,
88
+ data: requestConfig.data,
89
+ header: requestConfig.header,
90
+ timeout,
91
+ success: (result) => {
92
+ void this.handleSuccess<T>(result, requestConfig).then(resolve);
93
+ },
94
+ fail: (error) => {
95
+ resolve(TResp.TResponse.toError(error.errMsg));
96
+ }
97
+ });
98
+ } catch (error) {
99
+ const message = error instanceof Error ? error.message : "请求失败";
100
+ resolve(TResp.TResponse.toError(message));
101
+ }
102
+ });
103
+ })();
104
+ }
105
+
106
+ private async handleSuccess<T>(result: IWxRequestSuccess<TResp.IResponse<T>>, requestConfig: IWxInternalRequestConfig): Promise<TResp.IResponse<T>> {
107
+ if (result.statusCode < HTTP_STATUS_MIN || result.statusCode >= HTTP_STATUS_MAX) {
108
+ return TResp.TResponse.toError(`请求失败,状态码 ${result.statusCode}`);
109
+ }
110
+
111
+ let response: IWxResponse = {
112
+ data: result.data,
113
+ statusCode: result.statusCode,
114
+ config: requestConfig
115
+ };
116
+
117
+ if (!_.isNil(this.config.interceptorsResponse)) {
118
+ response = await this.config.interceptorsResponse(response);
119
+ }
120
+
121
+ this.tryHandleSessionExpired(response.data);
122
+
123
+ return response.data as TResp.IResponse<T>;
124
+ }
125
+
126
+ private tryHandleSessionExpired(data: unknown): void {
127
+ if (_.isNil(this.config.loseTokenState) || _.isNil(this.config.sessionExpiredErrorKey)) {
128
+ return;
129
+ }
130
+ if (_.isNil(data) || typeof data !== "object") {
131
+ return;
132
+ }
133
+ const resp = data as TResp.IResponse<unknown>;
134
+ if (resp.ERRORKEY === this.config.sessionExpiredErrorKey) {
135
+ this.config.loseTokenState();
136
+ }
137
+ }
138
+
139
+ private getMiniProgramWx(): IMiniProgramWx {
140
+ const miniProgramWx = (globalThis as { wx?: IMiniProgramWx }).wx;
141
+ if (_.isNil(miniProgramWx)) {
142
+ throw new Error("当前环境不存在微信小程序 wx");
143
+ }
144
+ return miniProgramWx;
145
+ }
146
+
147
+ private createRequestUrl(url: string, querys?: IWxQueryParams): string {
148
+ const baseURL = this.config.baseURL.endsWith("/") ? this.config.baseURL.slice(0, -1) : this.config.baseURL;
149
+ const path = url.startsWith("/") ? url : `/${url}`;
150
+ const mergedQuerys: IWxQueryParams = {
151
+ r: Date.now(),
152
+ ...this.createAuthQuerys(),
153
+ ...this.toLowerCase(querys)
154
+ };
155
+ const search = Object.entries(mergedQuerys)
156
+ .filter(([, value]) => !_.isNil(value))
157
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
158
+ .join("&");
159
+ if (search.length === 0) {
160
+ return `${baseURL}${path}`;
161
+ }
162
+ return `${baseURL}${path}?${search}`;
163
+ }
164
+
165
+ private createAuthQuerys(): IWxQueryParams {
166
+ const querys: IWxQueryParams = {};
167
+ if (!_.isNil(this.config.whobuild) && this.config.whobuild !== 0) {
168
+ querys.whobuild = this.config.whobuild;
169
+ }
170
+ if (!_.isNil(this.config.userIndx) && this.config.userIndx !== 0) {
171
+ querys.userindx = this.config.userIndx;
172
+ }
173
+ return querys;
174
+ }
175
+
176
+ private createHeader(contentType: string | undefined, headers?: Record<string, string>): Record<string, string> {
177
+ const header: Record<string, string> = {};
178
+ if (!_.isNil(contentType)) {
179
+ header["content-type"] = contentType;
180
+ }
181
+ if (!_.isNil(this.config.usrtoken) && this.config.usrtoken !== "") {
182
+ header.usrtoken = this.config.usrtoken;
183
+ }
184
+ if (!_.isNil(this.config.dbConfig) && this.config.dbConfig !== "") {
185
+ header.dbconfig = this.config.dbConfig;
186
+ }
187
+ if (!_.isNil(headers)) {
188
+ Object.assign(header, headers);
189
+ }
190
+ return header;
191
+ }
192
+
193
+ private toLowerCase(querys?: IWxQueryParams): IWxQueryParams {
194
+ if (_.isNil(querys)) {
195
+ return {};
196
+ }
197
+ return _.mapKeys(querys, (_value, key) => key.toLowerCase());
198
+ }
199
+ }
200
+
201
+ const createWxRequest = (config: IWxConfig): WxRequest => {
202
+ return WxRequest.create(config);
203
+ };
204
+
205
+ export { type WxRequest, createWxRequest };
package/src/types.ts ADDED
@@ -0,0 +1,83 @@
1
+ const REQUEST_TIMEOUT_MILLISECONDS = 60000;
2
+
3
+ export interface IWxQueryParams {
4
+ [key: string]: string | number | boolean | null | undefined;
5
+ }
6
+
7
+ /**
8
+ * 请求拦截入参/出参(对应 Web axios InternalAxiosRequestConfig)。
9
+ * 国密等可在此改 data、header,并挂 gmSm4KeyHex。
10
+ */
11
+ export interface IWxInternalRequestConfig {
12
+ /** 业务 path,不含 baseURL / query */
13
+ url: string;
14
+ method: "GET" | "POST";
15
+ querys?: IWxQueryParams;
16
+ data?: object;
17
+ header: Record<string, string>;
18
+ /** 国密会话 SM4,供响应拦截解密 */
19
+ gmSm4KeyHex?: string;
20
+ }
21
+
22
+ /**
23
+ * 响应拦截入参/出参(对应 Web axios AxiosResponse)。
24
+ */
25
+ export interface IWxResponse<T = unknown> {
26
+ data: T;
27
+ statusCode: number;
28
+ config: IWxInternalRequestConfig;
29
+ }
30
+
31
+ export type TWxInterceptorsRequest = (request: IWxInternalRequestConfig) => IWxInternalRequestConfig | Promise<IWxInternalRequestConfig>;
32
+
33
+ export type TWxInterceptorsResponse = (response: IWxResponse) => IWxResponse | Promise<IWxResponse>;
34
+
35
+ export interface IWxConfig {
36
+ baseURL: string;
37
+ timeout?: number;
38
+ whobuild?: number;
39
+ userIndx?: number;
40
+ usrtoken?: string;
41
+ dbConfig?: string;
42
+ /**
43
+ * 请求拦截(对齐 Web createRequest 的 interceptorsRequest)。
44
+ * 在内置鉴权 query/header 组装之后、真正 wx.request 之前调用。
45
+ */
46
+ interceptorsRequest?: TWxInterceptorsRequest;
47
+ /**
48
+ * 响应拦截(对齐 Web createRequest 的 interceptorsResponse)。
49
+ * 在 HTTP 成功拿到 body 之后、返回给业务之前调用。
50
+ */
51
+ interceptorsResponse?: TWxInterceptorsResponse;
52
+ /** 会话过期时回调(对齐 Web loseTokenState,按 ERRORKEY 判断) */
53
+ loseTokenState?: () => void;
54
+ /** 与 loseTokenState 配合;不传则不根据 ERRORKEY 踢登录 */
55
+ sessionExpiredErrorKey?: string;
56
+ }
57
+
58
+ export class TWxConfig implements IWxConfig {
59
+ baseURL: string = "";
60
+ timeout: number = REQUEST_TIMEOUT_MILLISECONDS;
61
+ whobuild: number = 0;
62
+ userIndx: number = 0;
63
+ usrtoken: string = "";
64
+ dbConfig: string = "";
65
+ interceptorsRequest?: TWxInterceptorsRequest = void 0;
66
+ interceptorsResponse?: TWxInterceptorsResponse = void 0;
67
+ loseTokenState?: () => void = void 0;
68
+ sessionExpiredErrorKey?: string = void 0;
69
+ }
70
+
71
+ export interface IWxLoginParams {
72
+ code: string;
73
+ encryptedData: string;
74
+ iv: string;
75
+ }
76
+
77
+ export class TWxLoginParams implements IWxLoginParams {
78
+ code: string = "";
79
+ encryptedData: string = "";
80
+ iv: string = "";
81
+ }
82
+
83
+ export { REQUEST_TIMEOUT_MILLISECONDS };
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2023"],
5
+ "module": "ESNext",
6
+ "skipLibCheck": true,
7
+
8
+ /* Bundler mode */
9
+ "moduleResolution": "bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "isolatedModules": true,
12
+ "moduleDetection": "force",
13
+ "noEmit": true,
14
+
15
+ /* Linting */
16
+ "strict": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noFallthroughCasesInSwitch": true,
20
+ "experimentalDecorators": true
21
+ }
22
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { defineConfig } from "vite";
2
+
3
+ import tsxResolveTypes from "vite-plugin-tsx-resolve-types";
4
+ import dts from "vite-plugin-dts";
5
+
6
+ export default defineConfig({
7
+ plugins: [
8
+ tsxResolveTypes(),
9
+ dts({
10
+ entryRoot: "src",
11
+ outDir: ["es"],
12
+ exclude: ["**/tests/**"]
13
+ })
14
+ ],
15
+ build: {
16
+ rollupOptions: {
17
+ external: ["lodash", "@ningboyz/types"],
18
+ output: [
19
+ {
20
+ preserveModules: true,
21
+ preserveModulesRoot: "src",
22
+ entryFileNames: "[name].js",
23
+ format: "esm",
24
+ dir: "es"
25
+ }
26
+ ]
27
+ },
28
+ lib: {
29
+ entry: "src/index.ts"
30
+ }
31
+ }
32
+ });
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.