@qzsy/pdd-core 0.1.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/LICENSE +21 -0
- package/dist/client.d.ts +68 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +308 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +214 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +187 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/sign.d.ts +46 -0
- package/dist/sign.d.ts.map +1 -0
- package/dist/sign.js +81 -0
- package/dist/sign.js.map +1 -0
- package/dist/token-strategy.d.ts +89 -0
- package/dist/token-strategy.d.ts.map +1 -0
- package/dist/token-strategy.js +159 -0
- package/dist/token-strategy.js.map +1 -0
- package/dist/token.d.ts +45 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +62 -0
- package/dist/token.js.map +1 -0
- package/dist/types.d.ts +304 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +29 -0
- package/dist/types.js.map +1 -0
- package/package.json +40 -0
package/dist/errors.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 调用方式的中文标签,写入 `message` 方便肉眼排查。
|
|
3
|
+
*/
|
|
4
|
+
const CALL_MODE_LABEL = {
|
|
5
|
+
merchant: "商家调用",
|
|
6
|
+
authorizer: "代调用",
|
|
7
|
+
app: "应用调用",
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* 把上下文拼进错误文案。
|
|
11
|
+
*
|
|
12
|
+
* @param fallback 没有上下文时的兜底文案
|
|
13
|
+
* @param ctx 排查字段
|
|
14
|
+
*/
|
|
15
|
+
export function formatPddErrorMessage(fallback, ctx) {
|
|
16
|
+
const who = ctx.callMode ? CALL_MODE_LABEL[ctx.callMode] : undefined;
|
|
17
|
+
const head = [who, ctx.clientId].filter(Boolean).join(" ");
|
|
18
|
+
const target = [ctx.method, ctx.type].filter(Boolean).join(" ");
|
|
19
|
+
const prefix = [head ? `[${head}]` : undefined, target].filter(Boolean).join(" ");
|
|
20
|
+
if (ctx.kind === "api") {
|
|
21
|
+
const detail = `${ctx.errorCode ?? ""} ${ctx.errorMsg ?? ""}`.trim();
|
|
22
|
+
const sub = [ctx.subCode, ctx.subMsg].filter((item) => item !== undefined && item !== "").join(" ");
|
|
23
|
+
const text = sub ? `${detail} (${sub})` : detail;
|
|
24
|
+
return prefix ? `${prefix} → ${text}` : fallback;
|
|
25
|
+
}
|
|
26
|
+
if (ctx.kind === "http") {
|
|
27
|
+
const status = ctx.status === undefined ? fallback : `HTTP ${ctx.status}`;
|
|
28
|
+
const detail = ctx.bodyText ? `${status}: ${ctx.bodyText}` : status;
|
|
29
|
+
return prefix ? `${prefix} → ${detail}` : fallback;
|
|
30
|
+
}
|
|
31
|
+
return prefix ? `${prefix} → ${fallback}` : fallback;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* SDK 错误基类。HTTP / 业务 / 配置错误都挂在这棵树上,可用 `instanceof PddError` 统一捕获。
|
|
35
|
+
*/
|
|
36
|
+
export class PddError extends Error {
|
|
37
|
+
name = "PddError";
|
|
38
|
+
/**
|
|
39
|
+
* 错误分类。
|
|
40
|
+
*/
|
|
41
|
+
kind;
|
|
42
|
+
/**
|
|
43
|
+
* 官方接口名。
|
|
44
|
+
*/
|
|
45
|
+
type;
|
|
46
|
+
/**
|
|
47
|
+
* HTTP 方法。
|
|
48
|
+
*/
|
|
49
|
+
method;
|
|
50
|
+
/**
|
|
51
|
+
* 发起方式。
|
|
52
|
+
*/
|
|
53
|
+
callMode;
|
|
54
|
+
/**
|
|
55
|
+
* 应用 `client_id`。
|
|
56
|
+
*/
|
|
57
|
+
clientId;
|
|
58
|
+
/**
|
|
59
|
+
* 本次请求的鉴权方式。
|
|
60
|
+
*/
|
|
61
|
+
auth;
|
|
62
|
+
/**
|
|
63
|
+
* 拼多多主错误码。
|
|
64
|
+
*/
|
|
65
|
+
errorCode;
|
|
66
|
+
/**
|
|
67
|
+
* 拼多多主错误信息。
|
|
68
|
+
*/
|
|
69
|
+
errorMsg;
|
|
70
|
+
/**
|
|
71
|
+
* 子错误码。
|
|
72
|
+
*/
|
|
73
|
+
subCode;
|
|
74
|
+
/**
|
|
75
|
+
* 子错误信息。
|
|
76
|
+
*/
|
|
77
|
+
subMsg;
|
|
78
|
+
/**
|
|
79
|
+
* 平台返回的请求追踪 ID。
|
|
80
|
+
*/
|
|
81
|
+
requestId;
|
|
82
|
+
/**
|
|
83
|
+
* HTTP 状态码。
|
|
84
|
+
*/
|
|
85
|
+
status;
|
|
86
|
+
/**
|
|
87
|
+
* 响应正文截断预览。
|
|
88
|
+
*/
|
|
89
|
+
bodyText;
|
|
90
|
+
/**
|
|
91
|
+
* @param message 错误说明
|
|
92
|
+
* @param init 排查上下文字段
|
|
93
|
+
*/
|
|
94
|
+
constructor(message, init = {}) {
|
|
95
|
+
const kind = init.kind ?? "config";
|
|
96
|
+
const ctx = {
|
|
97
|
+
kind,
|
|
98
|
+
type: init.type,
|
|
99
|
+
method: init.method,
|
|
100
|
+
callMode: init.callMode,
|
|
101
|
+
clientId: init.clientId,
|
|
102
|
+
auth: init.auth,
|
|
103
|
+
errorCode: init.errorCode,
|
|
104
|
+
errorMsg: init.errorMsg,
|
|
105
|
+
subCode: init.subCode,
|
|
106
|
+
subMsg: init.subMsg,
|
|
107
|
+
requestId: init.requestId,
|
|
108
|
+
status: init.status,
|
|
109
|
+
bodyText: init.bodyText,
|
|
110
|
+
};
|
|
111
|
+
super(formatPddErrorMessage(message, ctx), { cause: init.cause });
|
|
112
|
+
this.kind = kind;
|
|
113
|
+
this.type = init.type;
|
|
114
|
+
this.method = init.method;
|
|
115
|
+
this.callMode = init.callMode;
|
|
116
|
+
this.clientId = init.clientId;
|
|
117
|
+
this.auth = init.auth;
|
|
118
|
+
this.errorCode = init.errorCode;
|
|
119
|
+
this.errorMsg = init.errorMsg;
|
|
120
|
+
this.subCode = init.subCode;
|
|
121
|
+
this.subMsg = init.subMsg;
|
|
122
|
+
this.requestId = init.requestId;
|
|
123
|
+
this.status = init.status;
|
|
124
|
+
this.bodyText = init.bodyText;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 序列化为结构化字段,便于打日志。
|
|
128
|
+
*
|
|
129
|
+
* @returns 名称、文案与排查上下文
|
|
130
|
+
*/
|
|
131
|
+
toJSON() {
|
|
132
|
+
return {
|
|
133
|
+
name: this.name,
|
|
134
|
+
message: this.message,
|
|
135
|
+
kind: this.kind,
|
|
136
|
+
type: this.type,
|
|
137
|
+
method: this.method,
|
|
138
|
+
callMode: this.callMode,
|
|
139
|
+
clientId: this.clientId,
|
|
140
|
+
auth: this.auth,
|
|
141
|
+
errorCode: this.errorCode,
|
|
142
|
+
errorMsg: this.errorMsg,
|
|
143
|
+
subCode: this.subCode,
|
|
144
|
+
subMsg: this.subMsg,
|
|
145
|
+
requestId: this.requestId,
|
|
146
|
+
status: this.status,
|
|
147
|
+
bodyText: this.bodyText,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* HTTP 层失败:网络错误、非 2xx、或响应不是 JSON。
|
|
153
|
+
*/
|
|
154
|
+
export class PddHttpError extends PddError {
|
|
155
|
+
name = "PddHttpError";
|
|
156
|
+
/**
|
|
157
|
+
* @param message 错误说明
|
|
158
|
+
* @param init.status HTTP 状态码
|
|
159
|
+
* @param init.type 官方接口名
|
|
160
|
+
* @param init.bodyText 响应正文截断
|
|
161
|
+
* @param init.cause 原始异常
|
|
162
|
+
*/
|
|
163
|
+
constructor(message, init) {
|
|
164
|
+
super(message, { ...init, kind: "http" });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* 拼多多业务错误:HTTP 成功但存在 `error_response`。
|
|
169
|
+
*/
|
|
170
|
+
export class PddApiError extends PddError {
|
|
171
|
+
name = "PddApiError";
|
|
172
|
+
/**
|
|
173
|
+
* @param init.errorCode 主错误码
|
|
174
|
+
* @param init.errorMsg 主错误信息
|
|
175
|
+
* @param init.type 官方接口名
|
|
176
|
+
*/
|
|
177
|
+
constructor(init) {
|
|
178
|
+
super(`Pinduoduo API error ${init.errorCode}: ${init.errorMsg}`, { ...init, kind: "api" });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 视为令牌失效、需要刷新后重试一次的错误码。
|
|
183
|
+
*
|
|
184
|
+
* - `10019`:access_token 已过期
|
|
185
|
+
*/
|
|
186
|
+
export const STALE_ACCESS_TOKEN_CODES = new Set([10019]);
|
|
187
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { PddApiError, PddError, PddHttpError, formatPddErrorMessage, STALE_ACCESS_TOKEN_CODES, } from "./errors.ts";
|
|
2
|
+
export type { PddErrorContext, PddErrorInit, PddErrorKind } from "./errors.ts";
|
|
3
|
+
export { PddHttpClient } from "./client.ts";
|
|
4
|
+
export { MemoryTokenStore, accessTokenKey, authorizerAccessTokenKey, } from "./token.ts";
|
|
5
|
+
export { AuthorizerAccessTokenStrategy, MerchantAccessTokenStrategy, } from "./token-strategy.ts";
|
|
6
|
+
export type { PddAccessTokenStrategy } from "./token-strategy.ts";
|
|
7
|
+
export { isPddFile, pddResponseKey, signPddParams, stringifyPddParams, stringifyPddValue, } from "./sign.ts";
|
|
8
|
+
export type { AuthMode, HttpMethod, PddAccessTokenResponse, PddAppCredentials, PddAuthorizerCaller, PddCallMode, PddCallerKind, PddClientCaller, PddClientOptions, PddErrorBody, PddErrorResponse, PddMerchantCaller, PddOAuthTokenRequest, PddRequestOptions, QueryRecord, QueryValue, TokenKind, TokenStore, } from "./types.ts";
|
|
9
|
+
export { isAuthorizerCaller } from "./types.ts";
|
|
10
|
+
export { DEFAULT_ACCESS_TOKEN_EXPIRES_IN, DEFAULT_PDD_BASE_URL, DEFAULT_PDD_TOKEN_URL, DEFAULT_TIMEOUT_MS, TOKEN_EXPIRY_SKEW_MS, } from "./types.ts";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,wBAAwB,GACzB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,6BAA6B,EAC7B,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EACL,SAAS,EACT,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,QAAQ,EACR,UAAU,EACV,sBAAsB,EACtB,iBAAiB,EACjB,mBAAmB,EACnB,WAAW,EACX,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,SAAS,EACT,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EACL,+BAA+B,EAC/B,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { PddApiError, PddError, PddHttpError, formatPddErrorMessage, STALE_ACCESS_TOKEN_CODES, } from "./errors.js";
|
|
2
|
+
export { PddHttpClient } from "./client.js";
|
|
3
|
+
export { MemoryTokenStore, accessTokenKey, authorizerAccessTokenKey, } from "./token.js";
|
|
4
|
+
export { AuthorizerAccessTokenStrategy, MerchantAccessTokenStrategy, } from "./token-strategy.js";
|
|
5
|
+
export { isPddFile, pddResponseKey, signPddParams, stringifyPddParams, stringifyPddValue, } from "./sign.js";
|
|
6
|
+
export { isAuthorizerCaller } from "./types.js";
|
|
7
|
+
export { DEFAULT_ACCESS_TOKEN_EXPIRES_IN, DEFAULT_PDD_BASE_URL, DEFAULT_PDD_TOKEN_URL, DEFAULT_TIMEOUT_MS, TOKEN_EXPIRY_SKEW_MS, } from "./types.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,wBAAwB,GACzB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,6BAA6B,EAC7B,2BAA2B,GAC5B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,SAAS,EACT,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AAqBnB,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EACL,+BAA+B,EAC/B,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,YAAY,CAAC"}
|
package/dist/sign.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { QueryValue } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* 把业务参数收成可签名的字符串表。
|
|
4
|
+
*
|
|
5
|
+
* 跳过 `undefined` / `null` / 空字符串。数组和对象序列化为 JSON。
|
|
6
|
+
*
|
|
7
|
+
* @param params 业务参数
|
|
8
|
+
* @returns 仅含字符串值的参数表
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 官方 `FILE` 参数:二进制不参与 MD5 签名,走 multipart。
|
|
12
|
+
*
|
|
13
|
+
* @param value 请求参数值
|
|
14
|
+
* @returns 是否为浏览器 / Node 18+ 的 `Blob`
|
|
15
|
+
*/
|
|
16
|
+
export declare function isPddFile(value: unknown): value is Blob;
|
|
17
|
+
export declare function stringifyPddParams(params: object): Record<string, string>;
|
|
18
|
+
/**
|
|
19
|
+
* 单个参数值转字符串。
|
|
20
|
+
*
|
|
21
|
+
* @param value 原始值
|
|
22
|
+
* @returns 可签名字符串;应跳过时返回 `undefined`
|
|
23
|
+
*/
|
|
24
|
+
export declare function stringifyPddValue(value: QueryValue): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* 按官方规则生成大写 MD5 签名。
|
|
27
|
+
*
|
|
28
|
+
* 1. 去掉 `sign`,并跳过空字符串
|
|
29
|
+
* 2. 参数名按字典序排列
|
|
30
|
+
* 3. 拼接 `clientSecret + 参数名 + 参数值 + clientSecret`
|
|
31
|
+
* 4. MD5 后转大写
|
|
32
|
+
*
|
|
33
|
+
* @param params 已收成字符串的全部请求参数(含公共参数,不含 `sign`)
|
|
34
|
+
* @param clientSecret 应用密钥
|
|
35
|
+
* @returns 大写 MD5 签名
|
|
36
|
+
*/
|
|
37
|
+
export declare function signPddParams(params: Record<string, string>, clientSecret: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* 由官方接口名推导成功响应信封字段。
|
|
40
|
+
*
|
|
41
|
+
* `pdd.mall.info.get` → `mall_info_get_response`。
|
|
42
|
+
*
|
|
43
|
+
* @param type 官方接口名
|
|
44
|
+
*/
|
|
45
|
+
export declare function pddResponseKey(type: string): string;
|
|
46
|
+
//# sourceMappingURL=sign.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;GAOG;AACH;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,IAAI,CAEvD;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAazE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAQvE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAU1F;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD"}
|
package/dist/sign.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* 把业务参数收成可签名的字符串表。
|
|
4
|
+
*
|
|
5
|
+
* 跳过 `undefined` / `null` / 空字符串。数组和对象序列化为 JSON。
|
|
6
|
+
*
|
|
7
|
+
* @param params 业务参数
|
|
8
|
+
* @returns 仅含字符串值的参数表
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 官方 `FILE` 参数:二进制不参与 MD5 签名,走 multipart。
|
|
12
|
+
*
|
|
13
|
+
* @param value 请求参数值
|
|
14
|
+
* @returns 是否为浏览器 / Node 18+ 的 `Blob`
|
|
15
|
+
*/
|
|
16
|
+
export function isPddFile(value) {
|
|
17
|
+
return typeof Blob !== "undefined" && value instanceof Blob;
|
|
18
|
+
}
|
|
19
|
+
export function stringifyPddParams(params) {
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const [key, value] of Object.entries(params)) {
|
|
22
|
+
if (isPddFile(value)) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const text = stringifyPddValue(value);
|
|
26
|
+
if (text === undefined) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
out[key] = text;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 单个参数值转字符串。
|
|
35
|
+
*
|
|
36
|
+
* @param value 原始值
|
|
37
|
+
* @returns 可签名字符串;应跳过时返回 `undefined`
|
|
38
|
+
*/
|
|
39
|
+
export function stringifyPddValue(value) {
|
|
40
|
+
if (value === undefined || value === null || value === "") {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === "object") {
|
|
44
|
+
return JSON.stringify(value);
|
|
45
|
+
}
|
|
46
|
+
return String(value);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 按官方规则生成大写 MD5 签名。
|
|
50
|
+
*
|
|
51
|
+
* 1. 去掉 `sign`,并跳过空字符串
|
|
52
|
+
* 2. 参数名按字典序排列
|
|
53
|
+
* 3. 拼接 `clientSecret + 参数名 + 参数值 + clientSecret`
|
|
54
|
+
* 4. MD5 后转大写
|
|
55
|
+
*
|
|
56
|
+
* @param params 已收成字符串的全部请求参数(含公共参数,不含 `sign`)
|
|
57
|
+
* @param clientSecret 应用密钥
|
|
58
|
+
* @returns 大写 MD5 签名
|
|
59
|
+
*/
|
|
60
|
+
export function signPddParams(params, clientSecret) {
|
|
61
|
+
const keys = Object.keys(params)
|
|
62
|
+
.filter((key) => key !== "sign" && params[key] !== "")
|
|
63
|
+
.sort();
|
|
64
|
+
let raw = clientSecret;
|
|
65
|
+
for (const key of keys) {
|
|
66
|
+
raw += key + (params[key] ?? "");
|
|
67
|
+
}
|
|
68
|
+
raw += clientSecret;
|
|
69
|
+
return createHash("md5").update(raw, "utf8").digest("hex").toUpperCase();
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 由官方接口名推导成功响应信封字段。
|
|
73
|
+
*
|
|
74
|
+
* `pdd.mall.info.get` → `mall_info_get_response`。
|
|
75
|
+
*
|
|
76
|
+
* @param type 官方接口名
|
|
77
|
+
*/
|
|
78
|
+
export function pddResponseKey(type) {
|
|
79
|
+
return `${type.replace(/^pdd\./, "").replaceAll(".", "_")}_response`;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=sign.js.map
|
package/dist/sign.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sign.js","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;;;;;;GAOG;AACH;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,OAAO,OAAO,IAAI,KAAK,WAAW,IAAI,KAAK,YAAY,IAAI,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;YACrB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAmB,CAAC,CAAC;QACpD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,SAAS;QACX,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IAClB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAiB;IACjD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QAC1D,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,MAA8B,EAAE,YAAoB;IAChF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;SAC7B,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,MAAM,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;SACrD,IAAI,EAAE,CAAC;IACV,IAAI,GAAG,GAAG,YAAY,CAAC;IACvB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,GAAG,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACnC,CAAC;IACD,GAAG,IAAI,YAAY,CAAC;IACpB,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;AAC3E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,WAAW,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { PddAccessTokenResponse, PddCallMode, TokenStore } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* 业务接口的 `access_token` 获取策略。
|
|
4
|
+
*
|
|
5
|
+
* 商家自己持有令牌与调用方注入令牌的请求形态完全相同,只是令牌从哪来不同。
|
|
6
|
+
*/
|
|
7
|
+
export interface PddAccessTokenStrategy {
|
|
8
|
+
/**
|
|
9
|
+
* 发起方式。
|
|
10
|
+
*/
|
|
11
|
+
readonly callMode: PddCallMode;
|
|
12
|
+
/**
|
|
13
|
+
* 读取或刷新 `access_token`。写入请求时字段名仍是 `access_token`。
|
|
14
|
+
*
|
|
15
|
+
* @param forceRefresh 为 `true` 时忽略缓存并重新申请
|
|
16
|
+
*/
|
|
17
|
+
getAccessToken(forceRefresh?: boolean): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* 丢掉本地缓存。令牌失效重试前调用。
|
|
20
|
+
*/
|
|
21
|
+
invalidate(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 商家自己调用:使用已有 `accessToken`,可选地用 `refreshToken` 走 OAuth 刷新。
|
|
25
|
+
*/
|
|
26
|
+
export declare class MerchantAccessTokenStrategy implements PddAccessTokenStrategy {
|
|
27
|
+
#private;
|
|
28
|
+
readonly callMode: "merchant";
|
|
29
|
+
/**
|
|
30
|
+
* @param options.clientId 应用 `client_id`
|
|
31
|
+
* @param options.ownerId 授权主体
|
|
32
|
+
* @param options.tokenStore 令牌缓存
|
|
33
|
+
* @param options.accessToken 当前令牌
|
|
34
|
+
* @param options.refreshToken 刷新令牌
|
|
35
|
+
* @param options.expiresIn 当前令牌有效秒数
|
|
36
|
+
* @param options.refresh 用 `refresh_token` 换新票
|
|
37
|
+
*/
|
|
38
|
+
constructor(options: {
|
|
39
|
+
clientId: string;
|
|
40
|
+
ownerId?: string;
|
|
41
|
+
tokenStore: TokenStore;
|
|
42
|
+
accessToken?: string;
|
|
43
|
+
refreshToken?: string;
|
|
44
|
+
expiresIn?: number;
|
|
45
|
+
refresh: (refreshToken: string) => Promise<PddAccessTokenResponse>;
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* 读取或刷新商家 `access_token`。
|
|
49
|
+
*
|
|
50
|
+
* @param forceRefresh 为 `true` 时忽略缓存
|
|
51
|
+
*/
|
|
52
|
+
getAccessToken(forceRefresh?: boolean): Promise<string>;
|
|
53
|
+
/**
|
|
54
|
+
* 丢掉商家 `access_token` 缓存。
|
|
55
|
+
*/
|
|
56
|
+
invalidate(): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 调用方注入令牌:由外部提供 `access_token`。
|
|
60
|
+
*
|
|
61
|
+
* 写入请求时字段名仍是 `access_token`,与商家自己调用相同。
|
|
62
|
+
*/
|
|
63
|
+
export declare class AuthorizerAccessTokenStrategy implements PddAccessTokenStrategy {
|
|
64
|
+
#private;
|
|
65
|
+
readonly callMode: "authorizer";
|
|
66
|
+
/**
|
|
67
|
+
* @param options.clientId 应用 `client_id`
|
|
68
|
+
* @param options.ownerId 授权主体
|
|
69
|
+
* @param options.tokenStore 令牌缓存
|
|
70
|
+
* @param options.getAccessToken 读取授权令牌;失效重试时传入 `forceRefresh = true`
|
|
71
|
+
*/
|
|
72
|
+
constructor(options: {
|
|
73
|
+
clientId: string;
|
|
74
|
+
ownerId?: string;
|
|
75
|
+
tokenStore: TokenStore;
|
|
76
|
+
getAccessToken: (forceRefresh?: boolean) => Promise<string>;
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* 读取授权店铺令牌。
|
|
80
|
+
*
|
|
81
|
+
* @param forceRefresh 为 `true` 时忽略缓存并要求调用方换新
|
|
82
|
+
*/
|
|
83
|
+
getAccessToken(forceRefresh?: boolean): Promise<string>;
|
|
84
|
+
/**
|
|
85
|
+
* 丢掉授权令牌缓存。
|
|
86
|
+
*/
|
|
87
|
+
invalidate(): Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=token-strategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-strategy.d.ts","sourceRoot":"","sources":["../src/token-strategy.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,sBAAsB,EACtB,WAAW,EACX,UAAU,EACX,MAAM,YAAY,CAAC;AAEpB;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAE/B;;;;OAIG;IACH,cAAc,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAExD;;OAEG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AA6DD;;GAEG;AACH,qBAAa,2BAA4B,YAAW,sBAAsB;;IACxE,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IASxC;;;;;;;;OAQG;IACH,YAAY,OAAO,EAAE;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,UAAU,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;KACpE,EAUA;IAED;;;;OAIG;IACH,cAAc,CAAC,YAAY,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CA0BpD;IAED;;OAEG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAE1B;CACF;AAED;;;;GAIG;AACH,qBAAa,6BAA8B,YAAW,sBAAsB;;IAC1E,QAAQ,CAAC,QAAQ,EAAG,YAAY,CAAU;IAK1C;;;;;OAKG;IACH,YAAY,OAAO,EAAE;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,UAAU,CAAC;QACvB,cAAc,EAAE,CAAC,YAAY,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;KAC7D,EAMA;IAED;;;;OAIG;IACH,cAAc,CAAC,YAAY,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAMpD;IAED;;OAEG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAE1B;CACF"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { PddError } from "./errors.js";
|
|
2
|
+
import { accessTokenKey, authorizerAccessTokenKey } from "./token.js";
|
|
3
|
+
import { DEFAULT_ACCESS_TOKEN_EXPIRES_IN, TOKEN_EXPIRY_SKEW_MS, } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* 令牌获取过程中的共享缓存与单飞。
|
|
6
|
+
*/
|
|
7
|
+
class CachedTokenStrategy {
|
|
8
|
+
#store;
|
|
9
|
+
#key;
|
|
10
|
+
#inflight;
|
|
11
|
+
/**
|
|
12
|
+
* @param store 令牌存储
|
|
13
|
+
* @param key 存储键
|
|
14
|
+
*/
|
|
15
|
+
constructor(store, key) {
|
|
16
|
+
this.#store = store;
|
|
17
|
+
this.#key = key;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 读缓存或执行换票,并发调用合并为一次。
|
|
21
|
+
*
|
|
22
|
+
* @param forceRefresh 是否强制换票
|
|
23
|
+
* @param fetchToken 实际换票
|
|
24
|
+
*/
|
|
25
|
+
async getCached(forceRefresh, fetchToken) {
|
|
26
|
+
if (!forceRefresh) {
|
|
27
|
+
const cached = await this.#store.get(this.#key);
|
|
28
|
+
if (cached) {
|
|
29
|
+
return cached;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (!this.#inflight) {
|
|
33
|
+
this.#inflight = fetchToken().finally(() => {
|
|
34
|
+
this.#inflight = undefined;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return this.#inflight;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 写入缓存。
|
|
41
|
+
*
|
|
42
|
+
* @param token 令牌
|
|
43
|
+
* @param expiresIn 有效秒数
|
|
44
|
+
*/
|
|
45
|
+
async save(token, expiresIn) {
|
|
46
|
+
const ttlMs = Math.max(expiresIn * 1000 - TOKEN_EXPIRY_SKEW_MS, 1_000);
|
|
47
|
+
await this.#store.set(this.#key, token, ttlMs);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 删除缓存。
|
|
51
|
+
*/
|
|
52
|
+
invalidate() {
|
|
53
|
+
return this.#store.delete(this.#key);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 商家自己调用:使用已有 `accessToken`,可选地用 `refreshToken` 走 OAuth 刷新。
|
|
58
|
+
*/
|
|
59
|
+
export class MerchantAccessTokenStrategy {
|
|
60
|
+
callMode = "merchant";
|
|
61
|
+
#cache;
|
|
62
|
+
#clientId;
|
|
63
|
+
#initialToken;
|
|
64
|
+
#initialExpiresIn;
|
|
65
|
+
#refreshToken;
|
|
66
|
+
#refresh;
|
|
67
|
+
/**
|
|
68
|
+
* @param options.clientId 应用 `client_id`
|
|
69
|
+
* @param options.ownerId 授权主体
|
|
70
|
+
* @param options.tokenStore 令牌缓存
|
|
71
|
+
* @param options.accessToken 当前令牌
|
|
72
|
+
* @param options.refreshToken 刷新令牌
|
|
73
|
+
* @param options.expiresIn 当前令牌有效秒数
|
|
74
|
+
* @param options.refresh 用 `refresh_token` 换新票
|
|
75
|
+
*/
|
|
76
|
+
constructor(options) {
|
|
77
|
+
this.#cache = new CachedTokenStrategy(options.tokenStore, accessTokenKey(options.clientId, options.ownerId));
|
|
78
|
+
this.#clientId = options.clientId;
|
|
79
|
+
this.#initialToken = options.accessToken;
|
|
80
|
+
this.#initialExpiresIn = options.expiresIn ?? DEFAULT_ACCESS_TOKEN_EXPIRES_IN;
|
|
81
|
+
this.#refreshToken = options.refreshToken;
|
|
82
|
+
this.#refresh = options.refresh;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 读取或刷新商家 `access_token`。
|
|
86
|
+
*
|
|
87
|
+
* @param forceRefresh 为 `true` 时忽略缓存
|
|
88
|
+
*/
|
|
89
|
+
getAccessToken(forceRefresh = false) {
|
|
90
|
+
return this.#cache.getCached(forceRefresh, async () => {
|
|
91
|
+
if (!forceRefresh && this.#initialToken) {
|
|
92
|
+
const token = this.#initialToken;
|
|
93
|
+
this.#initialToken = undefined;
|
|
94
|
+
await this.#cache.save(token, this.#initialExpiresIn);
|
|
95
|
+
return token;
|
|
96
|
+
}
|
|
97
|
+
if (!this.#refreshToken) {
|
|
98
|
+
if (this.#initialToken && !forceRefresh) {
|
|
99
|
+
return this.#initialToken;
|
|
100
|
+
}
|
|
101
|
+
throw new PddError("缺少 access_token。商家接口需要先完成 OAuth 授权,或传入 refreshToken 以便过期后刷新。", {
|
|
102
|
+
kind: "config",
|
|
103
|
+
callMode: "merchant",
|
|
104
|
+
clientId: this.#clientId,
|
|
105
|
+
auth: "access_token",
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const data = await this.#refresh(this.#refreshToken);
|
|
109
|
+
this.#refreshToken = data.refresh_token;
|
|
110
|
+
await this.#cache.save(data.access_token, data.expires_in);
|
|
111
|
+
return data.access_token;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 丢掉商家 `access_token` 缓存。
|
|
116
|
+
*/
|
|
117
|
+
invalidate() {
|
|
118
|
+
return this.#cache.invalidate();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 调用方注入令牌:由外部提供 `access_token`。
|
|
123
|
+
*
|
|
124
|
+
* 写入请求时字段名仍是 `access_token`,与商家自己调用相同。
|
|
125
|
+
*/
|
|
126
|
+
export class AuthorizerAccessTokenStrategy {
|
|
127
|
+
callMode = "authorizer";
|
|
128
|
+
#cache;
|
|
129
|
+
#getAccessToken;
|
|
130
|
+
/**
|
|
131
|
+
* @param options.clientId 应用 `client_id`
|
|
132
|
+
* @param options.ownerId 授权主体
|
|
133
|
+
* @param options.tokenStore 令牌缓存
|
|
134
|
+
* @param options.getAccessToken 读取授权令牌;失效重试时传入 `forceRefresh = true`
|
|
135
|
+
*/
|
|
136
|
+
constructor(options) {
|
|
137
|
+
this.#cache = new CachedTokenStrategy(options.tokenStore, authorizerAccessTokenKey(options.clientId, options.ownerId));
|
|
138
|
+
this.#getAccessToken = options.getAccessToken;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 读取授权店铺令牌。
|
|
142
|
+
*
|
|
143
|
+
* @param forceRefresh 为 `true` 时忽略缓存并要求调用方换新
|
|
144
|
+
*/
|
|
145
|
+
getAccessToken(forceRefresh = false) {
|
|
146
|
+
return this.#cache.getCached(forceRefresh, async () => {
|
|
147
|
+
const token = await this.#getAccessToken(forceRefresh);
|
|
148
|
+
await this.#cache.save(token, DEFAULT_ACCESS_TOKEN_EXPIRES_IN);
|
|
149
|
+
return token;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 丢掉授权令牌缓存。
|
|
154
|
+
*/
|
|
155
|
+
invalidate() {
|
|
156
|
+
return this.#cache.invalidate();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=token-strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-strategy.js","sourceRoot":"","sources":["../src/token-strategy.ts"],"names":[],"mappings":"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"}
|
package/dist/token.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { TokenStore } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* 进程内令牌缓存。
|
|
4
|
+
*
|
|
5
|
+
* 仅适合单进程开发与测试。生产环境请换成 Redis 等跨进程实现。
|
|
6
|
+
*/
|
|
7
|
+
export declare class MemoryTokenStore implements TokenStore {
|
|
8
|
+
#private;
|
|
9
|
+
/**
|
|
10
|
+
* 读取未过期的值。
|
|
11
|
+
*
|
|
12
|
+
* @param key 存储键
|
|
13
|
+
* @returns 缓存值;不存在或已过期时返回 `undefined`
|
|
14
|
+
*/
|
|
15
|
+
get(key: string): Promise<string | undefined>;
|
|
16
|
+
/**
|
|
17
|
+
* 写入值。
|
|
18
|
+
*
|
|
19
|
+
* @param key 存储键
|
|
20
|
+
* @param token 令牌
|
|
21
|
+
* @param ttlMs 存活毫秒数
|
|
22
|
+
*/
|
|
23
|
+
set(key: string, token: string, ttlMs: number): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* 删除指定键。
|
|
26
|
+
*
|
|
27
|
+
* @param key 存储键
|
|
28
|
+
*/
|
|
29
|
+
delete(key: string): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 店铺 `access_token` 的存储键。
|
|
33
|
+
*
|
|
34
|
+
* @param clientId 应用 `client_id`
|
|
35
|
+
* @param ownerId 授权主体,未传时使用 `default`
|
|
36
|
+
*/
|
|
37
|
+
export declare function accessTokenKey(clientId: string, ownerId?: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* 调用方注入令牌时的存储键。
|
|
40
|
+
*
|
|
41
|
+
* @param clientId 应用 `client_id`
|
|
42
|
+
* @param ownerId 授权主体,未传时使用 `default`
|
|
43
|
+
*/
|
|
44
|
+
export declare function authorizerAccessTokenKey(clientId: string, ownerId?: string): string;
|
|
45
|
+
//# sourceMappingURL=token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAiB7C;;;;GAIG;AACH,qBAAa,gBAAiB,YAAW,UAAU;;IAGjD;;;;;OAKG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAUlD;IAED;;;;;;OAMG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElE;IAED;;;;OAIG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvC;CACF;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,SAAY,GAAG,MAAM,CAE5E;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,SAAY,GAAG,MAAM,CAEtF"}
|