@linkfox-web/api-user-center 0.1.0 → 0.2.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/README.md +3 -1
- package/dist/index.cjs +1 -313
- package/dist/index.d.cts +27 -2
- package/dist/index.d.ts +27 -2
- package/dist/index.js +1 -280
- package/package.json +3 -3
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ pnpm add @linkfox-web/http @linkfox-web/api-user-center
|
|
|
14
14
|
| `getWechatQrCode()` / `getWechatQrCodeStatus(sessionId)` / `loginByWechat(params)` | 微信扫码登录 |
|
|
15
15
|
| `getUserInfo()` | 用户信息与团队列表 |
|
|
16
16
|
| `switchTeam(teamId)` | 切换团队并写入会话 |
|
|
17
|
-
| `
|
|
17
|
+
| `getPersonalKey(teamId?)` | 当前用户在某团队下的个人 API Key 列表;`teamId` 为用户中心团队 ID,不传取当前团队 |
|
|
18
|
+
| `syncApiKey(teamId?)` | 取个人 API Key(优先 `USER_PRIMARY`)注入 `@linkfox-web/ai-tools` 配置并返回;只在内存,刷新页面或 `switchTeam` 后需重新调用 |
|
|
19
|
+
| `logout()` | 清除本地会话,并清掉已注入的 API Key |
|
|
18
20
|
|
|
19
21
|
登录函数的第二个参数可传 `{ sourceUrl }`,默认取 `location.href`。`source`、`systemId`、`clientChannel` 可通过 `configure({ services: { 'user-center': { … } } })` 覆盖。
|
package/dist/index.cjs
CHANGED
|
@@ -1,313 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/index.ts
|
|
31
|
-
var index_exports = {};
|
|
32
|
-
__export(index_exports, {
|
|
33
|
-
getImageVerifyCode: () => getImageVerifyCode,
|
|
34
|
-
getUserInfo: () => getUserInfo,
|
|
35
|
-
getVerifyCode: () => getVerifyCode,
|
|
36
|
-
getWechatQrCode: () => getWechatQrCode,
|
|
37
|
-
getWechatQrCodeStatus: () => getWechatQrCodeStatus,
|
|
38
|
-
loginByCode: () => loginByCode,
|
|
39
|
-
loginByPassword: () => loginByPassword,
|
|
40
|
-
loginByWechat: () => loginByWechat,
|
|
41
|
-
logout: () => logout,
|
|
42
|
-
resolveCurrentTeam: () => resolveCurrentTeam,
|
|
43
|
-
switchTeam: () => switchTeam,
|
|
44
|
-
syncCurrentTeam: () => syncCurrentTeam
|
|
45
|
-
});
|
|
46
|
-
module.exports = __toCommonJS(index_exports);
|
|
47
|
-
|
|
48
|
-
// src/capabilities.ts
|
|
49
|
-
var import_http3 = require("@linkfox-web/http");
|
|
50
|
-
|
|
51
|
-
// src/service.ts
|
|
52
|
-
var import_http = require("@linkfox-web/http");
|
|
53
|
-
|
|
54
|
-
// src/defaults.ts
|
|
55
|
-
var USER_CENTER_DEFAULTS = {
|
|
56
|
-
baseURL: "https://api.linkfox.com",
|
|
57
|
-
aesKey: "AR7x7zfje4nkim4t",
|
|
58
|
-
/** 请求头 source */
|
|
59
|
-
source: "ai-linkfox-web",
|
|
60
|
-
/** 账密登录的 systemId */
|
|
61
|
-
systemId: "LinkFoxAgent",
|
|
62
|
-
/** 发短信验证码的 clientChannel */
|
|
63
|
-
clientChannel: "LinkFoxAI"
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// src/service.ts
|
|
67
|
-
var AUTH_EXPIRED_CODES = /* @__PURE__ */ new Set(["30003", "30009", "30011", "30015", "30017", "30019"]);
|
|
68
|
-
var CLIENT_ID_KEY = "LINKFOX_CLIENT_ID";
|
|
69
|
-
function isEnvelope(body) {
|
|
70
|
-
return typeof body === "object" && body !== null && !Array.isArray(body);
|
|
71
|
-
}
|
|
72
|
-
function generateUUID() {
|
|
73
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
74
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
75
|
-
const r = Math.random() * 16 | 0;
|
|
76
|
-
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
function getClientId() {
|
|
80
|
-
try {
|
|
81
|
-
let id = localStorage.getItem(CLIENT_ID_KEY);
|
|
82
|
-
if (!id) {
|
|
83
|
-
id = generateUUID();
|
|
84
|
-
localStorage.setItem(CLIENT_ID_KEY, id);
|
|
85
|
-
}
|
|
86
|
-
return id;
|
|
87
|
-
} catch {
|
|
88
|
-
return "";
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function buildUid(userId) {
|
|
92
|
-
try {
|
|
93
|
-
return btoa(JSON.stringify({ a_id: getClientId(), d_id: userId }));
|
|
94
|
-
} catch {
|
|
95
|
-
return "";
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
var userCenter = (0, import_http.defineService)({
|
|
99
|
-
name: "user-center",
|
|
100
|
-
defaults: USER_CENTER_DEFAULTS,
|
|
101
|
-
baseHeaders: (ctx) => ({
|
|
102
|
-
source: ctx.config.source,
|
|
103
|
-
uid: buildUid(ctx.session?.userId ?? "")
|
|
104
|
-
}),
|
|
105
|
-
authorize: (ctx) => ctx.session ? { authorization: ctx.session.accessToken } : {},
|
|
106
|
-
isUnauthorized: (res, body) => res.status === 401 || isEnvelope(body) && body.success !== true && AUTH_EXPIRED_CODES.has(String(body.code ?? "")),
|
|
107
|
-
refreshOnUnauthorized: false,
|
|
108
|
-
unwrap: (res, body) => {
|
|
109
|
-
if (isEnvelope(body)) {
|
|
110
|
-
const ok = body.code === "OK" && body.success !== false || body.code === 200 && body.success !== false || body.success === true;
|
|
111
|
-
if (ok && res.ok) return body.data;
|
|
112
|
-
}
|
|
113
|
-
throw new import_http.LinkfoxHttpError({
|
|
114
|
-
service: "user-center",
|
|
115
|
-
status: res.status,
|
|
116
|
-
code: isEnvelope(body) ? body.code : void 0,
|
|
117
|
-
message: isEnvelope(body) && (body.message || body.msg) || res.statusText || `HTTP ${res.status}`,
|
|
118
|
-
body
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// src/user.ts
|
|
124
|
-
var import_http2 = require("@linkfox-web/http");
|
|
125
|
-
function getUserInfo() {
|
|
126
|
-
return fetchUserInfo(false);
|
|
127
|
-
}
|
|
128
|
-
function fetchUserInfo(skipRecovery) {
|
|
129
|
-
return userCenter.request({ method: "POST", path: "/linkFoxApp/api/userCenter/userInfo", body: {}, skipRecovery });
|
|
130
|
-
}
|
|
131
|
-
function resolveCurrentTeam(teamList) {
|
|
132
|
-
if (!teamList?.length) return void 0;
|
|
133
|
-
const selected = teamList.find((t) => t.isSelect === 1);
|
|
134
|
-
if (selected?.agentMemberId) return selected;
|
|
135
|
-
const personal = teamList.find((t) => t.teamType === 1);
|
|
136
|
-
if (personal?.agentMemberId) return personal;
|
|
137
|
-
return teamList.find((t) => t.agentMemberId);
|
|
138
|
-
}
|
|
139
|
-
function toTeam(team) {
|
|
140
|
-
if (!team.aiTeamId) return null;
|
|
141
|
-
return {
|
|
142
|
-
id: team.aiTeamId,
|
|
143
|
-
teamId: team.teamId,
|
|
144
|
-
name: team.teamName,
|
|
145
|
-
type: team.teamType,
|
|
146
|
-
logo: team.logo ?? "",
|
|
147
|
-
ownerId: team.ownerId
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
async function syncCurrentTeam(options) {
|
|
151
|
-
const info = await fetchUserInfo(options?.skipRecovery ?? false);
|
|
152
|
-
const current = resolveCurrentTeam(info.teamList);
|
|
153
|
-
const team = current ? toTeam(current) : null;
|
|
154
|
-
if (!team) return false;
|
|
155
|
-
(0, import_http2.setTeam)(team);
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
async function switchTeam(teamId) {
|
|
159
|
-
await userCenter.request({
|
|
160
|
-
method: "POST",
|
|
161
|
-
path: "/linkFoxApp/api/userCenter/team/switchTeam",
|
|
162
|
-
body: { targetTeamId: teamId }
|
|
163
|
-
});
|
|
164
|
-
const info = await getUserInfo();
|
|
165
|
-
const target = info.teamList.find((t) => t.teamId === teamId);
|
|
166
|
-
const team = target ? toTeam(target) : null;
|
|
167
|
-
if (team) (0, import_http2.setTeam)(team);
|
|
168
|
-
else (0, import_http2.clearTeam)();
|
|
169
|
-
return info;
|
|
170
|
-
}
|
|
171
|
-
function logout() {
|
|
172
|
-
(0, import_http2.clearSession)();
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// src/capabilities.ts
|
|
176
|
-
async function refresh(session) {
|
|
177
|
-
const data = await userCenter.request({
|
|
178
|
-
method: "POST",
|
|
179
|
-
path: "/cp/v1/user/refresh",
|
|
180
|
-
auth: "none",
|
|
181
|
-
skipRecovery: true,
|
|
182
|
-
headers: { authorization: session.refreshToken ?? "" }
|
|
183
|
-
});
|
|
184
|
-
if (!data?.accessToken) throw new Error("REFRESH_TOKEN_EXPIRED");
|
|
185
|
-
(0, import_http3.updateSession)({ accessToken: data.accessToken, refreshToken: data.refreshToken, expireAt: data.expireAt });
|
|
186
|
-
}
|
|
187
|
-
function installCapabilities() {
|
|
188
|
-
(0, import_http3.registerCapability)("refresh", refresh);
|
|
189
|
-
(0, import_http3.registerCapability)("resolveTeam", () => syncCurrentTeam({ skipRecovery: true }));
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// src/auth.ts
|
|
193
|
-
var import_http4 = require("@linkfox-web/http");
|
|
194
|
-
|
|
195
|
-
// src/crypto.ts
|
|
196
|
-
var import_aes = __toESM(require("crypto-js/aes"), 1);
|
|
197
|
-
var import_enc_utf8 = __toESM(require("crypto-js/enc-utf8"), 1);
|
|
198
|
-
var import_mode_ecb = __toESM(require("crypto-js/mode-ecb"), 1);
|
|
199
|
-
var import_pad_zeropadding = __toESM(require("crypto-js/pad-zeropadding"), 1);
|
|
200
|
-
function aesEncryptECB(plain, key) {
|
|
201
|
-
return import_aes.default.encrypt(plain, import_enc_utf8.default.parse(key), { mode: import_mode_ecb.default, padding: import_pad_zeropadding.default }).toString();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// src/auth.ts
|
|
205
|
-
function sourceUrlOf(options) {
|
|
206
|
-
return options?.sourceUrl ?? (typeof location !== "undefined" ? location.href : "");
|
|
207
|
-
}
|
|
208
|
-
async function completeLogin(data) {
|
|
209
|
-
if (!data?.accessToken) {
|
|
210
|
-
throw new import_http4.LinkfoxHttpError({ service: "user-center", status: 200, code: "INVALID_LOGIN_RESPONSE", message: "\u767B\u5F55\u54CD\u5E94\u7F3A\u5C11 accessToken", body: data });
|
|
211
|
-
}
|
|
212
|
-
(0, import_http4.setSession)(data);
|
|
213
|
-
try {
|
|
214
|
-
await syncCurrentTeam();
|
|
215
|
-
} catch {
|
|
216
|
-
}
|
|
217
|
-
return data;
|
|
218
|
-
}
|
|
219
|
-
function postLogin(path, body) {
|
|
220
|
-
return userCenter.request({ method: "POST", path, body, auth: "none" });
|
|
221
|
-
}
|
|
222
|
-
async function getImageVerifyCode() {
|
|
223
|
-
const uuid = generateUUID();
|
|
224
|
-
const blob = await userCenter.request({
|
|
225
|
-
path: "/user/v1/sendMessageCode.jpg",
|
|
226
|
-
query: { uuid },
|
|
227
|
-
auth: "none",
|
|
228
|
-
responseType: "blob"
|
|
229
|
-
});
|
|
230
|
-
const imageBase64 = await new Promise((resolve, reject) => {
|
|
231
|
-
const reader = new FileReader();
|
|
232
|
-
reader.onloadend = () => resolve(String(reader.result ?? ""));
|
|
233
|
-
reader.onerror = () => reject(reader.error);
|
|
234
|
-
reader.readAsDataURL(blob);
|
|
235
|
-
});
|
|
236
|
-
return { uuid, imageBase64 };
|
|
237
|
-
}
|
|
238
|
-
async function loginByPassword(params, options) {
|
|
239
|
-
const config = userCenter.config();
|
|
240
|
-
const data = await postLogin("/user/v2/web/login", {
|
|
241
|
-
type: "smsPwd",
|
|
242
|
-
method: "login",
|
|
243
|
-
sourceUrl: sourceUrlOf(options),
|
|
244
|
-
data: {
|
|
245
|
-
authPhone: params.authPhone,
|
|
246
|
-
auth: aesEncryptECB(params.password, config.aesKey),
|
|
247
|
-
uuid: params.uuid,
|
|
248
|
-
imageCode: params.imageCode,
|
|
249
|
-
systemId: config.systemId
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
return completeLogin(data);
|
|
253
|
-
}
|
|
254
|
-
async function getVerifyCode(params) {
|
|
255
|
-
await postLogin("/user/v3/web/login", {
|
|
256
|
-
type: "sms",
|
|
257
|
-
method: "getVerifyCode",
|
|
258
|
-
data: {
|
|
259
|
-
authPhone: params.authPhone,
|
|
260
|
-
areaCode: params.areaCode ?? "+86",
|
|
261
|
-
...params.uuid ? { uuid: params.uuid } : {},
|
|
262
|
-
...params.imageCode ? { imageCode: params.imageCode } : {},
|
|
263
|
-
clientChannel: userCenter.config().clientChannel
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
async function loginByCode(params, options) {
|
|
268
|
-
const data = await postLogin("/user/v3/web/login", {
|
|
269
|
-
type: "sms",
|
|
270
|
-
method: "login",
|
|
271
|
-
sourceUrl: sourceUrlOf(options),
|
|
272
|
-
data: {
|
|
273
|
-
authPhone: params.authPhone,
|
|
274
|
-
areaCode: params.areaCode ?? "+86",
|
|
275
|
-
authCode: params.authCode,
|
|
276
|
-
sourceChannel: params.sourceChannel ?? ""
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
return completeLogin(data);
|
|
280
|
-
}
|
|
281
|
-
function getWechatQrCode() {
|
|
282
|
-
return userCenter.request({
|
|
283
|
-
method: "POST",
|
|
284
|
-
path: "/user/v1/web/login/wechat/qr_url",
|
|
285
|
-
body: { type: "1" },
|
|
286
|
-
auth: "none"
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
function getWechatQrCodeStatus(sessionId) {
|
|
290
|
-
return userCenter.request({
|
|
291
|
-
path: "/user/v1/web/login/wechat",
|
|
292
|
-
query: { sessionId },
|
|
293
|
-
auth: "none"
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
async function loginByWechat(params, options) {
|
|
297
|
-
const data = await postLogin("/user/v2/web/login", {
|
|
298
|
-
type: "wechatQR",
|
|
299
|
-
method: "login",
|
|
300
|
-
sourceUrl: sourceUrlOf(options),
|
|
301
|
-
data: {
|
|
302
|
-
sessionId: params.sessionId,
|
|
303
|
-
...params.authPhone ? { authPhone: params.authPhone } : {},
|
|
304
|
-
...params.authCode ? { authCode: params.authCode } : {},
|
|
305
|
-
sourceChannel: params.sourceChannel ?? ""
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
return completeLogin(data);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// src/index.ts
|
|
312
|
-
installCapabilities();
|
|
313
|
-
//# sourceMappingURL=index.cjs.map
|
|
1
|
+
"use strict";var V=Object.create;var l=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var X=Object.getPrototypeOf,Y=Object.prototype.hasOwnProperty;var G=(e,t)=>{for(var n in t)l(e,n,{get:t[n],enumerable:!0})},U=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of j(t))!Y.call(e,i)&&i!==n&&l(e,i,{get:()=>t[i],enumerable:!(r=z(t,i))||r.enumerable});return e};var m=(e,t,n)=>(n=e!=null?V(X(e)):{},U(t||!e||!e.__esModule?l(n,"default",{value:e,enumerable:!0}):n,e)),J=e=>U(l({},"__esModule",{value:!0}),e);var re={};G(re,{getImageVerifyCode:()=>D,getPersonalKey:()=>I,getUserInfo:()=>d,getVerifyCode:()=>q,getWechatQrCode:()=>F,getWechatQrCodeStatus:()=>H,loginByCode:()=>B,loginByPassword:()=>Q,loginByWechat:()=>M,logout:()=>L,resolveCurrentTeam:()=>g,switchTeam:()=>b,syncApiKey:()=>E,syncCurrentTeam:()=>u});module.exports=J(re);var f=require("@linkfox-web/http");var h=require("@linkfox-web/http");var S={baseURL:"https://api.linkfox.com",aesKey:"AR7x7zfje4nkim4t",source:"ai-linkfox-web",systemId:"LinkFoxAgent",clientChannel:"LinkFoxAI"};var Z=new Set(["30003","30009","30011","30015","30017","30019"]),k="LINKFOX_CLIENT_ID";function p(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function y(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function $(){try{let e=localStorage.getItem(k);return e||(e=y(),localStorage.setItem(k,e)),e}catch{return""}}function ee(e){try{return btoa(JSON.stringify({a_id:$(),d_id:e}))}catch{return""}}var o=(0,h.defineService)({name:"user-center",defaults:S,baseHeaders:e=>({source:e.config.source,uid:ee(e.session?.userId??"")}),authorize:e=>e.session?{authorization:e.session.accessToken}:{},isUnauthorized:(e,t)=>e.status===401||p(t)&&t.success!==!0&&Z.has(String(t.code??"")),refreshOnUnauthorized:!1,unwrap:(e,t)=>{if(p(t)&&(t.code==="OK"&&t.success!==!1||t.code===200&&t.success!==!1||t.success===!0)&&e.ok)return t.data;throw new h.LinkfoxHttpError({service:"user-center",status:e.status,code:p(t)?t.code:void 0,message:p(t)&&(t.message||t.msg)||e.statusText||`HTTP ${e.status}`,body:t})}});var a=require("@linkfox-web/http");var s=require("@linkfox-web/http");async function I(e){return(await o.request({method:"POST",path:"/linkFoxApp/api/userCenter/api-credential/getPersonalKey",body:{teamId:e||await te()}}))?.credentials??[]}async function te(){let e=(0,s.getCurrentTeam)()?.teamId;if(e)return e;let{teamList:t}=await d(),n=(g(t)??t.find(r=>r.isSelect===1)??t[0])?.teamId;if(!n)throw new s.LinkfoxHttpError({service:"user-center",status:200,code:"TEAM_REQUIRED",message:"当前账号没有可用团队"});return n}async function E(e){let t=await I(e),n=t.find(r=>r.credentialType==="USER_PRIMARY"&&r.credential)??t.find(r=>r.credential);if(!n)throw new s.LinkfoxHttpError({service:"user-center",status:200,code:"API_KEY_NOT_FOUND",message:"当前账号没有可用的 API Key"});return T(n.credential),n.credential}function T(e){(0,s.configure)({services:{"ai-tools":{apiKey:e}}})}function d(){return R(!1)}function R(e){return o.request({method:"POST",path:"/linkFoxApp/api/userCenter/userInfo",body:{},skipRecovery:e})}function g(e){if(!e?.length)return;let t=e.find(r=>r.isSelect===1);if(t?.agentMemberId)return t;let n=e.find(r=>r.teamType===1);return n?.agentMemberId?n:e.find(r=>r.agentMemberId)}function v(e){return e.aiTeamId?{id:e.aiTeamId,teamId:e.teamId,name:e.teamName,type:e.teamType,logo:e.logo??"",ownerId:e.ownerId}:null}async function u(e){let t=await R(e?.skipRecovery??!1),n=g(t.teamList),r=n?v(n):null;return r?((0,a.setTeam)(r),!0):!1}async function b(e){await o.request({method:"POST",path:"/linkFoxApp/api/userCenter/team/switchTeam",body:{targetTeamId:e}});let t=await d(),n=t.teamList.find(i=>i.teamId===e),r=n?v(n):null;return r?(0,a.setTeam)(r):(0,a.clearTeam)(),t}function L(){(0,a.clearSession)(),T(void 0)}async function ne(e){let t=await o.request({method:"POST",path:"/cp/v1/user/refresh",auth:"none",skipRecovery:!0,headers:{authorization:e.refreshToken??""}});if(!t?.accessToken)throw new Error("REFRESH_TOKEN_EXPIRED");(0,f.updateSession)({accessToken:t.accessToken,refreshToken:t.refreshToken,expireAt:t.expireAt})}function A(){(0,f.registerCapability)("refresh",ne),(0,f.registerCapability)("resolveTeam",()=>u({skipRecovery:!0}))}var C=require("@linkfox-web/http");var O=m(require("crypto-js/aes"),1),_=m(require("crypto-js/enc-utf8"),1),N=m(require("crypto-js/mode-ecb"),1),K=m(require("crypto-js/pad-zeropadding"),1);function W(e,t){return O.default.encrypt(e,_.default.parse(t),{mode:N.default,padding:K.default}).toString()}function w(e){return e?.sourceUrl??(typeof location<"u"?location.href:"")}async function P(e){if(!e?.accessToken)throw new C.LinkfoxHttpError({service:"user-center",status:200,code:"INVALID_LOGIN_RESPONSE",message:"登录响应缺少 accessToken",body:e});(0,C.setSession)(e);try{await u()}catch{}return e}function x(e,t){return o.request({method:"POST",path:e,body:t,auth:"none"})}async function D(){let e=y(),t=await o.request({path:"/user/v1/sendMessageCode.jpg",query:{uuid:e},auth:"none",responseType:"blob"}),n=await new Promise((r,i)=>{let c=new FileReader;c.onloadend=()=>r(String(c.result??"")),c.onerror=()=>i(c.error),c.readAsDataURL(t)});return{uuid:e,imageBase64:n}}async function Q(e,t){let n=o.config(),r=await x("/user/v2/web/login",{type:"smsPwd",method:"login",sourceUrl:w(t),data:{authPhone:e.authPhone,auth:W(e.password,n.aesKey),uuid:e.uuid,imageCode:e.imageCode,systemId:n.systemId}});return P(r)}async function q(e){await x("/user/v3/web/login",{type:"sms",method:"getVerifyCode",data:{authPhone:e.authPhone,areaCode:e.areaCode??"+86",...e.uuid?{uuid:e.uuid}:{},...e.imageCode?{imageCode:e.imageCode}:{},clientChannel:o.config().clientChannel}})}async function B(e,t){let n=await x("/user/v3/web/login",{type:"sms",method:"login",sourceUrl:w(t),data:{authPhone:e.authPhone,areaCode:e.areaCode??"+86",authCode:e.authCode,sourceChannel:e.sourceChannel??""}});return P(n)}function F(){return o.request({method:"POST",path:"/user/v1/web/login/wechat/qr_url",body:{type:"1"},auth:"none"})}function H(e){return o.request({path:"/user/v1/web/login/wechat",query:{sessionId:e},auth:"none"})}async function M(e,t){let n=await x("/user/v2/web/login",{type:"wechatQR",method:"login",sourceUrl:w(t),data:{sessionId:e.sessionId,...e.authPhone?{authPhone:e.authPhone}:{},...e.authCode?{authCode:e.authCode}:{},sourceChannel:e.sourceChannel??""}});return P(n)}A();
|
package/dist/index.d.cts
CHANGED
|
@@ -105,9 +105,34 @@ declare function syncCurrentTeam(options?: {
|
|
|
105
105
|
}): Promise<boolean>;
|
|
106
106
|
/** 切换团队,成功后把新团队写进会话 */
|
|
107
107
|
declare function switchTeam(teamId: string): Promise<UserInfo>;
|
|
108
|
-
/**
|
|
108
|
+
/** 登出:只清本地会话,不调后端(与主站一致);同时清掉内存里的 API Key,避免下一个登录的用户沿用 */
|
|
109
109
|
declare function logout(): void;
|
|
110
110
|
|
|
111
|
+
interface ApiCredential {
|
|
112
|
+
credentialId: string;
|
|
113
|
+
/** API Key 本身,即工具网关的 Authorization 值 */
|
|
114
|
+
credential: string;
|
|
115
|
+
/** 脱敏前缀,如 `eyJhbGciOi***` */
|
|
116
|
+
keyPrefix: string;
|
|
117
|
+
/** 如 USER_PRIMARY */
|
|
118
|
+
credentialType: string;
|
|
119
|
+
/** 如 AGENT_LEGACY */
|
|
120
|
+
keyFormat: string;
|
|
121
|
+
version: number;
|
|
122
|
+
rotated: boolean;
|
|
123
|
+
ipWhitelist: string[];
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 查询当前登录用户在指定团队下的个人 API Key 列表。
|
|
127
|
+
* teamId 是用户中心的 teamId(不是 aiTeamId);不传时取会话里的当前团队,会话里没有再查用户信息选当前团队。
|
|
128
|
+
*/
|
|
129
|
+
declare function getPersonalKey(teamId?: string): Promise<ApiCredential[]>;
|
|
130
|
+
/**
|
|
131
|
+
* 获取个人 API Key(优先 USER_PRIMARY,teamId 规则同 getPersonalKey)并注入 @linkfox-web/ai-tools 的配置,返回该 Key。
|
|
132
|
+
* 只存在内存里,刷新页面后需重新调用;没有可用 Key 时抛 API_KEY_NOT_FOUND,且不改动已有配置。
|
|
133
|
+
*/
|
|
134
|
+
declare function syncApiKey(teamId?: string): Promise<string>;
|
|
135
|
+
|
|
111
136
|
interface UserCenterConfig {
|
|
112
137
|
baseURL: string;
|
|
113
138
|
aesKey: string;
|
|
@@ -121,4 +146,4 @@ declare module '@linkfox-web/http' {
|
|
|
121
146
|
}
|
|
122
147
|
}
|
|
123
148
|
|
|
124
|
-
export { type ImageVerifyCode, type LoginOptions, type LoginResult, type UserCenterConfig, type UserInfo, type UserInfoTeam, type WechatQrCode, type WechatQrCodeStatus, getImageVerifyCode, getUserInfo, getVerifyCode, getWechatQrCode, getWechatQrCodeStatus, loginByCode, loginByPassword, loginByWechat, logout, resolveCurrentTeam, switchTeam, syncCurrentTeam };
|
|
149
|
+
export { type ApiCredential, type ImageVerifyCode, type LoginOptions, type LoginResult, type UserCenterConfig, type UserInfo, type UserInfoTeam, type WechatQrCode, type WechatQrCodeStatus, getImageVerifyCode, getPersonalKey, getUserInfo, getVerifyCode, getWechatQrCode, getWechatQrCodeStatus, loginByCode, loginByPassword, loginByWechat, logout, resolveCurrentTeam, switchTeam, syncApiKey, syncCurrentTeam };
|
package/dist/index.d.ts
CHANGED
|
@@ -105,9 +105,34 @@ declare function syncCurrentTeam(options?: {
|
|
|
105
105
|
}): Promise<boolean>;
|
|
106
106
|
/** 切换团队,成功后把新团队写进会话 */
|
|
107
107
|
declare function switchTeam(teamId: string): Promise<UserInfo>;
|
|
108
|
-
/**
|
|
108
|
+
/** 登出:只清本地会话,不调后端(与主站一致);同时清掉内存里的 API Key,避免下一个登录的用户沿用 */
|
|
109
109
|
declare function logout(): void;
|
|
110
110
|
|
|
111
|
+
interface ApiCredential {
|
|
112
|
+
credentialId: string;
|
|
113
|
+
/** API Key 本身,即工具网关的 Authorization 值 */
|
|
114
|
+
credential: string;
|
|
115
|
+
/** 脱敏前缀,如 `eyJhbGciOi***` */
|
|
116
|
+
keyPrefix: string;
|
|
117
|
+
/** 如 USER_PRIMARY */
|
|
118
|
+
credentialType: string;
|
|
119
|
+
/** 如 AGENT_LEGACY */
|
|
120
|
+
keyFormat: string;
|
|
121
|
+
version: number;
|
|
122
|
+
rotated: boolean;
|
|
123
|
+
ipWhitelist: string[];
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 查询当前登录用户在指定团队下的个人 API Key 列表。
|
|
127
|
+
* teamId 是用户中心的 teamId(不是 aiTeamId);不传时取会话里的当前团队,会话里没有再查用户信息选当前团队。
|
|
128
|
+
*/
|
|
129
|
+
declare function getPersonalKey(teamId?: string): Promise<ApiCredential[]>;
|
|
130
|
+
/**
|
|
131
|
+
* 获取个人 API Key(优先 USER_PRIMARY,teamId 规则同 getPersonalKey)并注入 @linkfox-web/ai-tools 的配置,返回该 Key。
|
|
132
|
+
* 只存在内存里,刷新页面后需重新调用;没有可用 Key 时抛 API_KEY_NOT_FOUND,且不改动已有配置。
|
|
133
|
+
*/
|
|
134
|
+
declare function syncApiKey(teamId?: string): Promise<string>;
|
|
135
|
+
|
|
111
136
|
interface UserCenterConfig {
|
|
112
137
|
baseURL: string;
|
|
113
138
|
aesKey: string;
|
|
@@ -121,4 +146,4 @@ declare module '@linkfox-web/http' {
|
|
|
121
146
|
}
|
|
122
147
|
}
|
|
123
148
|
|
|
124
|
-
export { type ImageVerifyCode, type LoginOptions, type LoginResult, type UserCenterConfig, type UserInfo, type UserInfoTeam, type WechatQrCode, type WechatQrCodeStatus, getImageVerifyCode, getUserInfo, getVerifyCode, getWechatQrCode, getWechatQrCodeStatus, loginByCode, loginByPassword, loginByWechat, logout, resolveCurrentTeam, switchTeam, syncCurrentTeam };
|
|
149
|
+
export { type ApiCredential, type ImageVerifyCode, type LoginOptions, type LoginResult, type UserCenterConfig, type UserInfo, type UserInfoTeam, type WechatQrCode, type WechatQrCodeStatus, getImageVerifyCode, getPersonalKey, getUserInfo, getVerifyCode, getWechatQrCode, getWechatQrCodeStatus, loginByCode, loginByPassword, loginByWechat, logout, resolveCurrentTeam, switchTeam, syncApiKey, syncCurrentTeam };
|
package/dist/index.js
CHANGED
|
@@ -1,280 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { registerCapability, updateSession } from "@linkfox-web/http";
|
|
3
|
-
|
|
4
|
-
// src/service.ts
|
|
5
|
-
import { defineService, LinkfoxHttpError } from "@linkfox-web/http";
|
|
6
|
-
|
|
7
|
-
// src/defaults.ts
|
|
8
|
-
var USER_CENTER_DEFAULTS = {
|
|
9
|
-
baseURL: "https://api.linkfox.com",
|
|
10
|
-
aesKey: "AR7x7zfje4nkim4t",
|
|
11
|
-
/** 请求头 source */
|
|
12
|
-
source: "ai-linkfox-web",
|
|
13
|
-
/** 账密登录的 systemId */
|
|
14
|
-
systemId: "LinkFoxAgent",
|
|
15
|
-
/** 发短信验证码的 clientChannel */
|
|
16
|
-
clientChannel: "LinkFoxAI"
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// src/service.ts
|
|
20
|
-
var AUTH_EXPIRED_CODES = /* @__PURE__ */ new Set(["30003", "30009", "30011", "30015", "30017", "30019"]);
|
|
21
|
-
var CLIENT_ID_KEY = "LINKFOX_CLIENT_ID";
|
|
22
|
-
function isEnvelope(body) {
|
|
23
|
-
return typeof body === "object" && body !== null && !Array.isArray(body);
|
|
24
|
-
}
|
|
25
|
-
function generateUUID() {
|
|
26
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
27
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
28
|
-
const r = Math.random() * 16 | 0;
|
|
29
|
-
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
function getClientId() {
|
|
33
|
-
try {
|
|
34
|
-
let id = localStorage.getItem(CLIENT_ID_KEY);
|
|
35
|
-
if (!id) {
|
|
36
|
-
id = generateUUID();
|
|
37
|
-
localStorage.setItem(CLIENT_ID_KEY, id);
|
|
38
|
-
}
|
|
39
|
-
return id;
|
|
40
|
-
} catch {
|
|
41
|
-
return "";
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function buildUid(userId) {
|
|
45
|
-
try {
|
|
46
|
-
return btoa(JSON.stringify({ a_id: getClientId(), d_id: userId }));
|
|
47
|
-
} catch {
|
|
48
|
-
return "";
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
var userCenter = defineService({
|
|
52
|
-
name: "user-center",
|
|
53
|
-
defaults: USER_CENTER_DEFAULTS,
|
|
54
|
-
baseHeaders: (ctx) => ({
|
|
55
|
-
source: ctx.config.source,
|
|
56
|
-
uid: buildUid(ctx.session?.userId ?? "")
|
|
57
|
-
}),
|
|
58
|
-
authorize: (ctx) => ctx.session ? { authorization: ctx.session.accessToken } : {},
|
|
59
|
-
isUnauthorized: (res, body) => res.status === 401 || isEnvelope(body) && body.success !== true && AUTH_EXPIRED_CODES.has(String(body.code ?? "")),
|
|
60
|
-
refreshOnUnauthorized: false,
|
|
61
|
-
unwrap: (res, body) => {
|
|
62
|
-
if (isEnvelope(body)) {
|
|
63
|
-
const ok = body.code === "OK" && body.success !== false || body.code === 200 && body.success !== false || body.success === true;
|
|
64
|
-
if (ok && res.ok) return body.data;
|
|
65
|
-
}
|
|
66
|
-
throw new LinkfoxHttpError({
|
|
67
|
-
service: "user-center",
|
|
68
|
-
status: res.status,
|
|
69
|
-
code: isEnvelope(body) ? body.code : void 0,
|
|
70
|
-
message: isEnvelope(body) && (body.message || body.msg) || res.statusText || `HTTP ${res.status}`,
|
|
71
|
-
body
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// src/user.ts
|
|
77
|
-
import { clearSession, clearTeam, setTeam } from "@linkfox-web/http";
|
|
78
|
-
function getUserInfo() {
|
|
79
|
-
return fetchUserInfo(false);
|
|
80
|
-
}
|
|
81
|
-
function fetchUserInfo(skipRecovery) {
|
|
82
|
-
return userCenter.request({ method: "POST", path: "/linkFoxApp/api/userCenter/userInfo", body: {}, skipRecovery });
|
|
83
|
-
}
|
|
84
|
-
function resolveCurrentTeam(teamList) {
|
|
85
|
-
if (!teamList?.length) return void 0;
|
|
86
|
-
const selected = teamList.find((t) => t.isSelect === 1);
|
|
87
|
-
if (selected?.agentMemberId) return selected;
|
|
88
|
-
const personal = teamList.find((t) => t.teamType === 1);
|
|
89
|
-
if (personal?.agentMemberId) return personal;
|
|
90
|
-
return teamList.find((t) => t.agentMemberId);
|
|
91
|
-
}
|
|
92
|
-
function toTeam(team) {
|
|
93
|
-
if (!team.aiTeamId) return null;
|
|
94
|
-
return {
|
|
95
|
-
id: team.aiTeamId,
|
|
96
|
-
teamId: team.teamId,
|
|
97
|
-
name: team.teamName,
|
|
98
|
-
type: team.teamType,
|
|
99
|
-
logo: team.logo ?? "",
|
|
100
|
-
ownerId: team.ownerId
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
async function syncCurrentTeam(options) {
|
|
104
|
-
const info = await fetchUserInfo(options?.skipRecovery ?? false);
|
|
105
|
-
const current = resolveCurrentTeam(info.teamList);
|
|
106
|
-
const team = current ? toTeam(current) : null;
|
|
107
|
-
if (!team) return false;
|
|
108
|
-
setTeam(team);
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
async function switchTeam(teamId) {
|
|
112
|
-
await userCenter.request({
|
|
113
|
-
method: "POST",
|
|
114
|
-
path: "/linkFoxApp/api/userCenter/team/switchTeam",
|
|
115
|
-
body: { targetTeamId: teamId }
|
|
116
|
-
});
|
|
117
|
-
const info = await getUserInfo();
|
|
118
|
-
const target = info.teamList.find((t) => t.teamId === teamId);
|
|
119
|
-
const team = target ? toTeam(target) : null;
|
|
120
|
-
if (team) setTeam(team);
|
|
121
|
-
else clearTeam();
|
|
122
|
-
return info;
|
|
123
|
-
}
|
|
124
|
-
function logout() {
|
|
125
|
-
clearSession();
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// src/capabilities.ts
|
|
129
|
-
async function refresh(session) {
|
|
130
|
-
const data = await userCenter.request({
|
|
131
|
-
method: "POST",
|
|
132
|
-
path: "/cp/v1/user/refresh",
|
|
133
|
-
auth: "none",
|
|
134
|
-
skipRecovery: true,
|
|
135
|
-
headers: { authorization: session.refreshToken ?? "" }
|
|
136
|
-
});
|
|
137
|
-
if (!data?.accessToken) throw new Error("REFRESH_TOKEN_EXPIRED");
|
|
138
|
-
updateSession({ accessToken: data.accessToken, refreshToken: data.refreshToken, expireAt: data.expireAt });
|
|
139
|
-
}
|
|
140
|
-
function installCapabilities() {
|
|
141
|
-
registerCapability("refresh", refresh);
|
|
142
|
-
registerCapability("resolveTeam", () => syncCurrentTeam({ skipRecovery: true }));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// src/auth.ts
|
|
146
|
-
import { LinkfoxHttpError as LinkfoxHttpError2, setSession } from "@linkfox-web/http";
|
|
147
|
-
|
|
148
|
-
// src/crypto.ts
|
|
149
|
-
import AES from "crypto-js/aes";
|
|
150
|
-
import Utf8 from "crypto-js/enc-utf8";
|
|
151
|
-
import ECB from "crypto-js/mode-ecb";
|
|
152
|
-
import ZeroPadding from "crypto-js/pad-zeropadding";
|
|
153
|
-
function aesEncryptECB(plain, key) {
|
|
154
|
-
return AES.encrypt(plain, Utf8.parse(key), { mode: ECB, padding: ZeroPadding }).toString();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// src/auth.ts
|
|
158
|
-
function sourceUrlOf(options) {
|
|
159
|
-
return options?.sourceUrl ?? (typeof location !== "undefined" ? location.href : "");
|
|
160
|
-
}
|
|
161
|
-
async function completeLogin(data) {
|
|
162
|
-
if (!data?.accessToken) {
|
|
163
|
-
throw new LinkfoxHttpError2({ service: "user-center", status: 200, code: "INVALID_LOGIN_RESPONSE", message: "\u767B\u5F55\u54CD\u5E94\u7F3A\u5C11 accessToken", body: data });
|
|
164
|
-
}
|
|
165
|
-
setSession(data);
|
|
166
|
-
try {
|
|
167
|
-
await syncCurrentTeam();
|
|
168
|
-
} catch {
|
|
169
|
-
}
|
|
170
|
-
return data;
|
|
171
|
-
}
|
|
172
|
-
function postLogin(path, body) {
|
|
173
|
-
return userCenter.request({ method: "POST", path, body, auth: "none" });
|
|
174
|
-
}
|
|
175
|
-
async function getImageVerifyCode() {
|
|
176
|
-
const uuid = generateUUID();
|
|
177
|
-
const blob = await userCenter.request({
|
|
178
|
-
path: "/user/v1/sendMessageCode.jpg",
|
|
179
|
-
query: { uuid },
|
|
180
|
-
auth: "none",
|
|
181
|
-
responseType: "blob"
|
|
182
|
-
});
|
|
183
|
-
const imageBase64 = await new Promise((resolve, reject) => {
|
|
184
|
-
const reader = new FileReader();
|
|
185
|
-
reader.onloadend = () => resolve(String(reader.result ?? ""));
|
|
186
|
-
reader.onerror = () => reject(reader.error);
|
|
187
|
-
reader.readAsDataURL(blob);
|
|
188
|
-
});
|
|
189
|
-
return { uuid, imageBase64 };
|
|
190
|
-
}
|
|
191
|
-
async function loginByPassword(params, options) {
|
|
192
|
-
const config = userCenter.config();
|
|
193
|
-
const data = await postLogin("/user/v2/web/login", {
|
|
194
|
-
type: "smsPwd",
|
|
195
|
-
method: "login",
|
|
196
|
-
sourceUrl: sourceUrlOf(options),
|
|
197
|
-
data: {
|
|
198
|
-
authPhone: params.authPhone,
|
|
199
|
-
auth: aesEncryptECB(params.password, config.aesKey),
|
|
200
|
-
uuid: params.uuid,
|
|
201
|
-
imageCode: params.imageCode,
|
|
202
|
-
systemId: config.systemId
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
return completeLogin(data);
|
|
206
|
-
}
|
|
207
|
-
async function getVerifyCode(params) {
|
|
208
|
-
await postLogin("/user/v3/web/login", {
|
|
209
|
-
type: "sms",
|
|
210
|
-
method: "getVerifyCode",
|
|
211
|
-
data: {
|
|
212
|
-
authPhone: params.authPhone,
|
|
213
|
-
areaCode: params.areaCode ?? "+86",
|
|
214
|
-
...params.uuid ? { uuid: params.uuid } : {},
|
|
215
|
-
...params.imageCode ? { imageCode: params.imageCode } : {},
|
|
216
|
-
clientChannel: userCenter.config().clientChannel
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
async function loginByCode(params, options) {
|
|
221
|
-
const data = await postLogin("/user/v3/web/login", {
|
|
222
|
-
type: "sms",
|
|
223
|
-
method: "login",
|
|
224
|
-
sourceUrl: sourceUrlOf(options),
|
|
225
|
-
data: {
|
|
226
|
-
authPhone: params.authPhone,
|
|
227
|
-
areaCode: params.areaCode ?? "+86",
|
|
228
|
-
authCode: params.authCode,
|
|
229
|
-
sourceChannel: params.sourceChannel ?? ""
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
return completeLogin(data);
|
|
233
|
-
}
|
|
234
|
-
function getWechatQrCode() {
|
|
235
|
-
return userCenter.request({
|
|
236
|
-
method: "POST",
|
|
237
|
-
path: "/user/v1/web/login/wechat/qr_url",
|
|
238
|
-
body: { type: "1" },
|
|
239
|
-
auth: "none"
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
function getWechatQrCodeStatus(sessionId) {
|
|
243
|
-
return userCenter.request({
|
|
244
|
-
path: "/user/v1/web/login/wechat",
|
|
245
|
-
query: { sessionId },
|
|
246
|
-
auth: "none"
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
async function loginByWechat(params, options) {
|
|
250
|
-
const data = await postLogin("/user/v2/web/login", {
|
|
251
|
-
type: "wechatQR",
|
|
252
|
-
method: "login",
|
|
253
|
-
sourceUrl: sourceUrlOf(options),
|
|
254
|
-
data: {
|
|
255
|
-
sessionId: params.sessionId,
|
|
256
|
-
...params.authPhone ? { authPhone: params.authPhone } : {},
|
|
257
|
-
...params.authCode ? { authCode: params.authCode } : {},
|
|
258
|
-
sourceChannel: params.sourceChannel ?? ""
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
return completeLogin(data);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// src/index.ts
|
|
265
|
-
installCapabilities();
|
|
266
|
-
export {
|
|
267
|
-
getImageVerifyCode,
|
|
268
|
-
getUserInfo,
|
|
269
|
-
getVerifyCode,
|
|
270
|
-
getWechatQrCode,
|
|
271
|
-
getWechatQrCodeStatus,
|
|
272
|
-
loginByCode,
|
|
273
|
-
loginByPassword,
|
|
274
|
-
loginByWechat,
|
|
275
|
-
logout,
|
|
276
|
-
resolveCurrentTeam,
|
|
277
|
-
switchTeam,
|
|
278
|
-
syncCurrentTeam
|
|
279
|
-
};
|
|
280
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import{registerCapability as P,updateSession as Q}from"@linkfox-web/http";import{defineService as k,LinkfoxHttpError as E}from"@linkfox-web/http";var h={baseURL:"https://api.linkfox.com",aesKey:"AR7x7zfje4nkim4t",source:"ai-linkfox-web",systemId:"LinkFoxAgent",clientChannel:"LinkFoxAI"};var R=new Set(["30003","30009","30011","30015","30017","30019"]),C="LINKFOX_CLIENT_ID";function a(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function f(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function v(){try{let e=localStorage.getItem(C);return e||(e=f(),localStorage.setItem(C,e)),e}catch{return""}}function b(e){try{return btoa(JSON.stringify({a_id:v(),d_id:e}))}catch{return""}}var o=k({name:"user-center",defaults:h,baseHeaders:e=>({source:e.config.source,uid:b(e.session?.userId??"")}),authorize:e=>e.session?{authorization:e.session.accessToken}:{},isUnauthorized:(e,t)=>e.status===401||a(t)&&t.success!==!0&&R.has(String(t.code??"")),refreshOnUnauthorized:!1,unwrap:(e,t)=>{if(a(t)&&(t.code==="OK"&&t.success!==!1||t.code===200&&t.success!==!1||t.success===!0)&&e.ok)return t.data;throw new E({service:"user-center",status:e.status,code:a(t)?t.code:void 0,message:a(t)&&(t.message||t.msg)||e.statusText||`HTTP ${e.status}`,body:t})}});import{clearSession as N,clearTeam as K,setTeam as I}from"@linkfox-web/http";import{configure as L,getCurrentTeam as A,LinkfoxHttpError as x}from"@linkfox-web/http";async function y(e){return(await o.request({method:"POST",path:"/linkFoxApp/api/userCenter/api-credential/getPersonalKey",body:{teamId:e||await O()}}))?.credentials??[]}async function O(){let e=A()?.teamId;if(e)return e;let{teamList:t}=await u(),n=(c(t)??t.find(r=>r.isSelect===1)??t[0])?.teamId;if(!n)throw new x({service:"user-center",status:200,code:"TEAM_REQUIRED",message:"当前账号没有可用团队"});return n}async function _(e){let t=await y(e),n=t.find(r=>r.credentialType==="USER_PRIMARY"&&r.credential)??t.find(r=>r.credential);if(!n)throw new x({service:"user-center",status:200,code:"API_KEY_NOT_FOUND",message:"当前账号没有可用的 API Key"});return l(n.credential),n.credential}function l(e){L({services:{"ai-tools":{apiKey:e}}})}function u(){return T(!1)}function T(e){return o.request({method:"POST",path:"/linkFoxApp/api/userCenter/userInfo",body:{},skipRecovery:e})}function c(e){if(!e?.length)return;let t=e.find(r=>r.isSelect===1);if(t?.agentMemberId)return t;let n=e.find(r=>r.teamType===1);return n?.agentMemberId?n:e.find(r=>r.agentMemberId)}function w(e){return e.aiTeamId?{id:e.aiTeamId,teamId:e.teamId,name:e.teamName,type:e.teamType,logo:e.logo??"",ownerId:e.ownerId}:null}async function s(e){let t=await T(e?.skipRecovery??!1),n=c(t.teamList),r=n?w(n):null;return r?(I(r),!0):!1}async function W(e){await o.request({method:"POST",path:"/linkFoxApp/api/userCenter/team/switchTeam",body:{targetTeamId:e}});let t=await u(),n=t.teamList.find(g=>g.teamId===e),r=n?w(n):null;return r?I(r):K(),t}function D(){N(),l(void 0)}async function q(e){let t=await o.request({method:"POST",path:"/cp/v1/user/refresh",auth:"none",skipRecovery:!0,headers:{authorization:e.refreshToken??""}});if(!t?.accessToken)throw new Error("REFRESH_TOKEN_EXPIRED");Q({accessToken:t.accessToken,refreshToken:t.refreshToken,expireAt:t.expireAt})}function U(){P("refresh",q),P("resolveTeam",()=>s({skipRecovery:!0}))}import{LinkfoxHttpError as V,setSession as z}from"@linkfox-web/http";import B from"crypto-js/aes";import F from"crypto-js/enc-utf8";import H from"crypto-js/mode-ecb";import M from"crypto-js/pad-zeropadding";function S(e,t){return B.encrypt(e,F.parse(t),{mode:H,padding:M}).toString()}function m(e){return e?.sourceUrl??(typeof location<"u"?location.href:"")}async function p(e){if(!e?.accessToken)throw new V({service:"user-center",status:200,code:"INVALID_LOGIN_RESPONSE",message:"登录响应缺少 accessToken",body:e});z(e);try{await s()}catch{}return e}function d(e,t){return o.request({method:"POST",path:e,body:t,auth:"none"})}async function j(){let e=f(),t=await o.request({path:"/user/v1/sendMessageCode.jpg",query:{uuid:e},auth:"none",responseType:"blob"}),n=await new Promise((r,g)=>{let i=new FileReader;i.onloadend=()=>r(String(i.result??"")),i.onerror=()=>g(i.error),i.readAsDataURL(t)});return{uuid:e,imageBase64:n}}async function X(e,t){let n=o.config(),r=await d("/user/v2/web/login",{type:"smsPwd",method:"login",sourceUrl:m(t),data:{authPhone:e.authPhone,auth:S(e.password,n.aesKey),uuid:e.uuid,imageCode:e.imageCode,systemId:n.systemId}});return p(r)}async function Y(e){await d("/user/v3/web/login",{type:"sms",method:"getVerifyCode",data:{authPhone:e.authPhone,areaCode:e.areaCode??"+86",...e.uuid?{uuid:e.uuid}:{},...e.imageCode?{imageCode:e.imageCode}:{},clientChannel:o.config().clientChannel}})}async function G(e,t){let n=await d("/user/v3/web/login",{type:"sms",method:"login",sourceUrl:m(t),data:{authPhone:e.authPhone,areaCode:e.areaCode??"+86",authCode:e.authCode,sourceChannel:e.sourceChannel??""}});return p(n)}function J(){return o.request({method:"POST",path:"/user/v1/web/login/wechat/qr_url",body:{type:"1"},auth:"none"})}function Z(e){return o.request({path:"/user/v1/web/login/wechat",query:{sessionId:e},auth:"none"})}async function $(e,t){let n=await d("/user/v2/web/login",{type:"wechatQR",method:"login",sourceUrl:m(t),data:{sessionId:e.sessionId,...e.authPhone?{authPhone:e.authPhone}:{},...e.authCode?{authCode:e.authCode}:{},sourceChannel:e.sourceChannel??""}});return p(n)}U();export{j as getImageVerifyCode,y as getPersonalKey,u as getUserInfo,Y as getVerifyCode,J as getWechatQrCode,Z as getWechatQrCodeStatus,G as loginByCode,X as loginByPassword,$ as loginByWechat,D as logout,c as resolveCurrentTeam,W as switchTeam,_ as syncApiKey,s as syncCurrentTeam};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linkfox-web/api-user-center",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "LinkFox 用户中心接口:登录、刷新、用户信息、切换团队",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
"./dist/index.cjs"
|
|
32
32
|
],
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@linkfox-web/http": "^0.
|
|
34
|
+
"@linkfox-web/http": "^0.2.0"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"crypto-js": "4.2.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/crypto-js": "4.2.2",
|
|
41
|
-
"@linkfox-web/http": "0.
|
|
41
|
+
"@linkfox-web/http": "0.2.0"
|
|
42
42
|
},
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/capabilities.ts","../src/service.ts","../src/defaults.ts","../src/user.ts","../src/auth.ts","../src/crypto.ts"],"sourcesContent":["import { installCapabilities } from './capabilities'\n\n// 任何对本包的 import 都会注册「刷新」「补团队」能力,其他服务包因此在登录后能直接调通。\n// 写成显式调用而不是裸 import:裸 import 会被打包器按 sideEffects 摇掉(实测 esbuild 会)。\n// package.json 的 sideEffects 也必须保留入口产物,防止调用方的打包器摇掉这一行。\ninstallCapabilities()\n\nexport {\n getImageVerifyCode,\n getVerifyCode,\n getWechatQrCode,\n getWechatQrCodeStatus,\n loginByCode,\n loginByPassword,\n loginByWechat,\n} from './auth'\nexport type { ImageVerifyCode, LoginOptions, LoginResult, WechatQrCode, WechatQrCodeStatus } from './auth'\nexport { getUserInfo, logout, resolveCurrentTeam, switchTeam, syncCurrentTeam } from './user'\nexport type { UserInfo, UserInfoTeam } from './user'\nexport type { UserCenterConfig } from './service'\n","import { registerCapability, updateSession, type Session } from '@linkfox-web/http'\nimport { userCenter } from './service'\nimport { syncCurrentTeam } from './user'\n\ninterface RefreshResult {\n accessToken: string\n refreshToken: string\n expireAt?: number\n}\n\n/** 用 refreshToken 换新 token,只合并 token 字段,会话里的其他用户信息保留 */\nasync function refresh(session: Session): Promise<void> {\n const data = await userCenter.request<RefreshResult>({\n method: 'POST',\n path: '/cp/v1/user/refresh',\n auth: 'none',\n skipRecovery: true,\n headers: { authorization: session.refreshToken ?? '' },\n })\n if (!data?.accessToken) throw new Error('REFRESH_TOKEN_EXPIRED')\n updateSession({ accessToken: data.accessToken, refreshToken: data.refreshToken, expireAt: data.expireAt })\n}\n\n/** 把「刷新」「补团队」能力注册到 http,由入口模块顶层调用 */\nexport function installCapabilities(): void {\n registerCapability('refresh', refresh)\n // 恢复流程里 token 可能已过期:不能让用户中心自己清会话,要抛回去让 http 先刷新\n registerCapability('resolveTeam', () => syncCurrentTeam({ skipRecovery: true }))\n}\n","import { defineService, LinkfoxHttpError } from '@linkfox-web/http'\nimport { USER_CENTER_DEFAULTS } from './defaults'\n\nexport interface UserCenterConfig {\n baseURL: string\n aesKey: string\n source: string\n systemId: string\n clientChannel: string\n}\n\ndeclare module '@linkfox-web/http' {\n interface ServiceConfigMap {\n 'user-center': UserCenterConfig\n }\n}\n\n/** 用户中心的登录失效业务码(与现状一致,命中后不刷新,直接通知未登录) */\nconst AUTH_EXPIRED_CODES = new Set(['30003', '30009', '30011', '30015', '30017', '30019'])\n\nconst CLIENT_ID_KEY = 'LINKFOX_CLIENT_ID'\n\ninterface CenterEnvelope {\n code?: string | number\n success?: boolean\n message?: string\n msg?: string\n data?: unknown\n}\n\nfunction isEnvelope(body: unknown): body is CenterEnvelope {\n return typeof body === 'object' && body !== null && !Array.isArray(body)\n}\n\nexport function generateUUID(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID()\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)\n })\n}\n\n/** 设备级 clientId,持久化到 localStorage(与主站同键) */\nfunction getClientId(): string {\n try {\n let id = localStorage.getItem(CLIENT_ID_KEY)\n if (!id) {\n id = generateUUID()\n localStorage.setItem(CLIENT_ID_KEY, id)\n }\n return id\n } catch {\n return ''\n }\n}\n\n/** uid = btoa(JSON.stringify({ a_id: clientId, d_id: userId })) */\nfunction buildUid(userId: string): string {\n try {\n return btoa(JSON.stringify({ a_id: getClientId(), d_id: userId }))\n } catch {\n return ''\n }\n}\n\nexport const userCenter = defineService({\n name: 'user-center',\n defaults: USER_CENTER_DEFAULTS,\n baseHeaders: (ctx) => ({\n source: ctx.config.source,\n uid: buildUid(ctx.session?.userId ?? ''),\n }),\n authorize: (ctx): Record<string, string> => (ctx.session ? { authorization: ctx.session.accessToken } : {}),\n isUnauthorized: (res, body) =>\n res.status === 401 || (isEnvelope(body) && body.success !== true && AUTH_EXPIRED_CODES.has(String(body.code ?? ''))),\n refreshOnUnauthorized: false,\n unwrap: (res, body) => {\n if (isEnvelope(body)) {\n // 用户中心有两种成功信封:{ code: 'OK', success: true } 与 { code: 200 }\n const ok = (body.code === 'OK' && body.success !== false) || (body.code === 200 && body.success !== false) || body.success === true\n if (ok && res.ok) return body.data\n }\n throw new LinkfoxHttpError({\n service: 'user-center',\n status: res.status,\n code: isEnvelope(body) ? body.code : undefined,\n message: (isEnvelope(body) && (body.message || body.msg)) || res.statusText || `HTTP ${res.status}`,\n body,\n })\n },\n})\n","/**\n * 生产环境默认配置(按需求写死在包里,其他环境用 configure({ services }) 覆盖)。\n * AES 密钥各环境不同:覆盖 baseURL 切环境时必须同时覆盖 aesKey,否则登录失败。\n */\nexport const USER_CENTER_DEFAULTS = {\n baseURL: 'https://api.linkfox.com',\n aesKey: 'AR7x7zfje4nkim4t',\n /** 请求头 source */\n source: 'ai-linkfox-web',\n /** 账密登录的 systemId */\n systemId: 'LinkFoxAgent',\n /** 发短信验证码的 clientChannel */\n clientChannel: 'LinkFoxAI',\n}\n","import { clearSession, clearTeam, setTeam, type Team } from '@linkfox-web/http'\nimport { userCenter } from './service'\n\nexport interface UserInfoTeam {\n teamId: string\n teamName: string\n aiTeamId: string | null\n agentTeamId: string | null\n agentMemberId: string | null\n /** 1=所有者 2=成员 3=管理员 4=开放平台用户 */\n role: string\n logo: string | null\n /** 1=当前选中 */\n isSelect: number\n /** 1=个人 2=团队 */\n teamType: number\n ownerId: string\n mergeSourceInfo: string | null\n}\n\nexport interface UserInfo {\n id: string\n nickName: string\n phoneNum: string\n avatar: string | null\n verifyStatus: boolean\n bindWechat: boolean\n status: number\n /** 注销冷静期到期时间戳(毫秒) */\n freezeExpireTime?: number\n teamList: UserInfoTeam[]\n}\n\nexport function getUserInfo(): Promise<UserInfo> {\n return fetchUserInfo(false)\n}\n\n/** skipRecovery:未登录时直接抛错,不走本服务的清会话、通知流程(供其他服务的 401 恢复调用) */\nfunction fetchUserInfo(skipRecovery: boolean): Promise<UserInfo> {\n return userCenter.request<UserInfo>({ method: 'POST', path: '/linkFoxApp/api/userCenter/userInfo', body: {}, skipRecovery })\n}\n\n/** 选出当前团队:优先 isSelect=1,没有 agentMemberId 时回退个人账号,再回退任意可用团队 */\nexport function resolveCurrentTeam(teamList: UserInfoTeam[] | undefined): UserInfoTeam | undefined {\n if (!teamList?.length) return undefined\n const selected = teamList.find((t) => t.isSelect === 1)\n if (selected?.agentMemberId) return selected\n const personal = teamList.find((t) => t.teamType === 1)\n if (personal?.agentMemberId) return personal\n return teamList.find((t) => t.agentMemberId)\n}\n\nfunction toTeam(team: UserInfoTeam): Team | null {\n if (!team.aiTeamId) return null\n return {\n id: team.aiTeamId,\n teamId: team.teamId,\n name: team.teamName,\n type: team.teamType,\n logo: team.logo ?? '',\n ownerId: team.ownerId,\n }\n}\n\n/**\n * 拉用户信息并把当前团队写进会话;写到返回 true。\n * `skipRecovery` 为 true 时 token 失效直接抛错,由调用方决定是否刷新(401 恢复流程里用)。\n */\nexport async function syncCurrentTeam(options?: { skipRecovery?: boolean }): Promise<boolean> {\n const info = await fetchUserInfo(options?.skipRecovery ?? false)\n const current = resolveCurrentTeam(info.teamList)\n const team = current ? toTeam(current) : null\n // 解析不到时保持既有团队不动,避免误清其他站点写入的团队\n if (!team) return false\n setTeam(team)\n return true\n}\n\n/** 切换团队,成功后把新团队写进会话 */\nexport async function switchTeam(teamId: string): Promise<UserInfo> {\n await userCenter.request<unknown>({\n method: 'POST',\n path: '/linkFoxApp/api/userCenter/team/switchTeam',\n body: { targetTeamId: teamId },\n })\n const info = await getUserInfo()\n const target = info.teamList.find((t) => t.teamId === teamId)\n const team = target ? toTeam(target) : null\n if (team) setTeam(team)\n // 切到没有 AI 团队的账号(如个人账号)时移除条目,与主站一致\n else clearTeam()\n return info\n}\n\n/** 登出:只清本地会话,不调后端(与主站一致) */\nexport function logout(): void {\n clearSession()\n}\n","import { LinkfoxHttpError, setSession, type Session } from '@linkfox-web/http'\nimport { aesEncryptECB } from './crypto'\nimport { generateUUID, userCenter } from './service'\nimport { syncCurrentTeam } from './user'\n\n/** 登录接口返回:token 字段之外的用户信息原样写进会话 */\nexport interface LoginResult extends Session {\n userName?: string\n email?: string\n phoneNum?: string\n avatarUrl?: string\n loginType?: string\n newUser?: boolean\n aiNewUser?: boolean\n sourceChannel?: string\n}\n\nexport interface LoginOptions {\n /** 登录来源页,默认 location.href */\n sourceUrl?: string\n}\n\nfunction sourceUrlOf(options?: LoginOptions): string {\n return options?.sourceUrl ?? (typeof location !== 'undefined' ? location.href : '')\n}\n\n/**\n * 写会话后同步当前团队。拉团队失败不让登录失败:团队会在第一次被 Teable 等服务\n * 以缺团队拒绝时,由 resolveTeam 能力再补一次。\n */\nasync function completeLogin(data: LoginResult | undefined): Promise<LoginResult> {\n if (!data?.accessToken) {\n throw new LinkfoxHttpError({ service: 'user-center', status: 200, code: 'INVALID_LOGIN_RESPONSE', message: '登录响应缺少 accessToken', body: data })\n }\n setSession(data)\n try {\n await syncCurrentTeam()\n } catch {\n // 见函数注释\n }\n return data\n}\n\nfunction postLogin(path: string, body: unknown): Promise<LoginResult | undefined> {\n return userCenter.request<LoginResult | undefined>({ method: 'POST', path, body, auth: 'none' })\n}\n\nexport interface ImageVerifyCode {\n uuid: string\n /** data URL,可直接放进 img src */\n imageBase64: string\n}\n\n/** 获取图形验证码 */\nexport async function getImageVerifyCode(): Promise<ImageVerifyCode> {\n const uuid = generateUUID()\n const blob = await userCenter.request<Blob>({\n path: '/user/v1/sendMessageCode.jpg',\n query: { uuid },\n auth: 'none',\n responseType: 'blob',\n })\n const imageBase64 = await new Promise<string>((resolve, reject) => {\n const reader = new FileReader()\n reader.onloadend = () => resolve(String(reader.result ?? ''))\n reader.onerror = () => reject(reader.error)\n reader.readAsDataURL(blob)\n })\n return { uuid, imageBase64 }\n}\n\n/** 账密登录 */\nexport async function loginByPassword(\n params: { authPhone: string; password: string; imageCode: string; uuid: string },\n options?: LoginOptions,\n): Promise<LoginResult> {\n const config = userCenter.config()\n const data = await postLogin('/user/v2/web/login', {\n type: 'smsPwd',\n method: 'login',\n sourceUrl: sourceUrlOf(options),\n data: {\n authPhone: params.authPhone,\n auth: aesEncryptECB(params.password, config.aesKey),\n uuid: params.uuid,\n imageCode: params.imageCode,\n systemId: config.systemId,\n },\n })\n return completeLogin(data)\n}\n\n/** 发送短信验证码 */\nexport async function getVerifyCode(params: {\n authPhone: string\n areaCode?: string\n uuid?: string\n imageCode?: string\n}): Promise<void> {\n await postLogin('/user/v3/web/login', {\n type: 'sms',\n method: 'getVerifyCode',\n data: {\n authPhone: params.authPhone,\n areaCode: params.areaCode ?? '+86',\n ...(params.uuid ? { uuid: params.uuid } : {}),\n ...(params.imageCode ? { imageCode: params.imageCode } : {}),\n clientChannel: userCenter.config().clientChannel,\n },\n })\n}\n\n/** 短信验证码登录 */\nexport async function loginByCode(\n params: { authPhone: string; authCode: string; areaCode?: string; sourceChannel?: string },\n options?: LoginOptions,\n): Promise<LoginResult> {\n const data = await postLogin('/user/v3/web/login', {\n type: 'sms',\n method: 'login',\n sourceUrl: sourceUrlOf(options),\n data: {\n authPhone: params.authPhone,\n areaCode: params.areaCode ?? '+86',\n authCode: params.authCode,\n sourceChannel: params.sourceChannel ?? '',\n },\n })\n return completeLogin(data)\n}\n\nexport interface WechatQrCode {\n qrUrl: string\n sessionId: string\n}\n\n/** 获取微信登录二维码 */\nexport function getWechatQrCode(): Promise<WechatQrCode> {\n return userCenter.request<WechatQrCode>({\n method: 'POST',\n path: '/user/v1/web/login/wechat/qr_url',\n body: { type: '1' },\n auth: 'none',\n })\n}\n\nexport interface WechatQrCodeStatus {\n /** 2=已扫码 */\n status: number\n /** true 表示需要绑定手机号 */\n first: boolean\n}\n\n/** 轮询微信二维码扫码状态 */\nexport function getWechatQrCodeStatus(sessionId: string): Promise<WechatQrCodeStatus> {\n return userCenter.request<WechatQrCodeStatus>({\n path: '/user/v1/web/login/wechat',\n query: { sessionId },\n auth: 'none',\n })\n}\n\n/** 微信扫码登录;需要绑定手机号时附带 authPhone / authCode */\nexport async function loginByWechat(\n params: { sessionId: string; authPhone?: string; authCode?: string; sourceChannel?: string },\n options?: LoginOptions,\n): Promise<LoginResult> {\n const data = await postLogin('/user/v2/web/login', {\n type: 'wechatQR',\n method: 'login',\n sourceUrl: sourceUrlOf(options),\n data: {\n sessionId: params.sessionId,\n ...(params.authPhone ? { authPhone: params.authPhone } : {}),\n ...(params.authCode ? { authCode: params.authCode } : {}),\n sourceChannel: params.sourceChannel ?? '',\n },\n })\n return completeLogin(data)\n}\n","import AES from 'crypto-js/aes'\nimport Utf8 from 'crypto-js/enc-utf8'\nimport ECB from 'crypto-js/mode-ecb'\nimport ZeroPadding from 'crypto-js/pad-zeropadding'\n\n/** 登录密码加密:AES-ECB + ZeroPadding,与用户中心约定一致 */\nexport function aesEncryptECB(plain: string, key: string): string {\n return AES.encrypt(plain, Utf8.parse(key), { mode: ECB, padding: ZeroPadding }).toString()\n}\n"],"mappings":"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","names":["import_http","import_http","import_http","AES","Utf8","ECB","ZeroPadding"]}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/capabilities.ts","../src/service.ts","../src/defaults.ts","../src/user.ts","../src/auth.ts","../src/crypto.ts","../src/index.ts"],"sourcesContent":["import { registerCapability, updateSession, type Session } from '@linkfox-web/http'\nimport { userCenter } from './service'\nimport { syncCurrentTeam } from './user'\n\ninterface RefreshResult {\n accessToken: string\n refreshToken: string\n expireAt?: number\n}\n\n/** 用 refreshToken 换新 token,只合并 token 字段,会话里的其他用户信息保留 */\nasync function refresh(session: Session): Promise<void> {\n const data = await userCenter.request<RefreshResult>({\n method: 'POST',\n path: '/cp/v1/user/refresh',\n auth: 'none',\n skipRecovery: true,\n headers: { authorization: session.refreshToken ?? '' },\n })\n if (!data?.accessToken) throw new Error('REFRESH_TOKEN_EXPIRED')\n updateSession({ accessToken: data.accessToken, refreshToken: data.refreshToken, expireAt: data.expireAt })\n}\n\n/** 把「刷新」「补团队」能力注册到 http,由入口模块顶层调用 */\nexport function installCapabilities(): void {\n registerCapability('refresh', refresh)\n // 恢复流程里 token 可能已过期:不能让用户中心自己清会话,要抛回去让 http 先刷新\n registerCapability('resolveTeam', () => syncCurrentTeam({ skipRecovery: true }))\n}\n","import { defineService, LinkfoxHttpError } from '@linkfox-web/http'\nimport { USER_CENTER_DEFAULTS } from './defaults'\n\nexport interface UserCenterConfig {\n baseURL: string\n aesKey: string\n source: string\n systemId: string\n clientChannel: string\n}\n\ndeclare module '@linkfox-web/http' {\n interface ServiceConfigMap {\n 'user-center': UserCenterConfig\n }\n}\n\n/** 用户中心的登录失效业务码(与现状一致,命中后不刷新,直接通知未登录) */\nconst AUTH_EXPIRED_CODES = new Set(['30003', '30009', '30011', '30015', '30017', '30019'])\n\nconst CLIENT_ID_KEY = 'LINKFOX_CLIENT_ID'\n\ninterface CenterEnvelope {\n code?: string | number\n success?: boolean\n message?: string\n msg?: string\n data?: unknown\n}\n\nfunction isEnvelope(body: unknown): body is CenterEnvelope {\n return typeof body === 'object' && body !== null && !Array.isArray(body)\n}\n\nexport function generateUUID(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID()\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)\n })\n}\n\n/** 设备级 clientId,持久化到 localStorage(与主站同键) */\nfunction getClientId(): string {\n try {\n let id = localStorage.getItem(CLIENT_ID_KEY)\n if (!id) {\n id = generateUUID()\n localStorage.setItem(CLIENT_ID_KEY, id)\n }\n return id\n } catch {\n return ''\n }\n}\n\n/** uid = btoa(JSON.stringify({ a_id: clientId, d_id: userId })) */\nfunction buildUid(userId: string): string {\n try {\n return btoa(JSON.stringify({ a_id: getClientId(), d_id: userId }))\n } catch {\n return ''\n }\n}\n\nexport const userCenter = defineService({\n name: 'user-center',\n defaults: USER_CENTER_DEFAULTS,\n baseHeaders: (ctx) => ({\n source: ctx.config.source,\n uid: buildUid(ctx.session?.userId ?? ''),\n }),\n authorize: (ctx): Record<string, string> => (ctx.session ? { authorization: ctx.session.accessToken } : {}),\n isUnauthorized: (res, body) =>\n res.status === 401 || (isEnvelope(body) && body.success !== true && AUTH_EXPIRED_CODES.has(String(body.code ?? ''))),\n refreshOnUnauthorized: false,\n unwrap: (res, body) => {\n if (isEnvelope(body)) {\n // 用户中心有两种成功信封:{ code: 'OK', success: true } 与 { code: 200 }\n const ok = (body.code === 'OK' && body.success !== false) || (body.code === 200 && body.success !== false) || body.success === true\n if (ok && res.ok) return body.data\n }\n throw new LinkfoxHttpError({\n service: 'user-center',\n status: res.status,\n code: isEnvelope(body) ? body.code : undefined,\n message: (isEnvelope(body) && (body.message || body.msg)) || res.statusText || `HTTP ${res.status}`,\n body,\n })\n },\n})\n","/**\n * 生产环境默认配置(按需求写死在包里,其他环境用 configure({ services }) 覆盖)。\n * AES 密钥各环境不同:覆盖 baseURL 切环境时必须同时覆盖 aesKey,否则登录失败。\n */\nexport const USER_CENTER_DEFAULTS = {\n baseURL: 'https://api.linkfox.com',\n aesKey: 'AR7x7zfje4nkim4t',\n /** 请求头 source */\n source: 'ai-linkfox-web',\n /** 账密登录的 systemId */\n systemId: 'LinkFoxAgent',\n /** 发短信验证码的 clientChannel */\n clientChannel: 'LinkFoxAI',\n}\n","import { clearSession, clearTeam, setTeam, type Team } from '@linkfox-web/http'\nimport { userCenter } from './service'\n\nexport interface UserInfoTeam {\n teamId: string\n teamName: string\n aiTeamId: string | null\n agentTeamId: string | null\n agentMemberId: string | null\n /** 1=所有者 2=成员 3=管理员 4=开放平台用户 */\n role: string\n logo: string | null\n /** 1=当前选中 */\n isSelect: number\n /** 1=个人 2=团队 */\n teamType: number\n ownerId: string\n mergeSourceInfo: string | null\n}\n\nexport interface UserInfo {\n id: string\n nickName: string\n phoneNum: string\n avatar: string | null\n verifyStatus: boolean\n bindWechat: boolean\n status: number\n /** 注销冷静期到期时间戳(毫秒) */\n freezeExpireTime?: number\n teamList: UserInfoTeam[]\n}\n\nexport function getUserInfo(): Promise<UserInfo> {\n return fetchUserInfo(false)\n}\n\n/** skipRecovery:未登录时直接抛错,不走本服务的清会话、通知流程(供其他服务的 401 恢复调用) */\nfunction fetchUserInfo(skipRecovery: boolean): Promise<UserInfo> {\n return userCenter.request<UserInfo>({ method: 'POST', path: '/linkFoxApp/api/userCenter/userInfo', body: {}, skipRecovery })\n}\n\n/** 选出当前团队:优先 isSelect=1,没有 agentMemberId 时回退个人账号,再回退任意可用团队 */\nexport function resolveCurrentTeam(teamList: UserInfoTeam[] | undefined): UserInfoTeam | undefined {\n if (!teamList?.length) return undefined\n const selected = teamList.find((t) => t.isSelect === 1)\n if (selected?.agentMemberId) return selected\n const personal = teamList.find((t) => t.teamType === 1)\n if (personal?.agentMemberId) return personal\n return teamList.find((t) => t.agentMemberId)\n}\n\nfunction toTeam(team: UserInfoTeam): Team | null {\n if (!team.aiTeamId) return null\n return {\n id: team.aiTeamId,\n teamId: team.teamId,\n name: team.teamName,\n type: team.teamType,\n logo: team.logo ?? '',\n ownerId: team.ownerId,\n }\n}\n\n/**\n * 拉用户信息并把当前团队写进会话;写到返回 true。\n * `skipRecovery` 为 true 时 token 失效直接抛错,由调用方决定是否刷新(401 恢复流程里用)。\n */\nexport async function syncCurrentTeam(options?: { skipRecovery?: boolean }): Promise<boolean> {\n const info = await fetchUserInfo(options?.skipRecovery ?? false)\n const current = resolveCurrentTeam(info.teamList)\n const team = current ? toTeam(current) : null\n // 解析不到时保持既有团队不动,避免误清其他站点写入的团队\n if (!team) return false\n setTeam(team)\n return true\n}\n\n/** 切换团队,成功后把新团队写进会话 */\nexport async function switchTeam(teamId: string): Promise<UserInfo> {\n await userCenter.request<unknown>({\n method: 'POST',\n path: '/linkFoxApp/api/userCenter/team/switchTeam',\n body: { targetTeamId: teamId },\n })\n const info = await getUserInfo()\n const target = info.teamList.find((t) => t.teamId === teamId)\n const team = target ? toTeam(target) : null\n if (team) setTeam(team)\n // 切到没有 AI 团队的账号(如个人账号)时移除条目,与主站一致\n else clearTeam()\n return info\n}\n\n/** 登出:只清本地会话,不调后端(与主站一致) */\nexport function logout(): void {\n clearSession()\n}\n","import { LinkfoxHttpError, setSession, type Session } from '@linkfox-web/http'\nimport { aesEncryptECB } from './crypto'\nimport { generateUUID, userCenter } from './service'\nimport { syncCurrentTeam } from './user'\n\n/** 登录接口返回:token 字段之外的用户信息原样写进会话 */\nexport interface LoginResult extends Session {\n userName?: string\n email?: string\n phoneNum?: string\n avatarUrl?: string\n loginType?: string\n newUser?: boolean\n aiNewUser?: boolean\n sourceChannel?: string\n}\n\nexport interface LoginOptions {\n /** 登录来源页,默认 location.href */\n sourceUrl?: string\n}\n\nfunction sourceUrlOf(options?: LoginOptions): string {\n return options?.sourceUrl ?? (typeof location !== 'undefined' ? location.href : '')\n}\n\n/**\n * 写会话后同步当前团队。拉团队失败不让登录失败:团队会在第一次被 Teable 等服务\n * 以缺团队拒绝时,由 resolveTeam 能力再补一次。\n */\nasync function completeLogin(data: LoginResult | undefined): Promise<LoginResult> {\n if (!data?.accessToken) {\n throw new LinkfoxHttpError({ service: 'user-center', status: 200, code: 'INVALID_LOGIN_RESPONSE', message: '登录响应缺少 accessToken', body: data })\n }\n setSession(data)\n try {\n await syncCurrentTeam()\n } catch {\n // 见函数注释\n }\n return data\n}\n\nfunction postLogin(path: string, body: unknown): Promise<LoginResult | undefined> {\n return userCenter.request<LoginResult | undefined>({ method: 'POST', path, body, auth: 'none' })\n}\n\nexport interface ImageVerifyCode {\n uuid: string\n /** data URL,可直接放进 img src */\n imageBase64: string\n}\n\n/** 获取图形验证码 */\nexport async function getImageVerifyCode(): Promise<ImageVerifyCode> {\n const uuid = generateUUID()\n const blob = await userCenter.request<Blob>({\n path: '/user/v1/sendMessageCode.jpg',\n query: { uuid },\n auth: 'none',\n responseType: 'blob',\n })\n const imageBase64 = await new Promise<string>((resolve, reject) => {\n const reader = new FileReader()\n reader.onloadend = () => resolve(String(reader.result ?? ''))\n reader.onerror = () => reject(reader.error)\n reader.readAsDataURL(blob)\n })\n return { uuid, imageBase64 }\n}\n\n/** 账密登录 */\nexport async function loginByPassword(\n params: { authPhone: string; password: string; imageCode: string; uuid: string },\n options?: LoginOptions,\n): Promise<LoginResult> {\n const config = userCenter.config()\n const data = await postLogin('/user/v2/web/login', {\n type: 'smsPwd',\n method: 'login',\n sourceUrl: sourceUrlOf(options),\n data: {\n authPhone: params.authPhone,\n auth: aesEncryptECB(params.password, config.aesKey),\n uuid: params.uuid,\n imageCode: params.imageCode,\n systemId: config.systemId,\n },\n })\n return completeLogin(data)\n}\n\n/** 发送短信验证码 */\nexport async function getVerifyCode(params: {\n authPhone: string\n areaCode?: string\n uuid?: string\n imageCode?: string\n}): Promise<void> {\n await postLogin('/user/v3/web/login', {\n type: 'sms',\n method: 'getVerifyCode',\n data: {\n authPhone: params.authPhone,\n areaCode: params.areaCode ?? '+86',\n ...(params.uuid ? { uuid: params.uuid } : {}),\n ...(params.imageCode ? { imageCode: params.imageCode } : {}),\n clientChannel: userCenter.config().clientChannel,\n },\n })\n}\n\n/** 短信验证码登录 */\nexport async function loginByCode(\n params: { authPhone: string; authCode: string; areaCode?: string; sourceChannel?: string },\n options?: LoginOptions,\n): Promise<LoginResult> {\n const data = await postLogin('/user/v3/web/login', {\n type: 'sms',\n method: 'login',\n sourceUrl: sourceUrlOf(options),\n data: {\n authPhone: params.authPhone,\n areaCode: params.areaCode ?? '+86',\n authCode: params.authCode,\n sourceChannel: params.sourceChannel ?? '',\n },\n })\n return completeLogin(data)\n}\n\nexport interface WechatQrCode {\n qrUrl: string\n sessionId: string\n}\n\n/** 获取微信登录二维码 */\nexport function getWechatQrCode(): Promise<WechatQrCode> {\n return userCenter.request<WechatQrCode>({\n method: 'POST',\n path: '/user/v1/web/login/wechat/qr_url',\n body: { type: '1' },\n auth: 'none',\n })\n}\n\nexport interface WechatQrCodeStatus {\n /** 2=已扫码 */\n status: number\n /** true 表示需要绑定手机号 */\n first: boolean\n}\n\n/** 轮询微信二维码扫码状态 */\nexport function getWechatQrCodeStatus(sessionId: string): Promise<WechatQrCodeStatus> {\n return userCenter.request<WechatQrCodeStatus>({\n path: '/user/v1/web/login/wechat',\n query: { sessionId },\n auth: 'none',\n })\n}\n\n/** 微信扫码登录;需要绑定手机号时附带 authPhone / authCode */\nexport async function loginByWechat(\n params: { sessionId: string; authPhone?: string; authCode?: string; sourceChannel?: string },\n options?: LoginOptions,\n): Promise<LoginResult> {\n const data = await postLogin('/user/v2/web/login', {\n type: 'wechatQR',\n method: 'login',\n sourceUrl: sourceUrlOf(options),\n data: {\n sessionId: params.sessionId,\n ...(params.authPhone ? { authPhone: params.authPhone } : {}),\n ...(params.authCode ? { authCode: params.authCode } : {}),\n sourceChannel: params.sourceChannel ?? '',\n },\n })\n return completeLogin(data)\n}\n","import AES from 'crypto-js/aes'\nimport Utf8 from 'crypto-js/enc-utf8'\nimport ECB from 'crypto-js/mode-ecb'\nimport ZeroPadding from 'crypto-js/pad-zeropadding'\n\n/** 登录密码加密:AES-ECB + ZeroPadding,与用户中心约定一致 */\nexport function aesEncryptECB(plain: string, key: string): string {\n return AES.encrypt(plain, Utf8.parse(key), { mode: ECB, padding: ZeroPadding }).toString()\n}\n","import { installCapabilities } from './capabilities'\n\n// 任何对本包的 import 都会注册「刷新」「补团队」能力,其他服务包因此在登录后能直接调通。\n// 写成显式调用而不是裸 import:裸 import 会被打包器按 sideEffects 摇掉(实测 esbuild 会)。\n// package.json 的 sideEffects 也必须保留入口产物,防止调用方的打包器摇掉这一行。\ninstallCapabilities()\n\nexport {\n getImageVerifyCode,\n getVerifyCode,\n getWechatQrCode,\n getWechatQrCodeStatus,\n loginByCode,\n loginByPassword,\n loginByWechat,\n} from './auth'\nexport type { ImageVerifyCode, LoginOptions, LoginResult, WechatQrCode, WechatQrCodeStatus } from './auth'\nexport { getUserInfo, logout, resolveCurrentTeam, switchTeam, syncCurrentTeam } from './user'\nexport type { UserInfo, UserInfoTeam } from './user'\nexport type { UserCenterConfig } from './service'\n"],"mappings":"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","names":["LinkfoxHttpError","LinkfoxHttpError"]}
|