@linkfox-web/api-user-center 0.0.0-stage → 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/README.md +18 -2
- package/dist/index.cjs +313 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +124 -0
- package/dist/index.d.ts +124 -0
- package/dist/index.js +280 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -3
package/README.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @linkfox-web/api-user-center
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
LinkFox 用户中心接口。登录成功后自动写入 `@linkfox-web/http` 的会话并同步当前团队,其他 `@linkfox-web/api-*` 包随即可用;同时注册「刷新 token」「补团队」能力,供所有服务包在 401 时使用。
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm add @linkfox-web/http @linkfox-web/api-user-center
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
| 函数 | 说明 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `getImageVerifyCode()` | 图形验证码,返回 `{ uuid, imageBase64 }` |
|
|
12
|
+
| `getVerifyCode(params)` | 发送短信验证码 |
|
|
13
|
+
| `loginByPassword(params)` / `loginByCode(params)` | 账密 / 短信验证码登录 |
|
|
14
|
+
| `getWechatQrCode()` / `getWechatQrCodeStatus(sessionId)` / `loginByWechat(params)` | 微信扫码登录 |
|
|
15
|
+
| `getUserInfo()` | 用户信息与团队列表 |
|
|
16
|
+
| `switchTeam(teamId)` | 切换团队并写入会话 |
|
|
17
|
+
| `logout()` | 清除本地会话 |
|
|
18
|
+
|
|
19
|
+
登录函数的第二个参数可传 `{ sourceUrl }`,默认取 `location.href`。`source`、`systemId`、`clientChannel` 可通过 `configure({ services: { 'user-center': { … } } })` 覆盖。
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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.d.cts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Session } from '@linkfox-web/http';
|
|
2
|
+
|
|
3
|
+
/** 登录接口返回:token 字段之外的用户信息原样写进会话 */
|
|
4
|
+
interface LoginResult extends Session {
|
|
5
|
+
userName?: string;
|
|
6
|
+
email?: string;
|
|
7
|
+
phoneNum?: string;
|
|
8
|
+
avatarUrl?: string;
|
|
9
|
+
loginType?: string;
|
|
10
|
+
newUser?: boolean;
|
|
11
|
+
aiNewUser?: boolean;
|
|
12
|
+
sourceChannel?: string;
|
|
13
|
+
}
|
|
14
|
+
interface LoginOptions {
|
|
15
|
+
/** 登录来源页,默认 location.href */
|
|
16
|
+
sourceUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
interface ImageVerifyCode {
|
|
19
|
+
uuid: string;
|
|
20
|
+
/** data URL,可直接放进 img src */
|
|
21
|
+
imageBase64: string;
|
|
22
|
+
}
|
|
23
|
+
/** 获取图形验证码 */
|
|
24
|
+
declare function getImageVerifyCode(): Promise<ImageVerifyCode>;
|
|
25
|
+
/** 账密登录 */
|
|
26
|
+
declare function loginByPassword(params: {
|
|
27
|
+
authPhone: string;
|
|
28
|
+
password: string;
|
|
29
|
+
imageCode: string;
|
|
30
|
+
uuid: string;
|
|
31
|
+
}, options?: LoginOptions): Promise<LoginResult>;
|
|
32
|
+
/** 发送短信验证码 */
|
|
33
|
+
declare function getVerifyCode(params: {
|
|
34
|
+
authPhone: string;
|
|
35
|
+
areaCode?: string;
|
|
36
|
+
uuid?: string;
|
|
37
|
+
imageCode?: string;
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
/** 短信验证码登录 */
|
|
40
|
+
declare function loginByCode(params: {
|
|
41
|
+
authPhone: string;
|
|
42
|
+
authCode: string;
|
|
43
|
+
areaCode?: string;
|
|
44
|
+
sourceChannel?: string;
|
|
45
|
+
}, options?: LoginOptions): Promise<LoginResult>;
|
|
46
|
+
interface WechatQrCode {
|
|
47
|
+
qrUrl: string;
|
|
48
|
+
sessionId: string;
|
|
49
|
+
}
|
|
50
|
+
/** 获取微信登录二维码 */
|
|
51
|
+
declare function getWechatQrCode(): Promise<WechatQrCode>;
|
|
52
|
+
interface WechatQrCodeStatus {
|
|
53
|
+
/** 2=已扫码 */
|
|
54
|
+
status: number;
|
|
55
|
+
/** true 表示需要绑定手机号 */
|
|
56
|
+
first: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** 轮询微信二维码扫码状态 */
|
|
59
|
+
declare function getWechatQrCodeStatus(sessionId: string): Promise<WechatQrCodeStatus>;
|
|
60
|
+
/** 微信扫码登录;需要绑定手机号时附带 authPhone / authCode */
|
|
61
|
+
declare function loginByWechat(params: {
|
|
62
|
+
sessionId: string;
|
|
63
|
+
authPhone?: string;
|
|
64
|
+
authCode?: string;
|
|
65
|
+
sourceChannel?: string;
|
|
66
|
+
}, options?: LoginOptions): Promise<LoginResult>;
|
|
67
|
+
|
|
68
|
+
interface UserInfoTeam {
|
|
69
|
+
teamId: string;
|
|
70
|
+
teamName: string;
|
|
71
|
+
aiTeamId: string | null;
|
|
72
|
+
agentTeamId: string | null;
|
|
73
|
+
agentMemberId: string | null;
|
|
74
|
+
/** 1=所有者 2=成员 3=管理员 4=开放平台用户 */
|
|
75
|
+
role: string;
|
|
76
|
+
logo: string | null;
|
|
77
|
+
/** 1=当前选中 */
|
|
78
|
+
isSelect: number;
|
|
79
|
+
/** 1=个人 2=团队 */
|
|
80
|
+
teamType: number;
|
|
81
|
+
ownerId: string;
|
|
82
|
+
mergeSourceInfo: string | null;
|
|
83
|
+
}
|
|
84
|
+
interface UserInfo {
|
|
85
|
+
id: string;
|
|
86
|
+
nickName: string;
|
|
87
|
+
phoneNum: string;
|
|
88
|
+
avatar: string | null;
|
|
89
|
+
verifyStatus: boolean;
|
|
90
|
+
bindWechat: boolean;
|
|
91
|
+
status: number;
|
|
92
|
+
/** 注销冷静期到期时间戳(毫秒) */
|
|
93
|
+
freezeExpireTime?: number;
|
|
94
|
+
teamList: UserInfoTeam[];
|
|
95
|
+
}
|
|
96
|
+
declare function getUserInfo(): Promise<UserInfo>;
|
|
97
|
+
/** 选出当前团队:优先 isSelect=1,没有 agentMemberId 时回退个人账号,再回退任意可用团队 */
|
|
98
|
+
declare function resolveCurrentTeam(teamList: UserInfoTeam[] | undefined): UserInfoTeam | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* 拉用户信息并把当前团队写进会话;写到返回 true。
|
|
101
|
+
* `skipRecovery` 为 true 时 token 失效直接抛错,由调用方决定是否刷新(401 恢复流程里用)。
|
|
102
|
+
*/
|
|
103
|
+
declare function syncCurrentTeam(options?: {
|
|
104
|
+
skipRecovery?: boolean;
|
|
105
|
+
}): Promise<boolean>;
|
|
106
|
+
/** 切换团队,成功后把新团队写进会话 */
|
|
107
|
+
declare function switchTeam(teamId: string): Promise<UserInfo>;
|
|
108
|
+
/** 登出:只清本地会话,不调后端(与主站一致) */
|
|
109
|
+
declare function logout(): void;
|
|
110
|
+
|
|
111
|
+
interface UserCenterConfig {
|
|
112
|
+
baseURL: string;
|
|
113
|
+
aesKey: string;
|
|
114
|
+
source: string;
|
|
115
|
+
systemId: string;
|
|
116
|
+
clientChannel: string;
|
|
117
|
+
}
|
|
118
|
+
declare module '@linkfox-web/http' {
|
|
119
|
+
interface ServiceConfigMap {
|
|
120
|
+
'user-center': UserCenterConfig;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
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 };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Session } from '@linkfox-web/http';
|
|
2
|
+
|
|
3
|
+
/** 登录接口返回:token 字段之外的用户信息原样写进会话 */
|
|
4
|
+
interface LoginResult extends Session {
|
|
5
|
+
userName?: string;
|
|
6
|
+
email?: string;
|
|
7
|
+
phoneNum?: string;
|
|
8
|
+
avatarUrl?: string;
|
|
9
|
+
loginType?: string;
|
|
10
|
+
newUser?: boolean;
|
|
11
|
+
aiNewUser?: boolean;
|
|
12
|
+
sourceChannel?: string;
|
|
13
|
+
}
|
|
14
|
+
interface LoginOptions {
|
|
15
|
+
/** 登录来源页,默认 location.href */
|
|
16
|
+
sourceUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
interface ImageVerifyCode {
|
|
19
|
+
uuid: string;
|
|
20
|
+
/** data URL,可直接放进 img src */
|
|
21
|
+
imageBase64: string;
|
|
22
|
+
}
|
|
23
|
+
/** 获取图形验证码 */
|
|
24
|
+
declare function getImageVerifyCode(): Promise<ImageVerifyCode>;
|
|
25
|
+
/** 账密登录 */
|
|
26
|
+
declare function loginByPassword(params: {
|
|
27
|
+
authPhone: string;
|
|
28
|
+
password: string;
|
|
29
|
+
imageCode: string;
|
|
30
|
+
uuid: string;
|
|
31
|
+
}, options?: LoginOptions): Promise<LoginResult>;
|
|
32
|
+
/** 发送短信验证码 */
|
|
33
|
+
declare function getVerifyCode(params: {
|
|
34
|
+
authPhone: string;
|
|
35
|
+
areaCode?: string;
|
|
36
|
+
uuid?: string;
|
|
37
|
+
imageCode?: string;
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
/** 短信验证码登录 */
|
|
40
|
+
declare function loginByCode(params: {
|
|
41
|
+
authPhone: string;
|
|
42
|
+
authCode: string;
|
|
43
|
+
areaCode?: string;
|
|
44
|
+
sourceChannel?: string;
|
|
45
|
+
}, options?: LoginOptions): Promise<LoginResult>;
|
|
46
|
+
interface WechatQrCode {
|
|
47
|
+
qrUrl: string;
|
|
48
|
+
sessionId: string;
|
|
49
|
+
}
|
|
50
|
+
/** 获取微信登录二维码 */
|
|
51
|
+
declare function getWechatQrCode(): Promise<WechatQrCode>;
|
|
52
|
+
interface WechatQrCodeStatus {
|
|
53
|
+
/** 2=已扫码 */
|
|
54
|
+
status: number;
|
|
55
|
+
/** true 表示需要绑定手机号 */
|
|
56
|
+
first: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** 轮询微信二维码扫码状态 */
|
|
59
|
+
declare function getWechatQrCodeStatus(sessionId: string): Promise<WechatQrCodeStatus>;
|
|
60
|
+
/** 微信扫码登录;需要绑定手机号时附带 authPhone / authCode */
|
|
61
|
+
declare function loginByWechat(params: {
|
|
62
|
+
sessionId: string;
|
|
63
|
+
authPhone?: string;
|
|
64
|
+
authCode?: string;
|
|
65
|
+
sourceChannel?: string;
|
|
66
|
+
}, options?: LoginOptions): Promise<LoginResult>;
|
|
67
|
+
|
|
68
|
+
interface UserInfoTeam {
|
|
69
|
+
teamId: string;
|
|
70
|
+
teamName: string;
|
|
71
|
+
aiTeamId: string | null;
|
|
72
|
+
agentTeamId: string | null;
|
|
73
|
+
agentMemberId: string | null;
|
|
74
|
+
/** 1=所有者 2=成员 3=管理员 4=开放平台用户 */
|
|
75
|
+
role: string;
|
|
76
|
+
logo: string | null;
|
|
77
|
+
/** 1=当前选中 */
|
|
78
|
+
isSelect: number;
|
|
79
|
+
/** 1=个人 2=团队 */
|
|
80
|
+
teamType: number;
|
|
81
|
+
ownerId: string;
|
|
82
|
+
mergeSourceInfo: string | null;
|
|
83
|
+
}
|
|
84
|
+
interface UserInfo {
|
|
85
|
+
id: string;
|
|
86
|
+
nickName: string;
|
|
87
|
+
phoneNum: string;
|
|
88
|
+
avatar: string | null;
|
|
89
|
+
verifyStatus: boolean;
|
|
90
|
+
bindWechat: boolean;
|
|
91
|
+
status: number;
|
|
92
|
+
/** 注销冷静期到期时间戳(毫秒) */
|
|
93
|
+
freezeExpireTime?: number;
|
|
94
|
+
teamList: UserInfoTeam[];
|
|
95
|
+
}
|
|
96
|
+
declare function getUserInfo(): Promise<UserInfo>;
|
|
97
|
+
/** 选出当前团队:优先 isSelect=1,没有 agentMemberId 时回退个人账号,再回退任意可用团队 */
|
|
98
|
+
declare function resolveCurrentTeam(teamList: UserInfoTeam[] | undefined): UserInfoTeam | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* 拉用户信息并把当前团队写进会话;写到返回 true。
|
|
101
|
+
* `skipRecovery` 为 true 时 token 失效直接抛错,由调用方决定是否刷新(401 恢复流程里用)。
|
|
102
|
+
*/
|
|
103
|
+
declare function syncCurrentTeam(options?: {
|
|
104
|
+
skipRecovery?: boolean;
|
|
105
|
+
}): Promise<boolean>;
|
|
106
|
+
/** 切换团队,成功后把新团队写进会话 */
|
|
107
|
+
declare function switchTeam(teamId: string): Promise<UserInfo>;
|
|
108
|
+
/** 登出:只清本地会话,不调后端(与主站一致) */
|
|
109
|
+
declare function logout(): void;
|
|
110
|
+
|
|
111
|
+
interface UserCenterConfig {
|
|
112
|
+
baseURL: string;
|
|
113
|
+
aesKey: string;
|
|
114
|
+
source: string;
|
|
115
|
+
systemId: string;
|
|
116
|
+
clientChannel: string;
|
|
117
|
+
}
|
|
118
|
+
declare module '@linkfox-web/http' {
|
|
119
|
+
interface ServiceConfigMap {
|
|
120
|
+
'user-center': UserCenterConfig;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
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 };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// src/capabilities.ts
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linkfox-web/api-user-center",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "LinkFox 用户中心接口:登录、刷新、用户信息、切换团队",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://git.ziniao.com/a58-linkfoxagent/linkfox-agent/linkfox-frontend-sdk.git",
|
|
8
|
+
"directory": "packages/api-user-center"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"require": {
|
|
21
|
+
"types": "./dist/index.d.cts",
|
|
22
|
+
"default": "./dist/index.cjs"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"sideEffects": [
|
|
30
|
+
"./dist/index.js",
|
|
31
|
+
"./dist/index.cjs"
|
|
32
|
+
],
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@linkfox-web/http": "^0.1.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"crypto-js": "4.2.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/crypto-js": "4.2.2",
|
|
41
|
+
"@linkfox-web/http": "0.1.0"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
50
|
+
}
|
|
6
51
|
}
|