ibc-ai-web-sdk 2.1.1 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -13
- package/dist/index.cjs.js +35 -8
- package/dist/index.esm.js +35 -8
- package/dist/index.umd.js +35 -8
- package/dist/index.umd.min.js +1 -1
- package/dist-ts/config/defaultConfig.d.ts +1 -1
- package/dist-ts/types.d.ts +15 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,39 +84,75 @@ AIWebSDK.create({
|
|
|
84
84
|
| `headers` | object | `{}` | | 附加请求头 |
|
|
85
85
|
| `extendInfo` | object | `{}` | | 随请求发送的扩展信息 |
|
|
86
86
|
|
|
87
|
-
### Token
|
|
87
|
+
### Token 管理
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
SDK 的 Token 生命周期**应与应用层保持一致**,建议应用层为 SDK 签发的 Token TTL 与自身登录态 TTL 相同。
|
|
90
|
+
|
|
91
|
+
#### 正常刷新
|
|
92
|
+
|
|
93
|
+
提供 `refreshTokenFn`,SDK 会在 Token 即将过期时自动刷新(默认提前 60 秒)。
|
|
90
94
|
|
|
91
95
|
```js
|
|
92
96
|
createAIChatDialog({
|
|
93
|
-
token: '
|
|
94
|
-
tokenExpiresAt: Date.now() +
|
|
95
|
-
refreshToken: 'initial-refresh-token',
|
|
97
|
+
token: 'st-xxxx', // 由应用后端调 /v1/token/create 签发
|
|
98
|
+
tokenExpiresAt: Date.now() + 7200 * 1000, // SDK Token 过期时间戳(毫秒),建议与用户登录态一致
|
|
99
|
+
refreshToken: 'initial-refresh-token', // 可选:服务端刷新凭证
|
|
96
100
|
|
|
97
101
|
refreshTokenFn: async ({ token, refreshToken, userId }) => {
|
|
98
|
-
|
|
102
|
+
// 调自己的后端接口,后端用 API-Key 调 /v1/token/create 拿新 st- Token
|
|
103
|
+
const res = await fetch('/api/auth/refresh-sdk-token', {
|
|
99
104
|
method: 'POST',
|
|
100
105
|
headers: { 'Content-Type': 'application/json' },
|
|
101
|
-
body: JSON.stringify({
|
|
106
|
+
body: JSON.stringify({ userId })
|
|
102
107
|
});
|
|
103
108
|
const data = await res.json();
|
|
104
109
|
return {
|
|
105
|
-
token: data.
|
|
110
|
+
token: data.token,
|
|
106
111
|
expiresAt: data.expiresAt * 1000
|
|
107
112
|
};
|
|
108
113
|
}
|
|
109
114
|
});
|
|
110
115
|
```
|
|
111
116
|
|
|
117
|
+
#### Token 彻底失效
|
|
118
|
+
|
|
119
|
+
当 Token 过期且刷新失败(如刷新接口返回 401),或未配置 `refreshTokenFn` 时,SDK 会触发 `onAuthExpired` 回调。**应用层应在此回调中引导用户重新登录。**
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
createAIChatDialog({
|
|
123
|
+
token: 'st-xxxx',
|
|
124
|
+
tokenExpiresAt: Date.now() + 7200 * 1000,
|
|
125
|
+
|
|
126
|
+
onAuthExpired: ({ reason, message }) => {
|
|
127
|
+
console.log('SDK Token 已失效:', reason, message);
|
|
128
|
+
dialog.destroy(); // 销毁 SDK 实例
|
|
129
|
+
router.push('/login'); // 引导用户重新登录
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// 登录成功后:重新拿 Token,重新初始化 SDK
|
|
134
|
+
async function afterLogin(userId) {
|
|
135
|
+
// 1. 调后端通过 API-Key 签新 st- Token
|
|
136
|
+
const { token, expiresAt } = await fetchNewSdkToken(userId);
|
|
137
|
+
// 2. 重新初始化 SDK
|
|
138
|
+
dialog = createAIChatDialog({
|
|
139
|
+
token,
|
|
140
|
+
tokenExpiresAt: expiresAt * 1000,
|
|
141
|
+
onAuthExpired: () => { /* 同上 */ }
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
112
146
|
| 参数 | 类型 | 说明 |
|
|
113
147
|
|------|------|------|
|
|
114
|
-
| `token` | string | 认证 Token |
|
|
115
|
-
| `tokenExpiresAt` | number | Token
|
|
116
|
-
| `
|
|
148
|
+
| `token` | string | 认证 Token(由后端用 API-Key 调 `/v1/token/create` 签发) |
|
|
149
|
+
| `tokenExpiresAt` | number | Token 过期时间戳(毫秒)。建议与应用登录态 TTL 一致 |
|
|
150
|
+
| `tokenRefreshBufferMs` | number | 过期前多久提前刷新(毫秒),默认 60000 |
|
|
151
|
+
| `refreshToken` | string | 后端刷新凭证(可选) |
|
|
117
152
|
| `refreshTokenFn` | function | 刷新函数,返回新 token 信息 |
|
|
118
153
|
| `onTokenRefreshed` | function | 刷新成功回调 |
|
|
119
|
-
| `
|
|
154
|
+
| `onAuthExpired` | function | 认证彻底失效回调。**在此处引导用户重新登录** |
|
|
155
|
+
|
|
120
156
|
```
|
|
121
157
|
|
|
122
158
|
### 高级配置
|
|
@@ -431,7 +467,9 @@ data: {"conversationId":"conv_xxx","tokens":{"input":10,"output":15}}
|
|
|
431
467
|
|
|
432
468
|
### 认证
|
|
433
469
|
|
|
434
|
-
所有请求自动携带 `Authorization: Bearer {token}
|
|
470
|
+
所有请求自动携带 `Authorization: Bearer {token}`。Token 由应用后端通过 API-Key 调用 `/v1/token/create` 签发,建议 TTL 与应用登录态保持一致。
|
|
471
|
+
|
|
472
|
+
Token 过期后 SDK 触发 `onAuthExpired` 回调,应用层应引导用户重新登录。登录成功后调用 `updateConfig({ token, tokenExpiresAt, ... })` 或重新初始化 SDK 传入新 Token。
|
|
435
473
|
|
|
436
474
|
---
|
|
437
475
|
|
package/dist/index.cjs.js
CHANGED
|
@@ -195,12 +195,19 @@ class AIChatClient {
|
|
|
195
195
|
silent = false
|
|
196
196
|
} = {}) {
|
|
197
197
|
if (typeof this.config.refreshTokenFn !== 'function') {
|
|
198
|
-
const
|
|
198
|
+
const authErr = new Error('Token 已过期且未配置 refreshTokenFn');
|
|
199
|
+
authErr._authExpired = true;
|
|
200
|
+
authErr._authReason = reason;
|
|
199
201
|
if (!silent) {
|
|
200
202
|
this.stopBackgroundRefresh();
|
|
201
|
-
this.config.
|
|
203
|
+
this.config.onAuthExpired?.({
|
|
204
|
+
reason,
|
|
205
|
+
message: authErr.message,
|
|
206
|
+
noRefreshFn: true,
|
|
207
|
+
error: authErr
|
|
208
|
+
});
|
|
202
209
|
}
|
|
203
|
-
throw
|
|
210
|
+
throw authErr;
|
|
204
211
|
}
|
|
205
212
|
if (!this.refreshTokenPromise) {
|
|
206
213
|
this.refreshTokenPromise = Promise.resolve().then(() => this.config.refreshTokenFn({
|
|
@@ -227,8 +234,17 @@ class AIChatClient {
|
|
|
227
234
|
return this.config.token || '';
|
|
228
235
|
}
|
|
229
236
|
this.stopBackgroundRefresh();
|
|
230
|
-
|
|
231
|
-
|
|
237
|
+
const errMsg = error && (error.message || String(error)) || 'Token 刷新失败,请重新登录';
|
|
238
|
+
this.config.onAuthExpired?.({
|
|
239
|
+
reason,
|
|
240
|
+
message: errMsg,
|
|
241
|
+
noRefreshFn: false,
|
|
242
|
+
error
|
|
243
|
+
});
|
|
244
|
+
const authErr = new Error(errMsg);
|
|
245
|
+
authErr._authExpired = true;
|
|
246
|
+
authErr._authReason = reason;
|
|
247
|
+
throw authErr;
|
|
232
248
|
}).finally(() => {
|
|
233
249
|
this.refreshTokenPromise = null;
|
|
234
250
|
});
|
|
@@ -360,6 +376,9 @@ class AIChatClient {
|
|
|
360
376
|
}
|
|
361
377
|
return result;
|
|
362
378
|
} catch (error) {
|
|
379
|
+
if (error && error._authExpired) {
|
|
380
|
+
return this.createError(ERROR_CODES.AUTH_FAILED, error.message || '登录已过期,请重新登录');
|
|
381
|
+
}
|
|
363
382
|
if (error && error.name === 'AbortError') {
|
|
364
383
|
return this.createError(ERROR_CODES.TIMEOUT_ERROR, '请求超时,请稍后重试');
|
|
365
384
|
}
|
|
@@ -454,6 +473,11 @@ class AIChatClient {
|
|
|
454
473
|
try {
|
|
455
474
|
return await this.executeMessageStream(prompt, handlers, options, false);
|
|
456
475
|
} catch (error) {
|
|
476
|
+
if (error && error._authExpired) {
|
|
477
|
+
const message = error.message || '登录已过期,请重新登录';
|
|
478
|
+
handlers.onError?.(message, error);
|
|
479
|
+
return this.createError(ERROR_CODES.AUTH_FAILED, message);
|
|
480
|
+
}
|
|
457
481
|
if (error && error.name === 'AbortError') {
|
|
458
482
|
const message = '请求超时或已取消';
|
|
459
483
|
handlers.onError?.(message, error.sseData || error);
|
|
@@ -3598,7 +3622,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
3598
3622
|
tokenRefreshBufferMs: 60000,
|
|
3599
3623
|
refreshTokenFn: null,
|
|
3600
3624
|
onTokenRefreshed: null,
|
|
3601
|
-
|
|
3625
|
+
onAuthExpired: null,
|
|
3602
3626
|
authErrorCodes: [],
|
|
3603
3627
|
title: 'AI 助手',
|
|
3604
3628
|
welcomeText: 'Hi,我是智能助手 ~\n欢迎随时提问',
|
|
@@ -6064,7 +6088,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
6064
6088
|
if (!text || typeof text !== 'string') return false;
|
|
6065
6089
|
if (text === '\n' || text === '\n\n') return false;
|
|
6066
6090
|
const p = text.trim();
|
|
6067
|
-
if (!p) return
|
|
6091
|
+
if (!p) return false;
|
|
6068
6092
|
return RUNTIME_PLACEHOLDERS.some(k => p.includes(k));
|
|
6069
6093
|
}
|
|
6070
6094
|
_isEventLike(text) {
|
|
@@ -6300,8 +6324,11 @@ class AIChatDialog extends HTMLElement {
|
|
|
6300
6324
|
handleError(err) {
|
|
6301
6325
|
let displayMsg = '抱歉,网络请求失败,请稍后重试';
|
|
6302
6326
|
const errMsg = err?.message || '';
|
|
6327
|
+
const errCode = err?.code;
|
|
6303
6328
|
// 按错误类型分类
|
|
6304
|
-
if (
|
|
6329
|
+
if (err?._authExpired || errCode === 40301) {
|
|
6330
|
+
displayMsg = '登录已过期,请重新登录后再试';
|
|
6331
|
+
} else if (errMsg.includes('Failed to fetch') || errMsg.includes('NetworkError') || errMsg.includes('网络')) {
|
|
6305
6332
|
displayMsg = '网络连接失败,请检查网络后重试';
|
|
6306
6333
|
} else if (errMsg.includes('AbortError') || errMsg.includes('超时') || errMsg.includes('timeout')) {
|
|
6307
6334
|
displayMsg = '请求超时,请稍后重试';
|
package/dist/index.esm.js
CHANGED
|
@@ -191,12 +191,19 @@ class AIChatClient {
|
|
|
191
191
|
silent = false
|
|
192
192
|
} = {}) {
|
|
193
193
|
if (typeof this.config.refreshTokenFn !== 'function') {
|
|
194
|
-
const
|
|
194
|
+
const authErr = new Error('Token 已过期且未配置 refreshTokenFn');
|
|
195
|
+
authErr._authExpired = true;
|
|
196
|
+
authErr._authReason = reason;
|
|
195
197
|
if (!silent) {
|
|
196
198
|
this.stopBackgroundRefresh();
|
|
197
|
-
this.config.
|
|
199
|
+
this.config.onAuthExpired?.({
|
|
200
|
+
reason,
|
|
201
|
+
message: authErr.message,
|
|
202
|
+
noRefreshFn: true,
|
|
203
|
+
error: authErr
|
|
204
|
+
});
|
|
198
205
|
}
|
|
199
|
-
throw
|
|
206
|
+
throw authErr;
|
|
200
207
|
}
|
|
201
208
|
if (!this.refreshTokenPromise) {
|
|
202
209
|
this.refreshTokenPromise = Promise.resolve().then(() => this.config.refreshTokenFn({
|
|
@@ -223,8 +230,17 @@ class AIChatClient {
|
|
|
223
230
|
return this.config.token || '';
|
|
224
231
|
}
|
|
225
232
|
this.stopBackgroundRefresh();
|
|
226
|
-
|
|
227
|
-
|
|
233
|
+
const errMsg = error && (error.message || String(error)) || 'Token 刷新失败,请重新登录';
|
|
234
|
+
this.config.onAuthExpired?.({
|
|
235
|
+
reason,
|
|
236
|
+
message: errMsg,
|
|
237
|
+
noRefreshFn: false,
|
|
238
|
+
error
|
|
239
|
+
});
|
|
240
|
+
const authErr = new Error(errMsg);
|
|
241
|
+
authErr._authExpired = true;
|
|
242
|
+
authErr._authReason = reason;
|
|
243
|
+
throw authErr;
|
|
228
244
|
}).finally(() => {
|
|
229
245
|
this.refreshTokenPromise = null;
|
|
230
246
|
});
|
|
@@ -356,6 +372,9 @@ class AIChatClient {
|
|
|
356
372
|
}
|
|
357
373
|
return result;
|
|
358
374
|
} catch (error) {
|
|
375
|
+
if (error && error._authExpired) {
|
|
376
|
+
return this.createError(ERROR_CODES.AUTH_FAILED, error.message || '登录已过期,请重新登录');
|
|
377
|
+
}
|
|
359
378
|
if (error && error.name === 'AbortError') {
|
|
360
379
|
return this.createError(ERROR_CODES.TIMEOUT_ERROR, '请求超时,请稍后重试');
|
|
361
380
|
}
|
|
@@ -450,6 +469,11 @@ class AIChatClient {
|
|
|
450
469
|
try {
|
|
451
470
|
return await this.executeMessageStream(prompt, handlers, options, false);
|
|
452
471
|
} catch (error) {
|
|
472
|
+
if (error && error._authExpired) {
|
|
473
|
+
const message = error.message || '登录已过期,请重新登录';
|
|
474
|
+
handlers.onError?.(message, error);
|
|
475
|
+
return this.createError(ERROR_CODES.AUTH_FAILED, message);
|
|
476
|
+
}
|
|
453
477
|
if (error && error.name === 'AbortError') {
|
|
454
478
|
const message = '请求超时或已取消';
|
|
455
479
|
handlers.onError?.(message, error.sseData || error);
|
|
@@ -3594,7 +3618,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
3594
3618
|
tokenRefreshBufferMs: 60000,
|
|
3595
3619
|
refreshTokenFn: null,
|
|
3596
3620
|
onTokenRefreshed: null,
|
|
3597
|
-
|
|
3621
|
+
onAuthExpired: null,
|
|
3598
3622
|
authErrorCodes: [],
|
|
3599
3623
|
title: 'AI 助手',
|
|
3600
3624
|
welcomeText: 'Hi,我是智能助手 ~\n欢迎随时提问',
|
|
@@ -6060,7 +6084,7 @@ class AIChatDialog extends HTMLElement {
|
|
|
6060
6084
|
if (!text || typeof text !== 'string') return false;
|
|
6061
6085
|
if (text === '\n' || text === '\n\n') return false;
|
|
6062
6086
|
const p = text.trim();
|
|
6063
|
-
if (!p) return
|
|
6087
|
+
if (!p) return false;
|
|
6064
6088
|
return RUNTIME_PLACEHOLDERS.some(k => p.includes(k));
|
|
6065
6089
|
}
|
|
6066
6090
|
_isEventLike(text) {
|
|
@@ -6296,8 +6320,11 @@ class AIChatDialog extends HTMLElement {
|
|
|
6296
6320
|
handleError(err) {
|
|
6297
6321
|
let displayMsg = '抱歉,网络请求失败,请稍后重试';
|
|
6298
6322
|
const errMsg = err?.message || '';
|
|
6323
|
+
const errCode = err?.code;
|
|
6299
6324
|
// 按错误类型分类
|
|
6300
|
-
if (
|
|
6325
|
+
if (err?._authExpired || errCode === 40301) {
|
|
6326
|
+
displayMsg = '登录已过期,请重新登录后再试';
|
|
6327
|
+
} else if (errMsg.includes('Failed to fetch') || errMsg.includes('NetworkError') || errMsg.includes('网络')) {
|
|
6301
6328
|
displayMsg = '网络连接失败,请检查网络后重试';
|
|
6302
6329
|
} else if (errMsg.includes('AbortError') || errMsg.includes('超时') || errMsg.includes('timeout')) {
|
|
6303
6330
|
displayMsg = '请求超时,请稍后重试';
|
package/dist/index.umd.js
CHANGED
|
@@ -197,12 +197,19 @@
|
|
|
197
197
|
silent = false
|
|
198
198
|
} = {}) {
|
|
199
199
|
if (typeof this.config.refreshTokenFn !== 'function') {
|
|
200
|
-
const
|
|
200
|
+
const authErr = new Error('Token 已过期且未配置 refreshTokenFn');
|
|
201
|
+
authErr._authExpired = true;
|
|
202
|
+
authErr._authReason = reason;
|
|
201
203
|
if (!silent) {
|
|
202
204
|
this.stopBackgroundRefresh();
|
|
203
|
-
this.config.
|
|
205
|
+
this.config.onAuthExpired?.({
|
|
206
|
+
reason,
|
|
207
|
+
message: authErr.message,
|
|
208
|
+
noRefreshFn: true,
|
|
209
|
+
error: authErr
|
|
210
|
+
});
|
|
204
211
|
}
|
|
205
|
-
throw
|
|
212
|
+
throw authErr;
|
|
206
213
|
}
|
|
207
214
|
if (!this.refreshTokenPromise) {
|
|
208
215
|
this.refreshTokenPromise = Promise.resolve().then(() => this.config.refreshTokenFn({
|
|
@@ -229,8 +236,17 @@
|
|
|
229
236
|
return this.config.token || '';
|
|
230
237
|
}
|
|
231
238
|
this.stopBackgroundRefresh();
|
|
232
|
-
|
|
233
|
-
|
|
239
|
+
const errMsg = error && (error.message || String(error)) || 'Token 刷新失败,请重新登录';
|
|
240
|
+
this.config.onAuthExpired?.({
|
|
241
|
+
reason,
|
|
242
|
+
message: errMsg,
|
|
243
|
+
noRefreshFn: false,
|
|
244
|
+
error
|
|
245
|
+
});
|
|
246
|
+
const authErr = new Error(errMsg);
|
|
247
|
+
authErr._authExpired = true;
|
|
248
|
+
authErr._authReason = reason;
|
|
249
|
+
throw authErr;
|
|
234
250
|
}).finally(() => {
|
|
235
251
|
this.refreshTokenPromise = null;
|
|
236
252
|
});
|
|
@@ -362,6 +378,9 @@
|
|
|
362
378
|
}
|
|
363
379
|
return result;
|
|
364
380
|
} catch (error) {
|
|
381
|
+
if (error && error._authExpired) {
|
|
382
|
+
return this.createError(ERROR_CODES.AUTH_FAILED, error.message || '登录已过期,请重新登录');
|
|
383
|
+
}
|
|
365
384
|
if (error && error.name === 'AbortError') {
|
|
366
385
|
return this.createError(ERROR_CODES.TIMEOUT_ERROR, '请求超时,请稍后重试');
|
|
367
386
|
}
|
|
@@ -456,6 +475,11 @@
|
|
|
456
475
|
try {
|
|
457
476
|
return await this.executeMessageStream(prompt, handlers, options, false);
|
|
458
477
|
} catch (error) {
|
|
478
|
+
if (error && error._authExpired) {
|
|
479
|
+
const message = error.message || '登录已过期,请重新登录';
|
|
480
|
+
handlers.onError?.(message, error);
|
|
481
|
+
return this.createError(ERROR_CODES.AUTH_FAILED, message);
|
|
482
|
+
}
|
|
459
483
|
if (error && error.name === 'AbortError') {
|
|
460
484
|
const message = '请求超时或已取消';
|
|
461
485
|
handlers.onError?.(message, error.sseData || error);
|
|
@@ -3600,7 +3624,7 @@ Please report this to https://github.com/markedjs/marked.`,e){let s="<p>An error
|
|
|
3600
3624
|
tokenRefreshBufferMs: 60000,
|
|
3601
3625
|
refreshTokenFn: null,
|
|
3602
3626
|
onTokenRefreshed: null,
|
|
3603
|
-
|
|
3627
|
+
onAuthExpired: null,
|
|
3604
3628
|
authErrorCodes: [],
|
|
3605
3629
|
title: 'AI 助手',
|
|
3606
3630
|
welcomeText: 'Hi,我是智能助手 ~\n欢迎随时提问',
|
|
@@ -6066,7 +6090,7 @@ Please report this to https://github.com/markedjs/marked.`,e){let s="<p>An error
|
|
|
6066
6090
|
if (!text || typeof text !== 'string') return false;
|
|
6067
6091
|
if (text === '\n' || text === '\n\n') return false;
|
|
6068
6092
|
const p = text.trim();
|
|
6069
|
-
if (!p) return
|
|
6093
|
+
if (!p) return false;
|
|
6070
6094
|
return RUNTIME_PLACEHOLDERS.some(k => p.includes(k));
|
|
6071
6095
|
}
|
|
6072
6096
|
_isEventLike(text) {
|
|
@@ -6302,8 +6326,11 @@ Please report this to https://github.com/markedjs/marked.`,e){let s="<p>An error
|
|
|
6302
6326
|
handleError(err) {
|
|
6303
6327
|
let displayMsg = '抱歉,网络请求失败,请稍后重试';
|
|
6304
6328
|
const errMsg = err?.message || '';
|
|
6329
|
+
const errCode = err?.code;
|
|
6305
6330
|
// 按错误类型分类
|
|
6306
|
-
if (
|
|
6331
|
+
if (err?._authExpired || errCode === 40301) {
|
|
6332
|
+
displayMsg = '登录已过期,请重新登录后再试';
|
|
6333
|
+
} else if (errMsg.includes('Failed to fetch') || errMsg.includes('NetworkError') || errMsg.includes('网络')) {
|
|
6307
6334
|
displayMsg = '网络连接失败,请检查网络后重试';
|
|
6308
6335
|
} else if (errMsg.includes('AbortError') || errMsg.includes('超时') || errMsg.includes('timeout')) {
|
|
6309
6336
|
displayMsg = '请求超时,请稍后重试';
|