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