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