seaverse-auth-sdk 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/dist/index.js ADDED
@@ -0,0 +1,2403 @@
1
+ import axios from 'axios';
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+
6
+ /**
7
+ * 生成UUID的跨平台函数
8
+ * 支持 Node.js 和浏览器环境
9
+ */
10
+ function generateUUID() {
11
+ // Node.js 环境
12
+ if (typeof globalThis !== 'undefined' && globalThis.crypto?.randomUUID) {
13
+ return globalThis.crypto.randomUUID();
14
+ }
15
+ // 浏览器环境 - crypto.randomUUID (现代浏览器)
16
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
17
+ return crypto.randomUUID();
18
+ }
19
+ // 浏览器环境 - crypto.getRandomValues (备用方案)
20
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
21
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
22
+ const r = (crypto.getRandomValues(new Uint8Array(1))[0] % 16) | 0;
23
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
24
+ return v.toString(16);
25
+ });
26
+ }
27
+ // Fallback - 使用 Math.random() (不推荐用于生产环境)
28
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
29
+ const r = (Math.random() * 16) | 0;
30
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
31
+ return v.toString(16);
32
+ });
33
+ }
34
+ /**
35
+ * 内置Hook集合
36
+ */
37
+ class BuiltInHooks {
38
+ /**
39
+ * 创建日志记录Hook
40
+ */
41
+ static createLoggerHook(options) {
42
+ const logLevel = options?.logLevel || 'info';
43
+ const logRequestBody = options?.logRequestBody ?? false;
44
+ const logResponseBody = options?.logResponseBody ?? false;
45
+ const logHeaders = options?.logHeaders ?? false;
46
+ return {
47
+ type: 'beforeRequest',
48
+ name: 'logger',
49
+ priority: 100,
50
+ handler: async (context) => {
51
+ const { operationId, config } = context;
52
+ console.log(`[${logLevel.toUpperCase()}] ${config.method?.toUpperCase()} ${config.url}`);
53
+ console.log(`Operation ID: ${operationId}`);
54
+ if (logHeaders && config.headers) {
55
+ console.log('Headers:', JSON.stringify(config.headers, null, 2));
56
+ }
57
+ if (logRequestBody && config.data) {
58
+ console.log('Request Body:', JSON.stringify(config.data, null, 2));
59
+ }
60
+ // 添加afterResponse日志
61
+ if (context.response) {
62
+ console.log(`Response Status: ${context.response.status}`);
63
+ if (logResponseBody && context.response.data) {
64
+ console.log('Response Body:', JSON.stringify(context.response.data, null, 2));
65
+ }
66
+ }
67
+ },
68
+ };
69
+ }
70
+ /**
71
+ * 创建请求ID Hook
72
+ */
73
+ static createRequestIdHook() {
74
+ return {
75
+ type: 'beforeRequest',
76
+ name: 'request-id',
77
+ priority: 10,
78
+ handler: async (context) => {
79
+ const requestId = generateUUID();
80
+ context.config.headers = {
81
+ ...context.config.headers,
82
+ 'X-Request-ID': requestId,
83
+ };
84
+ context.metadata = context.metadata || {};
85
+ context.metadata.requestId = requestId;
86
+ },
87
+ };
88
+ }
89
+ /**
90
+ * 创建速率限制Hook
91
+ */
92
+ static createRateLimitHook(options) {
93
+ const requestsPerSecond = options?.requestsPerSecond;
94
+ // TODO: 实现基于分钟的速率限制
95
+ // const requestsPerMinute = options?.requestsPerMinute;
96
+ // 简单的令牌桶实现
97
+ let tokens = requestsPerSecond || 10;
98
+ let lastRefill = Date.now();
99
+ const refillTokens = () => {
100
+ const now = Date.now();
101
+ const timePassed = now - lastRefill;
102
+ if (requestsPerSecond && timePassed >= 1000) {
103
+ tokens = Math.min(requestsPerSecond, tokens + requestsPerSecond);
104
+ lastRefill = now;
105
+ }
106
+ };
107
+ return {
108
+ type: 'beforeRequest',
109
+ name: 'rate-limit',
110
+ priority: 5,
111
+ handler: async (_context) => {
112
+ refillTokens();
113
+ if (tokens <= 0) {
114
+ const waitTime = 1000 - (Date.now() - lastRefill);
115
+ if (waitTime > 0) {
116
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
117
+ refillTokens();
118
+ }
119
+ }
120
+ tokens--;
121
+ },
122
+ };
123
+ }
124
+ /**
125
+ * 创建缓存Hook(简化版)
126
+ */
127
+ static createCacheHook(options) {
128
+ const cache = new Map();
129
+ // TODO: 实现缓存过期逻辑
130
+ // const ttl = options?.ttl || 60000; // 默认60秒
131
+ const methods = options?.methods || ['GET'];
132
+ return {
133
+ type: 'beforeRequest',
134
+ name: 'cache',
135
+ priority: 20,
136
+ handler: async (context) => {
137
+ const method = context.config.method?.toUpperCase();
138
+ if (!method || !methods.includes(method)) {
139
+ return;
140
+ }
141
+ const cacheKey = `${method}:${context.config.url}:${JSON.stringify(context.config.params)}`;
142
+ const cached = cache.get(cacheKey);
143
+ if (cached && Date.now() < cached.expiresAt) {
144
+ // 从缓存返回(这里需要特殊处理,通常在拦截器中实现)
145
+ context.metadata = context.metadata || {};
146
+ context.metadata.cacheHit = true;
147
+ }
148
+ else {
149
+ // 缓存miss,需要在afterResponse中缓存结果
150
+ context.metadata = context.metadata || {};
151
+ context.metadata.cacheKey = cacheKey;
152
+ }
153
+ },
154
+ };
155
+ }
156
+ /**
157
+ * 创建性能监控Hook
158
+ */
159
+ static createMetricsHook(options) {
160
+ const collector = options?.collector || ((metrics) => {
161
+ console.log('Metrics:', metrics);
162
+ });
163
+ return {
164
+ type: 'beforeRequest',
165
+ name: 'metrics',
166
+ priority: 1,
167
+ handler: async (context) => {
168
+ context.metadata = context.metadata || {};
169
+ context.metadata.startTime = Date.now();
170
+ // 在afterResponse或onError中计算duration
171
+ if (context.response) {
172
+ const duration = Date.now() - context.metadata.startTime;
173
+ const metrics = {
174
+ operationId: context.operationId,
175
+ method: context.config.method?.toUpperCase() || 'UNKNOWN',
176
+ path: context.config.url || '',
177
+ statusCode: context.response.status,
178
+ duration,
179
+ timestamp: Date.now(),
180
+ };
181
+ collector(metrics);
182
+ }
183
+ else if (context.error) {
184
+ const duration = Date.now() - context.metadata.startTime;
185
+ const metrics = {
186
+ operationId: context.operationId,
187
+ method: context.config.method?.toUpperCase() || 'UNKNOWN',
188
+ path: context.config.url || '',
189
+ duration,
190
+ timestamp: Date.now(),
191
+ error: context.error.message,
192
+ };
193
+ collector(metrics);
194
+ }
195
+ },
196
+ };
197
+ }
198
+ /**
199
+ * 创建重试Hook(需要在interceptor中实现真正的重试逻辑)
200
+ */
201
+ static createRetryHook(maxRetries = 3, retryDelay = 1000) {
202
+ return {
203
+ type: 'onRetry',
204
+ name: 'retry',
205
+ priority: 1,
206
+ handler: async (context) => {
207
+ const retryCount = context.retryCount || 0;
208
+ console.log(`Retrying request (${retryCount}/${maxRetries})...`);
209
+ if (retryCount > 0) {
210
+ // 指数退避
211
+ const delay = retryDelay * Math.pow(2, retryCount - 1);
212
+ await new Promise((resolve) => setTimeout(resolve, delay));
213
+ }
214
+ },
215
+ };
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Hook管理器
221
+ */
222
+ class HookManager {
223
+ constructor(options) {
224
+ this.hooks = new Map();
225
+ // 初始化hook类型映射
226
+ this.hooks.set('beforeRequest', []);
227
+ this.hooks.set('afterResponse', []);
228
+ this.hooks.set('onError', []);
229
+ this.hooks.set('onRetry', []);
230
+ // 注册用户自定义hooks
231
+ if (options?.hooks) {
232
+ for (const hook of options.hooks) {
233
+ this.register(hook);
234
+ }
235
+ }
236
+ // 注册内置hooks
237
+ if (options?.enableBuiltInHooks) {
238
+ this.registerBuiltInHooks(options);
239
+ }
240
+ }
241
+ /**
242
+ * 注册hook
243
+ */
244
+ register(hook) {
245
+ const hooks = this.hooks.get(hook.type) || [];
246
+ hooks.push(hook);
247
+ // 按优先级排序(优先级数字越小越优先)
248
+ hooks.sort((a, b) => a.priority - b.priority);
249
+ this.hooks.set(hook.type, hooks);
250
+ }
251
+ /**
252
+ * 注销hook
253
+ */
254
+ unregister(type, name) {
255
+ const hooks = this.hooks.get(type) || [];
256
+ const filtered = hooks.filter((h) => h.name !== name);
257
+ this.hooks.set(type, filtered);
258
+ }
259
+ /**
260
+ * 执行beforeRequest hooks
261
+ */
262
+ async executeBeforeRequest(context) {
263
+ await this.executeHooks('beforeRequest', context);
264
+ }
265
+ /**
266
+ * 执行afterResponse hooks
267
+ */
268
+ async executeAfterResponse(context) {
269
+ await this.executeHooks('afterResponse', context);
270
+ }
271
+ /**
272
+ * 执行onError hooks
273
+ */
274
+ async executeOnError(context) {
275
+ await this.executeHooks('onError', context);
276
+ }
277
+ /**
278
+ * 执行onRetry hooks
279
+ */
280
+ async executeOnRetry(context) {
281
+ await this.executeHooks('onRetry', context);
282
+ }
283
+ /**
284
+ * 执行指定类型的hooks
285
+ */
286
+ async executeHooks(type, context) {
287
+ const hooks = this.hooks.get(type) || [];
288
+ for (const hook of hooks) {
289
+ // 检查过滤条件
290
+ if (!this.shouldExecuteHook(hook, context)) {
291
+ continue;
292
+ }
293
+ try {
294
+ await hook.handler(context);
295
+ }
296
+ catch (error) {
297
+ // Hook执行失败不应阻断请求
298
+ console.error(`Hook ${hook.name} failed:`, error);
299
+ }
300
+ }
301
+ }
302
+ /**
303
+ * 检查是否应该执行hook
304
+ */
305
+ shouldExecuteHook(hook, context) {
306
+ if (!hook.filter) {
307
+ return true;
308
+ }
309
+ const filter = hook.filter;
310
+ // 检查operationId过滤
311
+ if (filter.operationIds && filter.operationIds.length > 0) {
312
+ if (!filter.operationIds.includes(context.operationId)) {
313
+ return false;
314
+ }
315
+ }
316
+ // 检查method过滤
317
+ if (filter.methods && filter.methods.length > 0) {
318
+ const method = context.config.method?.toUpperCase();
319
+ if (!method || !filter.methods.includes(method)) {
320
+ return false;
321
+ }
322
+ }
323
+ // 检查path pattern过滤
324
+ if (filter.pathPatterns && filter.pathPatterns.length > 0) {
325
+ const url = context.config.url || '';
326
+ const matched = filter.pathPatterns.some((pattern) => pattern.test(url));
327
+ if (!matched) {
328
+ return false;
329
+ }
330
+ }
331
+ return true;
332
+ }
333
+ /**
334
+ * 注册内置hooks
335
+ */
336
+ registerBuiltInHooks(options) {
337
+ const { enableBuiltInHooks } = options;
338
+ if (enableBuiltInHooks?.logger) {
339
+ this.register(BuiltInHooks.createLoggerHook(options.loggerOptions));
340
+ }
341
+ if (enableBuiltInHooks?.requestId) {
342
+ this.register(BuiltInHooks.createRequestIdHook());
343
+ }
344
+ if (enableBuiltInHooks?.retry) {
345
+ // Retry hook需要特殊处理,通常在axios interceptor中实现
346
+ console.warn('Retry hook should be implemented in axios interceptor');
347
+ }
348
+ if (enableBuiltInHooks?.rateLimit) {
349
+ this.register(BuiltInHooks.createRateLimitHook(options.rateLimitOptions));
350
+ }
351
+ if (enableBuiltInHooks?.cache) {
352
+ // Cache hook需要特殊处理
353
+ console.warn('Cache hook requires special implementation');
354
+ }
355
+ if (enableBuiltInHooks?.metrics) {
356
+ this.register(BuiltInHooks.createMetricsHook(options.metricsOptions));
357
+ }
358
+ }
359
+ /**
360
+ * 获取所有注册的hooks
361
+ */
362
+ getAllHooks() {
363
+ return new Map(this.hooks);
364
+ }
365
+ /**
366
+ * 获取指定类型的hooks
367
+ */
368
+ getHooks(type) {
369
+ return this.hooks.get(type) || [];
370
+ }
371
+ /**
372
+ * 清除所有hooks
373
+ */
374
+ clear() {
375
+ this.hooks.clear();
376
+ this.hooks.set('beforeRequest', []);
377
+ this.hooks.set('afterResponse', []);
378
+ this.hooks.set('onError', []);
379
+ this.hooks.set('onRetry', []);
380
+ }
381
+ }
382
+
383
+ /**
384
+ * HTTP客户端,集成认证和Hook系统
385
+ */
386
+ class HttpClient {
387
+ constructor(options) {
388
+ this.auth = options.auth;
389
+ this.hookManager = new HookManager(options.hooks);
390
+ this.retryOptions = {
391
+ maxRetries: 3,
392
+ retryDelay: 1000,
393
+ retryStatusCodes: [408, 429, 500, 502, 503, 504],
394
+ ...options.retryOptions,
395
+ };
396
+ // 创建axios实例
397
+ this.client = axios.create({
398
+ baseURL: options.baseURL,
399
+ timeout: options.timeout || 30000,
400
+ headers: options.headers,
401
+ });
402
+ // 设置拦截器
403
+ this.setupInterceptors();
404
+ }
405
+ /**
406
+ * 设置请求和响应拦截器
407
+ */
408
+ setupInterceptors() {
409
+ // 请求拦截器
410
+ this.client.interceptors.request.use(async (config) => {
411
+ const operationId = config.headers?.['X-Operation-Id'] || 'unknown';
412
+ // 添加认证信息
413
+ if (this.auth) {
414
+ config = await this.auth.attachCredentials(config);
415
+ }
416
+ // 执行beforeRequest hooks
417
+ const hookContext = {
418
+ operationId,
419
+ config,
420
+ metadata: {},
421
+ };
422
+ await this.hookManager.executeBeforeRequest(hookContext);
423
+ return hookContext.config;
424
+ }, (error) => {
425
+ return Promise.reject(error);
426
+ });
427
+ // 响应拦截器
428
+ this.client.interceptors.response.use(async (response) => {
429
+ const operationId = response.config.headers?.['X-Operation-Id'] || 'unknown';
430
+ // 执行afterResponse hooks
431
+ const hookContext = {
432
+ operationId,
433
+ config: response.config,
434
+ response,
435
+ metadata: {},
436
+ };
437
+ await this.hookManager.executeAfterResponse(hookContext);
438
+ return hookContext.response || response;
439
+ }, async (error) => {
440
+ const operationId = error.config?.headers?.['X-Operation-Id'] || 'unknown';
441
+ // 执行onError hooks
442
+ const hookContext = {
443
+ operationId,
444
+ config: error.config,
445
+ error,
446
+ metadata: {},
447
+ };
448
+ await this.hookManager.executeOnError(hookContext);
449
+ // 重试逻辑
450
+ const shouldRetry = this.shouldRetryRequest(error);
451
+ const retryCount = error.config?._retryCount || 0;
452
+ if (shouldRetry && retryCount < this.retryOptions.maxRetries) {
453
+ // 执行onRetry hooks
454
+ hookContext.retryCount = retryCount;
455
+ await this.hookManager.executeOnRetry(hookContext);
456
+ // 计算退避延迟
457
+ const delay = this.retryOptions.retryDelay * Math.pow(2, retryCount);
458
+ await this.delay(delay);
459
+ // 更新重试计数
460
+ const config = error.config;
461
+ config._retryCount = retryCount + 1;
462
+ // 重新发起请求
463
+ return this.client.request(config);
464
+ }
465
+ return Promise.reject(hookContext.error || error);
466
+ });
467
+ }
468
+ /**
469
+ * 判断是否应该重试
470
+ */
471
+ shouldRetryRequest(error) {
472
+ // 自定义重试判断
473
+ if (this.retryOptions.shouldRetry) {
474
+ return this.retryOptions.shouldRetry(error);
475
+ }
476
+ // 没有响应或网络错误
477
+ if (!error.response) {
478
+ return true;
479
+ }
480
+ // 根据状态码判断
481
+ const statusCode = error.response.status;
482
+ return this.retryOptions.retryStatusCodes?.includes(statusCode) || false;
483
+ }
484
+ /**
485
+ * 延迟函数
486
+ */
487
+ delay(ms) {
488
+ return new Promise((resolve) => setTimeout(resolve, ms));
489
+ }
490
+ /**
491
+ * 发起请求
492
+ */
493
+ async request(config) {
494
+ return this.client.request(config);
495
+ }
496
+ /**
497
+ * GET请求
498
+ */
499
+ async get(url, config) {
500
+ return this.client.get(url, config);
501
+ }
502
+ /**
503
+ * POST请求
504
+ */
505
+ async post(url, data, config) {
506
+ return this.client.post(url, data, config);
507
+ }
508
+ /**
509
+ * PUT请求
510
+ */
511
+ async put(url, data, config) {
512
+ return this.client.put(url, data, config);
513
+ }
514
+ /**
515
+ * DELETE请求
516
+ */
517
+ async delete(url, config) {
518
+ return this.client.delete(url, config);
519
+ }
520
+ /**
521
+ * PATCH请求
522
+ */
523
+ async patch(url, data, config) {
524
+ return this.client.patch(url, data, config);
525
+ }
526
+ /**
527
+ * 获取底层axios实例
528
+ */
529
+ getAxiosInstance() {
530
+ return this.client;
531
+ }
532
+ }
533
+
534
+ /**
535
+ * 认证提供者抽象基类
536
+ */
537
+ class AuthProvider {
538
+ constructor(credentials) {
539
+ this.credentials = credentials;
540
+ }
541
+ /**
542
+ * 获取当前凭证
543
+ */
544
+ getCredentials() {
545
+ return this.credentials;
546
+ }
547
+ /**
548
+ * 更新凭证
549
+ */
550
+ updateCredentials(credentials) {
551
+ this.credentials = { ...this.credentials, ...credentials };
552
+ }
553
+ }
554
+
555
+ /**
556
+ * API Key认证提供者
557
+ */
558
+ class ApiKeyProvider extends AuthProvider {
559
+ constructor(credentials) {
560
+ super(credentials);
561
+ this.credentials = credentials;
562
+ }
563
+ async attachCredentials(config) {
564
+ const newConfig = { ...config };
565
+ if (this.credentials.location === 'header') {
566
+ // 添加到Header
567
+ newConfig.headers = {
568
+ ...newConfig.headers,
569
+ [this.credentials.name]: this.credentials.apiKey,
570
+ };
571
+ }
572
+ else if (this.credentials.location === 'query') {
573
+ // 添加到Query参数
574
+ newConfig.params = {
575
+ ...newConfig.params,
576
+ [this.credentials.name]: this.credentials.apiKey,
577
+ };
578
+ }
579
+ return newConfig;
580
+ }
581
+ async refreshCredentials() {
582
+ // API Key通常不需要刷新
583
+ return Promise.resolve();
584
+ }
585
+ async isValid() {
586
+ return Boolean(this.credentials.apiKey);
587
+ }
588
+ }
589
+
590
+ /**
591
+ * OAuth2认证提供者
592
+ */
593
+ class OAuth2Provider extends AuthProvider {
594
+ constructor(credentials) {
595
+ super(credentials);
596
+ this.credentials = credentials;
597
+ }
598
+ async attachCredentials(config) {
599
+ // 确保有有效的access token
600
+ if (!(await this.isValid())) {
601
+ await this.refreshCredentials();
602
+ }
603
+ const newConfig = { ...config };
604
+ newConfig.headers = {
605
+ ...newConfig.headers,
606
+ Authorization: `Bearer ${this.credentials.accessToken}`,
607
+ };
608
+ return newConfig;
609
+ }
610
+ async refreshCredentials() {
611
+ try {
612
+ const params = new URLSearchParams();
613
+ params.append('client_id', this.credentials.clientId);
614
+ params.append('client_secret', this.credentials.clientSecret);
615
+ if (this.credentials.refreshToken) {
616
+ // 使用refresh token刷新
617
+ params.append('grant_type', 'refresh_token');
618
+ params.append('refresh_token', this.credentials.refreshToken);
619
+ }
620
+ else {
621
+ // 使用client credentials流程
622
+ params.append('grant_type', 'client_credentials');
623
+ if (this.credentials.scope) {
624
+ params.append('scope', this.credentials.scope);
625
+ }
626
+ }
627
+ const response = await axios.post(this.credentials.tokenUrl, params, {
628
+ headers: {
629
+ 'Content-Type': 'application/x-www-form-urlencoded',
630
+ },
631
+ });
632
+ const tokenData = response.data;
633
+ // 更新凭证
634
+ this.credentials.accessToken = tokenData.access_token;
635
+ if (tokenData.refresh_token) {
636
+ this.credentials.refreshToken = tokenData.refresh_token;
637
+ }
638
+ if (tokenData.expires_in) {
639
+ this.credentials.expiresAt = Date.now() + tokenData.expires_in * 1000;
640
+ }
641
+ }
642
+ catch (error) {
643
+ throw new Error(`Failed to refresh OAuth2 token: ${error}`);
644
+ }
645
+ }
646
+ async isValid() {
647
+ if (!this.credentials.accessToken) {
648
+ return false;
649
+ }
650
+ if (this.credentials.expiresAt) {
651
+ // 提前5分钟刷新
652
+ const bufferTime = 5 * 60 * 1000;
653
+ return Date.now() < this.credentials.expiresAt - bufferTime;
654
+ }
655
+ return true;
656
+ }
657
+ }
658
+
659
+ /**
660
+ * JWT认证提供者
661
+ */
662
+ class JWTProvider extends AuthProvider {
663
+ constructor(credentials) {
664
+ super(credentials);
665
+ this.credentials = credentials;
666
+ }
667
+ async attachCredentials(config) {
668
+ const newConfig = { ...config };
669
+ const headerName = this.credentials.headerName || 'Authorization';
670
+ const token = this.credentials.token;
671
+ newConfig.headers = {
672
+ ...newConfig.headers,
673
+ [headerName]: headerName === 'Authorization' ? `Bearer ${token}` : token,
674
+ };
675
+ return newConfig;
676
+ }
677
+ async refreshCredentials() {
678
+ // JWT通常由外部系统管理,这里不实现刷新逻辑
679
+ return Promise.resolve();
680
+ }
681
+ async isValid() {
682
+ if (!this.credentials.token) {
683
+ return false;
684
+ }
685
+ // 可以在这里添加JWT过期检查
686
+ try {
687
+ const parts = this.credentials.token.split('.');
688
+ if (parts.length !== 3) {
689
+ return false;
690
+ }
691
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
692
+ if (payload.exp) {
693
+ // 检查是否过期(提前1分钟)
694
+ return Date.now() < payload.exp * 1000 - 60000;
695
+ }
696
+ return true;
697
+ }
698
+ catch {
699
+ return false;
700
+ }
701
+ }
702
+ }
703
+
704
+ /**
705
+ * Basic认证提供者
706
+ */
707
+ class BasicAuthProvider extends AuthProvider {
708
+ constructor(credentials) {
709
+ super(credentials);
710
+ this.credentials = credentials;
711
+ }
712
+ async attachCredentials(config) {
713
+ const newConfig = { ...config };
714
+ const token = Buffer.from(`${this.credentials.username}:${this.credentials.password}`).toString('base64');
715
+ newConfig.headers = {
716
+ ...newConfig.headers,
717
+ Authorization: `Basic ${token}`,
718
+ };
719
+ return newConfig;
720
+ }
721
+ async refreshCredentials() {
722
+ // Basic Auth不需要刷新
723
+ return Promise.resolve();
724
+ }
725
+ async isValid() {
726
+ return Boolean(this.credentials.username && this.credentials.password);
727
+ }
728
+ }
729
+
730
+ /**
731
+ * 自定义认证提供者
732
+ */
733
+ class CustomAuthProvider extends AuthProvider {
734
+ constructor(credentials) {
735
+ super(credentials);
736
+ this.credentials = credentials;
737
+ }
738
+ async attachCredentials(config) {
739
+ return this.credentials.handler(config);
740
+ }
741
+ async refreshCredentials() {
742
+ // 自定义认证由用户实现
743
+ return Promise.resolve();
744
+ }
745
+ async isValid() {
746
+ // 自定义认证默认认为总是有效
747
+ return true;
748
+ }
749
+ }
750
+
751
+ /**
752
+ * 配置管理器
753
+ */
754
+ class ConfigManager {
755
+ constructor(configDir) {
756
+ this.configDir = configDir || path.join(os.homedir(), '.openapi-sdk');
757
+ this.configPath = path.join(this.configDir, 'config.json');
758
+ }
759
+ /**
760
+ * 保存认证配置
761
+ */
762
+ async saveAuth(profileName, authConfig) {
763
+ await this.ensureConfigDir();
764
+ const config = await this.loadConfig();
765
+ config.profiles[profileName] = authConfig;
766
+ await fs.writeFile(this.configPath, JSON.stringify(config, null, 2), 'utf-8');
767
+ }
768
+ /**
769
+ * 加载认证配置
770
+ */
771
+ async loadAuth(profileName) {
772
+ const config = await this.loadConfig();
773
+ const authConfig = config.profiles[profileName];
774
+ if (!authConfig) {
775
+ throw new Error(`Profile '${profileName}' not found`);
776
+ }
777
+ return authConfig;
778
+ }
779
+ /**
780
+ * 列出所有profile
781
+ */
782
+ async listProfiles() {
783
+ const config = await this.loadConfig();
784
+ return Object.keys(config.profiles);
785
+ }
786
+ /**
787
+ * 删除profile
788
+ */
789
+ async removeProfile(profileName) {
790
+ const config = await this.loadConfig();
791
+ delete config.profiles[profileName];
792
+ await fs.writeFile(this.configPath, JSON.stringify(config, null, 2), 'utf-8');
793
+ }
794
+ /**
795
+ * 从文件加载配置
796
+ */
797
+ async loadFromFile(filePath) {
798
+ try {
799
+ const content = await fs.readFile(filePath, 'utf-8');
800
+ return JSON.parse(content);
801
+ }
802
+ catch (error) {
803
+ throw new Error(`Failed to load config from ${filePath}: ${error}`);
804
+ }
805
+ }
806
+ /**
807
+ * 保存配置到文件
808
+ */
809
+ async saveToFile(filePath, authConfig) {
810
+ try {
811
+ await fs.writeFile(filePath, JSON.stringify(authConfig, null, 2), 'utf-8');
812
+ }
813
+ catch (error) {
814
+ throw new Error(`Failed to save config to ${filePath}: ${error}`);
815
+ }
816
+ }
817
+ /**
818
+ * 加载完整配置文件
819
+ */
820
+ async loadConfig() {
821
+ try {
822
+ const content = await fs.readFile(this.configPath, 'utf-8');
823
+ return JSON.parse(content);
824
+ }
825
+ catch (error) {
826
+ if (error.code === 'ENOENT') {
827
+ // 文件不存在,返回默认配置
828
+ return { profiles: {} };
829
+ }
830
+ throw error;
831
+ }
832
+ }
833
+ /**
834
+ * 确保配置目录存在
835
+ */
836
+ async ensureConfigDir() {
837
+ try {
838
+ await fs.mkdir(this.configDir, { recursive: true });
839
+ }
840
+ catch (error) {
841
+ // 忽略错误,可能目录已存在
842
+ }
843
+ }
844
+ /**
845
+ * 获取配置目录路径
846
+ */
847
+ getConfigDir() {
848
+ return this.configDir;
849
+ }
850
+ /**
851
+ * 获取配置文件路径
852
+ */
853
+ getConfigPath() {
854
+ return this.configPath;
855
+ }
856
+ }
857
+
858
+ /**
859
+ * 跨平台环境配置工具
860
+ * 支持 Node.js 和浏览器环境
861
+ */
862
+ /**
863
+ * 全局配置存储(用于浏览器环境)
864
+ */
865
+ const globalConfig = {};
866
+ /**
867
+ * 获取环境变量或配置值
868
+ * 优先级:globalConfig > process.env (仅 Node.js)
869
+ */
870
+ function getEnvConfig(key) {
871
+ // 优先使用全局配置
872
+ if (globalConfig[key] !== undefined) {
873
+ return globalConfig[key];
874
+ }
875
+ // Node.js 环境
876
+ if (typeof process !== 'undefined' && process.env) {
877
+ return process.env[key];
878
+ }
879
+ return undefined;
880
+ }
881
+
882
+ /**
883
+ * 认证提供者工厂
884
+ */
885
+ class AuthFactory {
886
+ /**
887
+ * 根据配置创建认证提供者
888
+ */
889
+ static create(config) {
890
+ return this.createFromCredentials(config.credentials);
891
+ }
892
+ /**
893
+ * 根据凭证创建认证提供者
894
+ */
895
+ static createFromCredentials(credentials) {
896
+ switch (credentials.type) {
897
+ case 'apiKey':
898
+ return new ApiKeyProvider(credentials);
899
+ case 'oauth2':
900
+ return new OAuth2Provider(credentials);
901
+ case 'jwt':
902
+ return new JWTProvider(credentials);
903
+ case 'basic':
904
+ return new BasicAuthProvider(credentials);
905
+ case 'custom':
906
+ return new CustomAuthProvider(credentials);
907
+ default:
908
+ throw new Error(`Unsupported auth type: ${credentials.type}`);
909
+ }
910
+ }
911
+ /**
912
+ * 从环境变量创建认证提供者
913
+ */
914
+ static fromEnv() {
915
+ const authType = getEnvConfig('AUTH_TYPE');
916
+ if (!authType) {
917
+ throw new Error('AUTH_TYPE environment variable is not set');
918
+ }
919
+ let credentials;
920
+ switch (authType) {
921
+ case 'apiKey': {
922
+ const apiKey = getEnvConfig('API_KEY');
923
+ const location = getEnvConfig('API_KEY_LOCATION') || 'header';
924
+ const name = getEnvConfig('API_KEY_NAME') || 'X-API-Key';
925
+ if (!apiKey) {
926
+ throw new Error('API_KEY environment variable is required for apiKey auth');
927
+ }
928
+ credentials = {
929
+ type: 'apiKey',
930
+ apiKey,
931
+ location,
932
+ name,
933
+ };
934
+ break;
935
+ }
936
+ case 'oauth2': {
937
+ const clientId = getEnvConfig('OAUTH_CLIENT_ID');
938
+ const clientSecret = getEnvConfig('OAUTH_CLIENT_SECRET');
939
+ const tokenUrl = getEnvConfig('OAUTH_TOKEN_URL');
940
+ const accessToken = getEnvConfig('ACCESS_TOKEN');
941
+ const refreshToken = getEnvConfig('REFRESH_TOKEN');
942
+ const scope = getEnvConfig('OAUTH_SCOPE');
943
+ if (!clientId || !clientSecret || !tokenUrl) {
944
+ throw new Error('OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, and OAUTH_TOKEN_URL are required for oauth2 auth');
945
+ }
946
+ credentials = {
947
+ type: 'oauth2',
948
+ clientId,
949
+ clientSecret,
950
+ tokenUrl,
951
+ accessToken,
952
+ refreshToken,
953
+ scope,
954
+ };
955
+ break;
956
+ }
957
+ case 'jwt': {
958
+ const token = getEnvConfig('JWT_TOKEN');
959
+ const headerName = getEnvConfig('JWT_HEADER_NAME');
960
+ if (!token) {
961
+ throw new Error('JWT_TOKEN environment variable is required for jwt auth');
962
+ }
963
+ credentials = {
964
+ type: 'jwt',
965
+ token,
966
+ headerName,
967
+ };
968
+ break;
969
+ }
970
+ case 'basic': {
971
+ const username = getEnvConfig('BASIC_USERNAME');
972
+ const password = getEnvConfig('BASIC_PASSWORD');
973
+ if (!username || !password) {
974
+ throw new Error('BASIC_USERNAME and BASIC_PASSWORD are required for basic auth');
975
+ }
976
+ credentials = {
977
+ type: 'basic',
978
+ username,
979
+ password,
980
+ };
981
+ break;
982
+ }
983
+ default:
984
+ throw new Error(`Unsupported AUTH_TYPE: ${authType}`);
985
+ }
986
+ return this.createFromCredentials(credentials);
987
+ }
988
+ /**
989
+ * 从配置文件创建认证提供者
990
+ */
991
+ static async fromFile(path) {
992
+ const configManager = new ConfigManager();
993
+ const config = await configManager.loadFromFile(path);
994
+ return this.create(config);
995
+ }
996
+ /**
997
+ * 从profile创建认证提供者
998
+ */
999
+ static async fromProfile(profileName = 'default') {
1000
+ const configManager = new ConfigManager();
1001
+ const config = await configManager.loadAuth(profileName);
1002
+ return this.create(config);
1003
+ }
1004
+ }
1005
+
1006
+ /**
1007
+ * Type definitions for SeaVerse Dispatcher API
1008
+ * Generated from auth.yaml OpenAPI specification
1009
+ */
1010
+
1011
+ var models = /*#__PURE__*/Object.freeze({
1012
+ __proto__: null
1013
+ });
1014
+
1015
+ /**
1016
+ * SeaVerse Backend API Client
1017
+ * SeaVerse Dispatcher API - 云原生 AI Agent 平台的统一网关服务
1018
+ * @version 2.0.0
1019
+ */
1020
+ class SeaVerseBackendAPIClient {
1021
+ constructor(options = {}) {
1022
+ const httpOptions = {
1023
+ baseURL: options.baseURL || 'https://web.seaverse.dev',
1024
+ timeout: options.timeout,
1025
+ headers: options.headers,
1026
+ auth: options.auth || this.getDefaultAuth(),
1027
+ hooks: options.hooks || this.getDefaultHooks(),
1028
+ };
1029
+ this.httpClient = new HttpClient(httpOptions);
1030
+ }
1031
+ /**
1032
+ * Get default authentication configuration
1033
+ */
1034
+ getDefaultAuth() {
1035
+ const token = getEnvConfig('JWT_TOKEN');
1036
+ if (!token)
1037
+ return undefined;
1038
+ return AuthFactory.create({
1039
+ type: 'jwt',
1040
+ credentials: {
1041
+ type: 'jwt',
1042
+ token,
1043
+ },
1044
+ });
1045
+ }
1046
+ /**
1047
+ * Get default hooks configuration
1048
+ */
1049
+ getDefaultHooks() {
1050
+ return {
1051
+ hooks: [
1052
+ BuiltInHooks.createLoggerHook({
1053
+ logLevel: 'info',
1054
+ logRequestBody: true,
1055
+ logResponseBody: true,
1056
+ }),
1057
+ BuiltInHooks.createRequestIdHook(),
1058
+ ],
1059
+ };
1060
+ }
1061
+ // ============================================================================
1062
+ // Authentication & OAuth APIs
1063
+ // ============================================================================
1064
+ /**
1065
+ * Health check
1066
+ * Check if the service is healthy
1067
+ */
1068
+ async getHealth(options) {
1069
+ const config = {
1070
+ method: 'GET',
1071
+ url: `/health`,
1072
+ headers: {
1073
+ 'X-Operation-Id': 'getHealth',
1074
+ ...options?.headers,
1075
+ },
1076
+ ...options,
1077
+ };
1078
+ const response = await this.httpClient.request(config);
1079
+ return response.data;
1080
+ }
1081
+ /**
1082
+ * User registration
1083
+ * Register a new user with email verification
1084
+ */
1085
+ async register(data, options) {
1086
+ const config = {
1087
+ method: 'POST',
1088
+ url: `/api/auth/register`,
1089
+ data,
1090
+ headers: {
1091
+ 'X-Operation-Id': 'register',
1092
+ ...options?.headers,
1093
+ },
1094
+ ...options,
1095
+ };
1096
+ const response = await this.httpClient.request(config);
1097
+ return response.data;
1098
+ }
1099
+ /**
1100
+ * User login
1101
+ * Login with email and password
1102
+ */
1103
+ async login(data, options) {
1104
+ const config = {
1105
+ method: 'POST',
1106
+ url: `/api/auth/login`,
1107
+ data,
1108
+ headers: {
1109
+ 'X-Operation-Id': 'login',
1110
+ ...options?.headers,
1111
+ },
1112
+ ...options,
1113
+ };
1114
+ const response = await this.httpClient.request(config);
1115
+ return response.data;
1116
+ }
1117
+ /**
1118
+ * Get current user
1119
+ * Get the authenticated user's information
1120
+ */
1121
+ async getCurrentUser(options) {
1122
+ const config = {
1123
+ method: 'GET',
1124
+ url: `/api/auth/me`,
1125
+ headers: {
1126
+ 'X-Operation-Id': 'getCurrentUser',
1127
+ ...options?.headers,
1128
+ },
1129
+ ...options,
1130
+ };
1131
+ const response = await this.httpClient.request(config);
1132
+ return response.data;
1133
+ }
1134
+ /**
1135
+ * User logout
1136
+ * Logout the current user
1137
+ */
1138
+ async logout(options) {
1139
+ const config = {
1140
+ method: 'POST',
1141
+ url: `/api/auth/logout`,
1142
+ headers: {
1143
+ 'X-Operation-Id': 'logout',
1144
+ ...options?.headers,
1145
+ },
1146
+ ...options,
1147
+ };
1148
+ const response = await this.httpClient.request(config);
1149
+ return response.data;
1150
+ }
1151
+ /**
1152
+ * Request password reset
1153
+ * Send password reset email
1154
+ */
1155
+ async forgotPassword(data, options) {
1156
+ const config = {
1157
+ method: 'POST',
1158
+ url: `/api/auth/forgot-password`,
1159
+ data,
1160
+ headers: {
1161
+ 'X-Operation-Id': 'forgotPassword',
1162
+ ...options?.headers,
1163
+ },
1164
+ ...options,
1165
+ };
1166
+ const response = await this.httpClient.request(config);
1167
+ return response.data;
1168
+ }
1169
+ /**
1170
+ * Reset password
1171
+ * Reset password with token from email
1172
+ */
1173
+ async resetPassword(data, options) {
1174
+ const config = {
1175
+ method: 'POST',
1176
+ url: `/api/auth/reset-password`,
1177
+ data,
1178
+ headers: {
1179
+ 'X-Operation-Id': 'resetPassword',
1180
+ ...options?.headers,
1181
+ },
1182
+ ...options,
1183
+ };
1184
+ const response = await this.httpClient.request(config);
1185
+ return response.data;
1186
+ }
1187
+ /**
1188
+ * Get api-service token
1189
+ * Generate token for accessing api-service from sandbox
1190
+ */
1191
+ async getApiServiceToken(options) {
1192
+ const config = {
1193
+ method: 'GET',
1194
+ url: `/api/auth/api-service-token`,
1195
+ headers: {
1196
+ 'X-Operation-Id': 'getApiServiceToken',
1197
+ ...options?.headers,
1198
+ },
1199
+ ...options,
1200
+ };
1201
+ const response = await this.httpClient.request(config);
1202
+ return response.data;
1203
+ }
1204
+ // ============================================================================
1205
+ // OAuth APIs
1206
+ // ============================================================================
1207
+ /**
1208
+ * Exchange Google code for token
1209
+ * Exchange Google authorization code for JWT token (for app)
1210
+ */
1211
+ async googleCodeToToken(data, options) {
1212
+ const config = {
1213
+ method: 'POST',
1214
+ url: `/api/auth/google/code2token`,
1215
+ data,
1216
+ headers: {
1217
+ 'X-Operation-Id': 'googleCodeToToken',
1218
+ ...options?.headers,
1219
+ },
1220
+ ...options,
1221
+ };
1222
+ const response = await this.httpClient.request(config);
1223
+ return response.data;
1224
+ }
1225
+ /**
1226
+ * Unlink Google account
1227
+ * Unlink the user's Google account
1228
+ */
1229
+ async unlinkGoogle(options) {
1230
+ const config = {
1231
+ method: 'POST',
1232
+ url: `/api/auth/google/unlink`,
1233
+ headers: {
1234
+ 'X-Operation-Id': 'unlinkGoogle',
1235
+ ...options?.headers,
1236
+ },
1237
+ ...options,
1238
+ };
1239
+ const response = await this.httpClient.request(config);
1240
+ return response.data;
1241
+ }
1242
+ /**
1243
+ * Exchange Discord code for token
1244
+ */
1245
+ async discordCodeToToken(data, options) {
1246
+ const config = {
1247
+ method: 'POST',
1248
+ url: `/api/auth/discord/code2token`,
1249
+ data,
1250
+ headers: {
1251
+ 'X-Operation-Id': 'discordCodeToToken',
1252
+ ...options?.headers,
1253
+ },
1254
+ ...options,
1255
+ };
1256
+ const response = await this.httpClient.request(config);
1257
+ return response.data;
1258
+ }
1259
+ /**
1260
+ * Unlink Discord account
1261
+ */
1262
+ async unlinkDiscord(options) {
1263
+ const config = {
1264
+ method: 'POST',
1265
+ url: `/api/auth/discord/unlink`,
1266
+ headers: {
1267
+ 'X-Operation-Id': 'unlinkDiscord',
1268
+ ...options?.headers,
1269
+ },
1270
+ ...options,
1271
+ };
1272
+ const response = await this.httpClient.request(config);
1273
+ return response.data;
1274
+ }
1275
+ /**
1276
+ * Exchange GitHub code for token
1277
+ */
1278
+ async githubCodeToToken(data, options) {
1279
+ const config = {
1280
+ method: 'POST',
1281
+ url: `/api/auth/github/code2token`,
1282
+ data,
1283
+ headers: {
1284
+ 'X-Operation-Id': 'githubCodeToToken',
1285
+ ...options?.headers,
1286
+ },
1287
+ ...options,
1288
+ };
1289
+ const response = await this.httpClient.request(config);
1290
+ return response.data;
1291
+ }
1292
+ /**
1293
+ * Unlink GitHub account
1294
+ */
1295
+ async unlinkGithub(options) {
1296
+ const config = {
1297
+ method: 'POST',
1298
+ url: `/api/auth/github/unlink`,
1299
+ headers: {
1300
+ 'X-Operation-Id': 'unlinkGithub',
1301
+ ...options?.headers,
1302
+ },
1303
+ ...options,
1304
+ };
1305
+ const response = await this.httpClient.request(config);
1306
+ return response.data;
1307
+ }
1308
+ // ============================================================================
1309
+ // Container APIs
1310
+ // ============================================================================
1311
+ /**
1312
+ * List all containers
1313
+ */
1314
+ async listContainers(options) {
1315
+ const config = {
1316
+ method: 'GET',
1317
+ url: `/api/containers`,
1318
+ headers: {
1319
+ 'X-Operation-Id': 'listContainers',
1320
+ ...options?.headers,
1321
+ },
1322
+ ...options,
1323
+ };
1324
+ const response = await this.httpClient.request(config);
1325
+ return response.data;
1326
+ }
1327
+ /**
1328
+ * Register a container
1329
+ * Internal service call to register a new container
1330
+ */
1331
+ async registerContainer(data, options) {
1332
+ const config = {
1333
+ method: 'POST',
1334
+ url: `/api/containers`,
1335
+ data,
1336
+ headers: {
1337
+ 'X-Operation-Id': 'registerContainer',
1338
+ ...options?.headers,
1339
+ },
1340
+ ...options,
1341
+ };
1342
+ const response = await this.httpClient.request(config);
1343
+ return response.data;
1344
+ }
1345
+ /**
1346
+ * Get container by ID
1347
+ */
1348
+ async getContainer(id, options) {
1349
+ const config = {
1350
+ method: 'GET',
1351
+ url: `/api/containers/${id}`,
1352
+ headers: {
1353
+ 'X-Operation-Id': 'getContainer',
1354
+ ...options?.headers,
1355
+ },
1356
+ ...options,
1357
+ };
1358
+ const response = await this.httpClient.request(config);
1359
+ return response.data;
1360
+ }
1361
+ /**
1362
+ * Unregister a container
1363
+ * Internal service call to unregister a container
1364
+ */
1365
+ async unregisterContainer(id, options) {
1366
+ const config = {
1367
+ method: 'DELETE',
1368
+ url: `/api/containers/${id}`,
1369
+ headers: {
1370
+ 'X-Operation-Id': 'unregisterContainer',
1371
+ ...options?.headers,
1372
+ },
1373
+ ...options,
1374
+ };
1375
+ const response = await this.httpClient.request(config);
1376
+ return response.data;
1377
+ }
1378
+ /**
1379
+ * Get container statistics
1380
+ */
1381
+ async getContainerStats(options) {
1382
+ const config = {
1383
+ method: 'GET',
1384
+ url: `/api/containers/stats`,
1385
+ headers: {
1386
+ 'X-Operation-Id': 'getContainerStats',
1387
+ ...options?.headers,
1388
+ },
1389
+ ...options,
1390
+ };
1391
+ const response = await this.httpClient.request(config);
1392
+ return response.data;
1393
+ }
1394
+ /**
1395
+ * Container heartbeat
1396
+ * Internal service call to update container heartbeat
1397
+ */
1398
+ async containerHeartbeat(id, options) {
1399
+ const config = {
1400
+ method: 'POST',
1401
+ url: `/api/containers/${id}/heartbeat`,
1402
+ headers: {
1403
+ 'X-Operation-Id': 'containerHeartbeat',
1404
+ ...options?.headers,
1405
+ },
1406
+ ...options,
1407
+ };
1408
+ const response = await this.httpClient.request(config);
1409
+ return response.data;
1410
+ }
1411
+ // ============================================================================
1412
+ // Conversation APIs
1413
+ // ============================================================================
1414
+ /**
1415
+ * Get conversation status
1416
+ * Query conversation streaming status from Redis
1417
+ */
1418
+ async getConversationStatus(conversationId, options) {
1419
+ const config = {
1420
+ method: 'GET',
1421
+ url: `/api/conversations/${conversationId}/status`,
1422
+ headers: {
1423
+ 'X-Operation-Id': 'getConversationStatus',
1424
+ ...options?.headers,
1425
+ },
1426
+ ...options,
1427
+ };
1428
+ const response = await this.httpClient.request(config);
1429
+ return response.data;
1430
+ }
1431
+ // ============================================================================
1432
+ // Skills Marketplace APIs
1433
+ // ============================================================================
1434
+ /**
1435
+ * List marketplace skills
1436
+ */
1437
+ async listMarketplaceSkills(params, options) {
1438
+ const config = {
1439
+ method: 'GET',
1440
+ url: `/api/marketplace/skills`,
1441
+ params,
1442
+ headers: {
1443
+ 'X-Operation-Id': 'listMarketplaceSkills',
1444
+ ...options?.headers,
1445
+ },
1446
+ ...options,
1447
+ };
1448
+ const response = await this.httpClient.request(config);
1449
+ return response.data;
1450
+ }
1451
+ /**
1452
+ * Get skill details
1453
+ */
1454
+ async getMarketplaceSkill(skillId, options) {
1455
+ const config = {
1456
+ method: 'GET',
1457
+ url: `/api/marketplace/skills/${skillId}`,
1458
+ headers: {
1459
+ 'X-Operation-Id': 'getMarketplaceSkill',
1460
+ ...options?.headers,
1461
+ },
1462
+ ...options,
1463
+ };
1464
+ const response = await this.httpClient.request(config);
1465
+ return response.data;
1466
+ }
1467
+ /**
1468
+ * Install skill from marketplace
1469
+ */
1470
+ async installSkill(skillId, options) {
1471
+ const config = {
1472
+ method: 'POST',
1473
+ url: `/api/marketplace/skills/${skillId}/download`,
1474
+ headers: {
1475
+ 'X-Operation-Id': 'installSkill',
1476
+ ...options?.headers,
1477
+ },
1478
+ ...options,
1479
+ };
1480
+ const response = await this.httpClient.request(config);
1481
+ return response.data;
1482
+ }
1483
+ /**
1484
+ * List user installed skills
1485
+ */
1486
+ async listUserSkills(options) {
1487
+ const config = {
1488
+ method: 'GET',
1489
+ url: `/api/user/skills`,
1490
+ headers: {
1491
+ 'X-Operation-Id': 'listUserSkills',
1492
+ ...options?.headers,
1493
+ },
1494
+ ...options,
1495
+ };
1496
+ const response = await this.httpClient.request(config);
1497
+ return response.data;
1498
+ }
1499
+ /**
1500
+ * Uninstall skill
1501
+ */
1502
+ async uninstallSkill(localName, options) {
1503
+ const config = {
1504
+ method: 'DELETE',
1505
+ url: `/api/user/skills/${localName}`,
1506
+ headers: {
1507
+ 'X-Operation-Id': 'uninstallSkill',
1508
+ ...options?.headers,
1509
+ },
1510
+ ...options,
1511
+ };
1512
+ await this.httpClient.request(config);
1513
+ }
1514
+ // ============================================================================
1515
+ // Speech Service APIs
1516
+ // ============================================================================
1517
+ /**
1518
+ * Get Azure Speech token
1519
+ * Get temporary token for Azure Speech Service
1520
+ */
1521
+ async getSpeechToken(options) {
1522
+ const config = {
1523
+ method: 'GET',
1524
+ url: `/api/speech/token`,
1525
+ headers: {
1526
+ 'X-Operation-Id': 'getSpeechToken',
1527
+ ...options?.headers,
1528
+ },
1529
+ ...options,
1530
+ };
1531
+ const response = await this.httpClient.request(config);
1532
+ return response.data;
1533
+ }
1534
+ // ============================================================================
1535
+ // Deprecated/Legacy Methods (for backward compatibility)
1536
+ // ============================================================================
1537
+ /**
1538
+ * @deprecated Use register() instead
1539
+ */
1540
+ async postapiauthregister(data, options) {
1541
+ return this.register(data, options);
1542
+ }
1543
+ /**
1544
+ * @deprecated Use login() instead
1545
+ */
1546
+ async postapiauthlogin(data, options) {
1547
+ return this.login(data, options);
1548
+ }
1549
+ /**
1550
+ * @deprecated Use getHealth() instead
1551
+ */
1552
+ async gethealth(options) {
1553
+ return this.getHealth(options);
1554
+ }
1555
+ /**
1556
+ * @deprecated Use listContainers() instead
1557
+ */
1558
+ async getapicontainers(options) {
1559
+ return this.listContainers(options);
1560
+ }
1561
+ }
1562
+
1563
+ class AuthModal {
1564
+ constructor(options) {
1565
+ this.modal = null;
1566
+ this.currentView = 'login';
1567
+ this.client = options.client;
1568
+ this.options = options;
1569
+ }
1570
+ /**
1571
+ * Show the authentication modal
1572
+ */
1573
+ show(initialView = 'login') {
1574
+ this.currentView = initialView;
1575
+ if (!this.modal) {
1576
+ this.createModal();
1577
+ }
1578
+ this.modal.classList.remove('hidden');
1579
+ this.switchView(initialView);
1580
+ }
1581
+ /**
1582
+ * Hide the authentication modal
1583
+ */
1584
+ hide() {
1585
+ if (this.modal) {
1586
+ this.modal.classList.add('hidden');
1587
+ }
1588
+ }
1589
+ /**
1590
+ * Destroy the modal and remove from DOM
1591
+ */
1592
+ destroy() {
1593
+ if (this.modal) {
1594
+ this.modal.remove();
1595
+ this.modal = null;
1596
+ }
1597
+ }
1598
+ createModal() {
1599
+ const theme = this.options.theme || 'dark';
1600
+ // Create modal container
1601
+ this.modal = document.createElement('div');
1602
+ this.modal.id = 'authModal';
1603
+ this.modal.className = `auth-modal auth-modal-${theme}`;
1604
+ // Create backdrop
1605
+ const backdrop = document.createElement('div');
1606
+ backdrop.className = 'auth-modal-backdrop';
1607
+ this.modal.appendChild(backdrop);
1608
+ // Create content container
1609
+ const content = document.createElement('div');
1610
+ content.className = 'auth-modal-content';
1611
+ this.modal.appendChild(content);
1612
+ // Create left panel (branding)
1613
+ const leftPanel = this.createLeftPanel();
1614
+ content.appendChild(leftPanel);
1615
+ // Create right panel (forms)
1616
+ const rightPanel = this.createRightPanel();
1617
+ content.appendChild(rightPanel);
1618
+ // Append to body
1619
+ document.body.appendChild(this.modal);
1620
+ // Bind event listeners
1621
+ this.bindEventListeners();
1622
+ }
1623
+ createLeftPanel() {
1624
+ const leftPanel = document.createElement('div');
1625
+ leftPanel.className = 'auth-modal-left';
1626
+ // Logo
1627
+ const logo = document.createElement('div');
1628
+ logo.className = 'auth-modal-logo';
1629
+ const logoText = document.createElement('span');
1630
+ logoText.className = 'logo-text';
1631
+ logoText.textContent = 'SeaVerse';
1632
+ logo.appendChild(logoText);
1633
+ leftPanel.appendChild(logo);
1634
+ // Decoration
1635
+ const decoration = document.createElement('div');
1636
+ decoration.className = 'auth-modal-decoration';
1637
+ const orb1 = document.createElement('div');
1638
+ orb1.className = 'glow-orb glow-orb-1';
1639
+ const orb2 = document.createElement('div');
1640
+ orb2.className = 'glow-orb glow-orb-2';
1641
+ decoration.appendChild(orb1);
1642
+ decoration.appendChild(orb2);
1643
+ leftPanel.appendChild(decoration);
1644
+ // Branding
1645
+ const branding = document.createElement('div');
1646
+ branding.className = 'auth-modal-branding';
1647
+ const brandingTitle = document.createElement('h2');
1648
+ brandingTitle.className = 'branding-title';
1649
+ brandingTitle.textContent = 'Get Started with Us';
1650
+ const brandingSubtitle = document.createElement('p');
1651
+ brandingSubtitle.className = 'branding-subtitle';
1652
+ brandingSubtitle.textContent = "Every conversation changes this place. You're not just using Infinity. You're creating it.";
1653
+ branding.appendChild(brandingTitle);
1654
+ branding.appendChild(brandingSubtitle);
1655
+ leftPanel.appendChild(branding);
1656
+ return leftPanel;
1657
+ }
1658
+ createRightPanel() {
1659
+ const rightPanel = document.createElement('div');
1660
+ rightPanel.className = 'auth-modal-right';
1661
+ // Close button
1662
+ const closeBtn = document.createElement('button');
1663
+ closeBtn.className = 'auth-modal-close';
1664
+ closeBtn.setAttribute('aria-label', 'Close modal');
1665
+ closeBtn.innerHTML = '<svg width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
1666
+ rightPanel.appendChild(closeBtn);
1667
+ // Login form
1668
+ const loginForm = this.createLoginForm();
1669
+ rightPanel.appendChild(loginForm);
1670
+ // Signup form
1671
+ const signupForm = this.createSignupForm();
1672
+ rightPanel.appendChild(signupForm);
1673
+ // Forgot password form
1674
+ const forgotForm = this.createForgotPasswordForm();
1675
+ rightPanel.appendChild(forgotForm);
1676
+ // Success message
1677
+ const successMessage = this.createSuccessMessage();
1678
+ rightPanel.appendChild(successMessage);
1679
+ return rightPanel;
1680
+ }
1681
+ createLoginForm() {
1682
+ const container = document.createElement('div');
1683
+ container.id = 'loginForm';
1684
+ container.className = 'auth-form-view';
1685
+ // Header
1686
+ const header = document.createElement('div');
1687
+ header.className = 'auth-form-header';
1688
+ const title = document.createElement('h2');
1689
+ title.className = 'auth-form-title';
1690
+ title.textContent = 'Welcome back.';
1691
+ const subtitle = document.createElement('p');
1692
+ subtitle.className = 'auth-form-subtitle';
1693
+ subtitle.textContent = "Don't have an account? ";
1694
+ const signupLink = document.createElement('a');
1695
+ signupLink.href = '#';
1696
+ signupLink.id = 'showSignup';
1697
+ signupLink.className = 'link-primary';
1698
+ signupLink.textContent = 'Sign up';
1699
+ subtitle.appendChild(signupLink);
1700
+ header.appendChild(title);
1701
+ header.appendChild(subtitle);
1702
+ container.appendChild(header);
1703
+ // Form
1704
+ const form = document.createElement('form');
1705
+ form.id = 'loginFormElement';
1706
+ form.className = 'auth-form';
1707
+ // Email field
1708
+ const emailGroup = this.createFormGroup('loginEmail', 'Email', 'email', 'Email');
1709
+ form.appendChild(emailGroup);
1710
+ // Password field with forgot link
1711
+ const passwordGroup = document.createElement('div');
1712
+ passwordGroup.className = 'form-group';
1713
+ const groupHeader = document.createElement('div');
1714
+ groupHeader.className = 'form-group-header';
1715
+ const passwordLabel = document.createElement('label');
1716
+ passwordLabel.htmlFor = 'loginPassword';
1717
+ passwordLabel.className = 'form-label';
1718
+ passwordLabel.textContent = 'Password';
1719
+ const forgotLink = document.createElement('a');
1720
+ forgotLink.href = '#';
1721
+ forgotLink.id = 'forgotPasswordLink';
1722
+ forgotLink.className = 'forgot-password-link';
1723
+ forgotLink.textContent = 'Forgot Password?';
1724
+ groupHeader.appendChild(passwordLabel);
1725
+ groupHeader.appendChild(forgotLink);
1726
+ passwordGroup.appendChild(groupHeader);
1727
+ const passwordInputWrapper = this.createPasswordInput('loginPassword', 'Password');
1728
+ passwordGroup.appendChild(passwordInputWrapper);
1729
+ form.appendChild(passwordGroup);
1730
+ // Submit button
1731
+ const submitBtn = document.createElement('button');
1732
+ submitBtn.type = 'submit';
1733
+ submitBtn.id = 'loginButton';
1734
+ submitBtn.className = 'btn-auth-primary';
1735
+ const btnText = document.createElement('span');
1736
+ btnText.className = 'btn-text';
1737
+ btnText.textContent = 'Sign In';
1738
+ const btnLoader = document.createElement('span');
1739
+ btnLoader.className = 'btn-loader hidden';
1740
+ btnLoader.innerHTML = '<svg class="spinner" viewBox="0 0 24 24"><circle class="spinner-track" cx="12" cy="12" r="10"></circle><circle class="spinner-circle" cx="12" cy="12" r="10"></circle></svg>';
1741
+ submitBtn.appendChild(btnText);
1742
+ submitBtn.appendChild(btnLoader);
1743
+ form.appendChild(submitBtn);
1744
+ // Divider
1745
+ const divider = document.createElement('div');
1746
+ divider.className = 'divider';
1747
+ divider.textContent = 'OR SIGN IN WITH';
1748
+ form.appendChild(divider);
1749
+ // Social buttons grid (Google + GitHub)
1750
+ const socialGrid = document.createElement('div');
1751
+ socialGrid.className = 'social-buttons-grid';
1752
+ // Google button
1753
+ const googleBtn = this.createSocialButton('google', 'Google', 'login');
1754
+ socialGrid.appendChild(googleBtn);
1755
+ // GitHub button
1756
+ const githubBtn = this.createSocialButton('github', 'Github', 'login');
1757
+ socialGrid.appendChild(githubBtn);
1758
+ form.appendChild(socialGrid);
1759
+ // Discord button (full width)
1760
+ const discordBtn = this.createSocialButton('discord', 'Discord', 'login', true);
1761
+ form.appendChild(discordBtn);
1762
+ container.appendChild(form);
1763
+ return container;
1764
+ }
1765
+ createSignupForm() {
1766
+ const container = document.createElement('div');
1767
+ container.id = 'signupForm';
1768
+ container.className = 'auth-form-view hidden';
1769
+ // Header
1770
+ const header = document.createElement('div');
1771
+ header.className = 'auth-form-header';
1772
+ const title = document.createElement('h2');
1773
+ title.className = 'auth-form-title';
1774
+ title.textContent = 'Create an account.';
1775
+ const subtitle = document.createElement('p');
1776
+ subtitle.className = 'auth-form-subtitle';
1777
+ subtitle.textContent = 'Already have an account? ';
1778
+ const loginLink = document.createElement('a');
1779
+ loginLink.href = '#';
1780
+ loginLink.id = 'showLogin';
1781
+ loginLink.className = 'link-primary';
1782
+ loginLink.textContent = 'Sign in';
1783
+ subtitle.appendChild(loginLink);
1784
+ header.appendChild(title);
1785
+ header.appendChild(subtitle);
1786
+ container.appendChild(header);
1787
+ // Form
1788
+ const form = document.createElement('form');
1789
+ form.id = 'signupFormElement';
1790
+ form.className = 'auth-form';
1791
+ // Email field
1792
+ const emailGroup = this.createFormGroup('signupEmail', 'Email', 'email', 'Email');
1793
+ form.appendChild(emailGroup);
1794
+ // Password field
1795
+ const passwordGroup = document.createElement('div');
1796
+ passwordGroup.className = 'form-group';
1797
+ const passwordLabel = document.createElement('label');
1798
+ passwordLabel.htmlFor = 'signupPassword';
1799
+ passwordLabel.className = 'form-label';
1800
+ passwordLabel.textContent = 'Password';
1801
+ passwordGroup.appendChild(passwordLabel);
1802
+ const passwordInputWrapper = this.createPasswordInput('signupPassword', 'Password');
1803
+ passwordGroup.appendChild(passwordInputWrapper);
1804
+ const strengthText = document.createElement('p');
1805
+ strengthText.className = 'strength-text';
1806
+ strengthText.textContent = 'Use 8+ characters with mix of letters, numbers & symbols';
1807
+ passwordGroup.appendChild(strengthText);
1808
+ form.appendChild(passwordGroup);
1809
+ // Confirm password field
1810
+ const confirmGroup = document.createElement('div');
1811
+ confirmGroup.className = 'form-group';
1812
+ const confirmLabel = document.createElement('label');
1813
+ confirmLabel.htmlFor = 'signupPasswordConfirm';
1814
+ confirmLabel.className = 'form-label';
1815
+ confirmLabel.textContent = 'Confirm Password';
1816
+ confirmGroup.appendChild(confirmLabel);
1817
+ const confirmInputWrapper = this.createPasswordInput('signupPasswordConfirm', 'Confirm Password');
1818
+ confirmGroup.appendChild(confirmInputWrapper);
1819
+ form.appendChild(confirmGroup);
1820
+ // Submit button
1821
+ const submitBtn = document.createElement('button');
1822
+ submitBtn.type = 'submit';
1823
+ submitBtn.id = 'signupButton';
1824
+ submitBtn.className = 'btn-auth-primary';
1825
+ const btnText = document.createElement('span');
1826
+ btnText.className = 'btn-text';
1827
+ btnText.textContent = 'Sign Up';
1828
+ const btnLoader = document.createElement('span');
1829
+ btnLoader.className = 'btn-loader hidden';
1830
+ btnLoader.innerHTML = '<svg class="spinner" viewBox="0 0 24 24"><circle class="spinner-track" cx="12" cy="12" r="10"></circle><circle class="spinner-circle" cx="12" cy="12" r="10"></circle></svg>';
1831
+ submitBtn.appendChild(btnText);
1832
+ submitBtn.appendChild(btnLoader);
1833
+ form.appendChild(submitBtn);
1834
+ // Divider
1835
+ const divider = document.createElement('div');
1836
+ divider.className = 'divider';
1837
+ divider.textContent = 'OR SIGN UP WITH';
1838
+ form.appendChild(divider);
1839
+ // Social buttons grid (Google + GitHub)
1840
+ const socialGrid = document.createElement('div');
1841
+ socialGrid.className = 'social-buttons-grid';
1842
+ // Google button
1843
+ const googleBtn = this.createSocialButton('google', 'Google', 'signup');
1844
+ socialGrid.appendChild(googleBtn);
1845
+ // GitHub button
1846
+ const githubBtn = this.createSocialButton('github', 'Github', 'signup');
1847
+ socialGrid.appendChild(githubBtn);
1848
+ form.appendChild(socialGrid);
1849
+ // Discord button (full width)
1850
+ const discordBtn = this.createSocialButton('discord', 'Discord', 'signup', true);
1851
+ form.appendChild(discordBtn);
1852
+ container.appendChild(form);
1853
+ return container;
1854
+ }
1855
+ createForgotPasswordForm() {
1856
+ const container = document.createElement('div');
1857
+ container.id = 'forgotPasswordForm';
1858
+ container.className = 'auth-form-view hidden';
1859
+ // Icon
1860
+ const icon = document.createElement('div');
1861
+ icon.className = 'forgot-password-icon';
1862
+ icon.innerHTML = '<div class="icon-glow"></div><svg class="icon-lock" width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path class="lock-shackle" d="M7 11V7a5 5 0 0 1 10 0v4"/><circle class="lock-keyhole" cx="12" cy="16" r="1.5"/></svg>';
1863
+ container.appendChild(icon);
1864
+ // Header
1865
+ const header = document.createElement('div');
1866
+ header.className = 'auth-form-header';
1867
+ const title = document.createElement('h2');
1868
+ title.className = 'auth-form-title';
1869
+ title.textContent = 'Reset Password';
1870
+ const subtitle = document.createElement('p');
1871
+ subtitle.className = 'auth-form-subtitle';
1872
+ subtitle.textContent = "We'll send a magic link to your inbox";
1873
+ header.appendChild(title);
1874
+ header.appendChild(subtitle);
1875
+ container.appendChild(header);
1876
+ // Form
1877
+ const form = document.createElement('form');
1878
+ form.id = 'forgotPasswordFormElement';
1879
+ form.className = 'auth-form';
1880
+ const emailGroup = this.createFormGroup('resetEmail', 'Email Address', 'email', 'Enter your email');
1881
+ form.appendChild(emailGroup);
1882
+ const submitBtn = document.createElement('button');
1883
+ submitBtn.type = 'submit';
1884
+ submitBtn.id = 'resetButton';
1885
+ submitBtn.className = 'btn-auth-primary btn-forgot-password';
1886
+ submitBtn.innerHTML = '<span class="btn-text">Send Reset Link</span><svg class="btn-arrow" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>';
1887
+ form.appendChild(submitBtn);
1888
+ container.appendChild(form);
1889
+ // Footer
1890
+ const footer = document.createElement('div');
1891
+ footer.className = 'auth-footer forgot-footer';
1892
+ const backLink = document.createElement('a');
1893
+ backLink.href = '#';
1894
+ backLink.id = 'backToLogin';
1895
+ backLink.className = 'back-to-login';
1896
+ backLink.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg><span>Back to Sign In</span>';
1897
+ footer.appendChild(backLink);
1898
+ container.appendChild(footer);
1899
+ return container;
1900
+ }
1901
+ createSuccessMessage() {
1902
+ const container = document.createElement('div');
1903
+ container.id = 'authMessage';
1904
+ container.className = 'auth-form-view hidden';
1905
+ const content = document.createElement('div');
1906
+ content.className = 'auth-message-content';
1907
+ const icon = document.createElement('div');
1908
+ icon.className = 'message-icon';
1909
+ icon.innerHTML = '<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>';
1910
+ content.appendChild(icon);
1911
+ const title = document.createElement('h3');
1912
+ title.id = 'messageTitle';
1913
+ title.className = 'message-title';
1914
+ title.textContent = 'Success!';
1915
+ content.appendChild(title);
1916
+ const text = document.createElement('p');
1917
+ text.id = 'messageText';
1918
+ text.className = 'message-text';
1919
+ text.textContent = 'Operation completed successfully.';
1920
+ content.appendChild(text);
1921
+ const button = document.createElement('button');
1922
+ button.id = 'messageButton';
1923
+ button.className = 'btn-auth-primary';
1924
+ button.textContent = 'Continue';
1925
+ content.appendChild(button);
1926
+ container.appendChild(content);
1927
+ return container;
1928
+ }
1929
+ createFormGroup(id, label, type, placeholder) {
1930
+ const group = document.createElement('div');
1931
+ group.className = 'form-group';
1932
+ const labelEl = document.createElement('label');
1933
+ labelEl.htmlFor = id;
1934
+ labelEl.className = 'form-label';
1935
+ labelEl.textContent = label;
1936
+ group.appendChild(labelEl);
1937
+ const input = document.createElement('input');
1938
+ input.type = type;
1939
+ input.id = id;
1940
+ input.className = 'form-input';
1941
+ input.placeholder = placeholder;
1942
+ input.required = true;
1943
+ group.appendChild(input);
1944
+ return group;
1945
+ }
1946
+ createPasswordInput(id, placeholder) {
1947
+ const wrapper = document.createElement('div');
1948
+ wrapper.className = 'input-with-icon';
1949
+ const input = document.createElement('input');
1950
+ input.type = 'password';
1951
+ input.id = id;
1952
+ input.className = 'form-input';
1953
+ input.placeholder = placeholder;
1954
+ input.required = true;
1955
+ wrapper.appendChild(input);
1956
+ const toggleBtn = document.createElement('button');
1957
+ toggleBtn.type = 'button';
1958
+ toggleBtn.className = 'input-icon-btn toggle-password';
1959
+ toggleBtn.dataset.target = id;
1960
+ toggleBtn.innerHTML = '<svg class="icon-eye" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg><svg class="icon-eye-off hidden" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>';
1961
+ wrapper.appendChild(toggleBtn);
1962
+ return wrapper;
1963
+ }
1964
+ createSocialButton(provider, label, mode, fullWidth = false) {
1965
+ const button = document.createElement('button');
1966
+ button.type = 'button';
1967
+ button.className = fullWidth ? 'btn-social btn-social-full' : 'btn-social';
1968
+ button.id = `${provider}${mode === 'login' ? 'SignIn' : 'SignUp'}Modal`;
1969
+ // SVG icons for each provider
1970
+ const icons = {
1971
+ google: `<svg width="20" height="20" viewBox="0 0 24 24">
1972
+ <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
1973
+ <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
1974
+ <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
1975
+ <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
1976
+ </svg>`,
1977
+ github: `<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
1978
+ <path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.603-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0 1 12 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.167 22 16.418 22 12c0-5.523-4.477-10-10-10z"/>
1979
+ </svg>`,
1980
+ discord: `<svg width="20" height="20" viewBox="0 0 24 24" fill="#5865F2">
1981
+ <path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"/>
1982
+ </svg>`
1983
+ };
1984
+ button.innerHTML = icons[provider];
1985
+ const span = document.createElement('span');
1986
+ span.textContent = label;
1987
+ button.appendChild(span);
1988
+ return button;
1989
+ }
1990
+ bindEventListeners() {
1991
+ if (!this.modal)
1992
+ return;
1993
+ // Close button
1994
+ const closeBtn = this.modal.querySelector('.auth-modal-close');
1995
+ closeBtn?.addEventListener('click', () => this.hide());
1996
+ // Backdrop click to close
1997
+ const backdrop = this.modal.querySelector('.auth-modal-backdrop');
1998
+ backdrop?.addEventListener('click', () => this.hide());
1999
+ // View switching
2000
+ const showSignup = this.modal.querySelector('#showSignup');
2001
+ showSignup?.addEventListener('click', (e) => {
2002
+ e.preventDefault();
2003
+ this.switchView('signup');
2004
+ });
2005
+ const showLogin = this.modal.querySelector('#showLogin');
2006
+ showLogin?.addEventListener('click', (e) => {
2007
+ e.preventDefault();
2008
+ this.switchView('login');
2009
+ });
2010
+ const forgotPasswordLink = this.modal.querySelector('#forgotPasswordLink');
2011
+ forgotPasswordLink?.addEventListener('click', (e) => {
2012
+ e.preventDefault();
2013
+ this.switchView('forgot');
2014
+ });
2015
+ const backToLogin = this.modal.querySelector('#backToLogin');
2016
+ backToLogin?.addEventListener('click', (e) => {
2017
+ e.preventDefault();
2018
+ this.switchView('login');
2019
+ });
2020
+ // Password toggle
2021
+ const passwordToggles = this.modal.querySelectorAll('.toggle-password');
2022
+ passwordToggles.forEach((toggle) => {
2023
+ toggle.addEventListener('click', (e) => {
2024
+ e.preventDefault();
2025
+ const target = toggle.dataset.target;
2026
+ if (target) {
2027
+ const input = this.modal.querySelector(`#${target}`);
2028
+ const iconEye = toggle.querySelector('.icon-eye');
2029
+ const iconEyeOff = toggle.querySelector('.icon-eye-off');
2030
+ if (input.type === 'password') {
2031
+ input.type = 'text';
2032
+ iconEye?.classList.add('hidden');
2033
+ iconEyeOff?.classList.remove('hidden');
2034
+ }
2035
+ else {
2036
+ input.type = 'password';
2037
+ iconEye?.classList.remove('hidden');
2038
+ iconEyeOff?.classList.add('hidden');
2039
+ }
2040
+ }
2041
+ });
2042
+ });
2043
+ // Form submissions
2044
+ const loginForm = this.modal.querySelector('#loginFormElement');
2045
+ loginForm?.addEventListener('submit', (e) => this.handleLogin(e));
2046
+ const signupForm = this.modal.querySelector('#signupFormElement');
2047
+ signupForm?.addEventListener('submit', (e) => this.handleSignup(e));
2048
+ const forgotForm = this.modal.querySelector('#forgotPasswordFormElement');
2049
+ forgotForm?.addEventListener('submit', (e) => this.handleForgotPassword(e));
2050
+ // OAuth social login buttons
2051
+ this.bindSocialLoginButtons();
2052
+ }
2053
+ /**
2054
+ * Bind click events to social login buttons
2055
+ */
2056
+ bindSocialLoginButtons() {
2057
+ if (!this.modal)
2058
+ return;
2059
+ // Google login buttons
2060
+ const googleSignInBtn = this.modal.querySelector('#googleSignInModal');
2061
+ googleSignInBtn?.addEventListener('click', (e) => {
2062
+ e.preventDefault();
2063
+ this.startOAuthFlow('google');
2064
+ });
2065
+ const googleSignUpBtn = this.modal.querySelector('#googleSignUpModal');
2066
+ googleSignUpBtn?.addEventListener('click', (e) => {
2067
+ e.preventDefault();
2068
+ this.startOAuthFlow('google');
2069
+ });
2070
+ // Discord login buttons
2071
+ const discordSignInBtn = this.modal.querySelector('#discordSignInModal');
2072
+ discordSignInBtn?.addEventListener('click', (e) => {
2073
+ e.preventDefault();
2074
+ this.startOAuthFlow('discord');
2075
+ });
2076
+ const discordSignUpBtn = this.modal.querySelector('#discordSignUpModal');
2077
+ discordSignUpBtn?.addEventListener('click', (e) => {
2078
+ e.preventDefault();
2079
+ this.startOAuthFlow('discord');
2080
+ });
2081
+ // GitHub login buttons
2082
+ const githubSignInBtn = this.modal.querySelector('#githubSignInModal');
2083
+ githubSignInBtn?.addEventListener('click', (e) => {
2084
+ e.preventDefault();
2085
+ this.startOAuthFlow('github');
2086
+ });
2087
+ const githubSignUpBtn = this.modal.querySelector('#githubSignUpModal');
2088
+ githubSignUpBtn?.addEventListener('click', (e) => {
2089
+ e.preventDefault();
2090
+ this.startOAuthFlow('github');
2091
+ });
2092
+ }
2093
+ switchView(view) {
2094
+ if (!this.modal)
2095
+ return;
2096
+ const views = ['loginForm', 'signupForm', 'forgotPasswordForm', 'authMessage'];
2097
+ views.forEach((viewId) => {
2098
+ const element = this.modal.querySelector(`#${viewId}`);
2099
+ element?.classList.add('hidden');
2100
+ });
2101
+ const viewMap = {
2102
+ login: 'loginForm',
2103
+ signup: 'signupForm',
2104
+ forgot: 'forgotPasswordForm',
2105
+ message: 'authMessage',
2106
+ };
2107
+ const targetView = this.modal.querySelector(`#${viewMap[view]}`);
2108
+ targetView?.classList.remove('hidden');
2109
+ this.currentView = view;
2110
+ }
2111
+ async handleLogin(e) {
2112
+ e.preventDefault();
2113
+ const emailInput = this.modal?.querySelector('#loginEmail');
2114
+ const passwordInput = this.modal?.querySelector('#loginPassword');
2115
+ const submitBtn = this.modal?.querySelector('#loginButton');
2116
+ const btnText = submitBtn?.querySelector('.btn-text');
2117
+ const btnLoader = submitBtn?.querySelector('.btn-loader');
2118
+ if (!emailInput || !passwordInput || !submitBtn)
2119
+ return;
2120
+ const email = emailInput.value;
2121
+ const password = passwordInput.value;
2122
+ try {
2123
+ // Show loading state
2124
+ submitBtn.disabled = true;
2125
+ btnText?.classList.add('hidden');
2126
+ btnLoader?.classList.remove('hidden');
2127
+ // Call login API using new typed method
2128
+ const response = await this.client.login({
2129
+ email,
2130
+ password,
2131
+ });
2132
+ // Handle success
2133
+ if (response.token) {
2134
+ if (this.options.onLoginSuccess) {
2135
+ this.options.onLoginSuccess(response.token, response.user);
2136
+ }
2137
+ this.showMessage('Login Successful', 'Welcome back!');
2138
+ }
2139
+ else {
2140
+ throw new Error('Invalid response from server');
2141
+ }
2142
+ }
2143
+ catch (error) {
2144
+ // Handle error
2145
+ const errorMessage = error instanceof Error ? error.message : 'Login failed';
2146
+ this.showError(errorMessage);
2147
+ if (this.options.onError) {
2148
+ this.options.onError(error);
2149
+ }
2150
+ }
2151
+ finally {
2152
+ // Reset loading state
2153
+ submitBtn.disabled = false;
2154
+ btnText?.classList.remove('hidden');
2155
+ btnLoader?.classList.add('hidden');
2156
+ }
2157
+ }
2158
+ async handleSignup(e) {
2159
+ e.preventDefault();
2160
+ const emailInput = this.modal?.querySelector('#signupEmail');
2161
+ const passwordInput = this.modal?.querySelector('#signupPassword');
2162
+ const passwordConfirmInput = this.modal?.querySelector('#signupPasswordConfirm');
2163
+ const submitBtn = this.modal?.querySelector('#signupButton');
2164
+ const btnText = submitBtn?.querySelector('.btn-text');
2165
+ const btnLoader = submitBtn?.querySelector('.btn-loader');
2166
+ if (!emailInput || !passwordInput || !passwordConfirmInput || !submitBtn)
2167
+ return;
2168
+ const email = emailInput.value;
2169
+ const password = passwordInput.value;
2170
+ const passwordConfirm = passwordConfirmInput.value;
2171
+ // Validate passwords match
2172
+ if (password !== passwordConfirm) {
2173
+ this.showError('Passwords do not match');
2174
+ return;
2175
+ }
2176
+ try {
2177
+ // Show loading state
2178
+ submitBtn.disabled = true;
2179
+ btnText?.classList.add('hidden');
2180
+ btnLoader?.classList.remove('hidden');
2181
+ // Call signup API using new typed method
2182
+ const response = await this.client.register({
2183
+ email,
2184
+ password,
2185
+ });
2186
+ // Handle success - Note: register returns different response format
2187
+ if (response.success) {
2188
+ // After successful registration, perform login to get token
2189
+ const loginResponse = await this.client.login({ email, password });
2190
+ if (loginResponse.token) {
2191
+ if (this.options.onSignupSuccess) {
2192
+ this.options.onSignupSuccess(loginResponse.token, loginResponse.user);
2193
+ }
2194
+ this.showMessage('Account Created', response.message || 'Your account has been created successfully!');
2195
+ }
2196
+ }
2197
+ else {
2198
+ throw new Error('Registration failed');
2199
+ }
2200
+ }
2201
+ catch (error) {
2202
+ // Handle error
2203
+ const errorMessage = error instanceof Error ? error.message : 'Signup failed';
2204
+ this.showError(errorMessage);
2205
+ if (this.options.onError) {
2206
+ this.options.onError(error);
2207
+ }
2208
+ }
2209
+ finally {
2210
+ // Reset loading state
2211
+ submitBtn.disabled = false;
2212
+ btnText?.classList.remove('hidden');
2213
+ btnLoader?.classList.add('hidden');
2214
+ }
2215
+ }
2216
+ async handleForgotPassword(e) {
2217
+ e.preventDefault();
2218
+ const emailInput = this.modal?.querySelector('#resetEmail');
2219
+ const submitBtn = this.modal?.querySelector('#resetButton');
2220
+ if (!emailInput || !submitBtn)
2221
+ return;
2222
+ const email = emailInput.value;
2223
+ try {
2224
+ // Show loading state
2225
+ submitBtn.disabled = true;
2226
+ // TODO: Call forgot password API when available
2227
+ // await this.client.postapiauthforgotpassword({ email });
2228
+ // Show success message
2229
+ this.showMessage('Reset Link Sent', `We've sent a password reset link to ${email}`);
2230
+ }
2231
+ catch (error) {
2232
+ // Handle error
2233
+ const errorMessage = error instanceof Error ? error.message : 'Failed to send reset link';
2234
+ this.showError(errorMessage);
2235
+ if (this.options.onError) {
2236
+ this.options.onError(error);
2237
+ }
2238
+ }
2239
+ finally {
2240
+ // Reset loading state
2241
+ submitBtn.disabled = false;
2242
+ }
2243
+ }
2244
+ showMessage(title, message) {
2245
+ if (!this.modal)
2246
+ return;
2247
+ const messageTitle = this.modal.querySelector('#messageTitle');
2248
+ const messageText = this.modal.querySelector('#messageText');
2249
+ const messageButton = this.modal.querySelector('#messageButton');
2250
+ if (messageTitle)
2251
+ messageTitle.textContent = title;
2252
+ if (messageText)
2253
+ messageText.textContent = message;
2254
+ messageButton?.addEventListener('click', () => {
2255
+ this.hide();
2256
+ }, { once: true });
2257
+ this.switchView('message');
2258
+ }
2259
+ showError(message) {
2260
+ // Simple error display - you can enhance this
2261
+ alert(message);
2262
+ }
2263
+ // ============================================================================
2264
+ // OAuth Methods
2265
+ // ============================================================================
2266
+ /**
2267
+ * Start OAuth flow for a given provider
2268
+ */
2269
+ startOAuthFlow(provider) {
2270
+ const config = this.options.oauthConfig?.[provider];
2271
+ if (!config) {
2272
+ this.showError(`${provider} OAuth is not configured`);
2273
+ return;
2274
+ }
2275
+ // Store the current state (to verify callback)
2276
+ const state = this.generateRandomState();
2277
+ sessionStorage.setItem('oauth_state', state);
2278
+ sessionStorage.setItem('oauth_provider', provider);
2279
+ // Build authorization URL
2280
+ const authUrl = this.buildAuthUrl(provider, config, state);
2281
+ // Redirect to OAuth provider
2282
+ window.location.href = authUrl;
2283
+ }
2284
+ /**
2285
+ * Generate random state for CSRF protection
2286
+ */
2287
+ generateRandomState() {
2288
+ const array = new Uint8Array(32);
2289
+ crypto.getRandomValues(array);
2290
+ return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
2291
+ }
2292
+ /**
2293
+ * Build authorization URL for each provider
2294
+ */
2295
+ buildAuthUrl(provider, config, state) {
2296
+ const params = new URLSearchParams({
2297
+ client_id: config.clientId,
2298
+ redirect_uri: config.redirectUri,
2299
+ state,
2300
+ response_type: 'code',
2301
+ });
2302
+ // Provider-specific configurations
2303
+ switch (provider) {
2304
+ case 'google':
2305
+ params.append('scope', config.scope || 'openid email profile');
2306
+ params.append('access_type', 'offline');
2307
+ params.append('prompt', 'consent');
2308
+ return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
2309
+ case 'discord':
2310
+ params.append('scope', config.scope || 'identify email');
2311
+ return `https://discord.com/api/oauth2/authorize?${params.toString()}`;
2312
+ case 'github':
2313
+ params.append('scope', config.scope || 'read:user user:email');
2314
+ return `https://github.com/login/oauth/authorize?${params.toString()}`;
2315
+ default:
2316
+ throw new Error(`Unknown provider: ${provider}`);
2317
+ }
2318
+ }
2319
+ /**
2320
+ * Handle OAuth callback
2321
+ * Call this method from your redirect page with the code and state from URL
2322
+ */
2323
+ async handleOAuthCallback(code, state) {
2324
+ try {
2325
+ // Verify state
2326
+ const storedState = sessionStorage.getItem('oauth_state');
2327
+ const provider = sessionStorage.getItem('oauth_provider');
2328
+ if (!storedState || storedState !== state) {
2329
+ throw new Error('Invalid state parameter - possible CSRF attack');
2330
+ }
2331
+ if (!provider) {
2332
+ throw new Error('No provider stored in session');
2333
+ }
2334
+ // Clear stored state
2335
+ sessionStorage.removeItem('oauth_state');
2336
+ sessionStorage.removeItem('oauth_provider');
2337
+ // Exchange code for token using the appropriate SDK method
2338
+ let response;
2339
+ switch (provider) {
2340
+ case 'google':
2341
+ response = await this.client.googleCodeToToken({ code });
2342
+ break;
2343
+ case 'discord':
2344
+ response = await this.client.discordCodeToToken({ code });
2345
+ break;
2346
+ case 'github':
2347
+ response = await this.client.githubCodeToToken({ code });
2348
+ break;
2349
+ default:
2350
+ throw new Error(`Unknown provider: ${provider}`);
2351
+ }
2352
+ // Handle success
2353
+ if (response.token) {
2354
+ if (this.options.onLoginSuccess) {
2355
+ this.options.onLoginSuccess(response.token, response.user);
2356
+ }
2357
+ return {
2358
+ success: true,
2359
+ token: response.token,
2360
+ user: response.user,
2361
+ };
2362
+ }
2363
+ else {
2364
+ throw new Error('No token received from server');
2365
+ }
2366
+ }
2367
+ catch (error) {
2368
+ const err = error instanceof Error ? error : new Error('OAuth authentication failed');
2369
+ if (this.options.onError) {
2370
+ this.options.onError(err);
2371
+ }
2372
+ return {
2373
+ success: false,
2374
+ error: err,
2375
+ };
2376
+ }
2377
+ }
2378
+ /**
2379
+ * Check if current page is an OAuth callback and handle it automatically
2380
+ */
2381
+ static async handleOAuthCallbackFromUrl(client, options) {
2382
+ const urlParams = new URLSearchParams(window.location.search);
2383
+ const code = urlParams.get('code');
2384
+ const state = urlParams.get('state');
2385
+ if (!code || !state) {
2386
+ return null; // Not an OAuth callback
2387
+ }
2388
+ const modal = new AuthModal({
2389
+ client,
2390
+ ...options,
2391
+ });
2392
+ return await modal.handleOAuthCallback(code, state);
2393
+ }
2394
+ }
2395
+ /**
2396
+ * Create and show auth modal
2397
+ */
2398
+ function createAuthModal(options) {
2399
+ return new AuthModal(options);
2400
+ }
2401
+
2402
+ export { AuthFactory, AuthModal, AuthProvider, BuiltInHooks, SeaVerseBackendAPIClient, createAuthModal, models };
2403
+ //# sourceMappingURL=index.js.map