@quantabit/sdk-config 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,825 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+
3
+ /**
4
+ * @quantabit/sdk-config - BaseApiClient
5
+ *
6
+ * SDK API 客户端基类
7
+ * 封装所有通用的配置、请求、Token 管理逻辑
8
+ *
9
+ * 使用方式:
10
+ * ```javascript
11
+ * import { BaseApiClient } from '@quantabit/sdk-config';
12
+ *
13
+ * class MyApiClient extends BaseApiClient {
14
+ * constructor(config = {}) {
15
+ * super('/my-module', config); // 只需传入模块路径
16
+ * }
17
+ *
18
+ * // 专注于业务方法
19
+ * async getList() {
20
+ * return this.request('/list');
21
+ * }
22
+ * }
23
+ * ```
24
+ */
25
+
26
+
27
+ /**
28
+ * SDK API 客户端基类
29
+ * 所有 SDK 的 API 客户端都应继承此类
30
+ */
31
+ class BaseApiClient {
32
+ /**
33
+ * 构造函数
34
+ * @param {string} modulePath - 模块路径,如 '/auth', '/points', '/wallet'
35
+ * @param {Object} config - 配置选项
36
+ * @param {string} config.baseUrl - 自定义基础 URL(覆盖全局配置)
37
+ * @param {number} config.timeout - 自定义超时时间(毫秒)
38
+ * @param {Function} config.onError - 错误回调
39
+ * @param {Function} config.getLanguage - 获取语言的函数
40
+ */
41
+ constructor(modulePath = '', config = {}) {
42
+ this.modulePath = modulePath;
43
+ this._customBaseUrl = config.baseUrl;
44
+ this._customTimeout = config.timeout;
45
+ this.token = null;
46
+ this.onError = config.onError;
47
+ this.getLanguage = config.getLanguage || (() => 'zh');
48
+
49
+ // 从全局配置初始化
50
+ this._updateFromGlobalConfig(getConfig());
51
+
52
+ // 订阅配置变更
53
+ this._unsubscribe = subscribeConfig((newConfig) => {
54
+ this._updateFromGlobalConfig(newConfig);
55
+ });
56
+ }
57
+
58
+ /**
59
+ * 从全局配置更新本地配置
60
+ * @private
61
+ */
62
+ _updateFromGlobalConfig(globalConfig) {
63
+ // 只有未自定义时才使用全局配置
64
+ if (!this._customBaseUrl) {
65
+ this.baseUrl = `${globalConfig.apiBaseUrl}${this.modulePath}`;
66
+ } else {
67
+ this.baseUrl = this._customBaseUrl;
68
+ }
69
+
70
+ if (!this._customTimeout) {
71
+ this.timeout = globalConfig.timeout || 30000;
72
+ } else {
73
+ this.timeout = this._customTimeout;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * 设置 Token
79
+ * @param {string} token - 访问令牌
80
+ */
81
+ setToken(token) {
82
+ this.token = token;
83
+ }
84
+
85
+ /**
86
+ * 设置基础 URL
87
+ * @param {string} url - 新的基础 URL
88
+ */
89
+ setBaseUrl(url) {
90
+ this._customBaseUrl = url;
91
+ this.baseUrl = url;
92
+ }
93
+
94
+ /**
95
+ * 获取当前使用的 Token
96
+ * 优先使用实例 Token,否则从存储获取
97
+ * @returns {string|null}
98
+ */
99
+ getToken() {
100
+ return this.token || getToken();
101
+ }
102
+
103
+ /**
104
+ * 发送 HTTP 请求
105
+ * @param {string} endpoint - API 端点(相对于 baseUrl)
106
+ * @param {Object} options - fetch 选项
107
+ * @returns {Promise<any>} 响应数据
108
+ */
109
+ async request(endpoint, options = {}) {
110
+ const url = `${this.baseUrl}${endpoint}`;
111
+
112
+ // 构建请求头
113
+ const headers = {
114
+ 'Content-Type': 'application/json',
115
+ 'Accept-Language': this.getLanguage(),
116
+ ...options.headers,
117
+ };
118
+
119
+ // 添加认证头
120
+ const token = this.getToken();
121
+ if (token) {
122
+ headers['Authorization'] = `Bearer ${token}`;
123
+ }
124
+
125
+ // 超时控制
126
+ const controller = new AbortController();
127
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
128
+
129
+ try {
130
+ const response = await fetch(url, {
131
+ ...options,
132
+ headers,
133
+ signal: controller.signal,
134
+ });
135
+
136
+ clearTimeout(timeoutId);
137
+
138
+ // 解析响应
139
+ const data = await response.json();
140
+
141
+ // 处理错误响应
142
+ if (!response.ok) {
143
+ const error = new Error(data.message || data.detail || `HTTP ${response.status}`);
144
+ error.code = data.code || `HTTP_${response.status}`;
145
+ error.status = response.status;
146
+ error.data = data;
147
+
148
+ this.onError?.(error);
149
+ logger.error(`${this.constructor.name} Error:`, error.message);
150
+ throw error;
151
+ }
152
+
153
+ return data;
154
+ } catch (error) {
155
+ clearTimeout(timeoutId);
156
+
157
+ // 处理超时错误
158
+ if (error.name === 'AbortError') {
159
+ const timeoutError = new Error('Request timeout');
160
+ timeoutError.code = 'TIMEOUT';
161
+ this.onError?.(timeoutError);
162
+ throw timeoutError;
163
+ }
164
+
165
+ // 处理网络错误
166
+ if (error.name === 'TypeError' && error.message.includes('fetch')) {
167
+ const networkError = new Error('Network error');
168
+ networkError.code = 'NETWORK_ERROR';
169
+ this.onError?.(networkError);
170
+ throw networkError;
171
+ }
172
+
173
+ throw error;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * GET 请求便捷方法
179
+ * @param {string} endpoint - API 端点
180
+ * @param {Object} params - 查询参数
181
+ */
182
+ async get(endpoint, params = {}) {
183
+ const query = new URLSearchParams(params).toString();
184
+ const url = query ? `${endpoint}?${query}` : endpoint;
185
+ return this.request(url, { method: 'GET' });
186
+ }
187
+
188
+ /**
189
+ * POST 请求便捷方法
190
+ * @param {string} endpoint - API 端点
191
+ * @param {Object} data - 请求体数据
192
+ */
193
+ async post(endpoint, data = {}) {
194
+ return this.request(endpoint, {
195
+ method: 'POST',
196
+ body: JSON.stringify(data),
197
+ });
198
+ }
199
+
200
+ /**
201
+ * PUT 请求便捷方法
202
+ * @param {string} endpoint - API 端点
203
+ * @param {Object} data - 请求体数据
204
+ */
205
+ async put(endpoint, data = {}) {
206
+ return this.request(endpoint, {
207
+ method: 'PUT',
208
+ body: JSON.stringify(data),
209
+ });
210
+ }
211
+
212
+ /**
213
+ * DELETE 请求便捷方法
214
+ * @param {string} endpoint - API 端点
215
+ * @param {Object} data - 请求体数据(可选)
216
+ */
217
+ async delete(endpoint, data = null) {
218
+ const options = { method: 'DELETE' };
219
+ if (data) {
220
+ options.body = JSON.stringify(data);
221
+ }
222
+ return this.request(endpoint, options);
223
+ }
224
+
225
+ /**
226
+ * PATCH 请求便捷方法
227
+ * @param {string} endpoint - API 端点
228
+ * @param {Object} data - 请求体数据
229
+ */
230
+ async patch(endpoint, data = {}) {
231
+ return this.request(endpoint, {
232
+ method: 'PATCH',
233
+ body: JSON.stringify(data),
234
+ });
235
+ }
236
+
237
+ /**
238
+ * 销毁实例,取消配置订阅
239
+ */
240
+ destroy() {
241
+ if (this._unsubscribe) {
242
+ this._unsubscribe();
243
+ this._unsubscribe = null;
244
+ }
245
+ }
246
+ }
247
+
248
+ /**
249
+ * 创建 API 客户端的工厂函数
250
+ * 用于快速创建简单的 API 客户端
251
+ *
252
+ * @param {string} modulePath - 模块路径
253
+ * @param {Object} methods - 方法定义对象
254
+ * @returns {BaseApiClient} API 客户端实例
255
+ *
256
+ * @example
257
+ * const myApi = createApiClient('/my-module', {
258
+ * getList: (client) => client.get('/list'),
259
+ * create: (client, data) => client.post('/', data),
260
+ * });
261
+ */
262
+ function createApiClient(modulePath, methods = {}) {
263
+ const client = new BaseApiClient(modulePath);
264
+
265
+ // 绑定自定义方法
266
+ Object.entries(methods).forEach(([name, fn]) => {
267
+ client[name] = (...args) => fn(client, ...args);
268
+ });
269
+
270
+ return client;
271
+ }
272
+
273
+ /**
274
+ * @quantabit/sdk-config
275
+ *
276
+ * Qbit_DID SDK 统一配置管理
277
+ *
278
+ * 所有 SDK 共享此配置,修改一处即可全局生效:
279
+ * - API 基础 URL
280
+ * - 超时时间
281
+ * - 认证配置
282
+ * - 调试模式
283
+ *
284
+ * 使用方式:
285
+ * 1. 直接使用默认配置
286
+ * import { getConfig } from '@quantabit/sdk-config';
287
+ * const config = getConfig();
288
+ *
289
+ * 2. 应用启动时初始化配置
290
+ * import { initConfig } from '@quantabit/sdk-config';
291
+ * initConfig({ apiBaseUrl: 'https://api.example.com' });
292
+ *
293
+ * 3. 使用 React Hook
294
+ * import { useSDKConfig } from '@quantabit/sdk-config';
295
+ * const { config, setApiBaseUrl } = useSDKConfig();
296
+ */
297
+
298
+ // ============ 默认配置 ============
299
+
300
+ /**
301
+ * 默认 API 配置
302
+ * 注意:生产环境应通过 initConfig 覆盖这些值
303
+ */
304
+ const DEFAULT_CONFIG = {
305
+ // === API 配置 ===
306
+ apiBaseUrl: '/api/v1', // API 基础路径(相对路径,由后端代理)
307
+ apiFullUrl: '', // 完整 API URL(需通过 initConfig 配置)
308
+
309
+ // === 超时配置 ===
310
+ timeout: 30000, // 请求超时(毫秒)
311
+ retryCount: 3, // 重试次数
312
+ retryDelay: 1000, // 重试延迟(毫秒)
313
+
314
+ // === 认证配置 ===
315
+ tokenStorageKey: 'qbit_did_access_token',
316
+ refreshTokenStorageKey: 'qbit_did_refresh_token',
317
+ currentSessionKey: 'qbit_did_current',
318
+ autoRefreshToken: true, // 自动刷新 Token
319
+
320
+ // === WebSocket 配置 ===
321
+ wsBaseUrl: '', // WebSocket URL(需通过 initConfig 配置)
322
+ wsReconnect: true,
323
+ wsReconnectInterval: 3000,
324
+
325
+ // === 调试配置 ===
326
+ debug: process.env.NODE_ENV !== 'production',
327
+ logLevel: 'info', // 'debug' | 'info' | 'warn' | 'error'
328
+
329
+ // === 环境配置 ===
330
+ environment: process.env.NODE_ENV || 'development',
331
+
332
+ // === 语言配置 ===
333
+ language: 'zh', // 默认语言
334
+ fallbackLanguage: 'en', // 回退语言
335
+ supportedLanguages: ['zh', 'en', 'ja', 'ko'], // 支持的语言
336
+ languageStorageKey: 'qbit_did_language', // localStorage key
337
+ };
338
+
339
+ // ============ 配置状态 ============
340
+
341
+ let _config = { ...DEFAULT_CONFIG };
342
+ let _listeners = new Set();
343
+ let _languageListeners = new Set(); // 语言变更监听器
344
+
345
+ // ============ 核心函数 ============
346
+
347
+ /**
348
+ * 获取当前配置
349
+ * @returns {object} 当前配置对象
350
+ */
351
+ function getConfig() {
352
+ return { ..._config };
353
+ }
354
+
355
+ /**
356
+ * 初始化 SDK 配置
357
+ * 应在应用启动时调用一次
358
+ *
359
+ * @param {object} options - 配置选项
360
+ * @param {string} options.apiBaseUrl - API 基础 URL(如 '/api/v1' 或完整 URL)
361
+ * @param {number} options.timeout - 请求超时时间(毫秒)
362
+ * @param {boolean} options.debug - 是否开启调试模式
363
+ * @param {string} options.environment - 环境标识
364
+ */
365
+ function initConfig(options = {}) {
366
+ _config = {
367
+ ..._config,
368
+ ...options,
369
+ };
370
+
371
+ // 如果提供了完整 URL,同时更新 apiFullUrl
372
+ if (options.apiBaseUrl && options.apiBaseUrl.startsWith('http')) {
373
+ _config.apiFullUrl = options.apiBaseUrl;
374
+ // 对于完整 URL,apiBaseUrl 保持原样
375
+ }
376
+
377
+ // 通知所有监听者
378
+ _notifyListeners();
379
+
380
+ if (_config.debug) {
381
+ console.log('[SDK Config] 配置已初始化:', _config);
382
+ }
383
+
384
+ return _config;
385
+ }
386
+
387
+ /**
388
+ * 设置 API 基础 URL
389
+ * 便捷方法,用于动态切换 API 地址
390
+ *
391
+ * @param {string} url - 新的 API URL
392
+ */
393
+ function setApiBaseUrl(url) {
394
+ _config.apiBaseUrl = url;
395
+ if (url.startsWith('http')) {
396
+ _config.apiFullUrl = url;
397
+ }
398
+ _notifyListeners();
399
+
400
+ if (_config.debug) {
401
+ console.log('[SDK Config] API URL 已更新:', url);
402
+ }
403
+ }
404
+
405
+ /**
406
+ * 设置 WebSocket URL
407
+ *
408
+ * @param {string} url - 新的 WebSocket URL
409
+ */
410
+ function setWsBaseUrl(url) {
411
+ _config.wsBaseUrl = url;
412
+ _notifyListeners();
413
+ }
414
+
415
+ /**
416
+ * 设置调试模式
417
+ *
418
+ * @param {boolean} debug - 是否开启调试
419
+ */
420
+ function setDebug(debug) {
421
+ _config.debug = debug;
422
+ _notifyListeners();
423
+ }
424
+
425
+ /**
426
+ * 重置为默认配置
427
+ */
428
+ function resetConfig() {
429
+ _config = { ...DEFAULT_CONFIG };
430
+ _notifyListeners();
431
+ }
432
+
433
+ // ============ 配置监听 ============
434
+
435
+ /**
436
+ * 订阅配置变更
437
+ *
438
+ * @param {function} listener - 监听回调
439
+ * @returns {function} 取消订阅函数
440
+ */
441
+ function subscribeConfig(listener) {
442
+ _listeners.add(listener);
443
+ return () => _listeners.delete(listener);
444
+ }
445
+
446
+ function _notifyListeners() {
447
+ const config = getConfig();
448
+ _listeners.forEach(listener => {
449
+ try {
450
+ listener(config);
451
+ } catch (err) {
452
+ console.error('[SDK Config] 监听器执行错误:', err);
453
+ }
454
+ });
455
+ }
456
+
457
+ // ============ 环境预设 ============
458
+
459
+ /**
460
+ * 环境预设配置
461
+ * 快速切换不同环境的配置
462
+ */
463
+ const ENVIRONMENT_PRESETS = {
464
+ development: {
465
+ apiBaseUrl: '/api/v1',
466
+ apiFullUrl: 'http://localhost:3000/api/v1',
467
+ wsBaseUrl: 'ws://localhost:3000/ws',
468
+ debug: true,
469
+ logLevel: 'debug',
470
+ },
471
+ staging: {
472
+ apiBaseUrl: '/api/v1',
473
+ apiFullUrl: '', // 请通过 initWithEnvironment('staging', { apiFullUrl: '...' }) 配置
474
+ wsBaseUrl: '', // 请通过 initWithEnvironment('staging', { wsBaseUrl: '...' }) 配置
475
+ debug: true,
476
+ logLevel: 'info',
477
+ },
478
+ production: {
479
+ apiBaseUrl: '/api/v1',
480
+ apiFullUrl: '', // 请通过 initWithEnvironment('production', { apiFullUrl: '...' }) 配置
481
+ wsBaseUrl: '', // 请通过 initWithEnvironment('production', { wsBaseUrl: '...' }) 配置
482
+ debug: false,
483
+ logLevel: 'error',
484
+ },
485
+ };
486
+
487
+ /**
488
+ * 使用环境预设初始化配置
489
+ *
490
+ * @param {string} env - 环境名称 ('development' | 'staging' | 'production')
491
+ * @param {object} overrides - 额外覆盖配置
492
+ */
493
+ function initWithEnvironment(env, overrides = {}) {
494
+ const preset = ENVIRONMENT_PRESETS[env];
495
+ if (!preset) {
496
+ console.warn(`[SDK Config] 未知环境: ${env}, 使用默认配置`);
497
+ return initConfig(overrides);
498
+ }
499
+
500
+ return initConfig({
501
+ ...preset,
502
+ environment: env,
503
+ ...overrides,
504
+ });
505
+ }
506
+
507
+ /**
508
+ * React Hook: 获取和管理 SDK 配置
509
+ *
510
+ * @example
511
+ * const { config, setApiBaseUrl, initConfig } = useSDKConfig();
512
+ *
513
+ * // 读取配置
514
+ * console.log(config.apiBaseUrl);
515
+ *
516
+ * // 动态修改 API URL
517
+ * setApiBaseUrl('https://new-api.example.com');
518
+ */
519
+ function useSDKConfig() {
520
+ const [config, setConfigState] = useState(getConfig);
521
+
522
+ useEffect(() => {
523
+ const unsubscribe = subscribeConfig(setConfigState);
524
+ return unsubscribe;
525
+ }, []);
526
+
527
+ const updateConfig = useCallback((options) => {
528
+ initConfig(options);
529
+ }, []);
530
+
531
+ const updateApiUrl = useCallback((url) => {
532
+ setApiBaseUrl(url);
533
+ }, []);
534
+
535
+ const updateWsUrl = useCallback((url) => {
536
+ setWsBaseUrl(url);
537
+ }, []);
538
+
539
+ const reset = useCallback(() => {
540
+ resetConfig();
541
+ }, []);
542
+
543
+ return {
544
+ config,
545
+ initConfig: updateConfig,
546
+ setApiBaseUrl: updateApiUrl,
547
+ setWsBaseUrl: updateWsUrl,
548
+ resetConfig: reset,
549
+ initWithEnvironment,
550
+ };
551
+ }
552
+
553
+ // ============ 请求工具 ============
554
+
555
+ /**
556
+ * 构建完整的 API URL
557
+ *
558
+ * @param {string} endpoint - API 端点(如 '/auth/login')
559
+ * @param {boolean} useFullUrl - 是否使用完整 URL
560
+ * @returns {string} 完整的 URL
561
+ */
562
+ function buildApiUrl(endpoint, useFullUrl = false) {
563
+ const baseUrl = useFullUrl ? _config.apiFullUrl : _config.apiBaseUrl;
564
+ // 确保不会出现双斜杠
565
+ const cleanEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
566
+ const cleanBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
567
+ return `${cleanBase}${cleanEndpoint}`;
568
+ }
569
+
570
+ /**
571
+ * 获取认证 Token
572
+ *
573
+ * @returns {string|null} Token 或 null
574
+ */
575
+ function getToken() {
576
+ if (typeof window === 'undefined') return null;
577
+ return localStorage.getItem(_config.tokenStorageKey);
578
+ }
579
+
580
+ /**
581
+ * 获取刷新 Token
582
+ *
583
+ * @returns {string|null} Refresh Token 或 null
584
+ */
585
+ function getRefreshToken() {
586
+ if (typeof window === 'undefined') return null;
587
+ return localStorage.getItem(_config.refreshTokenStorageKey);
588
+ }
589
+
590
+ /**
591
+ * 保存认证 Token
592
+ *
593
+ * @param {string} accessToken - Access Token
594
+ * @param {string} refreshToken - Refresh Token(可选)
595
+ */
596
+ function saveTokens(accessToken, refreshToken) {
597
+ if (typeof window === 'undefined') return;
598
+ localStorage.setItem(_config.tokenStorageKey, accessToken);
599
+ if (refreshToken) {
600
+ localStorage.setItem(_config.refreshTokenStorageKey, refreshToken);
601
+ }
602
+ }
603
+
604
+ /**
605
+ * 清除所有 Token
606
+ */
607
+ function clearTokens() {
608
+ if (typeof window === 'undefined') return;
609
+ localStorage.removeItem(_config.tokenStorageKey);
610
+ localStorage.removeItem(_config.refreshTokenStorageKey);
611
+ localStorage.removeItem(_config.currentSessionKey);
612
+ }
613
+
614
+ /**
615
+ * 检查是否已认证
616
+ *
617
+ * @returns {boolean} 是否有有效 Token
618
+ */
619
+ function isAuthenticated() {
620
+ return !!getToken();
621
+ }
622
+
623
+ // ============ 日志工具 ============
624
+
625
+ const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
626
+
627
+ /**
628
+ * SDK 日志工具
629
+ * 根据配置的日志级别输出日志
630
+ */
631
+ const logger = {
632
+ debug: (...args) => {
633
+ if (_config.debug && LOG_LEVELS[_config.logLevel] <= LOG_LEVELS.debug) {
634
+ console.debug('[SDK]', ...args);
635
+ }
636
+ },
637
+ info: (...args) => {
638
+ if (_config.debug && LOG_LEVELS[_config.logLevel] <= LOG_LEVELS.info) {
639
+ console.info('[SDK]', ...args);
640
+ }
641
+ },
642
+ warn: (...args) => {
643
+ if (LOG_LEVELS[_config.logLevel] <= LOG_LEVELS.warn) {
644
+ console.warn('[SDK]', ...args);
645
+ }
646
+ },
647
+ error: (...args) => {
648
+ console.error('[SDK]', ...args);
649
+ },
650
+ };
651
+
652
+ // ============ 语言管理 ============
653
+
654
+ /**
655
+ * 获取当前语言
656
+ * 优先从 localStorage 读取,否则使用默认配置
657
+ *
658
+ * @returns {string} 当前语言代码
659
+ */
660
+ function getLanguage() {
661
+ // SSR 安全检查
662
+ if (typeof window === 'undefined') {
663
+ return _config.language;
664
+ }
665
+
666
+ // 尝试从 localStorage 读取
667
+ const stored = localStorage.getItem(_config.languageStorageKey);
668
+ if (stored && _config.supportedLanguages.includes(stored)) {
669
+ return stored;
670
+ }
671
+
672
+ // 尝试从浏览器获取
673
+ const browserLang = navigator.language?.split('-')[0];
674
+ if (browserLang && _config.supportedLanguages.includes(browserLang)) {
675
+ return browserLang;
676
+ }
677
+
678
+ return _config.language;
679
+ }
680
+
681
+ /**
682
+ * 设置全局语言
683
+ * 会通知所有订阅的 SDK 和组件
684
+ *
685
+ * @param {string} lang - 语言代码 ('zh' | 'en' | 'ja' | 'ko')
686
+ * @returns {boolean} 是否设置成功
687
+ */
688
+ function setLanguage(lang) {
689
+ // 验证语言代码
690
+ if (!_config.supportedLanguages.includes(lang)) {
691
+ logger.warn(`不支持的语言: ${lang}, 支持的语言: ${_config.supportedLanguages.join(', ')}`);
692
+ return false;
693
+ }
694
+
695
+ const previousLang = _config.language;
696
+ _config.language = lang;
697
+
698
+ // 持久化到 localStorage
699
+ if (typeof window !== 'undefined') {
700
+ localStorage.setItem(_config.languageStorageKey, lang);
701
+
702
+ // 触发全局 CustomEvent,让不直接依赖此模块的 SDK 也能收到通知
703
+ try {
704
+ const event = new CustomEvent('qbit-did:language-change', {
705
+ detail: { language: lang, oldLanguage: previousLang }
706
+ });
707
+ window.dispatchEvent(event);
708
+ } catch (e) {
709
+ // 忽略事件触发错误
710
+ }
711
+ }
712
+
713
+ // 通知所有语言监听器
714
+ _notifyLanguageListeners(lang, previousLang);
715
+
716
+ if (_config.debug) {
717
+ console.log(`[SDK Config] 语言已切换: ${previousLang} -> ${lang}`);
718
+ }
719
+
720
+ return true;
721
+ }
722
+
723
+ /**
724
+ * 初始化语言(应用启动时调用)
725
+ * 从 localStorage 或浏览器偏好读取
726
+ */
727
+ function initLanguage() {
728
+ const detectedLang = getLanguage();
729
+ _config.language = detectedLang;
730
+
731
+ if (_config.debug) {
732
+ console.log(`[SDK Config] 语言已初始化: ${detectedLang}`);
733
+ }
734
+
735
+ return detectedLang;
736
+ }
737
+
738
+ /**
739
+ * 订阅语言变更
740
+ *
741
+ * @param {function} listener - 回调函数 (newLang, oldLang) => void
742
+ * @returns {function} 取消订阅函数
743
+ */
744
+ function subscribeLanguage(listener) {
745
+ _languageListeners.add(listener);
746
+ return () => _languageListeners.delete(listener);
747
+ }
748
+
749
+ function _notifyLanguageListeners(newLang, oldLang) {
750
+ _languageListeners.forEach(listener => {
751
+ try {
752
+ listener(newLang, oldLang);
753
+ } catch (err) {
754
+ console.error('[SDK Config] 语言监听器执行错误:', err);
755
+ }
756
+ });
757
+ }
758
+
759
+ /**
760
+ * React Hook: 获取和管理语言
761
+ *
762
+ * @example
763
+ * const { language, setLanguage, supportedLanguages } = useLanguage();
764
+ *
765
+ * // 切换语言
766
+ * setLanguage('en');
767
+ */
768
+ function useLanguage() {
769
+ const [language, setLangState] = useState(getLanguage);
770
+
771
+ useEffect(() => {
772
+ // 初始化语言
773
+ const currentLang = getLanguage();
774
+ setLangState(currentLang);
775
+
776
+ // 订阅语言变更
777
+ const unsubscribe = subscribeLanguage((newLang) => {
778
+ setLangState(newLang);
779
+ });
780
+
781
+ return unsubscribe;
782
+ }, []);
783
+
784
+ const updateLanguage = useCallback((lang) => {
785
+ setLanguage(lang);
786
+ }, []);
787
+
788
+ return {
789
+ language,
790
+ setLanguage: updateLanguage,
791
+ supportedLanguages: _config.supportedLanguages,
792
+ fallbackLanguage: _config.fallbackLanguage,
793
+ };
794
+ }
795
+
796
+ // ============ 导出 ============
797
+
798
+ var index = {
799
+ getConfig,
800
+ initConfig,
801
+ setApiBaseUrl,
802
+ setWsBaseUrl,
803
+ setDebug,
804
+ resetConfig,
805
+ subscribeConfig,
806
+ initWithEnvironment,
807
+ ENVIRONMENT_PRESETS,
808
+ buildApiUrl,
809
+ getToken,
810
+ getRefreshToken,
811
+ saveTokens,
812
+ clearTokens,
813
+ isAuthenticated,
814
+ logger,
815
+ useSDKConfig,
816
+ // 语言管理
817
+ getLanguage,
818
+ setLanguage,
819
+ initLanguage,
820
+ subscribeLanguage,
821
+ useLanguage,
822
+ };
823
+
824
+ export { BaseApiClient, ENVIRONMENT_PRESETS, buildApiUrl, clearTokens, createApiClient, index as default, getConfig, getLanguage, getRefreshToken, getToken, initConfig, initLanguage, initWithEnvironment, isAuthenticated, logger, resetConfig, saveTokens, setApiBaseUrl, setDebug, setLanguage, setWsBaseUrl, subscribeConfig, subscribeLanguage, useLanguage, useSDKConfig };
825
+ //# sourceMappingURL=index.esm.js.map